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

snatalenko / node-cqrs / 22268237579

22 Feb 2026 01:41AM UTC coverage: 86.867% (-7.5%) from 94.396%
22268237579

Pull #28

github

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

475 of 691 branches covered (68.74%)

882 of 973 new or added lines in 65 files covered. (90.65%)

49 existing lines in 13 files now uncovered.

1217 of 1401 relevant lines covered (86.87%)

21.53 hits per line

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

77.78
/src/in-memory/InMemoryView.ts
1
import { InMemoryLock } from './InMemoryLock.ts';
18✔
2
import type { IViewLocker, Identifier, IObjectStorage } from '../interfaces/index.ts';
3
import { nextCycle } from './utils/index.ts';
18✔
4

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

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

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

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

27
        #lock: InMemoryLock;
28

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

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

39
        constructor() {
40
                this.#lock = new InMemoryLock();
36✔
41

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

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

54
        /** Release the lock */
55
        async unlock(): Promise<void> {
56
                return this.#lock.unlock();
25✔
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
                if (eventType !== 'ready')
3!
62
                        throw new TypeError(`Unexpected event type: ${eventType}`);
×
63

64
                return this.#lock.once('unlocked');
3✔
65
        }
66

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

77
        /** Get record with a given key; await until the view is restored */
78
        async get(key: Identifier, options?: { nowait?: boolean }): Promise<TRecord | undefined> {
79
                if (!key)
19!
80
                        throw new TypeError('key argument required');
×
81

82
                if (!this.ready && !options?.nowait)
19✔
83
                        await this.once('ready');
1✔
84

85
                await nextCycle();
19✔
86

87
                return this._map.get(key);
19✔
88
        }
89

90
        /**
91
         * Get record with a given key synchronously
92
         */
93
        getSync(key: Identifier): TRecord | undefined {
94
                if (!key)
×
95
                        throw new TypeError('key argument required');
×
96

97
                return this._map.get(key);
×
98
        }
99

100
        /** Get all records matching an optional filter */
101
        async getAll(filter?: (r: TRecord | undefined, i: Identifier) => boolean):
102
                Promise<Array<[Identifier, TRecord | undefined]>> {
103
                if (filter && typeof filter !== 'function')
3✔
104
                        throw new TypeError('filter argument, when defined, must be a Function');
1✔
105

106
                if (!this.ready)
2✔
107
                        await this.once('ready');
1✔
108

109
                await nextCycle();
2✔
110

111
                const r: Array<[Identifier, TRecord | undefined]> = [];
2✔
112
                for (const entry of this._map.entries()) {
2✔
113
                        if (!filter || filter(entry[1], entry[0]))
5✔
114
                                r.push(entry);
3✔
115
                }
116

117
                return r;
2✔
118
        }
119

120
        /** Create record with a given key and value */
121
        async create(key: Identifier, value: TRecord = {} as TRecord) {
×
122
                if (!key)
36!
123
                        throw new TypeError('key argument required');
×
124
                if (typeof value === 'function')
36✔
125
                        throw new TypeError('value argument must be an instance of an Object');
1✔
126

127
                if (this._map.has(key))
35✔
128
                        throw new Error(`Key '${key}' already exists`);
1✔
129

130
                this._map.set(key, value);
34✔
131
        }
132

133
        /** Update existing view record */
134
        async update(key: Identifier, update: (r: TRecord) => TRecord) {
135
                if (!key)
1!
136
                        throw new TypeError('key argument required');
×
137
                if (typeof update !== 'function')
1!
138
                        throw new TypeError('update argument must be a Function');
×
139

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

143
                return this._update(key, update);
×
144
        }
145

146
        /** Update existing view record or create new */
147
        async updateEnforcingNew(key: Identifier, update: (r?: TRecord) => TRecord) {
148
                if (!key)
19!
149
                        throw new TypeError('key argument required');
×
150
                if (typeof update !== 'function')
19!
151
                        throw new TypeError('update argument must be a Function');
×
152

153
                if (!this._map.has(key))
19✔
154
                        return this.create(key, applyUpdate(undefined, update));
12✔
155

156
                return this._update(key, update);
7✔
157
        }
158

159
        /** Update all records that match filter criteria */
160
        async updateAll(filter: (r: TRecord) => boolean, update: (r: TRecord) => TRecord) {
161
                if (filter && typeof filter !== 'function')
1!
162
                        throw new TypeError('filter argument, when specified, must be a Function');
×
163
                if (typeof update !== 'function')
1!
164
                        throw new TypeError('update argument must be a Function');
×
165

166
                for (const [key, value] of this._map) {
1✔
167
                        if (!filter || filter(value))
2✔
168
                                await this._update(key, update);
1✔
169
                }
170
        }
171

172
        /** Update existing record */
173
        private async _update(key: Identifier, update: (r: TRecord) => TRecord) {
174
                const value = this._map.get(key);
8✔
175
                if (!value)
8!
NEW
176
                        throw new Error(`Key '${key}' does not exist`);
×
177

178
                const updatedValue = applyUpdate(value, update);
8✔
179
                if (updatedValue === undefined)
8!
180
                        return;
×
181

182
                this._map.set(key, updatedValue);
8✔
183
        }
184

185
        /** Delete record */
186
        async delete(key: Identifier) {
187
                if (!key)
3!
188
                        throw new TypeError('key argument required');
×
189

190
                this._map.delete(key);
3✔
191
        }
192

193
        /** Delete all records that match filter criteria */
194
        async deleteAll(filter: (r?: TRecord) => boolean) {
195
                if (filter && typeof filter !== 'function')
1!
196
                        throw new TypeError('filter argument, when specified, must be a Function');
×
197

198
                for (const [key, value] of this._map) {
1✔
199
                        if (!filter || filter(value))
2✔
200
                                await this.delete(key);
1✔
201
                }
202
        }
203

204
        /** Get view summary as string */
205
        toString(): string {
206
                return `${this.size} record${this.size !== 1 ? 's' : ''}`;
2✔
207
        }
208
}
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