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

snatalenko / node-cqrs / 10223129684

02 Aug 2024 11:08PM UTC coverage: 94.639%. First build
10223129684

Pull #21

github

snatalenko
Separate github workflows for tests and coveralls
Pull Request #21: Migrate to TypeScript

552 of 854 branches covered (64.64%)

2231 of 2360 new or added lines in 28 files covered. (94.53%)

2348 of 2481 relevant lines covered (94.64%)

21.9 hits per line

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

71.35
/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 &&
22✔
24
        'lock' in view &&
22✔
25
        'unlock' in view &&
22✔
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✔
NEW
33
export abstract class AbstractProjection<TView extends IProjectionView | IPersistentView> implements IProjection<TView> {
×
NEW
34

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

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

96✔
51
                return this.#view;
96✔
52
        }
96✔
NEW
53

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

×
NEW
57
        protected _logger?: ILogger;
×
NEW
58

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

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

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

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

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

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

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

×
NEW
101
        /** Pass event to projection event handler */
×
NEW
102
        async project(event: IEvent): Promise<void> {
✔
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✔
NEW
109

×
NEW
110
        /** Pass event to projection event handler, without awaiting for restore operation to complete */
×
NEW
111
        protected async _project(event: IEvent): Promise<void> {
✔
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✔
NEW
118

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

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

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

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

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

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

18✔
149
                for await (const event of eventsIterable) {
18✔
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`);
18!
160
        }
18✔
NEW
161

×
NEW
162
        /** Handle error on restoring. Logs and throws error by default */
×
NEW
163
        protected _onRestoringError(error: Error, event: IEvent) {
✔
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✔
NEW
171
}
×
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