@nymphjs/pubsub 1.0.0-beta.11 → 1.0.0-beta.111

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,7 +642,7 @@ 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
 
@@ -370,7 +655,7 @@ describe('Nymph REST Server and Client', () => {
370
655
  }
371
656
  if (receivedAdd && receivedRemove) {
372
657
  subscription.unsubscribe();
373
- resolve(true);
658
+ resolve();
374
659
  }
375
660
  } else if (Array.isArray(update)) {
376
661
  expect(update.length).toEqual(1);
@@ -386,12 +671,13 @@ describe('Nymph REST Server and Client', () => {
386
671
  it('entity subscription is updated', async () => {
387
672
  let jane = await createJane();
388
673
 
389
- await new Promise(async (resolve) => {
674
+ await new Promise<void>(async (resolve) => {
390
675
  let mdate = 0;
391
676
  const subscription = pubsub.subscribeWith(jane, async () => {
677
+ expect(jane.guid).not.toBeNull();
392
678
  if (mdate > 0 && (jane.mdate ?? -1) === mdate) {
393
679
  subscription.unsubscribe();
394
- resolve(true);
680
+ resolve();
395
681
  }
396
682
  });
397
683
 
@@ -411,17 +697,84 @@ describe('Nymph REST Server and Client', () => {
411
697
  expect(jane.salary).toEqual(9000000);
412
698
  });
413
699
 
700
+ it("doesn't allow subscription of a restricted entity class", async () => {
701
+ let receivedBadUpdate = false;
702
+ let error = null;
703
+
704
+ await new Promise<void>((resolve) => {
705
+ pubsub.subscribeEntities(
706
+ { class: Restricted },
707
+ {
708
+ type: '&',
709
+ equal: ['name', 'Jane Doe'],
710
+ },
711
+ )(
712
+ async () => {
713
+ receivedBadUpdate = true;
714
+ resolve();
715
+ },
716
+ (e: any) => {
717
+ error = e;
718
+ resolve();
719
+ },
720
+ );
721
+ });
722
+
723
+ expect(receivedBadUpdate).toEqual(false);
724
+ expect(error).toEqual('Not accessible.');
725
+ });
726
+
727
+ it("doesn't notify of new pubsub disabled entity class", async () => {
728
+ let receivedBadUpdate = false;
729
+
730
+ await new Promise<void>(async (resolve) => {
731
+ const subscription = await new Promise<
732
+ PubSubSubscription<
733
+ PubSubUpdate<(PubSubDisabledClass & PubSubDisabledData)[]>
734
+ >
735
+ >(async (resolve) => {
736
+ let updated = false;
737
+ const subscription = pubsub.subscribeEntities({
738
+ class: PubSubDisabled,
739
+ })(async (update) => {
740
+ if (updated) {
741
+ receivedBadUpdate = true;
742
+ } else {
743
+ expect(update).toEqual([]);
744
+ updated = true;
745
+ try {
746
+ const entity = await PubSubDisabled.factory();
747
+ entity.name = 'Someone';
748
+ if (!(await entity.$save())) {
749
+ throw new Error("Couldn't save.");
750
+ }
751
+ resolve(subscription);
752
+ } catch (e: any) {
753
+ console.error('Error creating entity: ', e);
754
+ throw e;
755
+ }
756
+ }
757
+ });
758
+ });
759
+ await new Promise<void>((resolve) => setTimeout(resolve, 200));
760
+ subscription.unsubscribe();
761
+ resolve();
762
+ });
763
+
764
+ expect(receivedBadUpdate).toEqual(false);
765
+ });
766
+
414
767
  it('new uid', async () => {
415
- await new Promise(async (resolve) => {
768
+ await new Promise<void>(async (resolve) => {
416
769
  const subscription = pubsub.subscribeUID('testNewUID')(
417
770
  async (value) => {
418
771
  expect(value).toEqual(directValue);
419
772
  subscription.unsubscribe();
420
- resolve(true);
773
+ resolve();
421
774
  },
422
775
  (err) => {
423
776
  expect(err.status).toEqual(404);
424
- }
777
+ },
425
778
  );
426
779
 
427
780
  const directValue = await nymph.newUID('testNewUID');
@@ -429,7 +782,7 @@ describe('Nymph REST Server and Client', () => {
429
782
  });
430
783
 
431
784
  it('increasing uids', async () => {
432
- await new Promise(async (resolve) => {
785
+ await new Promise<void>(async (resolve) => {
433
786
  let receivedFirst = false;
434
787
  let lastUpdate: number = 0;
435
788
  const subscription = pubsub.subscribeUID('testIncUID')(
@@ -444,12 +797,12 @@ describe('Nymph REST Server and Client', () => {
444
797
  lastUpdate = value;
445
798
  if (value == 100) {
446
799
  subscription.unsubscribe();
447
- resolve(true);
800
+ resolve();
448
801
  }
449
802
  },
450
803
  (err) => {
451
804
  expect(err.status).toEqual(404);
452
- }
805
+ },
453
806
  );
454
807
 
455
808
  await nymph.deleteUID('testIncUID');
@@ -462,16 +815,16 @@ describe('Nymph REST Server and Client', () => {
462
815
  });
463
816
 
464
817
  it('set uid', async () => {
465
- await new Promise(async (resolve) => {
818
+ await new Promise<void>(async (resolve) => {
466
819
  const subscription = pubsub.subscribeUID('testSetUID')(
467
820
  async (value) => {
468
821
  expect(value).toEqual(123);
469
822
  subscription.unsubscribe();
470
- resolve(true);
823
+ resolve();
471
824
  },
472
825
  (err) => {
473
826
  expect(err.status).toEqual(404);
474
- }
827
+ },
475
828
  );
476
829
 
477
830
  await nymph.setUID('testSetUID', 123);
@@ -479,7 +832,7 @@ describe('Nymph REST Server and Client', () => {
479
832
  });
480
833
 
481
834
  it('rename uid from old name', async () => {
482
- await new Promise(async (resolve) => {
835
+ await new Promise<void>(async (resolve) => {
483
836
  let updated = false;
484
837
  const subscription = pubsub.subscribeUID('testRenameUID')(
485
838
  async (value, event) => {
@@ -487,15 +840,15 @@ describe('Nymph REST Server and Client', () => {
487
840
  expect(event).toEqual('renameUID');
488
841
  expect(value).toEqual(null);
489
842
  subscription.unsubscribe();
490
- resolve(true);
491
- } else {
843
+ resolve();
844
+ } else if (event === 'setUID') {
492
845
  expect(value).toEqual(456);
493
846
  updated = true;
494
847
  }
495
848
  },
496
849
  (err) => {
497
850
  expect(err.status).toEqual(404);
498
- }
851
+ },
499
852
  );
500
853
 
501
854
  await nymph.setUID('testRenameUID', 456);
@@ -504,17 +857,17 @@ describe('Nymph REST Server and Client', () => {
504
857
  });
505
858
 
506
859
  it('rename uid from new name', async () => {
507
- await new Promise(async (resolve) => {
860
+ await new Promise<void>(async (resolve) => {
508
861
  const subscription = pubsub.subscribeUID('newRename2UID')(
509
862
  async (value, event) => {
510
863
  expect(event).toEqual('setUID');
511
864
  expect(value).toEqual(456);
512
865
  subscription.unsubscribe();
513
- resolve(true);
866
+ resolve();
514
867
  },
515
868
  (err) => {
516
869
  expect(err.status).toEqual(404);
517
- }
870
+ },
518
871
  );
519
872
 
520
873
  await nymph.setUID('testRename2UID', 456);
@@ -525,38 +878,51 @@ describe('Nymph REST Server and Client', () => {
525
878
  it('delete uid', async () => {
526
879
  await nymph.setUID('testDeleteUID', 789);
527
880
 
528
- await new Promise(async (resolve) => {
881
+ await new Promise<void>(async (resolve) => {
529
882
  let updated = false;
530
883
  const subscription = pubsub.subscribeUID('testDeleteUID')(
531
884
  async (value, event) => {
532
885
  if (updated && event === 'deleteUID') {
533
886
  expect(value).toEqual(null);
534
887
  subscription.unsubscribe();
535
- resolve(true);
888
+ resolve();
536
889
  } else {
537
890
  expect(value).toEqual(789);
538
891
  updated = true;
892
+ await nymph.deleteUID('testDeleteUID');
539
893
  }
540
894
  },
541
895
  (err) => {
542
896
  expect(err.status).toEqual(404);
543
- }
897
+ },
544
898
  );
545
-
546
- await nymph.deleteUID('testDeleteUID');
547
899
  });
548
900
  });
549
901
 
902
+ beforeEach(async () => {
903
+ pubsub.connect();
904
+ while (!pubsub.isConnectionOpen()) {
905
+ await new Promise<void>((resolve) => setTimeout(resolve, 20));
906
+ }
907
+ });
908
+
550
909
  afterAll(async () => {
551
- // avoid jest open handle error
552
- const closed = new Promise((resolve) => {
910
+ // Don't publish anything after the tests.
911
+ removePublisher();
912
+
913
+ // Avoid jest open handle errors.
914
+ const closed = new Promise<void>((resolve) => {
553
915
  pubsub.on('disconnect', () => {
554
- resolve(true);
916
+ resolve();
555
917
  });
556
918
  });
557
919
  pubsub.close(); // close PubSub client.
558
920
  await closed;
559
921
  pubsubServer.close(); // close PubSub server.
560
922
  server.close(); // close REST server.
923
+
924
+ // Wait for the next event loop to prevent websocket importing something
925
+ // after jest teardown.
926
+ await new Promise((resolve) => setImmediate(resolve));
561
927
  });
562
928
  });