@nymphjs/pubsub 1.0.0-beta.13 → 1.0.0-beta.130

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.
@@ -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 { Nymph, PubSub } from '@nymphjs/client-node';
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:5081/'],
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
- PubSubServer.initPublisher(pubSubConfig, nymphServer);
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(5080);
42
+ const server = app.listen(5082);
32
43
 
33
- const pubsubServer = createServer(5081, pubSubConfig, nymphServer);
44
+ const pubsubServer = createServer(5083, pubSubConfig, nymphServer);
34
45
 
35
46
  const nymphOptions = {
36
- restUrl: 'http://localhost:5080/',
37
- pubsubUrl: 'ws://localhost:5081/',
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(true);
125
+ resolve();
112
126
  } else {
113
127
  expect(update).toEqual([]);
114
128
  updated = true;
@@ -122,14 +136,14 @@ describe('Nymph REST Server and Client', () => {
122
136
  let guid: string;
123
137
  let committed = false;
124
138
 
125
- await new Promise((resolve) => {
139
+ await new Promise<void>((resolve) => {
126
140
  let updated = false;
127
141
  const subscription = pubsub.subscribeEntities(
128
142
  { class: Employee },
129
143
  {
130
144
  type: '&',
131
145
  equal: ['name', 'Steve Transaction'],
132
- }
146
+ },
133
147
  )(async (update) => {
134
148
  if (updated) {
135
149
  if (committed) {
@@ -139,14 +153,12 @@ describe('Nymph REST Server and Client', () => {
139
153
  throw new Error('Update arrived before transaction committed.');
140
154
  }
141
155
  subscription.unsubscribe();
142
- resolve(true);
156
+ resolve();
143
157
  } else {
144
158
  expect(update).toEqual([]);
145
159
  updated = true;
146
160
  const tnymph = await nymphServer.startTransaction('steve');
147
- const Employee = tnymph.getEntityClass(
148
- EmployeeModel.class
149
- ) as typeof EmployeeModel;
161
+ const Employee = tnymph.getEntityClass(EmployeeModel);
150
162
  const steve = await Employee.factory();
151
163
  steve.name = 'Steve Transaction';
152
164
  steve.current = true;
@@ -157,7 +169,7 @@ describe('Nymph REST Server and Client', () => {
157
169
  try {
158
170
  await steve.$save();
159
171
  guid = steve.guid as string;
160
- await new Promise((res) => setTimeout(res, 1000));
172
+ await new Promise<void>((res) => setTimeout(res, 1000));
161
173
  committed = true;
162
174
  await tnymph.commit('steve');
163
175
  } catch (e: any) {
@@ -172,14 +184,14 @@ describe('Nymph REST Server and Client', () => {
172
184
  it('not notified of new match when transaction rolled back', async () => {
173
185
  let guid: string;
174
186
 
175
- await new Promise((resolve) => {
187
+ await new Promise<void>((resolve) => {
176
188
  let updated = false;
177
189
  const subscription = pubsub.subscribeEntities(
178
190
  { class: Employee },
179
191
  {
180
192
  type: '&',
181
193
  equal: ['name', 'Steve Rollback'],
182
- }
194
+ },
183
195
  )(async (update) => {
184
196
  if (updated) {
185
197
  if ('added' in update && update.added === guid) {
@@ -190,9 +202,7 @@ describe('Nymph REST Server and Client', () => {
190
202
  expect(update).toEqual([]);
191
203
  updated = true;
192
204
  const tnymph = await nymphServer.startTransaction('steve');
193
- const Employee = tnymph.getEntityClass(
194
- EmployeeModel.class
195
- ) as typeof EmployeeModel;
205
+ const Employee = tnymph.getEntityClass(EmployeeModel);
196
206
  const steve = await Employee.factory();
197
207
  steve.name = 'Steve Rollback';
198
208
  steve.current = true;
@@ -203,11 +213,11 @@ describe('Nymph REST Server and Client', () => {
203
213
  try {
204
214
  await steve.$save();
205
215
  guid = steve.guid as string;
206
- await new Promise((res) => setTimeout(res, 600));
216
+ await new Promise<void>((res) => setTimeout(res, 600));
207
217
  await tnymph.rollback('steve');
208
- await new Promise((res) => setTimeout(res, 600));
218
+ await new Promise<void>((res) => setTimeout(res, 600));
209
219
  subscription.unsubscribe();
210
- resolve(true);
220
+ resolve();
211
221
  } catch (e: any) {
212
222
  console.error('Error creating entity: ', e);
213
223
  throw e;
@@ -221,14 +231,14 @@ describe('Nymph REST Server and Client', () => {
221
231
  let guid: string;
222
232
  let committed = false;
223
233
 
224
- await new Promise((resolve) => {
234
+ await new Promise<void>((resolve) => {
225
235
  let updated = false;
226
236
  const subscription = pubsub.subscribeEntities(
227
237
  { class: Employee },
228
238
  {
229
239
  type: '&',
230
240
  equal: ['name', 'Steve Complex'],
231
- }
241
+ },
232
242
  )(async (update) => {
233
243
  if (updated) {
234
244
  if (committed) {
@@ -238,7 +248,7 @@ describe('Nymph REST Server and Client', () => {
238
248
  throw new Error('Update arrived before transaction committed.');
239
249
  }
240
250
  subscription.unsubscribe();
241
- resolve(true);
251
+ resolve();
242
252
  } else {
243
253
  expect(update).toEqual([]);
244
254
  updated = true;
@@ -246,9 +256,7 @@ describe('Nymph REST Server and Client', () => {
246
256
 
247
257
  // Start a transaction that ultimately gets rolled back.
248
258
  const tnymphB = await tnymphTop.startTransaction('steve-b');
249
- const EmployeeB = tnymphB.getEntityClass(
250
- EmployeeModel.class
251
- ) as typeof EmployeeModel;
259
+ const EmployeeB = tnymphB.getEntityClass(EmployeeModel);
252
260
  const badSteve = await EmployeeB.factory();
253
261
  badSteve.name = 'Steve Complex';
254
262
  badSteve.current = true;
@@ -258,9 +266,9 @@ describe('Nymph REST Server and Client', () => {
258
266
  badSteve.title = 'Seniorer Person';
259
267
  try {
260
268
  await badSteve.$save();
261
- await new Promise((res) => setTimeout(res, 200));
269
+ await new Promise<void>((res) => setTimeout(res, 200));
262
270
  await tnymphB.rollback('steve-b');
263
- await new Promise((res) => setTimeout(res, 200));
271
+ await new Promise<void>((res) => setTimeout(res, 200));
264
272
  } catch (e: any) {
265
273
  console.error('Error creating entity: ', e);
266
274
  throw e;
@@ -268,9 +276,7 @@ describe('Nymph REST Server and Client', () => {
268
276
 
269
277
  // Start a transaction that ultimately gets committed.
270
278
  const tnymphA = await tnymphTop.startTransaction('steve-a');
271
- const EmployeeA = tnymphA.getEntityClass(
272
- EmployeeModel.class
273
- ) as typeof EmployeeModel;
279
+ const EmployeeA = tnymphA.getEntityClass(EmployeeModel);
274
280
  const goodSteve = await EmployeeA.factory();
275
281
  goodSteve.name = 'Steve Complex';
276
282
  goodSteve.current = true;
@@ -281,9 +287,9 @@ describe('Nymph REST Server and Client', () => {
281
287
  try {
282
288
  await goodSteve.$save();
283
289
  guid = goodSteve.guid as string;
284
- await new Promise((res) => setTimeout(res, 200));
290
+ await new Promise<void>((res) => setTimeout(res, 200));
285
291
  await tnymphA.commit('steve-a');
286
- await new Promise((res) => setTimeout(res, 400));
292
+ await new Promise<void>((res) => setTimeout(res, 400));
287
293
  committed = true;
288
294
  await tnymphTop.commit('steve-top');
289
295
  } catch (e: any) {
@@ -299,7 +305,7 @@ describe('Nymph REST Server and Client', () => {
299
305
  let jane = await createJane();
300
306
  let entities: (EmployeeClass & EmployeeData)[] = [];
301
307
 
302
- await new Promise((resolve) => {
308
+ await new Promise<void>((resolve) => {
303
309
  let mdate = 0;
304
310
  if (jane.guid == null) {
305
311
  throw new Error('Entity is null.');
@@ -309,13 +315,13 @@ describe('Nymph REST Server and Client', () => {
309
315
  {
310
316
  type: '&',
311
317
  guid: jane.guid,
312
- }
318
+ },
313
319
  )(async (update) => {
314
320
  pubsub.updateArray(entities, update);
315
321
 
316
322
  if (mdate > 0 && (entities[0]?.mdate ?? -1) === mdate) {
317
323
  subscription.unsubscribe();
318
- resolve(true);
324
+ resolve();
319
325
  } else if (Array.isArray(update)) {
320
326
  expect(update.length).toEqual(1);
321
327
  expect(entities[0].salary).toEqual(8000000);
@@ -334,7 +340,7 @@ describe('Nymph REST Server and Client', () => {
334
340
  let [jane, john] = await createBossJane();
335
341
  let entities: (EmployeeClass & EmployeeData)[] = [];
336
342
 
337
- await new Promise((resolve) => {
343
+ await new Promise<void>((resolve) => {
338
344
  let mdate = 0;
339
345
  if (jane.guid == null || john.guid == null) {
340
346
  throw new Error('Entity is null.');
@@ -347,13 +353,13 @@ describe('Nymph REST Server and Client', () => {
347
353
  'subordinates',
348
354
  [{ class: Employee }, { type: '&', guid: john.guid }],
349
355
  ],
350
- }
356
+ },
351
357
  )(async (update) => {
352
358
  pubsub.updateArray(entities, update);
353
359
 
354
360
  if (mdate > 0 && (entities[0]?.mdate ?? -1) === mdate) {
355
361
  subscription.unsubscribe();
356
- resolve(true);
362
+ resolve();
357
363
  } else if (Array.isArray(update)) {
358
364
  expect(update.length).toEqual(1);
359
365
  expect(entities[0].salary).toEqual(8000000);
@@ -372,44 +378,150 @@ describe('Nymph REST Server and Client', () => {
372
378
  let [jane, john] = await createBossJane();
373
379
  let entities: (EmployeeClass & EmployeeData)[] = [];
374
380
 
375
- await new Promise((resolve) => {
376
- if (jane.guid == null || john.guid == null) {
377
- throw new Error('Entity is null.');
378
- }
379
- const subscription = pubsub.subscribeEntities(
380
- { class: Employee },
381
- {
382
- type: '&',
383
- guid: jane.guid,
384
- qref: [
385
- 'subordinates',
386
- [{ class: Employee }, { type: '&', '!truthy': 'current' }],
387
- ],
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.');
388
393
  }
389
- )(async (update) => {
390
- pubsub.updateArray(entities, update);
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);
391
412
 
392
- if (entities.length) {
393
- subscription.unsubscribe();
394
- resolve(true);
395
- } else if (Array.isArray(update)) {
396
- expect(update.length).toEqual(0);
413
+ // John gets fired.
414
+ john.current = false;
415
+ await john.$save();
416
+ }
417
+ });
418
+ }),
419
+ ).toEqual(true);
397
420
 
398
- // John gets fired.
399
- john.current = false;
400
- await john.$save();
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.');
401
491
  }
402
- });
403
- });
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);
404
516
 
405
- expect(entities.length).toEqual(1);
517
+ expect(entities.length).toEqual(2);
406
518
  });
407
519
 
408
520
  it('notified of removed match for qref query', async () => {
409
521
  let [jane, john] = await createBossJane();
410
522
  let entities: (EmployeeClass & EmployeeData)[] = [];
411
523
 
412
- await new Promise((resolve) => {
524
+ await new Promise<void>((resolve) => {
413
525
  if (jane.guid == null || john.guid == null) {
414
526
  throw new Error('Entity is null.');
415
527
  }
@@ -422,13 +534,13 @@ describe('Nymph REST Server and Client', () => {
422
534
  'subordinates',
423
535
  [{ class: Employee }, { type: '&', truthy: 'current' }],
424
536
  ],
425
- }
537
+ },
426
538
  )(async (update) => {
427
539
  pubsub.updateArray(entities, update);
428
540
 
429
541
  if (!entities.length) {
430
542
  subscription.unsubscribe();
431
- resolve(true);
543
+ resolve();
432
544
  } else if (Array.isArray(update)) {
433
545
  expect(update.length).toEqual(1);
434
546
 
@@ -447,9 +559,9 @@ describe('Nymph REST Server and Client', () => {
447
559
  let entities: (EmployeeClass & EmployeeData)[] = [];
448
560
 
449
561
  // Wait for change to propagate. (Only needed since we're not going across network.)
450
- await new Promise((resolve) => setTimeout(() => resolve(true), 10));
562
+ await new Promise<void>((resolve) => setTimeout(() => resolve(), 10));
451
563
 
452
- await new Promise((resolve) => {
564
+ await new Promise<void>((resolve) => {
453
565
  // Should only receive 1 update, since we waited.
454
566
  let updated = false;
455
567
  if (jane.guid == null) {
@@ -460,13 +572,13 @@ describe('Nymph REST Server and Client', () => {
460
572
  {
461
573
  type: '&',
462
574
  guid: jane.guid,
463
- }
575
+ },
464
576
  )(async (update) => {
465
577
  pubsub.updateArray(entities, update);
466
578
 
467
579
  if (updated) {
468
580
  subscription.unsubscribe();
469
- resolve(true);
581
+ resolve();
470
582
  } else if (Array.isArray(update)) {
471
583
  expect(update.length).toEqual(1);
472
584
  expect(entities[0].salary).toEqual(8000000);
@@ -482,40 +594,36 @@ describe('Nymph REST Server and Client', () => {
482
594
  });
483
595
 
484
596
  it('notified of entity delete', async () => {
485
- let jane = await createJane();
597
+ let jane: EmployeeClass & EmployeeData;
486
598
  let entities: (EmployeeClass & EmployeeData)[] = [];
487
599
 
488
- // Wait for change to propagate. (Only needed since we're not going across network.)
489
- await new Promise((resolve) => setTimeout(() => resolve(true), 10));
490
-
491
- let length: number = -1;
492
- await new Promise((resolve) => {
493
- let updated = false;
494
- if (jane.guid == null) {
495
- throw new Error('Entity is null.');
496
- }
600
+ let removed = false;
601
+ await new Promise<void>((resolve) => {
497
602
  const subscription = pubsub.subscribeEntities(
498
603
  { class: Employee },
499
604
  {
500
605
  type: '&',
501
606
  equal: ['name', 'Jane Doe'],
502
- }
607
+ },
503
608
  )(async (update) => {
504
609
  pubsub.updateArray(entities, update);
505
610
 
506
- if (updated) {
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) {
507
619
  subscription.unsubscribe();
508
- resolve(true);
509
- } else if (Array.isArray(update)) {
510
- expect(update.length).toBeGreaterThan(0);
511
- updated = true;
512
- length = update.length;
513
- await jane.$delete();
620
+ removed = true;
621
+ resolve();
514
622
  }
515
623
  });
516
624
  });
517
625
 
518
- expect(entities.length).toEqual(length - 1);
626
+ expect(removed).toBeTruthy();
519
627
  });
520
628
 
521
629
  it('entire match is updated', async () => {
@@ -524,9 +632,9 @@ describe('Nymph REST Server and Client', () => {
524
632
  await createJane();
525
633
 
526
634
  // Wait for change to propagate. (Only needed since we're not going across network.)
527
- await new Promise((resolve) => setTimeout(() => resolve(true), 10));
635
+ await new Promise<void>((resolve) => setTimeout(() => resolve(), 10));
528
636
 
529
- await new Promise((resolve) => {
637
+ await new Promise<void>((resolve) => {
530
638
  let receivedRemove = false;
531
639
  let receivedAdd = false;
532
640
  const subscription = pubsub.subscribeEntities(
@@ -534,20 +642,26 @@ describe('Nymph REST Server and Client', () => {
534
642
  {
535
643
  type: '&',
536
644
  equal: ['name', 'Jane Doe'],
537
- }
645
+ },
538
646
  )(async (update) => {
539
647
  pubsub.updateArray(entities, update);
540
648
 
541
649
  if (jane) {
542
- if ('removed' in update) {
543
- receivedRemove = true;
544
- }
545
- if ('added' in update) {
546
- receivedAdd = true;
547
- }
548
- if (receivedAdd && receivedRemove) {
549
- subscription.unsubscribe();
550
- resolve(true);
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
+ }
551
665
  }
552
666
  } else if (Array.isArray(update)) {
553
667
  expect(update.length).toEqual(1);
@@ -563,12 +677,13 @@ describe('Nymph REST Server and Client', () => {
563
677
  it('entity subscription is updated', async () => {
564
678
  let jane = await createJane();
565
679
 
566
- await new Promise(async (resolve) => {
680
+ await new Promise<void>(async (resolve) => {
567
681
  let mdate = 0;
568
682
  const subscription = pubsub.subscribeWith(jane, async () => {
683
+ expect(jane.guid).not.toBeNull();
569
684
  if (mdate > 0 && (jane.mdate ?? -1) === mdate) {
570
685
  subscription.unsubscribe();
571
- resolve(true);
686
+ resolve();
572
687
  }
573
688
  });
574
689
 
@@ -588,17 +703,86 @@ describe('Nymph REST Server and Client', () => {
588
703
  expect(jane.salary).toEqual(9000000);
589
704
  });
590
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
+ try {
755
+ await entity.$save();
756
+ } catch (e: any) {
757
+ throw new Error("Couldn't save.");
758
+ }
759
+ resolve(subscription);
760
+ } catch (e: any) {
761
+ console.error('Error creating entity: ', e);
762
+ throw e;
763
+ }
764
+ }
765
+ });
766
+ });
767
+ await new Promise<void>((resolve) => setTimeout(resolve, 200));
768
+ subscription.unsubscribe();
769
+ resolve();
770
+ });
771
+
772
+ expect(receivedBadUpdate).toEqual(false);
773
+ });
774
+
591
775
  it('new uid', async () => {
592
- await new Promise(async (resolve) => {
776
+ await new Promise<void>(async (resolve) => {
593
777
  const subscription = pubsub.subscribeUID('testNewUID')(
594
778
  async (value) => {
595
779
  expect(value).toEqual(directValue);
596
780
  subscription.unsubscribe();
597
- resolve(true);
781
+ resolve();
598
782
  },
599
783
  (err) => {
600
784
  expect(err.status).toEqual(404);
601
- }
785
+ },
602
786
  );
603
787
 
604
788
  const directValue = await nymph.newUID('testNewUID');
@@ -606,49 +790,58 @@ describe('Nymph REST Server and Client', () => {
606
790
  });
607
791
 
608
792
  it('increasing uids', async () => {
609
- await new Promise(async (resolve) => {
793
+ await new Promise<void>(async (finalResolve) => {
610
794
  let receivedFirst = false;
611
795
  let lastUpdate: number = 0;
796
+
797
+ // Don't go too fast.
798
+ let { promise, resolve } = Promise.withResolvers<void>();
799
+
612
800
  const subscription = pubsub.subscribeUID('testIncUID')(
613
801
  async (value) => {
614
802
  if (!receivedFirst) {
615
803
  receivedFirst = true;
616
804
  expect(value).toBeNull();
805
+ resolve();
617
806
  return;
618
807
  }
619
808
 
620
809
  expect(value).toEqual(lastUpdate + 1);
621
810
  lastUpdate = value;
811
+ resolve();
812
+
622
813
  if (value == 100) {
623
814
  subscription.unsubscribe();
624
- resolve(true);
815
+ finalResolve();
625
816
  }
626
817
  },
627
818
  (err) => {
628
819
  expect(err.status).toEqual(404);
629
- }
820
+ },
630
821
  );
631
822
 
632
823
  await nymph.deleteUID('testIncUID');
633
824
 
634
825
  let directValue: number = -1;
635
826
  while (directValue < 100) {
827
+ await promise;
828
+ ({ promise, resolve } = Promise.withResolvers<void>());
636
829
  directValue = await nymph.newUID('testIncUID');
637
830
  }
638
831
  });
639
832
  });
640
833
 
641
834
  it('set uid', async () => {
642
- await new Promise(async (resolve) => {
835
+ await new Promise<void>(async (resolve) => {
643
836
  const subscription = pubsub.subscribeUID('testSetUID')(
644
837
  async (value) => {
645
838
  expect(value).toEqual(123);
646
839
  subscription.unsubscribe();
647
- resolve(true);
840
+ resolve();
648
841
  },
649
842
  (err) => {
650
843
  expect(err.status).toEqual(404);
651
- }
844
+ },
652
845
  );
653
846
 
654
847
  await nymph.setUID('testSetUID', 123);
@@ -656,7 +849,7 @@ describe('Nymph REST Server and Client', () => {
656
849
  });
657
850
 
658
851
  it('rename uid from old name', async () => {
659
- await new Promise(async (resolve) => {
852
+ await new Promise<void>(async (resolve) => {
660
853
  let updated = false;
661
854
  const subscription = pubsub.subscribeUID('testRenameUID')(
662
855
  async (value, event) => {
@@ -664,15 +857,15 @@ describe('Nymph REST Server and Client', () => {
664
857
  expect(event).toEqual('renameUID');
665
858
  expect(value).toEqual(null);
666
859
  subscription.unsubscribe();
667
- resolve(true);
668
- } else {
860
+ resolve();
861
+ } else if (event === 'setUID') {
669
862
  expect(value).toEqual(456);
670
863
  updated = true;
671
864
  }
672
865
  },
673
866
  (err) => {
674
867
  expect(err.status).toEqual(404);
675
- }
868
+ },
676
869
  );
677
870
 
678
871
  await nymph.setUID('testRenameUID', 456);
@@ -681,17 +874,17 @@ describe('Nymph REST Server and Client', () => {
681
874
  });
682
875
 
683
876
  it('rename uid from new name', async () => {
684
- await new Promise(async (resolve) => {
877
+ await new Promise<void>(async (resolve) => {
685
878
  const subscription = pubsub.subscribeUID('newRename2UID')(
686
879
  async (value, event) => {
687
880
  expect(event).toEqual('setUID');
688
881
  expect(value).toEqual(456);
689
882
  subscription.unsubscribe();
690
- resolve(true);
883
+ resolve();
691
884
  },
692
885
  (err) => {
693
886
  expect(err.status).toEqual(404);
694
- }
887
+ },
695
888
  );
696
889
 
697
890
  await nymph.setUID('testRename2UID', 456);
@@ -702,38 +895,51 @@ describe('Nymph REST Server and Client', () => {
702
895
  it('delete uid', async () => {
703
896
  await nymph.setUID('testDeleteUID', 789);
704
897
 
705
- await new Promise(async (resolve) => {
898
+ await new Promise<void>(async (resolve) => {
706
899
  let updated = false;
707
900
  const subscription = pubsub.subscribeUID('testDeleteUID')(
708
901
  async (value, event) => {
709
902
  if (updated && event === 'deleteUID') {
710
903
  expect(value).toEqual(null);
711
904
  subscription.unsubscribe();
712
- resolve(true);
905
+ resolve();
713
906
  } else {
714
907
  expect(value).toEqual(789);
715
908
  updated = true;
909
+ await nymph.deleteUID('testDeleteUID');
716
910
  }
717
911
  },
718
912
  (err) => {
719
913
  expect(err.status).toEqual(404);
720
- }
914
+ },
721
915
  );
722
-
723
- await nymph.deleteUID('testDeleteUID');
724
916
  });
725
917
  });
726
918
 
919
+ beforeEach(async () => {
920
+ pubsub.connect();
921
+ while (!pubsub.isConnectionOpen()) {
922
+ await new Promise<void>((resolve) => setTimeout(resolve, 20));
923
+ }
924
+ });
925
+
727
926
  afterAll(async () => {
728
- // avoid jest open handle error
729
- const closed = new Promise((resolve) => {
927
+ // Don't publish anything after the tests.
928
+ removePublisher();
929
+
930
+ // Avoid jest open handle errors.
931
+ const closed = new Promise<void>((resolve) => {
730
932
  pubsub.on('disconnect', () => {
731
- resolve(true);
933
+ resolve();
732
934
  });
733
935
  });
734
936
  pubsub.close(); // close PubSub client.
735
937
  await closed;
736
938
  pubsubServer.close(); // close PubSub server.
737
939
  server.close(); // close REST server.
940
+
941
+ // Wait for the next event loop to prevent websocket importing something
942
+ // after jest teardown.
943
+ await new Promise((resolve) => setImmediate(resolve));
738
944
  });
739
945
  });