@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.
- package/CHANGELOG.md +454 -0
- package/README.md +8 -8
- package/dist/PubSub.d.ts +77 -12
- package/dist/PubSub.js +467 -103
- package/dist/PubSub.js.map +1 -1
- package/dist/PubSub.test.js +394 -65
- package/dist/PubSub.test.js.map +1 -1
- package/dist/PubSub.types.d.ts +6 -1
- package/dist/PubSub.types.js +1 -2
- package/dist/conf/d.d.ts +23 -0
- package/dist/conf/d.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 +2 -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 +11 -2
- package/package.json +23 -23
- package/src/PubSub.test.ts +467 -101
- package/src/PubSub.ts +548 -286
- package/src/PubSub.types.ts +2 -1
- package/src/conf/defaults.ts +2 -2
- package/src/conf/index.ts +2 -2
- package/src/createServer.ts +8 -8
- package/src/index.ts +4 -4
- package/tsconfig.json +5 -3
- package/typedoc.json +4 -0
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,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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
//
|
|
552
|
-
|
|
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(
|
|
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
|
});
|