@nymphjs/pubsub 1.0.0-beta.12 → 1.0.0-beta.121

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;
@@ -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(true);
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(true);
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
- await new Promise((resolve) => {
199
- if (jane.guid == null || john.guid == null) {
200
- throw new Error('Entity is null.');
201
- }
202
- const subscription = pubsub.subscribeEntities(
203
- { class: Employee },
204
- {
205
- type: '&',
206
- guid: jane.guid,
207
- qref: [
208
- 'subordinates',
209
- [{ class: Employee }, { type: '&', '!truthy': 'current' }],
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
- )(async (update) => {
213
- 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);
214
412
 
215
- if (entities.length) {
216
- subscription.unsubscribe();
217
- resolve(true);
218
- } else if (Array.isArray(update)) {
219
- expect(update.length).toEqual(0);
413
+ // John gets fired.
414
+ john.current = false;
415
+ await john.$save();
416
+ }
417
+ });
418
+ }),
419
+ ).toEqual(true);
220
420
 
221
- // John gets fired.
222
- john.current = false;
223
- 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.');
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(1);
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(true);
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(true), 10));
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(true);
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 = await createJane();
597
+ let jane: EmployeeClass & EmployeeData;
309
598
  let entities: (EmployeeClass & EmployeeData)[] = [];
310
599
 
311
- // Wait for change to propagate. (Only needed since we're not going across network.)
312
- await new Promise((resolve) => setTimeout(() => resolve(true), 10));
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 (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) {
330
619
  subscription.unsubscribe();
331
- resolve(true);
332
- } else if (Array.isArray(update)) {
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(entities.length).toEqual(length - 1);
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(true), 10));
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
- if ('removed' in update) {
366
- receivedRemove = true;
367
- }
368
- if ('added' in update) {
369
- receivedAdd = true;
370
- }
371
- if (receivedAdd && receivedRemove) {
372
- subscription.unsubscribe();
373
- 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
+ }
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(true);
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(true);
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(true);
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(true);
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(true);
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(true);
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(true);
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
- // avoid jest open handle error
552
- const closed = new Promise((resolve) => {
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(true);
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
  });