• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

snatalenko / node-cqrs / 10223224402

02 Aug 2024 11:19PM UTC coverage: 96.856%. First build
10223224402

Pull #21

github

snatalenko
Delete travis config
Pull Request #21: Migrate to TypeScript

570 of 859 branches covered (66.36%)

2282 of 2360 new or added lines in 28 files covered. (96.69%)

2403 of 2481 relevant lines covered (96.86%)

21.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.83
/src/AbstractProjection.ts
1
import { InMemoryView } from './infrastructure/InMemoryView';
4✔
2

4✔
3
import {
4✔
4
        IProjectionView,
4✔
5
        IEvent,
4✔
6
        IPersistentView,
4✔
7
        IEventStore,
4✔
8
        IExtendableLogger,
4✔
9
        ILogger,
4✔
10
        IProjection,
4✔
11
        IViewFactory
4✔
12
} from "./interfaces";
4✔
13

4✔
14
import {
4✔
15
        getClassName,
4✔
16
        validateHandlers,
4✔
17
        getHandler,
4✔
18
        getHandledMessageTypes,
4✔
19
        subscribe
4✔
20
} from './utils';
4✔
21

4✔
22
const isProjectionView = (view: IProjectionView) =>
4✔
23
        'ready' in view &&
24✔
24
        'lock' in view &&
24✔
25
        'unlock' in view &&
24✔
26
        'once' in view;
4✔
27

4✔
28
const asProjectionView = (view: any): IProjectionView => (isProjectionView(view) ? view : undefined);
4!
29

4✔
30
/**
4✔
31
 * Base class for Projection definition
4✔
32
 */
4✔
33
export abstract class AbstractProjection<TView extends IProjectionView | IPersistentView> implements IProjection<TView> {
2✔
34

2✔
35
        /**
2✔
36
         * Optional list of event types being handled by projection.
2✔
37
         * Can be overridden in projection implementation.
2✔
38
         * If not overridden, will detect event types from event handlers declared on the Projection class
2✔
39
         */
2✔
40
        static get handles(): string[] | undefined {
2✔
41
                return undefined;
12✔
42
        }
12✔
43

2✔
44
        /**
2✔
45
         * Default view associated with projection
2✔
46
         */
2✔
47
        get view(): TView {
2✔
48
                if (!this.#view)
104✔
49
                        this.#view = this.#viewFactory();
104✔
50

104✔
51
                return this.#view;
104✔
52
        }
104✔
53

2✔
54
        #viewFactory: IViewFactory<TView>;
2✔
55
        #view?: TView;
2✔
56

2✔
57
        protected _logger?: ILogger;
2✔
58

2✔
59
        get collectionName(): string {
2✔
NEW
60
                return getClassName(this);
×
NEW
61
        }
×
62

2✔
63
        /**
2✔
64
         * Indicates if view should be restored from EventStore on start.
2✔
65
         * Override for custom behavior.
2✔
66
         */
2✔
67
        get shouldRestoreView(): boolean | Promise<boolean> {
2✔
68
                return (this.view instanceof Map)
20✔
69
                        || (this.view instanceof InMemoryView);
20✔
70
        }
20✔
71

2✔
72
        constructor({
2✔
73
                view,
32✔
74
                viewFactory = InMemoryView.factory,
32✔
75
                logger
32✔
76
        }: {
32✔
77
                view?: TView,
32✔
78
                viewFactory?: IViewFactory<TView>,
32✔
79
                logger?: ILogger | IExtendableLogger
32✔
80
        } = {}) {
32✔
81
                validateHandlers(this);
32✔
82

32✔
83
                this.#viewFactory = view ?
32✔
84
                        () => view :
32✔
85
                        viewFactory;
32✔
86

32✔
87
                this._logger = logger && 'child' in logger ?
32!
88
                        logger.child({ service: getClassName(this) }) :
32✔
89
                        logger;
32✔
90
        }
32✔
91

2✔
92
        /** Subscribe to event store */
2✔
93
        async subscribe(eventStore: IEventStore): Promise<void> {
2✔
94
                subscribe(eventStore, this, {
8✔
95
                        masterHandler: (e: IEvent) => this.project(e)
8✔
96
                });
8✔
97

8✔
98
                await this.restore(eventStore);
8✔
99
        }
6✔
100

2✔
101
        /** Pass event to projection event handler */
2✔
102
        async project(event: IEvent): Promise<void> {
2✔
103
                const concurrentView = asProjectionView(this.view);
4✔
104
                if (concurrentView && !concurrentView.ready)
4✔
105
                        await concurrentView.once('ready');
4✔
106

4✔
107
                return this._project(event);
4✔
108
        }
4✔
109

2✔
110
        /** Pass event to projection event handler, without awaiting for restore operation to complete */
2✔
111
        protected async _project(event: IEvent): Promise<void> {
2✔
112
                const handler = getHandler(this, event.type);
32✔
113
                if (!handler)
32✔
114
                        throw new Error(`'${event.type}' handler is not defined or not a function`);
32✔
115

30✔
116
                return handler.call(this, event);
30✔
117
        }
30✔
118

2✔
119
        /** Restore projection view from event store */
2✔
120
        async restore(eventStore: IEventStore): Promise<void> {
2✔
121
                // lock the view to ensure same restoring procedure
20✔
122
                // won't be performed by another projection instance
20✔
123
                const concurrentView = asProjectionView(this.view);
20✔
124
                if (concurrentView)
20✔
125
                        await concurrentView.lock();
20✔
126

20✔
127
                const shouldRestore = await this.shouldRestoreView;
20✔
128
                if (shouldRestore)
20✔
129
                        await this._restore(eventStore);
20✔
130

16✔
131
                if (concurrentView)
16✔
132
                        concurrentView.unlock();
16✔
133
        }
20✔
134

2✔
135
        /** Restore projection view from event store */
2✔
136
        protected async _restore(eventStore: IEventStore): Promise<void> {
2✔
137
                if (!eventStore)
20✔
138
                        throw new TypeError('eventStore argument required');
20!
139
                if (typeof eventStore.getAllEvents !== 'function')
20✔
140
                        throw new TypeError('eventStore.getAllEvents must be a Function');
20!
141

20✔
142
                this._logger?.debug('retrieving events and restoring projection...');
20!
143

20✔
144
                const messageTypes = getHandledMessageTypes(this);
20✔
145
                const eventsIterable = eventStore.getAllEvents(messageTypes);
20✔
146
                let eventsCount = 0;
20✔
147
                const startTs = Date.now();
20✔
148

20✔
149
                for await (const event of eventsIterable) {
20✔
150
                        try {
26✔
151
                                await this._project(event);
26✔
152
                                eventsCount += 1;
24✔
153
                        }
24✔
154
                        catch (err) {
26✔
155
                                this._onRestoringError(err, event);
2✔
156
                        }
2✔
157
                }
26✔
158

16✔
159
                this._logger?.info(`view restored (${this.view}) from ${eventsCount} event(s) in ${Date.now() - startTs} ms`);
20!
160
        }
20✔
161

2✔
162
        /** Handle error on restoring. Logs and throws error by default */
2✔
163
        protected _onRestoringError(error: Error, event: IEvent) {
2✔
164
                this._logger?.error(`view restoring has failed (view will remain locked): ${error.message}`, {
2!
165
                        service: getClassName(this),
2✔
166
                        event,
2✔
167
                        stack: error.stack
2✔
168
                });
2✔
169
                throw error;
2✔
170
        }
2✔
171
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc