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

snatalenko / node-cqrs / 22745197368

06 Mar 2026 01:43AM UTC coverage: 95.287% (+0.9%) from 94.396%
22745197368

Pull #28

github

web-flow
Merge dd3952cc7 into 828e39903
Pull Request #28: TypeScript and event dispatching pipeline refactoring

428 of 528 branches covered (81.06%)

1043 of 1091 new or added lines in 65 files covered. (95.6%)

3 existing lines in 2 files now uncovered.

1294 of 1358 relevant lines covered (95.29%)

31.11 hits per line

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

91.43
/src/in-memory/InMemoryView.ts
1
import { InMemoryLock } from './InMemoryLock.ts';
19✔
2
import type { IViewLocker, Identifier, IObjectStorage } from '../interfaces/index.ts';
3
import { nextCycle } from './utils/index.ts';
19✔
4
import { assertDefined, assertFunction } from '../utils/assert.ts';
19✔
5

6
/**
7
 * Update given value with an update Cb and return updated value.
8
 * Wrapper is needed for backward compatibility with update methods that were modifying the passed in objects directly
9
 */
10
function applyUpdate<T>(view: T, update: (r: T) => T): T {
11
        const valueReturnedByUpdate = update(view);
20✔
12
        return valueReturnedByUpdate === undefined ?
20✔
13
                view :
14
                valueReturnedByUpdate;
15
}
16

17
/**
18
 * In-memory Projection View, which suspends get()'s until it is ready
19
 */
20
export class InMemoryView<TRecord> implements IViewLocker, IObjectStorage<TRecord> {
19✔
21

22
        static factory<TView>(): TView {
23
                return (new InMemoryView() as unknown) as TView;
×
24
        }
25

26
        protected _map: Map<Identifier, TRecord> = new Map();
33✔
27

28
        #lock: InMemoryLock;
29

30
        /** Whether the view is restored */
31
        get ready(): boolean {
32
                return !this.#lock.locked;
24✔
33
        }
34

35
        /** Number of records in the View */
36
        get size(): number {
37
                return this._map.size;
6✔
38
        }
39

40
        constructor() {
41
                this.#lock = new InMemoryLock();
33✔
42

43
                // explicitly bind the `get` method to this object for easier using in Promises
44
                Object.defineProperty(this, this.get.name, {
33✔
45
                        value: this.get.bind(this)
46
                });
47
        }
48

49
        /** Lock the view to prevent concurrent modifications */
50
        async lock(): Promise<boolean> {
51
                return this.#lock.lock();
9✔
52
        }
53

54
        /** Release the lock */
55
        async unlock(): Promise<void> {
56
                return this.#lock.unlock();
22✔
57
        }
58

59
        /** Create a Promise which will resolve to a first emitted event of a given type */
60
        once(eventType: 'ready'): Promise<any> {
61
                return this.#lock.once(eventType);
3✔
62
        }
63

64
        /**
65
         * Check if view contains a record with a given key.
66
         * This is the only synchronous method, so make sure to check the `ready` flag, if necessary
67
         *
68
         * @deprecated Use `async get()` instead
69
         */
70
        has(key: Identifier): boolean {
71
                return this._map.has(key);
2✔
72
        }
73

74
        /** Get record with a given key; await until the view is restored */
75
        async get(key: Identifier, options?: { nowait?: boolean }): Promise<TRecord | undefined> {
76
                assertDefined(key, 'key');
19✔
77

78
                if (!this.ready && !options?.nowait)
19✔
79
                        await this.once('ready');
1✔
80

81
                await nextCycle();
19✔
82

83
                return this._map.get(key);
19✔
84
        }
85

86
        /**
87
         * Get record with a given key synchronously
88
         */
89
        getSync(key: Identifier): TRecord | undefined {
NEW
90
                assertDefined(key, 'key');
×
91

92
                return this._map.get(key);
×
93
        }
94

95
        /** Get all records matching an optional filter */
96
        async getAll(filter?: (r: TRecord | undefined, i: Identifier) => boolean):
97
                Promise<Array<[Identifier, TRecord | undefined]>> {
98
                if (filter)
3✔
99
                        assertFunction(filter, 'filter');
3✔
100

101
                if (!this.ready)
2✔
102
                        await this.once('ready');
1✔
103

104
                await nextCycle();
2✔
105

106
                const r: Array<[Identifier, TRecord | undefined]> = [];
2✔
107
                for (const entry of this._map.entries()) {
2✔
108
                        if (!filter || filter(entry[1], entry[0]))
5✔
109
                                r.push(entry);
3✔
110
                }
111

112
                return r;
2✔
113
        }
114

115
        /** Create record with a given key and value */
116
        async create(key: Identifier, value: TRecord = {} as TRecord) {
×
117
                assertDefined(key, 'key');
36✔
118
                if (typeof value === 'function')
36✔
119
                        throw new TypeError('value argument must be an instance of an Object');
1✔
120

121
                if (this._map.has(key))
35✔
122
                        throw new Error(`Key '${key}' already exists`);
1✔
123

124
                this._map.set(key, value);
34✔
125
        }
126

127
        /** Update existing view record */
128
        async update(key: Identifier, update: (r: TRecord) => TRecord) {
129
                assertDefined(key, 'key');
1✔
130
                assertFunction(update, 'update');
1✔
131

132
                if (!this._map.has(key))
1✔
133
                        throw new Error(`Key '${key}' does not exist`);
1✔
134

135
                return this._update(key, update);
×
136
        }
137

138
        /** Update existing view record or create new */
139
        async updateEnforcingNew(key: Identifier, update: (r?: TRecord) => TRecord) {
140
                assertDefined(key, 'key');
19✔
141
                assertFunction(update, 'update');
19✔
142

143
                if (!this._map.has(key))
19✔
144
                        return this.create(key, applyUpdate(undefined, update));
12✔
145

146
                return this._update(key, update);
7✔
147
        }
148

149
        /** Update all records that match filter criteria */
150
        async updateAll(filter: (r: TRecord) => boolean, update: (r: TRecord) => TRecord) {
151
                if (filter)
1✔
152
                        assertFunction(filter, 'filter');
1✔
153
                assertFunction(update, 'update');
1✔
154

155
                for (const [key, value] of this._map) {
1✔
156
                        if (!filter || filter(value))
2✔
157
                                await this._update(key, update);
1✔
158
                }
159
        }
160

161
        /** Update existing record */
162
        private async _update(key: Identifier, update: (r: TRecord) => TRecord) {
163
                const value = this._map.get(key);
8✔
164
                if (!value)
8!
NEW
165
                        throw new Error(`Key '${key}' does not exist`);
×
166

167
                const updatedValue = applyUpdate(value, update);
8✔
168
                if (updatedValue === undefined)
8!
169
                        return;
×
170

171
                this._map.set(key, updatedValue);
8✔
172
        }
173

174
        /** Delete record */
175
        async delete(key: Identifier) {
176
                assertDefined(key, 'key');
3✔
177

178
                this._map.delete(key);
3✔
179
        }
180

181
        /** Delete all records that match filter criteria */
182
        async deleteAll(filter: (r?: TRecord) => boolean) {
183
                if (filter)
1✔
184
                        assertFunction(filter, 'filter');
1✔
185

186
                for (const [key, value] of this._map) {
1✔
187
                        if (!filter || filter(value))
2✔
188
                                await this.delete(key);
1✔
189
                }
190
        }
191

192
        /** Get view summary as string */
193
        toString(): string {
194
                return `${this.size} record${this.size !== 1 ? 's' : ''}`;
2✔
195
        }
196
}
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