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

snatalenko / node-cqrs / 10999473543

23 Sep 2024 05:35PM UTC coverage: 94.648% (+0.002%) from 94.646%
10999473543

push

github

snatalenko
1.0.0-rc.3

560 of 862 branches covered (64.97%)

2352 of 2485 relevant lines covered (94.65%)

21.91 hits per line

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

71.51
/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): view is IProjectionView =>
4✔
23
        'ready' in view &&
22✔
24
        'lock' in view &&
22✔
25
        'unlock' in view &&
22✔
26
        'once' in view;
4✔
27

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

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

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

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

96✔
52
                return this.#view;
96✔
53
        }
96✔
54

×
55
        #viewFactory: IViewFactory<TView>;
×
56
        #view?: TView;
×
57

×
58
        protected _logger?: ILogger;
×
59

×
60
        get collectionName(): string {
×
61
                return getClassName(this);
×
62
        }
×
63

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

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

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

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

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

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

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

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

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

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

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

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

16✔
132
                if (concurrentView)
16✔
133
                        concurrentView.unlock();
16✔
134
        }
18✔
135

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

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

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

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

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

×
163
        /** Handle error on restoring. Logs and throws error by default */
×
164
        protected _onRestoringError(error: Error, event: IEvent) {
✔
165
                this._logger?.error(`view restoring has failed (view will remain locked): ${error.message}`, {
2!
166
                        service: getClassName(this),
2✔
167
                        event,
2✔
168
                        stack: error.stack
2✔
169
                });
2✔
170
                throw error;
2✔
171
        }
2✔
172
}
×
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

© 2026 Coveralls, Inc