• 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

98.56
/src/infrastructure/InMemoryView.ts
1
import { InMemoryLock } from './InMemoryLock';
4✔
2
import { IProjectionView, Identifier } from "../interfaces";
4✔
3
import { nextCycle } from './utils';
4✔
4

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

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

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

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

42✔
27
        #lock: InMemoryLock;
42✔
28

42✔
29
        #asyncWrites: boolean;
42✔
30

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

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

42✔
41
        constructor(options?: {
42✔
42
                /** Indicates if writes should be submitted asynchronously */
64✔
43
                asyncWrites?: boolean
64✔
44
        }) {
64✔
45
                this.#asyncWrites = options?.asyncWrites ?? false;
64!
46

64✔
47
                this.#lock = new InMemoryLock();
64✔
48

64✔
49
                // explicitly bind the `get` method to this object for easier using in Promises
64✔
50
                Object.defineProperty(this, this.get.name, {
64✔
51
                        value: this.get.bind(this)
64✔
52
                });
64✔
53
        }
64✔
54

42✔
55
        /** Lock the view to prevent concurrent modifications */
42✔
56
        async lock(): Promise<boolean> {
42✔
57
                await this.#lock.lock();
22✔
58
                return this.#lock.locked;
22✔
59
        }
22✔
60

42✔
61
        /** Release the lock */
42✔
62
        async unlock(): Promise<void> {
42✔
63
                return this.#lock.unlock();
46✔
64
        }
46✔
65

42✔
66
        /** Create a Promise which will resolve to a first emitted event of a given type */
42✔
67
        once(eventType: 'ready'): Promise<any> {
42✔
68
                if (eventType !== 'ready')
6✔
69
                        throw new TypeError(`Unexpected event type: ${eventType}`);
6!
70

6✔
71
                return this.#lock.once('unlocked');
6✔
72
        }
6✔
73

42✔
74
        /**
42✔
75
         * Check if view contains a record with a given key.
42✔
76
         * This is the only synchronous method, so make sure to check the `ready` flag, if necessary
42✔
77
         *
42✔
78
         * @deprecated Use `async get()` instead
42✔
79
         */
42✔
80
        has(key: Identifier): boolean {
42✔
81
                return this._map.has(key);
4✔
82
        }
4✔
83

42✔
84
        /** Get record with a given key; await until the view is restored */
42✔
85
        async get(key: Identifier, options?: { nowait?: boolean }): Promise<TRecord | undefined> {
42✔
86
                if (!key)
38✔
87
                        throw new TypeError('key argument required');
38!
88

38✔
89
                if (!this.ready && !options?.nowait)
38✔
90
                        await this.once('ready');
38✔
91

38✔
92
                await nextCycle();
38✔
93

38✔
94
                return this._map.get(key);
38✔
95
        }
38✔
96

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

4✔
103
                if (!this.ready)
4✔
104
                        await this.once('ready');
6✔
105

4✔
106
                await nextCycle();
4✔
107

4✔
108
                const r: Array<[Identifier, TRecord | undefined]> = [];
4✔
109
                for (const entry of this._map.entries()) {
6✔
110
                        if (!filter || filter(entry[1], entry[0]))
10✔
111
                                r.push(entry);
10✔
112
                }
10✔
113

4✔
114
                return r;
4✔
115
        }
4✔
116

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

70✔
124
                if (this.#asyncWrites)
70✔
125
                        await nextCycle();
72!
126

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

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

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

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

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

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

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

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

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

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

42✔
172
        /** Update existing record */
42✔
173
        private async _update(key: Identifier, update: (r?: TRecord) => TRecord) {
42✔
174
                const value = this._map.get(key);
16✔
175
                const updatedValue = applyUpdate(value, update);
16✔
176

16✔
177
                if (this.#asyncWrites)
16✔
178
                        await nextCycle();
16!
179

16✔
180
                this._map.set(key, updatedValue);
16✔
181
        }
16✔
182

42✔
183
        /** Delete record */
42✔
184
        async delete(key: Identifier) {
42✔
185
                if (!key)
6✔
186
                        throw new TypeError('key argument required');
6!
187

6✔
188
                if (this.#asyncWrites)
6✔
189
                        await nextCycle();
6!
190

6✔
191
                this._map.delete(key);
6✔
192
        }
6✔
193

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

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

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