@nymphjs/server 1.0.0-beta.13 → 1.0.0-beta.131

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/src/index.test.ts CHANGED
@@ -1,14 +1,16 @@
1
1
  import express from 'express';
2
2
  import SQLite3Driver from '@nymphjs/driver-sqlite3';
3
3
  import { Nymph as NymphServer } from '@nymphjs/nymph';
4
- import { Nymph } from '@nymphjs/client-node';
5
- import { Entity } from '@nymphjs/client';
4
+ import { Nymph, Entity, HttpError } from '@nymphjs/client';
6
5
 
7
- import createServer from './index';
6
+ import createServer from './index.js';
8
7
  import {
9
8
  EmployeeModel as EmployeeModelClass,
10
9
  Employee as EmployeeClass,
11
- } from './testArtifacts';
10
+ RestrictedModel as RestrictedModelClass,
11
+ Restricted as RestrictedClass,
12
+ EmployeeData,
13
+ } from './testArtifacts.js';
12
14
 
13
15
  const sqliteConfig = {
14
16
  filename: ':memory:',
@@ -16,15 +18,18 @@ const sqliteConfig = {
16
18
 
17
19
  const nymphServer = new NymphServer({}, new SQLite3Driver(sqliteConfig));
18
20
  const EmployeeModel = nymphServer.addEntityClass(EmployeeModelClass);
21
+ const RestrictedModel = nymphServer.addEntityClass(RestrictedModelClass);
19
22
 
20
23
  const app = express();
21
24
  app.use('/test', createServer(nymphServer));
22
25
  const server = app.listen(5080);
23
26
 
27
+ const REST_URL = 'http://localhost:5080/test/';
24
28
  const nymph = new Nymph({
25
- restUrl: 'http://localhost:5080/test/',
29
+ restUrl: REST_URL,
26
30
  });
27
31
  const Employee = nymph.addEntityClass(EmployeeClass);
32
+ const Restricted = nymph.addEntityClass(RestrictedClass);
28
33
 
29
34
  describe('Nymph REST Server and Client', () => {
30
35
  async function createJane() {
@@ -129,7 +134,7 @@ describe('Nymph REST Server and Client', () => {
129
134
  {
130
135
  type: '&',
131
136
  ref: ['subordinates', jane],
132
- }
137
+ },
133
138
  );
134
139
 
135
140
  expect(checkSteve?.guid).toEqual(steve.guid);
@@ -142,7 +147,7 @@ describe('Nymph REST Server and Client', () => {
142
147
  'subordinates',
143
148
  [{ class: Employee }, { type: '&', guid: jane.guid }],
144
149
  ],
145
- }
150
+ },
146
151
  );
147
152
 
148
153
  expect(checkSteveQref?.guid).toEqual(steve.guid);
@@ -156,7 +161,7 @@ describe('Nymph REST Server and Client', () => {
156
161
  expect(entity.$hasTag('test', 'test2')).toEqual(true);
157
162
  entity.$addTag('test', 'test3', 'test4', 'test5', 'test6');
158
163
  expect(entity.$hasTag('test', 'test3', 'test4', 'test5', 'test6')).toEqual(
159
- true
164
+ true,
160
165
  );
161
166
  entity.$removeTag('test2');
162
167
  expect(!entity.$hasTag('test2')).toEqual(true);
@@ -178,10 +183,10 @@ describe('Nymph REST Server and Client', () => {
178
183
  'nymph_entity_reference',
179
184
  jane.guid,
180
185
  'Employee',
181
- ]);
182
- await entity.$ready();
186
+ ]) as EmployeeClass & EmployeeData;
187
+ await entity.$wake();
183
188
 
184
- expect(jane?.name).toEqual('Jane Doe');
189
+ expect(entity?.name).toEqual('Jane Doe');
185
190
  });
186
191
 
187
192
  it('change an entity', async () => {
@@ -232,13 +237,51 @@ describe('Nymph REST Server and Client', () => {
232
237
  {
233
238
  type: '&',
234
239
  equal: ['name', 'Jane Doe'],
235
- }
240
+ },
236
241
  );
237
242
 
238
243
  expect(jane).not.toBeNull();
239
244
  expect(jane?.guid).not.toBeNull();
240
245
  });
241
246
 
247
+ it('try to get a non-existent entity', async () => {
248
+ let error = { status: 200 };
249
+ try {
250
+ await nymph.getEntity(
251
+ {
252
+ class: Employee,
253
+ },
254
+ {
255
+ type: '&',
256
+ equal: ['name', 'Non Existent'],
257
+ },
258
+ );
259
+ } catch (e: any) {
260
+ error = e;
261
+ }
262
+ expect(error.status).toEqual(404);
263
+ });
264
+
265
+ it('try to get a non-existent entity without error', async () => {
266
+ const nymph = new Nymph({
267
+ restUrl: REST_URL,
268
+ returnNullOnNotFound: true,
269
+ });
270
+ const Employee = nymph.addEntityClass(EmployeeClass);
271
+
272
+ const nonexistent = await nymph.getEntity(
273
+ {
274
+ class: Employee,
275
+ },
276
+ {
277
+ type: '&',
278
+ equal: ['name', 'Non Existent'],
279
+ },
280
+ );
281
+
282
+ expect(nonexistent).toBeNull();
283
+ });
284
+
242
285
  it('get entities', async () => {
243
286
  for (let i = 0; i < 4; i++) {
244
287
  await createJane();
@@ -252,7 +295,7 @@ describe('Nymph REST Server and Client', () => {
252
295
  {
253
296
  type: '&',
254
297
  tag: ['employee'],
255
- }
298
+ },
256
299
  );
257
300
 
258
301
  expect(entities.length).toEqual(4);
@@ -278,13 +321,13 @@ describe('Nymph REST Server and Client', () => {
278
321
 
279
322
  const resultSelectors = await nymph.getEntities(
280
323
  { class: Employee },
281
- { type: '&', equal: ['name', 'Jane Doe'] }
324
+ { type: '&', equal: ['name', 'Jane Doe'] },
282
325
  );
283
326
 
284
327
  // Testing count return with selectors...
285
328
  const resultSelectorsCount = await nymph.getEntities(
286
329
  { class: Employee, return: 'count' },
287
- { type: '&', equal: ['name', 'Jane Doe'] }
330
+ { type: '&', equal: ['name', 'Jane Doe'] },
288
331
  );
289
332
  expect(resultSelectorsCount).toBeGreaterThanOrEqual(1);
290
333
  expect(resultSelectorsCount).toEqual(resultSelectors.length);
@@ -307,7 +350,7 @@ describe('Nymph REST Server and Client', () => {
307
350
  // Testing empty count...
308
351
  const resultSelectorsEmpty = await nymph.getEntities(
309
352
  { class: Employee, return: 'count' },
310
- { type: '&', tag: 'pickle' }
353
+ { type: '&', tag: 'pickle' },
311
354
  );
312
355
  expect(resultSelectorsEmpty).toEqual(0);
313
356
  });
@@ -326,7 +369,7 @@ describe('Nymph REST Server and Client', () => {
326
369
  {
327
370
  type: '&',
328
371
  tag: 'employee',
329
- }
372
+ },
330
373
  );
331
374
 
332
375
  expect(entities.length).toEqual(4);
@@ -352,7 +395,7 @@ describe('Nymph REST Server and Client', () => {
352
395
  {
353
396
  type: '&',
354
397
  like: ['name', '%Jane%'],
355
- }
398
+ },
356
399
  );
357
400
 
358
401
  expect(entities.length).toEqual(4);
@@ -378,7 +421,7 @@ describe('Nymph REST Server and Client', () => {
378
421
  type: '|',
379
422
  like: ['name', '%Jane%'],
380
423
  },
381
- }
424
+ },
382
425
  );
383
426
 
384
427
  expect(entities.length).toEqual(4);
@@ -413,7 +456,7 @@ describe('Nymph REST Server and Client', () => {
413
456
  },
414
457
  ],
415
458
  },
416
- }
459
+ },
417
460
  );
418
461
 
419
462
  expect(entities.length).toEqual(4);
@@ -430,15 +473,14 @@ describe('Nymph REST Server and Client', () => {
430
473
  throw new Error('Entity is null.');
431
474
  }
432
475
 
433
- const deleted = await jane.$delete();
434
- expect(deleted).toEqual(true);
476
+ await jane.$delete();
435
477
 
436
478
  const check = await nymph.getEntities(
437
479
  { class: Employee },
438
480
  {
439
481
  type: '&',
440
482
  guid,
441
- }
483
+ },
442
484
  );
443
485
 
444
486
  expect(check.length).toEqual(0);
@@ -461,7 +503,7 @@ describe('Nymph REST Server and Client', () => {
461
503
  {
462
504
  type: '|',
463
505
  guid: guids,
464
- }
506
+ },
465
507
  );
466
508
 
467
509
  expect(check.length).toEqual(0);
@@ -478,7 +520,7 @@ describe('Nymph REST Server and Client', () => {
478
520
  error = e;
479
521
  }
480
522
  expect(error.message).toEqual(
481
- "Can't use Entity class directly from the front end."
523
+ "Can't use Entity class directly from the front end.",
482
524
  );
483
525
  });
484
526
 
@@ -503,6 +545,25 @@ describe('Nymph REST Server and Client', () => {
503
545
  expect(error.error.name).toEqual('BadFunctionCallError');
504
546
  });
505
547
 
548
+ it('handle server side static iterator error', async () => {
549
+ let error: any = { status: 0, error: { name: '' } };
550
+ const data = await Employee.throwErrorStaticIterable();
551
+
552
+ let count = 0;
553
+ for await (let value of data) {
554
+ count++;
555
+ if (value instanceof Error) {
556
+ error = value;
557
+ } else {
558
+ expect(value).toEqual(count);
559
+ }
560
+ }
561
+
562
+ expect(count).toEqual(2);
563
+ expect(error.status).toEqual(500);
564
+ expect(error.error.name).toEqual('BadFunctionCallError');
565
+ });
566
+
506
567
  it('handle server side error', async () => {
507
568
  const jane = await createJane();
508
569
 
@@ -549,6 +610,28 @@ describe('Nymph REST Server and Client', () => {
549
610
  expect(data).toEqual(10);
550
611
  });
551
612
 
613
+ it('call a server side static iterator method', async () => {
614
+ const data = await Employee.testStaticIterable(5);
615
+
616
+ let count = 0;
617
+ for await (let value of data) {
618
+ count++;
619
+ expect(value).toEqual(5 + count);
620
+ }
621
+
622
+ expect(count).toEqual(3);
623
+ });
624
+
625
+ it('aborts a server side static iterator method', async () => {
626
+ const data = await Employee.testStaticIterableAbort();
627
+ data.abortController.abort();
628
+
629
+ // Wait 1 second to ensure server receives abort signal.
630
+ await new Promise((resolve) => setTimeout(resolve, 1000));
631
+
632
+ expect(true).toBeTruthy();
633
+ });
634
+
552
635
  it('call a stateless server side method', async () => {
553
636
  const jane = await createJane();
554
637
 
@@ -577,7 +660,7 @@ describe('Nymph REST Server and Client', () => {
577
660
  {
578
661
  class: Employee,
579
662
  },
580
- jane1.guid
663
+ jane1.guid,
581
664
  );
582
665
  if (jane1 == null || jane2 == null) {
583
666
  throw new Error('Entity is null.');
@@ -603,7 +686,7 @@ describe('Nymph REST Server and Client', () => {
603
686
  {
604
687
  class: Employee,
605
688
  },
606
- first.guid
689
+ first.guid,
607
690
  );
608
691
 
609
692
  if (second == null || second.guid == null) {
@@ -635,7 +718,7 @@ describe('Nymph REST Server and Client', () => {
635
718
  {
636
719
  class: Employee,
637
720
  },
638
- first.guid
721
+ first.guid,
639
722
  );
640
723
 
641
724
  if (second == null || second.guid == null) {
@@ -658,7 +741,7 @@ describe('Nymph REST Server and Client', () => {
658
741
  {
659
742
  type: '&',
660
743
  '!guid': first.guid,
661
- }
744
+ },
662
745
  );
663
746
 
664
747
  if (third == null || third.guid == null) {
@@ -668,6 +751,66 @@ describe('Nymph REST Server and Client', () => {
668
751
  expect(first.$is(third)).toEqual(false);
669
752
  });
670
753
 
754
+ it("doesn't allow creation of a restricted entity class", async () => {
755
+ const attempt = await Restricted.factory();
756
+ attempt.name = 'Jane Doe';
757
+
758
+ let error = null;
759
+
760
+ try {
761
+ await attempt.$save();
762
+ } catch (e: any) {
763
+ error = e;
764
+ }
765
+
766
+ expect(error).toBeInstanceOf(HttpError);
767
+ expect(error.status).toEqual(403);
768
+ });
769
+
770
+ it("doesn't allow search of a restricted entity class", async () => {
771
+ let error = null;
772
+
773
+ try {
774
+ await nymph.getEntity({
775
+ class: Restricted,
776
+ });
777
+ } catch (e: any) {
778
+ error = e;
779
+ }
780
+
781
+ expect(error).toBeInstanceOf(HttpError);
782
+ expect(error.status).toEqual(403);
783
+ });
784
+
785
+ it("doesn't allow methods of a restricted entity class", async () => {
786
+ const attempt = await Restricted.factory();
787
+ attempt.name = 'Jane Doe';
788
+
789
+ let error = null;
790
+
791
+ try {
792
+ await attempt.$testMethod(1);
793
+ } catch (e: any) {
794
+ error = e;
795
+ }
796
+
797
+ expect(error).toBeInstanceOf(HttpError);
798
+ expect(error.status).toEqual(403);
799
+ });
800
+
801
+ it("doesn't allow static methods of a restricted entity class", async () => {
802
+ let error = null;
803
+
804
+ try {
805
+ await Restricted.testStatic(1);
806
+ } catch (e: any) {
807
+ error = e;
808
+ }
809
+
810
+ expect(error).toBeInstanceOf(HttpError);
811
+ expect(error.status).toEqual(403);
812
+ });
813
+
671
814
  it('get a new UID', async () => {
672
815
  const uidValue = await nymph.newUID('employee');
673
816
  expect(typeof uidValue).toEqual('number');
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { createServer } from './createServer';
1
+ import { createServer } from './createServer.js';
2
2
 
3
- export * from './HttpError';
4
- export * from './statusDescriptions';
5
- export * from './createServer';
3
+ export * from './statusDescriptions.js';
4
+ export * from './createServer.js';
6
5
 
7
6
  export default createServer;
@@ -1,8 +1,10 @@
1
- import { Entity as EntityServer, EntityInvalidDataError } from '@nymphjs/nymph';
1
+ import {
2
+ Entity as EntityServer,
3
+ EntityInvalidDataError,
4
+ HttpError,
5
+ } from '@nymphjs/nymph';
2
6
  import { Entity } from '@nymphjs/client';
3
7
 
4
- import { HttpError } from './HttpError';
5
-
6
8
  export type EmployeeBaseData<T> = {
7
9
  name?: string;
8
10
  id?: number;
@@ -39,7 +41,13 @@ export class EmployeeModel extends EntityServer<EmployeeModelData> {
39
41
  '$throwHttpError',
40
42
  '$throwHttpErrorWithDescription',
41
43
  ];
42
- public static clientEnabledStaticMethods = ['testStatic', 'throwErrorStatic'];
44
+ public static clientEnabledStaticMethods = [
45
+ 'testStatic',
46
+ 'testStaticIterable',
47
+ 'testStaticIterableAbort',
48
+ 'throwErrorStatic',
49
+ 'throwErrorStaticIterable',
50
+ ];
43
51
  protected $protectedTags = ['employee'];
44
52
  protected $allowlistTags? = ['boss', 'bigcheese'];
45
53
  protected $allowlistData? = [
@@ -57,37 +65,25 @@ export class EmployeeModel extends EntityServer<EmployeeModelData> {
57
65
  'building',
58
66
  ];
59
67
 
60
- static async factory(
61
- guid?: string
62
- ): Promise<EmployeeModel & EmployeeModelData> {
63
- return (await super.factory(guid)) as EmployeeModel & EmployeeModelData;
64
- }
65
-
66
- static factorySync(guid?: string): EmployeeModel & EmployeeModelData {
67
- return super.factorySync(guid) as EmployeeModel & EmployeeModelData;
68
- }
69
-
70
- constructor(guid?: string) {
71
- super(guid);
68
+ constructor() {
69
+ super();
72
70
 
73
- if (this.guid == null) {
74
- this.$addTag('employee');
75
- this.$data.current = true;
76
- this.$data.startDate = Date.now();
77
- this.$data.subordinates = [];
78
- if (!IS_MANAGER) {
79
- this.$privateData.push('salary');
80
- }
71
+ this.$addTag('employee');
72
+ this.$data.current = true;
73
+ this.$data.startDate = Date.now();
74
+ this.$data.subordinates = [];
75
+ if (!IS_MANAGER) {
76
+ this.$privateData.push('salary');
81
77
  }
82
78
  }
83
79
 
84
80
  public async $save() {
85
81
  // Validate employee data.
86
82
  const error = new EntityInvalidDataError('Invalid entity data.');
87
- if (this.$data.name == null || this.$data.name == '') {
83
+ if (this.$data.name == null || this.$data.name === '') {
88
84
  error.addField('name');
89
85
  }
90
- if (this.$data.title == null || this.$data.title == '') {
86
+ if (this.$data.title == null || this.$data.title === '') {
91
87
  error.addField('title');
92
88
  }
93
89
  if (this.$data.startDate == null) {
@@ -117,10 +113,33 @@ export class EmployeeModel extends EntityServer<EmployeeModelData> {
117
113
  return value * 2;
118
114
  }
119
115
 
116
+ public static *testStaticIterable(value: number) {
117
+ yield value + 1;
118
+ yield value + 2;
119
+ yield value + 3;
120
+ }
121
+
122
+ public static *testStaticIterableAbort(): Iterator<number, void, boolean> {
123
+ let aborted = yield 1;
124
+
125
+ if (!aborted) {
126
+ throw new Error(
127
+ "testStaticIterableAbort wasn't aborted after the first iteration.",
128
+ );
129
+ }
130
+ }
131
+
120
132
  public static throwErrorStatic() {
121
133
  throw new BadFunctionCallError('This function only throws errors.');
122
134
  }
123
135
 
136
+ public static *throwErrorStaticIterable() {
137
+ yield 1;
138
+ throw new BadFunctionCallError(
139
+ 'This function throws errors after the first iteration.',
140
+ );
141
+ }
142
+
124
143
  public $throwError() {
125
144
  throw new BadFunctionCallError('This function only throws errors.');
126
145
  }
@@ -151,22 +170,12 @@ export class Employee extends Entity<EmployeeData> {
151
170
  // The name of the server class
152
171
  public static class = 'Employee';
153
172
 
154
- constructor(guid?: string) {
155
- super(guid);
156
-
157
- if (guid == null) {
158
- this.$addTag('employee');
159
- this.$data.current = true;
160
- this.$data.subordinates = [];
161
- }
162
- }
163
-
164
- static async factory(guid?: string): Promise<Employee & EmployeeData> {
165
- return (await super.factory(guid)) as Employee & EmployeeData;
166
- }
173
+ constructor() {
174
+ super();
167
175
 
168
- static factorySync(guid?: string): Employee & EmployeeData {
169
- return super.factorySync(guid) as Employee & EmployeeData;
176
+ this.$addTag('employee');
177
+ this.$data.current = true;
178
+ this.$data.subordinates = [];
170
179
  }
171
180
 
172
181
  $testMethod(value: number) {
@@ -193,13 +202,133 @@ export class Employee extends Entity<EmployeeData> {
193
202
  return this.serverCallStatic('testStatic', [value]);
194
203
  }
195
204
 
205
+ static testStaticIterable(value: number) {
206
+ return this.serverCallStaticIterator('testStaticIterable', [value]);
207
+ }
208
+
209
+ static testStaticIterableAbort() {
210
+ return this.serverCallStaticIterator('testStaticIterableAbort', []);
211
+ }
212
+
196
213
  static throwErrorStatic() {
197
214
  return this.serverCallStatic('throwErrorStatic', []);
198
215
  }
199
216
 
217
+ static throwErrorStaticIterable() {
218
+ return this.serverCallStaticIterator('throwErrorStaticIterable', []);
219
+ }
220
+
200
221
  static inaccessibleMethod() {
201
222
  return this.serverCallStatic('inaccessibleMethod', []);
202
223
  }
203
224
  }
204
225
 
205
- export default Employee;
226
+ export type RestrictedModelData = {
227
+ name: string;
228
+ };
229
+
230
+ /**
231
+ * This class is a test class that extends the Entity class.
232
+ */
233
+ export class RestrictedModel extends EntityServer<RestrictedModelData> {
234
+ static ETYPE = 'restricted';
235
+ static class = 'Restricted';
236
+
237
+ public static restEnabled = false;
238
+
239
+ constructor() {
240
+ super();
241
+
242
+ this.$data.name = '';
243
+ }
244
+
245
+ public async $save() {
246
+ // Validate entity data.
247
+ const error = new EntityInvalidDataError('Invalid entity data.');
248
+ if (this.$data.name == null || this.$data.name === '') {
249
+ error.addField('name');
250
+ }
251
+ if (error.getFields().length) {
252
+ throw error;
253
+ }
254
+ return await super.$save();
255
+ }
256
+
257
+ $testMethod(value: string) {
258
+ return value;
259
+ }
260
+
261
+ public static testStatic(value: number) {
262
+ return value;
263
+ }
264
+ }
265
+
266
+ export type RestrictedData = {
267
+ name: string;
268
+ };
269
+
270
+ export class Restricted extends Entity<RestrictedData> {
271
+ // The name of the server class
272
+ public static class = 'Restricted';
273
+
274
+ constructor() {
275
+ super();
276
+
277
+ this.$data.name = '';
278
+ }
279
+
280
+ $testMethod(value: number) {
281
+ return this.$serverCall('$testMethod', [value]);
282
+ }
283
+
284
+ static testStatic(value: number) {
285
+ return this.serverCallStatic('testStatic', [value]);
286
+ }
287
+ }
288
+
289
+ export type PubSubDisabledModelData = {
290
+ name: string;
291
+ };
292
+
293
+ /**
294
+ * This class is a test class that extends the Entity class.
295
+ */
296
+ export class PubSubDisabledModel extends EntityServer<PubSubDisabledModelData> {
297
+ static ETYPE = 'pubsub_disabled';
298
+ static class = 'PubSubDisabled';
299
+
300
+ public static pubSubEnabled = false;
301
+
302
+ constructor() {
303
+ super();
304
+
305
+ this.$data.name = '';
306
+ }
307
+
308
+ public async $save() {
309
+ // Validate entity data.
310
+ const error = new EntityInvalidDataError('Invalid entity data.');
311
+ if (this.$data.name == null || this.$data.name === '') {
312
+ error.addField('name');
313
+ }
314
+ if (error.getFields().length) {
315
+ throw error;
316
+ }
317
+ return await super.$save();
318
+ }
319
+ }
320
+
321
+ export type PubSubDisabledData = {
322
+ name: string;
323
+ };
324
+
325
+ export class PubSubDisabled extends Entity<PubSubDisabledData> {
326
+ // The name of the server class
327
+ public static class = 'PubSubDisabled';
328
+
329
+ constructor() {
330
+ super();
331
+
332
+ this.$data.name = '';
333
+ }
334
+ }
package/tsconfig.json CHANGED
@@ -2,10 +2,13 @@
2
2
  "extends": "@tsconfig/recommended/tsconfig.json",
3
3
 
4
4
  "compilerOptions": {
5
- "lib": ["ES2021"],
6
- "target": "ES2021",
5
+ "types": ["node", "jest"],
6
+ "module": "nodenext",
7
+ "moduleResolution": "nodenext",
8
+ "lib": ["es2024"],
9
+ "target": "es2024",
7
10
  "noImplicitAny": true,
8
- "removeComments": true,
11
+ "removeComments": false,
9
12
  "sourceMap": true,
10
13
  "outDir": "dist",
11
14
  "resolveJsonModule": true,
package/typedoc.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": ["../../typedoc.base.json"],
3
+ "entryPoints": ["src/index.ts"]
4
+ }
@@ -1,5 +0,0 @@
1
- export declare class HttpError extends Error {
2
- status?: number;
3
- statusText?: string;
4
- constructor(message: string, status?: number, statusText?: string);
5
- }