@nymphjs/pubsub 1.0.0-beta.12 → 1.0.0-beta.120
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +490 -0
- package/README.md +8 -8
- package/dist/PubSub.d.ts +84 -13
- package/dist/PubSub.js +626 -184
- package/dist/PubSub.js.map +1 -1
- package/dist/PubSub.test.js +407 -73
- package/dist/PubSub.test.js.map +1 -1
- package/dist/PubSub.types.d.ts +11 -2
- package/dist/PubSub.types.js +1 -2
- package/dist/conf/defaults.d.ts +1 -1
- package/dist/conf/defaults.js +4 -4
- package/dist/conf/defaults.js.map +1 -1
- package/dist/conf/index.d.ts +31 -2
- package/dist/conf/index.js +2 -8
- package/dist/conf/index.js.map +1 -1
- package/dist/createServer.d.ts +2 -2
- package/dist/createServer.js +14 -15
- package/dist/createServer.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +7 -27
- package/dist/index.js.map +1 -1
- package/jest.config.js +31 -5
- package/package.json +32 -26
- package/src/PubSub.test.ts +481 -109
- package/src/PubSub.ts +749 -399
- package/src/PubSub.types.ts +11 -6
- package/src/conf/defaults.ts +2 -2
- package/src/conf/index.ts +31 -2
- package/src/createServer.ts +8 -8
- package/src/index.ts +4 -4
- package/tsconfig.json +6 -3
- package/typedoc.json +4 -0
- package/dist/conf/d.d.ts +0 -7
- package/dist/conf/d.js +0 -3
- package/dist/conf/d.js.map +0 -1
- package/src/conf/d.ts +0 -30
package/src/PubSub.test.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
+
import ws from 'websocket';
|
|
2
3
|
import SQLite3Driver from '@nymphjs/driver-sqlite3';
|
|
3
4
|
import { Nymph as NymphServer } from '@nymphjs/nymph';
|
|
4
|
-
import {
|
|
5
|
+
import type { PubSubSubscription, PubSubUpdate } from '@nymphjs/client';
|
|
6
|
+
import { Nymph, PubSub } from '@nymphjs/client';
|
|
5
7
|
import createRestServer from '@nymphjs/server';
|
|
6
8
|
import {
|
|
7
9
|
EmployeeModel as EmployeeModelClass,
|
|
8
10
|
Employee as EmployeeClass,
|
|
9
11
|
EmployeeData,
|
|
12
|
+
RestrictedModel as RestrictedModelClass,
|
|
13
|
+
Restricted as RestrictedClass,
|
|
14
|
+
PubSubDisabledModel as PubSubDisabledModelClass,
|
|
15
|
+
PubSubDisabled as PubSubDisabledClass,
|
|
16
|
+
PubSubDisabledData,
|
|
10
17
|
} from '@nymphjs/server/dist/testArtifacts.js';
|
|
11
18
|
|
|
12
|
-
import createServer from './index';
|
|
13
|
-
import PubSubServer from './PubSub';
|
|
19
|
+
import createServer from './index.js';
|
|
20
|
+
import PubSubServer from './PubSub.js';
|
|
14
21
|
|
|
15
22
|
const sqliteConfig = {
|
|
16
23
|
filename: ':memory:',
|
|
@@ -18,28 +25,35 @@ const sqliteConfig = {
|
|
|
18
25
|
|
|
19
26
|
const pubSubConfig = {
|
|
20
27
|
originIsAllowed: () => true,
|
|
21
|
-
entries: ['ws://localhost:
|
|
28
|
+
entries: ['ws://localhost:5083/'],
|
|
22
29
|
logger: () => {},
|
|
23
30
|
};
|
|
24
31
|
|
|
25
32
|
const nymphServer = new NymphServer({}, new SQLite3Driver(sqliteConfig));
|
|
26
33
|
const EmployeeModel = nymphServer.addEntityClass(EmployeeModelClass);
|
|
27
|
-
|
|
34
|
+
const RestrictedModel = nymphServer.addEntityClass(RestrictedModelClass);
|
|
35
|
+
const PubSubDisabledModel = nymphServer.addEntityClass(
|
|
36
|
+
PubSubDisabledModelClass,
|
|
37
|
+
);
|
|
38
|
+
const removePublisher = PubSubServer.initPublisher(pubSubConfig, nymphServer);
|
|
28
39
|
|
|
29
40
|
const app = express();
|
|
30
41
|
app.use(createRestServer(nymphServer));
|
|
31
|
-
const server = app.listen(
|
|
42
|
+
const server = app.listen(5082);
|
|
32
43
|
|
|
33
|
-
const pubsubServer = createServer(
|
|
44
|
+
const pubsubServer = createServer(5083, pubSubConfig, nymphServer);
|
|
34
45
|
|
|
35
46
|
const nymphOptions = {
|
|
36
|
-
restUrl: 'http://localhost:
|
|
37
|
-
pubsubUrl: 'ws://localhost:
|
|
47
|
+
restUrl: 'http://localhost:5082/',
|
|
48
|
+
pubsubUrl: 'ws://localhost:5083/',
|
|
38
49
|
noConsole: true,
|
|
50
|
+
WebSocket: ws.w3cwebsocket as unknown as typeof WebSocket,
|
|
39
51
|
};
|
|
40
52
|
const nymph = new Nymph(nymphOptions);
|
|
41
53
|
const pubsub = new PubSub(nymphOptions, nymph);
|
|
42
54
|
const Employee = nymph.addEntityClass(EmployeeClass);
|
|
55
|
+
const Restricted = nymph.addEntityClass(RestrictedClass);
|
|
56
|
+
const PubSubDisabled = nymph.addEntityClass(PubSubDisabledClass);
|
|
43
57
|
|
|
44
58
|
describe('Nymph REST Server and Client', () => {
|
|
45
59
|
async function createJane() {
|
|
@@ -93,22 +107,22 @@ describe('Nymph REST Server and Client', () => {
|
|
|
93
107
|
it('notified of new match', async () => {
|
|
94
108
|
let jane: Promise<EmployeeClass>;
|
|
95
109
|
|
|
96
|
-
await new Promise((resolve) => {
|
|
110
|
+
await new Promise<void>((resolve) => {
|
|
97
111
|
let updated = false;
|
|
98
112
|
const subscription = pubsub.subscribeEntities(
|
|
99
113
|
{ class: Employee },
|
|
100
114
|
{
|
|
101
115
|
type: '&',
|
|
102
116
|
equal: ['name', 'Jane Doe'],
|
|
103
|
-
}
|
|
117
|
+
},
|
|
104
118
|
)(async (update) => {
|
|
105
119
|
if (updated) {
|
|
106
120
|
expect('added' in update && update.added).toEqual((await jane).guid);
|
|
107
121
|
expect('data' in update && update.data.guid).toEqual(
|
|
108
|
-
(await jane).guid
|
|
122
|
+
(await jane).guid,
|
|
109
123
|
);
|
|
110
124
|
subscription.unsubscribe();
|
|
111
|
-
resolve(
|
|
125
|
+
resolve();
|
|
112
126
|
} else {
|
|
113
127
|
expect(update).toEqual([]);
|
|
114
128
|
updated = true;
|
|
@@ -118,11 +132,180 @@ describe('Nymph REST Server and Client', () => {
|
|
|
118
132
|
});
|
|
119
133
|
});
|
|
120
134
|
|
|
135
|
+
it('notified of new match only after transaction committed', async () => {
|
|
136
|
+
let guid: string;
|
|
137
|
+
let committed = false;
|
|
138
|
+
|
|
139
|
+
await new Promise<void>((resolve) => {
|
|
140
|
+
let updated = false;
|
|
141
|
+
const subscription = pubsub.subscribeEntities(
|
|
142
|
+
{ class: Employee },
|
|
143
|
+
{
|
|
144
|
+
type: '&',
|
|
145
|
+
equal: ['name', 'Steve Transaction'],
|
|
146
|
+
},
|
|
147
|
+
)(async (update) => {
|
|
148
|
+
if (updated) {
|
|
149
|
+
if (committed) {
|
|
150
|
+
expect('added' in update && update.added).toEqual(guid);
|
|
151
|
+
expect('data' in update && update.data.guid).toEqual(guid);
|
|
152
|
+
} else {
|
|
153
|
+
throw new Error('Update arrived before transaction committed.');
|
|
154
|
+
}
|
|
155
|
+
subscription.unsubscribe();
|
|
156
|
+
resolve();
|
|
157
|
+
} else {
|
|
158
|
+
expect(update).toEqual([]);
|
|
159
|
+
updated = true;
|
|
160
|
+
const tnymph = await nymphServer.startTransaction('steve');
|
|
161
|
+
const Employee = tnymph.getEntityClass(EmployeeModel);
|
|
162
|
+
const steve = await Employee.factory();
|
|
163
|
+
steve.name = 'Steve Transaction';
|
|
164
|
+
steve.current = true;
|
|
165
|
+
steve.salary = 8000000;
|
|
166
|
+
steve.startDate = Date.now();
|
|
167
|
+
steve.subordinates = [];
|
|
168
|
+
steve.title = 'Seniorer Person';
|
|
169
|
+
try {
|
|
170
|
+
await steve.$save();
|
|
171
|
+
guid = steve.guid as string;
|
|
172
|
+
await new Promise<void>((res) => setTimeout(res, 1000));
|
|
173
|
+
committed = true;
|
|
174
|
+
await tnymph.commit('steve');
|
|
175
|
+
} catch (e: any) {
|
|
176
|
+
console.error('Error creating entity: ', e);
|
|
177
|
+
throw e;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('not notified of new match when transaction rolled back', async () => {
|
|
185
|
+
let guid: string;
|
|
186
|
+
|
|
187
|
+
await new Promise<void>((resolve) => {
|
|
188
|
+
let updated = false;
|
|
189
|
+
const subscription = pubsub.subscribeEntities(
|
|
190
|
+
{ class: Employee },
|
|
191
|
+
{
|
|
192
|
+
type: '&',
|
|
193
|
+
equal: ['name', 'Steve Rollback'],
|
|
194
|
+
},
|
|
195
|
+
)(async (update) => {
|
|
196
|
+
if (updated) {
|
|
197
|
+
if ('added' in update && update.added === guid) {
|
|
198
|
+
throw new Error('Update arrived after transaction rolled back.');
|
|
199
|
+
}
|
|
200
|
+
throw new Error('Update arrived unrelated to transaction.');
|
|
201
|
+
} else {
|
|
202
|
+
expect(update).toEqual([]);
|
|
203
|
+
updated = true;
|
|
204
|
+
const tnymph = await nymphServer.startTransaction('steve');
|
|
205
|
+
const Employee = tnymph.getEntityClass(EmployeeModel);
|
|
206
|
+
const steve = await Employee.factory();
|
|
207
|
+
steve.name = 'Steve Rollback';
|
|
208
|
+
steve.current = true;
|
|
209
|
+
steve.salary = 8000000;
|
|
210
|
+
steve.startDate = Date.now();
|
|
211
|
+
steve.subordinates = [];
|
|
212
|
+
steve.title = 'Seniorer Person';
|
|
213
|
+
try {
|
|
214
|
+
await steve.$save();
|
|
215
|
+
guid = steve.guid as string;
|
|
216
|
+
await new Promise<void>((res) => setTimeout(res, 600));
|
|
217
|
+
await tnymph.rollback('steve');
|
|
218
|
+
await new Promise<void>((res) => setTimeout(res, 600));
|
|
219
|
+
subscription.unsubscribe();
|
|
220
|
+
resolve();
|
|
221
|
+
} catch (e: any) {
|
|
222
|
+
console.error('Error creating entity: ', e);
|
|
223
|
+
throw e;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('notified of new match after complex transactions committed', async () => {
|
|
231
|
+
let guid: string;
|
|
232
|
+
let committed = false;
|
|
233
|
+
|
|
234
|
+
await new Promise<void>((resolve) => {
|
|
235
|
+
let updated = false;
|
|
236
|
+
const subscription = pubsub.subscribeEntities(
|
|
237
|
+
{ class: Employee },
|
|
238
|
+
{
|
|
239
|
+
type: '&',
|
|
240
|
+
equal: ['name', 'Steve Complex'],
|
|
241
|
+
},
|
|
242
|
+
)(async (update) => {
|
|
243
|
+
if (updated) {
|
|
244
|
+
if (committed) {
|
|
245
|
+
expect('added' in update && update.added).toEqual(guid);
|
|
246
|
+
expect('data' in update && update.data.guid).toEqual(guid);
|
|
247
|
+
} else {
|
|
248
|
+
throw new Error('Update arrived before transaction committed.');
|
|
249
|
+
}
|
|
250
|
+
subscription.unsubscribe();
|
|
251
|
+
resolve();
|
|
252
|
+
} else {
|
|
253
|
+
expect(update).toEqual([]);
|
|
254
|
+
updated = true;
|
|
255
|
+
const tnymphTop = await nymphServer.startTransaction('steve-top');
|
|
256
|
+
|
|
257
|
+
// Start a transaction that ultimately gets rolled back.
|
|
258
|
+
const tnymphB = await tnymphTop.startTransaction('steve-b');
|
|
259
|
+
const EmployeeB = tnymphB.getEntityClass(EmployeeModel);
|
|
260
|
+
const badSteve = await EmployeeB.factory();
|
|
261
|
+
badSteve.name = 'Steve Complex';
|
|
262
|
+
badSteve.current = true;
|
|
263
|
+
badSteve.salary = 8000000;
|
|
264
|
+
badSteve.startDate = Date.now();
|
|
265
|
+
badSteve.subordinates = [];
|
|
266
|
+
badSteve.title = 'Seniorer Person';
|
|
267
|
+
try {
|
|
268
|
+
await badSteve.$save();
|
|
269
|
+
await new Promise<void>((res) => setTimeout(res, 200));
|
|
270
|
+
await tnymphB.rollback('steve-b');
|
|
271
|
+
await new Promise<void>((res) => setTimeout(res, 200));
|
|
272
|
+
} catch (e: any) {
|
|
273
|
+
console.error('Error creating entity: ', e);
|
|
274
|
+
throw e;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Start a transaction that ultimately gets committed.
|
|
278
|
+
const tnymphA = await tnymphTop.startTransaction('steve-a');
|
|
279
|
+
const EmployeeA = tnymphA.getEntityClass(EmployeeModel);
|
|
280
|
+
const goodSteve = await EmployeeA.factory();
|
|
281
|
+
goodSteve.name = 'Steve Complex';
|
|
282
|
+
goodSteve.current = true;
|
|
283
|
+
goodSteve.salary = 8000000;
|
|
284
|
+
goodSteve.startDate = Date.now();
|
|
285
|
+
goodSteve.subordinates = [];
|
|
286
|
+
goodSteve.title = 'Seniorer Person';
|
|
287
|
+
try {
|
|
288
|
+
await goodSteve.$save();
|
|
289
|
+
guid = goodSteve.guid as string;
|
|
290
|
+
await new Promise<void>((res) => setTimeout(res, 200));
|
|
291
|
+
await tnymphA.commit('steve-a');
|
|
292
|
+
await new Promise<void>((res) => setTimeout(res, 400));
|
|
293
|
+
committed = true;
|
|
294
|
+
await tnymphTop.commit('steve-top');
|
|
295
|
+
} catch (e: any) {
|
|
296
|
+
console.error('Error creating entity: ', e);
|
|
297
|
+
throw e;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
121
304
|
it('notified of entity update', async () => {
|
|
122
305
|
let jane = await createJane();
|
|
123
306
|
let entities: (EmployeeClass & EmployeeData)[] = [];
|
|
124
307
|
|
|
125
|
-
await new Promise((resolve) => {
|
|
308
|
+
await new Promise<void>((resolve) => {
|
|
126
309
|
let mdate = 0;
|
|
127
310
|
if (jane.guid == null) {
|
|
128
311
|
throw new Error('Entity is null.');
|
|
@@ -132,13 +315,13 @@ describe('Nymph REST Server and Client', () => {
|
|
|
132
315
|
{
|
|
133
316
|
type: '&',
|
|
134
317
|
guid: jane.guid,
|
|
135
|
-
}
|
|
318
|
+
},
|
|
136
319
|
)(async (update) => {
|
|
137
320
|
pubsub.updateArray(entities, update);
|
|
138
321
|
|
|
139
322
|
if (mdate > 0 && (entities[0]?.mdate ?? -1) === mdate) {
|
|
140
323
|
subscription.unsubscribe();
|
|
141
|
-
resolve(
|
|
324
|
+
resolve();
|
|
142
325
|
} else if (Array.isArray(update)) {
|
|
143
326
|
expect(update.length).toEqual(1);
|
|
144
327
|
expect(entities[0].salary).toEqual(8000000);
|
|
@@ -157,7 +340,7 @@ describe('Nymph REST Server and Client', () => {
|
|
|
157
340
|
let [jane, john] = await createBossJane();
|
|
158
341
|
let entities: (EmployeeClass & EmployeeData)[] = [];
|
|
159
342
|
|
|
160
|
-
await new Promise((resolve) => {
|
|
343
|
+
await new Promise<void>((resolve) => {
|
|
161
344
|
let mdate = 0;
|
|
162
345
|
if (jane.guid == null || john.guid == null) {
|
|
163
346
|
throw new Error('Entity is null.');
|
|
@@ -170,13 +353,13 @@ describe('Nymph REST Server and Client', () => {
|
|
|
170
353
|
'subordinates',
|
|
171
354
|
[{ class: Employee }, { type: '&', guid: john.guid }],
|
|
172
355
|
],
|
|
173
|
-
}
|
|
356
|
+
},
|
|
174
357
|
)(async (update) => {
|
|
175
358
|
pubsub.updateArray(entities, update);
|
|
176
359
|
|
|
177
360
|
if (mdate > 0 && (entities[0]?.mdate ?? -1) === mdate) {
|
|
178
361
|
subscription.unsubscribe();
|
|
179
|
-
resolve(
|
|
362
|
+
resolve();
|
|
180
363
|
} else if (Array.isArray(update)) {
|
|
181
364
|
expect(update.length).toEqual(1);
|
|
182
365
|
expect(entities[0].salary).toEqual(8000000);
|
|
@@ -195,44 +378,150 @@ describe('Nymph REST Server and Client', () => {
|
|
|
195
378
|
let [jane, john] = await createBossJane();
|
|
196
379
|
let entities: (EmployeeClass & EmployeeData)[] = [];
|
|
197
380
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
],
|
|
381
|
+
// Create employees that matches qref to test when multiple things match.
|
|
382
|
+
let oldEmployee = await createJane();
|
|
383
|
+
oldEmployee.current = false;
|
|
384
|
+
await oldEmployee.$save();
|
|
385
|
+
let oldEmployee2 = await createJane();
|
|
386
|
+
oldEmployee2.current = false;
|
|
387
|
+
await oldEmployee2.$save();
|
|
388
|
+
|
|
389
|
+
expect(
|
|
390
|
+
await new Promise<boolean>((resolve) => {
|
|
391
|
+
if (jane.guid == null || john.guid == null) {
|
|
392
|
+
throw new Error('Entity is null.');
|
|
211
393
|
}
|
|
212
|
-
|
|
213
|
-
|
|
394
|
+
const subscription = pubsub.subscribeEntities(
|
|
395
|
+
{ class: Employee },
|
|
396
|
+
{
|
|
397
|
+
type: '&',
|
|
398
|
+
guid: jane.guid,
|
|
399
|
+
qref: [
|
|
400
|
+
'subordinates',
|
|
401
|
+
[{ class: Employee }, { type: '&', '!truthy': 'current' }],
|
|
402
|
+
],
|
|
403
|
+
},
|
|
404
|
+
)(async (update) => {
|
|
405
|
+
pubsub.updateArray(entities, update);
|
|
406
|
+
|
|
407
|
+
if (entities.length) {
|
|
408
|
+
subscription.unsubscribe();
|
|
409
|
+
resolve(true);
|
|
410
|
+
} else if (Array.isArray(update)) {
|
|
411
|
+
expect(update.length).toEqual(0);
|
|
214
412
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
413
|
+
// John gets fired.
|
|
414
|
+
john.current = false;
|
|
415
|
+
await john.$save();
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}),
|
|
419
|
+
).toEqual(true);
|
|
220
420
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
421
|
+
expect(entities.length).toEqual(1);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('notified of new match for qref query in transaction', async () => {
|
|
425
|
+
let entities: (EmployeeClass & EmployeeData)[] = [];
|
|
426
|
+
|
|
427
|
+
const john = await EmployeeModel.factory();
|
|
428
|
+
john.name = 'John Der';
|
|
429
|
+
john.current = true;
|
|
430
|
+
john.salary = 8000000;
|
|
431
|
+
john.startDate = Date.now();
|
|
432
|
+
john.subordinates = [];
|
|
433
|
+
john.title = 'Junior Person';
|
|
434
|
+
try {
|
|
435
|
+
await john.$save();
|
|
436
|
+
} catch (e: any) {
|
|
437
|
+
console.error('Error creating entity: ', e);
|
|
438
|
+
throw e;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const jane = await EmployeeModel.factory();
|
|
442
|
+
jane.name = 'Jane Doe';
|
|
443
|
+
jane.current = true;
|
|
444
|
+
jane.salary = 8000000;
|
|
445
|
+
jane.startDate = Date.now();
|
|
446
|
+
jane.subordinates = [john];
|
|
447
|
+
jane.title = 'Seniorer Person';
|
|
448
|
+
try {
|
|
449
|
+
await jane.$save();
|
|
450
|
+
} catch (e: any) {
|
|
451
|
+
console.error('Error creating entity: ', e);
|
|
452
|
+
throw e;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function createNewBoss() {
|
|
456
|
+
// Create employee that matches qref.
|
|
457
|
+
const tnymph = await nymphServer.startTransaction('qref-test');
|
|
458
|
+
const tnymphDeep = await tnymph.startTransaction('qref-deep-test');
|
|
459
|
+
|
|
460
|
+
const TEmployeeModel = tnymph.getEntityClass(EmployeeModel);
|
|
461
|
+
const newBoss = await TEmployeeModel.factory();
|
|
462
|
+
newBoss.name = 'Jill Doe';
|
|
463
|
+
newBoss.current = false;
|
|
464
|
+
newBoss.salary = 8000000;
|
|
465
|
+
newBoss.startDate = Date.now();
|
|
466
|
+
newBoss.subordinates = [john];
|
|
467
|
+
newBoss.title = 'Seniorer Person';
|
|
468
|
+
try {
|
|
469
|
+
await newBoss.$save();
|
|
470
|
+
} catch (e: any) {
|
|
471
|
+
console.error('Error creating entity: ', e);
|
|
472
|
+
throw e;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
newBoss.current = true;
|
|
476
|
+
try {
|
|
477
|
+
await newBoss.$save();
|
|
478
|
+
} catch (e: any) {
|
|
479
|
+
console.error('Error creating entity: ', e);
|
|
480
|
+
throw e;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
await tnymphDeep.commit('qref-deep-test');
|
|
484
|
+
await tnymph.commit('qref-test');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
expect(
|
|
488
|
+
await new Promise<boolean>((resolve) => {
|
|
489
|
+
if (jane.guid == null || john.guid == null) {
|
|
490
|
+
throw new Error('Entity is null.');
|
|
224
491
|
}
|
|
225
|
-
|
|
226
|
-
|
|
492
|
+
const subscription = pubsub.subscribeEntities(
|
|
493
|
+
{ class: Employee },
|
|
494
|
+
{
|
|
495
|
+
type: '&',
|
|
496
|
+
truthy: 'current',
|
|
497
|
+
qref: [
|
|
498
|
+
'subordinates',
|
|
499
|
+
[{ class: Employee }, { type: '&', guid: john.guid }],
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
)(async (update) => {
|
|
503
|
+
pubsub.updateArray(entities, update);
|
|
504
|
+
|
|
505
|
+
if (Array.isArray(update)) {
|
|
506
|
+
expect(entities.length).toEqual(1);
|
|
507
|
+
await createNewBoss();
|
|
508
|
+
} else {
|
|
509
|
+
expect(entities.length).toEqual(2);
|
|
510
|
+
subscription.unsubscribe();
|
|
511
|
+
resolve(true);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}),
|
|
515
|
+
).toEqual(true);
|
|
227
516
|
|
|
228
|
-
expect(entities.length).toEqual(
|
|
517
|
+
expect(entities.length).toEqual(2);
|
|
229
518
|
});
|
|
230
519
|
|
|
231
520
|
it('notified of removed match for qref query', async () => {
|
|
232
521
|
let [jane, john] = await createBossJane();
|
|
233
522
|
let entities: (EmployeeClass & EmployeeData)[] = [];
|
|
234
523
|
|
|
235
|
-
await new Promise((resolve) => {
|
|
524
|
+
await new Promise<void>((resolve) => {
|
|
236
525
|
if (jane.guid == null || john.guid == null) {
|
|
237
526
|
throw new Error('Entity is null.');
|
|
238
527
|
}
|
|
@@ -245,13 +534,13 @@ describe('Nymph REST Server and Client', () => {
|
|
|
245
534
|
'subordinates',
|
|
246
535
|
[{ class: Employee }, { type: '&', truthy: 'current' }],
|
|
247
536
|
],
|
|
248
|
-
}
|
|
537
|
+
},
|
|
249
538
|
)(async (update) => {
|
|
250
539
|
pubsub.updateArray(entities, update);
|
|
251
540
|
|
|
252
541
|
if (!entities.length) {
|
|
253
542
|
subscription.unsubscribe();
|
|
254
|
-
resolve(
|
|
543
|
+
resolve();
|
|
255
544
|
} else if (Array.isArray(update)) {
|
|
256
545
|
expect(update.length).toEqual(1);
|
|
257
546
|
|
|
@@ -270,9 +559,9 @@ describe('Nymph REST Server and Client', () => {
|
|
|
270
559
|
let entities: (EmployeeClass & EmployeeData)[] = [];
|
|
271
560
|
|
|
272
561
|
// Wait for change to propagate. (Only needed since we're not going across network.)
|
|
273
|
-
await new Promise((resolve) => setTimeout(() => resolve(
|
|
562
|
+
await new Promise<void>((resolve) => setTimeout(() => resolve(), 10));
|
|
274
563
|
|
|
275
|
-
await new Promise((resolve) => {
|
|
564
|
+
await new Promise<void>((resolve) => {
|
|
276
565
|
// Should only receive 1 update, since we waited.
|
|
277
566
|
let updated = false;
|
|
278
567
|
if (jane.guid == null) {
|
|
@@ -283,13 +572,13 @@ describe('Nymph REST Server and Client', () => {
|
|
|
283
572
|
{
|
|
284
573
|
type: '&',
|
|
285
574
|
guid: jane.guid,
|
|
286
|
-
}
|
|
575
|
+
},
|
|
287
576
|
)(async (update) => {
|
|
288
577
|
pubsub.updateArray(entities, update);
|
|
289
578
|
|
|
290
579
|
if (updated) {
|
|
291
580
|
subscription.unsubscribe();
|
|
292
|
-
resolve(
|
|
581
|
+
resolve();
|
|
293
582
|
} else if (Array.isArray(update)) {
|
|
294
583
|
expect(update.length).toEqual(1);
|
|
295
584
|
expect(entities[0].salary).toEqual(8000000);
|
|
@@ -305,40 +594,36 @@ describe('Nymph REST Server and Client', () => {
|
|
|
305
594
|
});
|
|
306
595
|
|
|
307
596
|
it('notified of entity delete', async () => {
|
|
308
|
-
let jane
|
|
597
|
+
let jane: EmployeeClass & EmployeeData;
|
|
309
598
|
let entities: (EmployeeClass & EmployeeData)[] = [];
|
|
310
599
|
|
|
311
|
-
|
|
312
|
-
await new Promise((resolve) =>
|
|
313
|
-
|
|
314
|
-
let length: number = -1;
|
|
315
|
-
await new Promise((resolve) => {
|
|
316
|
-
let updated = false;
|
|
317
|
-
if (jane.guid == null) {
|
|
318
|
-
throw new Error('Entity is null.');
|
|
319
|
-
}
|
|
600
|
+
let removed = false;
|
|
601
|
+
await new Promise<void>((resolve) => {
|
|
320
602
|
const subscription = pubsub.subscribeEntities(
|
|
321
603
|
{ class: Employee },
|
|
322
604
|
{
|
|
323
605
|
type: '&',
|
|
324
606
|
equal: ['name', 'Jane Doe'],
|
|
325
|
-
}
|
|
607
|
+
},
|
|
326
608
|
)(async (update) => {
|
|
327
609
|
pubsub.updateArray(entities, update);
|
|
328
610
|
|
|
329
|
-
if (
|
|
611
|
+
if (Array.isArray(update)) {
|
|
612
|
+
jane = await createJane();
|
|
613
|
+
if (jane.guid == null) {
|
|
614
|
+
throw new Error('Entity is null.');
|
|
615
|
+
}
|
|
616
|
+
} else if ('added' in update) {
|
|
617
|
+
await entities.find((e) => e.guid === update.added)?.$delete();
|
|
618
|
+
} else if ('removed' in update && update.removed === jane.guid) {
|
|
330
619
|
subscription.unsubscribe();
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
expect(update.length).toBeGreaterThan(0);
|
|
334
|
-
updated = true;
|
|
335
|
-
length = update.length;
|
|
336
|
-
await jane.$delete();
|
|
620
|
+
removed = true;
|
|
621
|
+
resolve();
|
|
337
622
|
}
|
|
338
623
|
});
|
|
339
624
|
});
|
|
340
625
|
|
|
341
|
-
expect(
|
|
626
|
+
expect(removed).toBeTruthy();
|
|
342
627
|
});
|
|
343
628
|
|
|
344
629
|
it('entire match is updated', async () => {
|
|
@@ -347,9 +632,9 @@ describe('Nymph REST Server and Client', () => {
|
|
|
347
632
|
await createJane();
|
|
348
633
|
|
|
349
634
|
// Wait for change to propagate. (Only needed since we're not going across network.)
|
|
350
|
-
await new Promise((resolve) => setTimeout(() => resolve(
|
|
635
|
+
await new Promise<void>((resolve) => setTimeout(() => resolve(), 10));
|
|
351
636
|
|
|
352
|
-
await new Promise((resolve) => {
|
|
637
|
+
await new Promise<void>((resolve) => {
|
|
353
638
|
let receivedRemove = false;
|
|
354
639
|
let receivedAdd = false;
|
|
355
640
|
const subscription = pubsub.subscribeEntities(
|
|
@@ -357,20 +642,26 @@ describe('Nymph REST Server and Client', () => {
|
|
|
357
642
|
{
|
|
358
643
|
type: '&',
|
|
359
644
|
equal: ['name', 'Jane Doe'],
|
|
360
|
-
}
|
|
645
|
+
},
|
|
361
646
|
)(async (update) => {
|
|
362
647
|
pubsub.updateArray(entities, update);
|
|
363
648
|
|
|
364
649
|
if (jane) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
650
|
+
const updates =
|
|
651
|
+
'multiple' in update && update.multiple
|
|
652
|
+
? update.messages
|
|
653
|
+
: [update];
|
|
654
|
+
for (let curUpdate of updates) {
|
|
655
|
+
if ('removed' in curUpdate) {
|
|
656
|
+
receivedRemove = true;
|
|
657
|
+
}
|
|
658
|
+
if ('added' in curUpdate) {
|
|
659
|
+
receivedAdd = true;
|
|
660
|
+
}
|
|
661
|
+
if (receivedAdd && receivedRemove) {
|
|
662
|
+
subscription.unsubscribe();
|
|
663
|
+
resolve();
|
|
664
|
+
}
|
|
374
665
|
}
|
|
375
666
|
} else if (Array.isArray(update)) {
|
|
376
667
|
expect(update.length).toEqual(1);
|
|
@@ -386,12 +677,13 @@ describe('Nymph REST Server and Client', () => {
|
|
|
386
677
|
it('entity subscription is updated', async () => {
|
|
387
678
|
let jane = await createJane();
|
|
388
679
|
|
|
389
|
-
await new Promise(async (resolve) => {
|
|
680
|
+
await new Promise<void>(async (resolve) => {
|
|
390
681
|
let mdate = 0;
|
|
391
682
|
const subscription = pubsub.subscribeWith(jane, async () => {
|
|
683
|
+
expect(jane.guid).not.toBeNull();
|
|
392
684
|
if (mdate > 0 && (jane.mdate ?? -1) === mdate) {
|
|
393
685
|
subscription.unsubscribe();
|
|
394
|
-
resolve(
|
|
686
|
+
resolve();
|
|
395
687
|
}
|
|
396
688
|
});
|
|
397
689
|
|
|
@@ -411,17 +703,84 @@ describe('Nymph REST Server and Client', () => {
|
|
|
411
703
|
expect(jane.salary).toEqual(9000000);
|
|
412
704
|
});
|
|
413
705
|
|
|
706
|
+
it("doesn't allow subscription of a restricted entity class", async () => {
|
|
707
|
+
let receivedBadUpdate = false;
|
|
708
|
+
let error = null;
|
|
709
|
+
|
|
710
|
+
await new Promise<void>((resolve) => {
|
|
711
|
+
pubsub.subscribeEntities(
|
|
712
|
+
{ class: Restricted },
|
|
713
|
+
{
|
|
714
|
+
type: '&',
|
|
715
|
+
equal: ['name', 'Jane Doe'],
|
|
716
|
+
},
|
|
717
|
+
)(
|
|
718
|
+
async () => {
|
|
719
|
+
receivedBadUpdate = true;
|
|
720
|
+
resolve();
|
|
721
|
+
},
|
|
722
|
+
(e: any) => {
|
|
723
|
+
error = e;
|
|
724
|
+
resolve();
|
|
725
|
+
},
|
|
726
|
+
);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
expect(receivedBadUpdate).toEqual(false);
|
|
730
|
+
expect(error).toEqual('Not accessible.');
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it("doesn't notify of new pubsub disabled entity class", async () => {
|
|
734
|
+
let receivedBadUpdate = false;
|
|
735
|
+
|
|
736
|
+
await new Promise<void>(async (resolve) => {
|
|
737
|
+
const subscription = await new Promise<
|
|
738
|
+
PubSubSubscription<
|
|
739
|
+
PubSubUpdate<(PubSubDisabledClass & PubSubDisabledData)[]>
|
|
740
|
+
>
|
|
741
|
+
>(async (resolve) => {
|
|
742
|
+
let updated = false;
|
|
743
|
+
const subscription = pubsub.subscribeEntities({
|
|
744
|
+
class: PubSubDisabled,
|
|
745
|
+
})(async (update) => {
|
|
746
|
+
if (updated) {
|
|
747
|
+
receivedBadUpdate = true;
|
|
748
|
+
} else {
|
|
749
|
+
expect(update).toEqual([]);
|
|
750
|
+
updated = true;
|
|
751
|
+
try {
|
|
752
|
+
const entity = await PubSubDisabled.factory();
|
|
753
|
+
entity.name = 'Someone';
|
|
754
|
+
if (!(await entity.$save())) {
|
|
755
|
+
throw new Error("Couldn't save.");
|
|
756
|
+
}
|
|
757
|
+
resolve(subscription);
|
|
758
|
+
} catch (e: any) {
|
|
759
|
+
console.error('Error creating entity: ', e);
|
|
760
|
+
throw e;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
});
|
|
765
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 200));
|
|
766
|
+
subscription.unsubscribe();
|
|
767
|
+
resolve();
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
expect(receivedBadUpdate).toEqual(false);
|
|
771
|
+
});
|
|
772
|
+
|
|
414
773
|
it('new uid', async () => {
|
|
415
|
-
await new Promise(async (resolve) => {
|
|
774
|
+
await new Promise<void>(async (resolve) => {
|
|
416
775
|
const subscription = pubsub.subscribeUID('testNewUID')(
|
|
417
776
|
async (value) => {
|
|
418
777
|
expect(value).toEqual(directValue);
|
|
419
778
|
subscription.unsubscribe();
|
|
420
|
-
resolve(
|
|
779
|
+
resolve();
|
|
421
780
|
},
|
|
422
781
|
(err) => {
|
|
423
782
|
expect(err.status).toEqual(404);
|
|
424
|
-
}
|
|
783
|
+
},
|
|
425
784
|
);
|
|
426
785
|
|
|
427
786
|
const directValue = await nymph.newUID('testNewUID');
|
|
@@ -429,7 +788,7 @@ describe('Nymph REST Server and Client', () => {
|
|
|
429
788
|
});
|
|
430
789
|
|
|
431
790
|
it('increasing uids', async () => {
|
|
432
|
-
await new Promise(async (resolve) => {
|
|
791
|
+
await new Promise<void>(async (resolve) => {
|
|
433
792
|
let receivedFirst = false;
|
|
434
793
|
let lastUpdate: number = 0;
|
|
435
794
|
const subscription = pubsub.subscribeUID('testIncUID')(
|
|
@@ -444,12 +803,12 @@ describe('Nymph REST Server and Client', () => {
|
|
|
444
803
|
lastUpdate = value;
|
|
445
804
|
if (value == 100) {
|
|
446
805
|
subscription.unsubscribe();
|
|
447
|
-
resolve(
|
|
806
|
+
resolve();
|
|
448
807
|
}
|
|
449
808
|
},
|
|
450
809
|
(err) => {
|
|
451
810
|
expect(err.status).toEqual(404);
|
|
452
|
-
}
|
|
811
|
+
},
|
|
453
812
|
);
|
|
454
813
|
|
|
455
814
|
await nymph.deleteUID('testIncUID');
|
|
@@ -462,16 +821,16 @@ describe('Nymph REST Server and Client', () => {
|
|
|
462
821
|
});
|
|
463
822
|
|
|
464
823
|
it('set uid', async () => {
|
|
465
|
-
await new Promise(async (resolve) => {
|
|
824
|
+
await new Promise<void>(async (resolve) => {
|
|
466
825
|
const subscription = pubsub.subscribeUID('testSetUID')(
|
|
467
826
|
async (value) => {
|
|
468
827
|
expect(value).toEqual(123);
|
|
469
828
|
subscription.unsubscribe();
|
|
470
|
-
resolve(
|
|
829
|
+
resolve();
|
|
471
830
|
},
|
|
472
831
|
(err) => {
|
|
473
832
|
expect(err.status).toEqual(404);
|
|
474
|
-
}
|
|
833
|
+
},
|
|
475
834
|
);
|
|
476
835
|
|
|
477
836
|
await nymph.setUID('testSetUID', 123);
|
|
@@ -479,7 +838,7 @@ describe('Nymph REST Server and Client', () => {
|
|
|
479
838
|
});
|
|
480
839
|
|
|
481
840
|
it('rename uid from old name', async () => {
|
|
482
|
-
await new Promise(async (resolve) => {
|
|
841
|
+
await new Promise<void>(async (resolve) => {
|
|
483
842
|
let updated = false;
|
|
484
843
|
const subscription = pubsub.subscribeUID('testRenameUID')(
|
|
485
844
|
async (value, event) => {
|
|
@@ -487,15 +846,15 @@ describe('Nymph REST Server and Client', () => {
|
|
|
487
846
|
expect(event).toEqual('renameUID');
|
|
488
847
|
expect(value).toEqual(null);
|
|
489
848
|
subscription.unsubscribe();
|
|
490
|
-
resolve(
|
|
491
|
-
} else {
|
|
849
|
+
resolve();
|
|
850
|
+
} else if (event === 'setUID') {
|
|
492
851
|
expect(value).toEqual(456);
|
|
493
852
|
updated = true;
|
|
494
853
|
}
|
|
495
854
|
},
|
|
496
855
|
(err) => {
|
|
497
856
|
expect(err.status).toEqual(404);
|
|
498
|
-
}
|
|
857
|
+
},
|
|
499
858
|
);
|
|
500
859
|
|
|
501
860
|
await nymph.setUID('testRenameUID', 456);
|
|
@@ -504,17 +863,17 @@ describe('Nymph REST Server and Client', () => {
|
|
|
504
863
|
});
|
|
505
864
|
|
|
506
865
|
it('rename uid from new name', async () => {
|
|
507
|
-
await new Promise(async (resolve) => {
|
|
866
|
+
await new Promise<void>(async (resolve) => {
|
|
508
867
|
const subscription = pubsub.subscribeUID('newRename2UID')(
|
|
509
868
|
async (value, event) => {
|
|
510
869
|
expect(event).toEqual('setUID');
|
|
511
870
|
expect(value).toEqual(456);
|
|
512
871
|
subscription.unsubscribe();
|
|
513
|
-
resolve(
|
|
872
|
+
resolve();
|
|
514
873
|
},
|
|
515
874
|
(err) => {
|
|
516
875
|
expect(err.status).toEqual(404);
|
|
517
|
-
}
|
|
876
|
+
},
|
|
518
877
|
);
|
|
519
878
|
|
|
520
879
|
await nymph.setUID('testRename2UID', 456);
|
|
@@ -525,38 +884,51 @@ describe('Nymph REST Server and Client', () => {
|
|
|
525
884
|
it('delete uid', async () => {
|
|
526
885
|
await nymph.setUID('testDeleteUID', 789);
|
|
527
886
|
|
|
528
|
-
await new Promise(async (resolve) => {
|
|
887
|
+
await new Promise<void>(async (resolve) => {
|
|
529
888
|
let updated = false;
|
|
530
889
|
const subscription = pubsub.subscribeUID('testDeleteUID')(
|
|
531
890
|
async (value, event) => {
|
|
532
891
|
if (updated && event === 'deleteUID') {
|
|
533
892
|
expect(value).toEqual(null);
|
|
534
893
|
subscription.unsubscribe();
|
|
535
|
-
resolve(
|
|
894
|
+
resolve();
|
|
536
895
|
} else {
|
|
537
896
|
expect(value).toEqual(789);
|
|
538
897
|
updated = true;
|
|
898
|
+
await nymph.deleteUID('testDeleteUID');
|
|
539
899
|
}
|
|
540
900
|
},
|
|
541
901
|
(err) => {
|
|
542
902
|
expect(err.status).toEqual(404);
|
|
543
|
-
}
|
|
903
|
+
},
|
|
544
904
|
);
|
|
545
|
-
|
|
546
|
-
await nymph.deleteUID('testDeleteUID');
|
|
547
905
|
});
|
|
548
906
|
});
|
|
549
907
|
|
|
908
|
+
beforeEach(async () => {
|
|
909
|
+
pubsub.connect();
|
|
910
|
+
while (!pubsub.isConnectionOpen()) {
|
|
911
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 20));
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
|
|
550
915
|
afterAll(async () => {
|
|
551
|
-
//
|
|
552
|
-
|
|
916
|
+
// Don't publish anything after the tests.
|
|
917
|
+
removePublisher();
|
|
918
|
+
|
|
919
|
+
// Avoid jest open handle errors.
|
|
920
|
+
const closed = new Promise<void>((resolve) => {
|
|
553
921
|
pubsub.on('disconnect', () => {
|
|
554
|
-
resolve(
|
|
922
|
+
resolve();
|
|
555
923
|
});
|
|
556
924
|
});
|
|
557
925
|
pubsub.close(); // close PubSub client.
|
|
558
926
|
await closed;
|
|
559
927
|
pubsubServer.close(); // close PubSub server.
|
|
560
928
|
server.close(); // close REST server.
|
|
929
|
+
|
|
930
|
+
// Wait for the next event loop to prevent websocket importing something
|
|
931
|
+
// after jest teardown.
|
|
932
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
561
933
|
});
|
|
562
934
|
});
|