@nymphjs/client 1.0.0-beta.1 → 1.0.0-beta.100

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/README.md +7 -7
  3. package/asyncitertest.js +53 -0
  4. package/dist/Entity.d.ts +156 -0
  5. package/dist/Entity.js +598 -0
  6. package/dist/Entity.js.map +1 -0
  7. package/dist/Entity.types.d.ts +218 -0
  8. package/dist/Entity.types.js +2 -0
  9. package/dist/Entity.types.js.map +1 -0
  10. package/dist/EntityWeakCache.d.ts +6 -0
  11. package/dist/EntityWeakCache.js +27 -0
  12. package/dist/EntityWeakCache.js.map +1 -0
  13. package/dist/HttpRequester.d.ts +78 -0
  14. package/dist/HttpRequester.js +365 -0
  15. package/dist/HttpRequester.js.map +1 -0
  16. package/dist/Nymph.d.ts +116 -0
  17. package/dist/Nymph.js +459 -0
  18. package/dist/Nymph.js.map +1 -0
  19. package/dist/Nymph.types.d.ts +177 -0
  20. package/dist/Nymph.types.js +2 -0
  21. package/dist/Nymph.types.js.map +1 -0
  22. package/dist/PubSub.d.ts +63 -0
  23. package/dist/PubSub.js +599 -0
  24. package/dist/PubSub.js.map +1 -0
  25. package/dist/PubSub.types.d.ts +31 -0
  26. package/dist/PubSub.types.js +2 -0
  27. package/dist/PubSub.types.js.map +1 -0
  28. package/dist/entityRefresh.d.ts +5 -0
  29. package/dist/entityRefresh.js +75 -0
  30. package/dist/entityRefresh.js.map +1 -0
  31. package/dist/index.d.ts +13 -0
  32. package/dist/index.js +13 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/utils.d.ts +8 -0
  35. package/dist/utils.js +93 -0
  36. package/dist/utils.js.map +1 -0
  37. package/jest.config.js +11 -2
  38. package/lib/Entity.d.ts +112 -8
  39. package/lib/Entity.js +159 -77
  40. package/lib/Entity.js.map +1 -1
  41. package/lib/Entity.types.d.ts +144 -9
  42. package/lib/EntityWeakCache.js +3 -2
  43. package/lib/EntityWeakCache.js.map +1 -1
  44. package/lib/HttpRequester.d.ts +44 -8
  45. package/lib/HttpRequester.js +236 -21
  46. package/lib/HttpRequester.js.map +1 -1
  47. package/lib/Nymph.d.ts +43 -11
  48. package/lib/Nymph.js +102 -19
  49. package/lib/Nymph.js.map +1 -1
  50. package/lib/Nymph.types.d.ts +73 -1
  51. package/lib/PubSub.d.ts +15 -9
  52. package/lib/PubSub.js +148 -87
  53. package/lib/PubSub.js.map +1 -1
  54. package/lib/PubSub.types.d.ts +6 -1
  55. package/lib/entityRefresh.js +16 -0
  56. package/lib/entityRefresh.js.map +1 -1
  57. package/lib/utils.js +11 -0
  58. package/lib/utils.js.map +1 -1
  59. package/package.json +23 -27
  60. package/src/Entity.ts +171 -108
  61. package/src/Entity.types.ts +29 -47
  62. package/src/EntityWeakCache.ts +8 -6
  63. package/src/HttpRequester.ts +308 -35
  64. package/src/Nymph.ts +212 -96
  65. package/src/Nymph.types.ts +51 -2
  66. package/src/PubSub.ts +214 -141
  67. package/src/PubSub.types.ts +10 -5
  68. package/src/entityRefresh.ts +6 -6
  69. package/src/index.ts +10 -10
  70. package/src/utils.ts +12 -5
  71. package/tsconfig.json +6 -4
  72. package/typedoc.json +4 -0
  73. package/dist/index.js.LICENSE.txt +0 -8
  74. package/webpack.config.js +0 -28
package/src/Nymph.ts CHANGED
@@ -1,24 +1,27 @@
1
- import Entity from './Entity';
2
- import {
1
+ import Entity, { type EntityInstanceType } from './Entity.js';
2
+ import type {
3
3
  EntityConstructor,
4
- EntityData,
5
4
  EntityInterface,
6
5
  EntityJson,
7
6
  ServerCallResponse,
8
7
  ServerCallStaticResponse,
9
- } from './Entity.types';
10
- import EntityWeakCache from './EntityWeakCache';
11
- import HttpRequester from './HttpRequester';
12
- import {
8
+ } from './Entity.types.js';
9
+ import EntityWeakCache from './EntityWeakCache.js';
10
+ import type { AbortableAsyncIterator } from './HttpRequester.js';
11
+ import HttpRequester, { ClientError } from './HttpRequester.js';
12
+ import type {
13
13
  EventType,
14
14
  NymphOptions,
15
15
  Options,
16
16
  RequestCallback,
17
17
  ResponseCallback,
18
18
  Selector,
19
- } from './Nymph.types';
20
- import PubSub from './PubSub';
21
- import { entitiesToReferences, entityConstructorsToClassNames } from './utils';
19
+ } from './Nymph.types.js';
20
+ import type PubSub from './PubSub.js';
21
+ import {
22
+ entitiesToReferences,
23
+ entityConstructorsToClassNames,
24
+ } from './utils.js';
22
25
 
23
26
  let requester: HttpRequester;
24
27
 
@@ -36,28 +39,48 @@ export default class Nymph {
36
39
  /**
37
40
  * The entity class for this instance of Nymph.
38
41
  */
39
- public Entity: typeof Entity = Entity;
42
+ public Entity: typeof Entity;
40
43
 
41
44
  private requestCallbacks: RequestCallback[] = [];
42
45
  private responseCallbacks: ResponseCallback[] = [];
43
46
  private restUrl: string = '';
44
47
  private weakCache = false;
48
+ /**
49
+ * Headers that will be sent with every request.
50
+ *
51
+ * These are used by Tilmeld for authentication.
52
+ */
53
+ public headers: { [k: string]: string } = {};
54
+ /**
55
+ * The entity cache.
56
+ */
45
57
  public cache = new EntityWeakCache();
58
+ /**
59
+ * Return `null` or empty array instead of error when entity/ies not found.
60
+ */
61
+ public returnNullOnNotFound = false;
46
62
 
47
63
  public constructor(NymphOptions: NymphOptions) {
48
64
  this.restUrl = NymphOptions.restUrl;
49
65
  // @ts-ignore TS doesn't know about WeakRef.
50
66
  this.weakCache = !!NymphOptions.weakCache && typeof WeakRef !== 'undefined';
67
+ if (
68
+ 'returnNullOnNotFound' in NymphOptions &&
69
+ NymphOptions.returnNullOnNotFound != null
70
+ ) {
71
+ this.returnNullOnNotFound = NymphOptions.returnNullOnNotFound;
72
+ }
51
73
 
52
- class NymphEntity<T extends EntityData = EntityData> extends Entity<T> {}
53
- NymphEntity.nymph = this;
54
- this.Entity = NymphEntity;
55
- this.addEntityClass(NymphEntity);
74
+ this.Entity = this.addEntityClass(Entity);
56
75
 
57
76
  requester = new HttpRequester(
58
- 'fetch' in NymphOptions ? NymphOptions.fetch : undefined
77
+ 'fetch' in NymphOptions ? NymphOptions.fetch : undefined,
59
78
  );
60
79
 
80
+ if ('renewTokens' in NymphOptions && !NymphOptions.renewTokens) {
81
+ this.headers['X-Tilmeld-Token-Renewal'] = 'off';
82
+ }
83
+
61
84
  requester.on('request', (_requester, url, options) => {
62
85
  for (let i = 0; i < this.requestCallbacks.length; i++) {
63
86
  this.requestCallbacks[i] && this.requestCallbacks[i](url, options);
@@ -71,25 +94,51 @@ export default class Nymph {
71
94
  });
72
95
  }
73
96
 
74
- public addEntityClass(entityClass: EntityConstructor) {
75
- this.entityClasses[entityClass.class] = entityClass;
76
- entityClass.nymph = this;
97
+ /**
98
+ * Add your class to this instance.
99
+ *
100
+ * This will create a class that extends your class within this instance of
101
+ * Nymph and return it. You can then use this class's constructor and methods,
102
+ * which will use this instance of Nymph.
103
+ *
104
+ * Because this creates a subclass, don't use the class returned from
105
+ * `getEntityClass` to check with `instanceof`. Instead, use the base class
106
+ * that you passed into this method.
107
+ */
108
+ public addEntityClass<T extends EntityConstructor>(entityClass: T): T {
109
+ const nymph = this;
110
+ class NymphEntity extends entityClass {
111
+ static nymph: Nymph = nymph;
112
+
113
+ constructor(...args: any[]) {
114
+ super(...args);
115
+ }
116
+ }
117
+ this.entityClasses[entityClass.class] = NymphEntity;
118
+ return NymphEntity;
77
119
  }
78
120
 
79
- public getEntityClass(className: string) {
80
- if (this.entityClasses.hasOwnProperty(className)) {
81
- const EntityClass = this.entityClasses[className];
82
- EntityClass.nymph = this;
83
- return EntityClass;
121
+ public getEntityClass<T extends EntityConstructor>(className: T): T;
122
+ public getEntityClass(className: string): EntityConstructor;
123
+ public getEntityClass<T extends EntityConstructor = EntityConstructor>(
124
+ className: T | string,
125
+ ): T | EntityConstructor {
126
+ let key: string | null = null;
127
+ if (typeof className === 'string') {
128
+ key = className;
129
+ } else {
130
+ key = className.class;
84
131
  }
85
- throw new ClassNotAvailableError(
86
- "Tried to get class that's not available: " + className
87
- );
132
+ if (key in this.entityClasses) {
133
+ return this.entityClasses[key];
134
+ }
135
+ throw new ClassNotAvailableError('Tried to use class: ' + key);
88
136
  }
89
137
 
90
138
  public async newUID(name: string) {
91
139
  const data = await requester.POST({
92
140
  url: this.restUrl,
141
+ headers: { ...this.headers },
93
142
  dataType: 'text',
94
143
  data: { action: 'uid', data: name },
95
144
  });
@@ -99,6 +148,7 @@ export default class Nymph {
99
148
  public async setUID(name: string, value: number) {
100
149
  return await requester.PUT({
101
150
  url: this.restUrl,
151
+ headers: { ...this.headers },
102
152
  dataType: 'json',
103
153
  data: { action: 'uid', data: { name, value } },
104
154
  });
@@ -107,6 +157,7 @@ export default class Nymph {
107
157
  public async getUID(name: string) {
108
158
  const data = await requester.GET({
109
159
  url: this.restUrl,
160
+ headers: { ...this.headers },
110
161
  dataType: 'text',
111
162
  data: { action: 'uid', data: name },
112
163
  });
@@ -116,6 +167,7 @@ export default class Nymph {
116
167
  public async deleteUID(name: string) {
117
168
  return await requester.DELETE({
118
169
  url: this.restUrl,
170
+ headers: { ...this.headers },
119
171
  dataType: 'text',
120
172
  data: { action: 'uid', data: name },
121
173
  });
@@ -139,7 +191,7 @@ export default class Nymph {
139
191
  ) {
140
192
  throw new InvalidRequestError(
141
193
  'Due to REST restriction, you can only create new entities or ' +
142
- 'update existing entities, not both at the same time.'
194
+ 'update existing entities, not both at the same time.',
143
195
  );
144
196
  }
145
197
  });
@@ -149,7 +201,7 @@ export default class Nymph {
149
201
  public async patchEntity(entity: EntityInterface) {
150
202
  if (entity.guid == null) {
151
203
  throw new InvalidRequestError(
152
- "You can't patch an entity that hasn't yet been saved."
204
+ "You can't patch an entity that hasn't yet been saved.",
153
205
  );
154
206
  }
155
207
 
@@ -166,7 +218,7 @@ export default class Nymph {
166
218
  if (cur.guid == null) {
167
219
  throw new InvalidRequestError(
168
220
  'Due to REST restriction, you can only create new entities or ' +
169
- 'update existing entities, not both at the same time.'
221
+ 'update existing entities, not both at the same time.',
170
222
  );
171
223
  }
172
224
  });
@@ -178,22 +230,23 @@ export default class Nymph {
178
230
  entity: T,
179
231
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
180
232
  data: { [k: string]: any },
181
- plural: false
233
+ plural: false,
182
234
  ): Promise<T>;
183
235
  private async requestWithMethod<T extends EntityInterface>(
184
236
  entity: T[],
185
237
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
186
238
  data: { [k: string]: any },
187
- plural: true
239
+ plural: true,
188
240
  ): Promise<T[]>;
189
241
  private async requestWithMethod<T extends EntityInterface>(
190
242
  entity: T | T[],
191
243
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
192
244
  data: { [k: string]: any },
193
- plural: boolean
245
+ plural: boolean,
194
246
  ): Promise<T | T[]> {
195
247
  const response = await requester[method]({
196
248
  url: this.restUrl,
249
+ headers: { ...this.headers },
197
250
  dataType: 'json',
198
251
  data: {
199
252
  action: plural ? 'entities' : 'entity',
@@ -206,7 +259,7 @@ export default class Nymph {
206
259
  typeof response[i].guid !== 'undefined' &&
207
260
  (e.guid == null || e.guid === response[i].guid)
208
261
  ? e.$init(response[i])
209
- : e
262
+ : e,
210
263
  ) as T[];
211
264
  } else if (!Array.isArray(entity) && typeof response.guid !== 'undefined') {
212
265
  return entity.$init(response) as T;
@@ -225,29 +278,42 @@ export default class Nymph {
225
278
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
226
279
  options: Options<T>,
227
280
  ...selectors: Selector[]
228
- ): Promise<ReturnType<T['factorySync']> | null>;
281
+ ): Promise<EntityInstanceType<T> | null>;
229
282
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
230
283
  options: Options<T> & { return: 'count' },
231
- guid: string
284
+ guid: string,
232
285
  ): Promise<number>;
233
286
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
234
287
  options: Options<T> & { return: 'guid' },
235
- guid: string
288
+ guid: string,
236
289
  ): Promise<string | null>;
237
290
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
238
291
  options: Options<T>,
239
- guid: string
240
- ): Promise<ReturnType<T['factorySync']> | null>;
292
+ guid: string,
293
+ ): Promise<EntityInstanceType<T> | null>;
241
294
  public async getEntity<T extends EntityConstructor = EntityConstructor>(
242
295
  options: Options<T>,
243
296
  ...selectors: Selector[] | string[]
244
- ): Promise<ReturnType<T['factorySync']> | string | number | null> {
245
- // @ts-ignore: Implementation signatures of overloads are not externally visible.
246
- const data = (await this.getEntityData(options, ...selectors)) as
247
- | EntityJson<T>
248
- | string
249
- | number
250
- | null;
297
+ ): Promise<EntityInstanceType<T> | string | number | null> {
298
+ let data: any = null;
299
+ try {
300
+ // @ts-ignore: Implementation signatures of overloads are not externally visible.
301
+ data = (await this.getEntityData(options, ...selectors)) as
302
+ | EntityJson<T>
303
+ | string
304
+ | number
305
+ | null;
306
+ } catch (e: any) {
307
+ if (
308
+ this.returnNullOnNotFound &&
309
+ e instanceof ClientError &&
310
+ e.status === 404
311
+ ) {
312
+ data = null;
313
+ } else {
314
+ throw e;
315
+ }
316
+ }
251
317
 
252
318
  if (options.return && options.return === 'count') {
253
319
  return Number(data ?? 0) as number;
@@ -277,23 +343,23 @@ export default class Nymph {
277
343
  ): Promise<EntityJson<T> | null>;
278
344
  public async getEntityData<T extends EntityConstructor = EntityConstructor>(
279
345
  options: Options<T> & { return: 'count' },
280
- guid: string
346
+ guid: string,
281
347
  ): Promise<number>;
282
348
  public async getEntityData<T extends EntityConstructor = EntityConstructor>(
283
349
  options: Options<T> & { return: 'guid' },
284
- guid: string
350
+ guid: string,
285
351
  ): Promise<string | null>;
286
352
  public async getEntityData<T extends EntityConstructor = EntityConstructor>(
287
353
  options: Options<T>,
288
- guid: string
354
+ guid: string,
289
355
  ): Promise<EntityJson<T> | null>;
290
356
  public async getEntityData<T extends EntityConstructor = EntityConstructor>(
291
357
  options: Options<T>,
292
358
  ...selectors: Selector[] | string[]
293
359
  ): Promise<EntityJson<T> | string | number | null> {
294
- if (options.class === this.getEntityClass('Entity')) {
360
+ if (options.class instanceof Entity) {
295
361
  throw new InvalidRequestError(
296
- "You can't make REST requests with the base Entity class."
362
+ "You can't make REST requests with the base Entity class.",
297
363
  );
298
364
  }
299
365
  // Set up options and selectors.
@@ -302,6 +368,7 @@ export default class Nymph {
302
368
  }
303
369
  const data = await requester.GET({
304
370
  url: this.restUrl,
371
+ headers: { ...this.headers },
305
372
  dataType: 'json',
306
373
  data: {
307
374
  action: 'entity',
@@ -329,25 +396,42 @@ export default class Nymph {
329
396
  public async getEntities<T extends EntityConstructor = EntityConstructor>(
330
397
  options: Options<T>,
331
398
  ...selectors: Selector[]
332
- ): Promise<ReturnType<T['factorySync']>[]>;
399
+ ): Promise<EntityInstanceType<T>[]>;
333
400
  public async getEntities<T extends EntityConstructor = EntityConstructor>(
334
401
  options: Options<T>,
335
402
  ...selectors: Selector[]
336
- ): Promise<ReturnType<T['factorySync']>[] | string[] | number> {
337
- const data = await requester.GET({
338
- url: this.restUrl,
339
- dataType: 'json',
340
- data: {
341
- action: 'entities',
342
- data: [
343
- { ...options, class: options.class.class },
344
- ...entityConstructorsToClassNames(selectors),
345
- ],
346
- },
347
- });
403
+ ): Promise<EntityInstanceType<T>[] | string[] | number> {
404
+ let data = null;
405
+ try {
406
+ data = await requester.GET({
407
+ url: this.restUrl,
408
+ headers: { ...this.headers },
409
+ dataType: 'json',
410
+ data: {
411
+ action: 'entities',
412
+ data: [
413
+ { ...options, class: options.class.class },
414
+ ...entityConstructorsToClassNames(selectors),
415
+ ],
416
+ },
417
+ });
418
+ } catch (e: any) {
419
+ if (
420
+ this.returnNullOnNotFound &&
421
+ e instanceof ClientError &&
422
+ e.status === 404
423
+ ) {
424
+ data = null;
425
+ } else {
426
+ throw e;
427
+ }
428
+ }
348
429
 
349
430
  if (options.return && options.return === 'count') {
350
- return Number(data);
431
+ return Number(data ?? 0) as number;
432
+ }
433
+ if (data == null) {
434
+ return [];
351
435
  }
352
436
  if (options.return && options.return === 'guid') {
353
437
  return data;
@@ -356,12 +440,12 @@ export default class Nymph {
356
440
  }
357
441
 
358
442
  public initEntity<T extends EntityConstructor = EntityConstructor>(
359
- entityJSON: EntityJson<T>
360
- ): ReturnType<T['factorySync']> {
443
+ entityJSON: EntityJson<T>,
444
+ ): EntityInstanceType<T> {
361
445
  const EntityClass = this.getEntityClass(entityJSON.class);
362
446
  if (!EntityClass) {
363
447
  throw new ClassNotAvailableError(
364
- entityJSON.class + ' class cannot be found.'
448
+ entityJSON.class + ' class cannot be found.',
365
449
  );
366
450
  }
367
451
  let entity = EntityClass.factorySync();
@@ -369,30 +453,28 @@ export default class Nymph {
369
453
  // Try to get it from cache.
370
454
  const entityFromCache = this.cache.get(
371
455
  EntityClass,
372
- entityJSON.guid || ''
456
+ entityJSON.guid || '',
373
457
  );
374
458
  if (entityFromCache != null) {
375
- entity = entityFromCache;
459
+ entity = entityFromCache as EntityInstanceType<T>;
376
460
  }
377
461
  }
378
- return entity.$init(entityJSON) as ReturnType<T['factorySync']>;
462
+ return entity.$init(entityJSON) as EntityInstanceType<T>;
379
463
  }
380
464
 
381
465
  public getEntityFromCache<T extends EntityConstructor = EntityConstructor>(
382
466
  EntityClass: EntityConstructor,
383
- guid: string
384
- ): ReturnType<T['factorySync']> | null {
467
+ guid: string,
468
+ ): EntityInstanceType<T> | null {
385
469
  if (!this.weakCache) {
386
470
  return null;
387
471
  }
388
- return this.cache.get(EntityClass, guid) as ReturnType<
389
- T['factorySync']
390
- > | null;
472
+ return this.cache.get(EntityClass, guid) as EntityInstanceType<T> | null;
391
473
  }
392
474
 
393
475
  public setEntityToCache(
394
476
  EntityClass: EntityConstructor,
395
- entity: EntityInterface
477
+ entity: EntityInterface,
396
478
  ) {
397
479
  if (!this.weakCache) {
398
480
  return;
@@ -404,10 +486,7 @@ export default class Nymph {
404
486
  if (Array.isArray(item)) {
405
487
  // Recurse into lower arrays.
406
488
  return item.map((entry) => this.initEntitiesFromData(entry)) as T;
407
- } else if (
408
- item instanceof Object &&
409
- !(item instanceof this.getEntityClass('Entity'))
410
- ) {
489
+ } else if (item instanceof Object && !(item instanceof Entity)) {
411
490
  if (
412
491
  item.hasOwnProperty('class') &&
413
492
  item.hasOwnProperty('guid') &&
@@ -431,10 +510,11 @@ export default class Nymph {
431
510
 
432
511
  public async deleteEntity(
433
512
  entity: EntityInterface | EntityInterface[],
434
- _plural = false
513
+ _plural = false,
435
514
  ) {
436
515
  return await requester.DELETE({
437
516
  url: this.restUrl,
517
+ headers: { ...this.headers },
438
518
  dataType: 'json',
439
519
  data: {
440
520
  action: _plural ? 'entities' : 'entity',
@@ -460,10 +540,11 @@ export default class Nymph {
460
540
  entity: EntityInterface,
461
541
  method: string,
462
542
  params: any[],
463
- stateless = false
543
+ stateless = false,
464
544
  ): Promise<ServerCallResponse> {
465
545
  const data = await requester.POST({
466
546
  url: this.restUrl,
547
+ headers: { ...this.headers },
467
548
  dataType: 'json',
468
549
  data: {
469
550
  action: 'method',
@@ -485,10 +566,11 @@ export default class Nymph {
485
566
  public async serverCallStatic(
486
567
  className: string,
487
568
  method: string,
488
- params: any[]
569
+ params: any[],
489
570
  ): Promise<ServerCallStaticResponse> {
490
571
  const data = await requester.POST({
491
572
  url: this.restUrl,
573
+ headers: { ...this.headers },
492
574
  dataType: 'json',
493
575
  data: {
494
576
  action: 'method',
@@ -504,19 +586,57 @@ export default class Nymph {
504
586
  return this.initEntitiesFromData(data);
505
587
  }
506
588
 
589
+ public async serverCallStaticIterator(
590
+ className: string,
591
+ method: string,
592
+ params: any[],
593
+ ): Promise<AbortableAsyncIterator<ServerCallStaticResponse>> {
594
+ const iterable = await requester.POST_ITERATOR({
595
+ url: this.restUrl,
596
+ headers: { ...this.headers },
597
+ dataType: 'json',
598
+ data: {
599
+ action: 'method',
600
+ data: {
601
+ class: className,
602
+ static: true,
603
+ method: method,
604
+ iterator: true,
605
+ params: entitiesToReferences(entityConstructorsToClassNames(params)),
606
+ },
607
+ },
608
+ });
609
+
610
+ const that = this;
611
+ const iterator: AbortableAsyncIterator = {
612
+ abortController: iterable.abortController,
613
+ async *[Symbol.asyncIterator]() {
614
+ for await (let response of iterable) {
615
+ if (response instanceof Error) {
616
+ yield response;
617
+ } else {
618
+ yield that.initEntitiesFromData(response);
619
+ }
620
+ }
621
+ },
622
+ };
623
+
624
+ return iterator;
625
+ }
626
+
507
627
  public on<T extends EventType>(
508
628
  event: T,
509
629
  callback: T extends 'request'
510
630
  ? RequestCallback
511
631
  : T extends 'response'
512
- ? ResponseCallback
513
- : never
632
+ ? ResponseCallback
633
+ : never,
514
634
  ) {
515
635
  const prop = (event + 'Callbacks') as T extends 'request'
516
636
  ? 'requestCallbacks'
517
637
  : T extends 'request'
518
- ? 'responseCallbacks'
519
- : never;
638
+ ? 'responseCallbacks'
639
+ : never;
520
640
  if (!(prop in this)) {
521
641
  throw new Error('Invalid event type.');
522
642
  }
@@ -530,14 +650,14 @@ export default class Nymph {
530
650
  callback: T extends 'request'
531
651
  ? RequestCallback
532
652
  : T extends 'response'
533
- ? ResponseCallback
534
- : never
653
+ ? ResponseCallback
654
+ : never,
535
655
  ) {
536
656
  const prop = (event + 'Callbacks') as T extends 'request'
537
657
  ? 'requestCallbacks'
538
658
  : T extends 'request'
539
- ? 'responseCallbacks'
540
- : never;
659
+ ? 'responseCallbacks'
660
+ : never;
541
661
  if (!(prop in this)) {
542
662
  return false;
543
663
  }
@@ -549,10 +669,6 @@ export default class Nymph {
549
669
  }
550
670
  return true;
551
671
  }
552
-
553
- public setXsrfToken(token: string | null) {
554
- requester.setXsrfToken(token);
555
- }
556
672
  }
557
673
 
558
674
  export class ClassNotAvailableError extends Error {
@@ -1,4 +1,4 @@
1
- import { EntityConstructor, EntityInterface } from './Entity.types';
1
+ import { EntityConstructor, EntityInterface } from './Entity.types.js';
2
2
 
3
3
  export type NymphOptions = {
4
4
  /**
@@ -17,10 +17,18 @@ export type NymphOptions = {
17
17
  * A WebSocket implementation.
18
18
  */
19
19
  WebSocket?: typeof WebSocket;
20
+ /**
21
+ * Return `null` or empty array instead of error when entity/ies not found.
22
+ */
23
+ returnNullOnNotFound?: boolean;
20
24
  /**
21
25
  * Whether to not output status messages to the console.
22
26
  */
23
27
  noConsole?: boolean;
28
+ /**
29
+ * Don't automatically try to connect to PubSub server.
30
+ */
31
+ noAutoconnect?: boolean;
24
32
  /**
25
33
  * Use a WeakRef based cache of entities.
26
34
  *
@@ -40,6 +48,16 @@ export type NymphOptions = {
40
48
  * can help to synchronize them correctly and avoid data conflicts.
41
49
  */
42
50
  weakCache?: boolean;
51
+ /**
52
+ * Whether to renew tokens when a request is made.
53
+ *
54
+ * If you turn this off, the client will request that the server not renew an
55
+ * authentication token, even if it is within the renewal time of the
56
+ * expiration date.
57
+ *
58
+ * This defaults to true.
59
+ */
60
+ renewTokens?: boolean;
43
61
  };
44
62
 
45
63
  export type EventType = 'request' | 'response';
@@ -49,12 +67,37 @@ export type RequestCallback = (url: string, options: RequestInit) => void;
49
67
  export type ResponseCallback = (response: Response, text: string) => void;
50
68
 
51
69
  export type Options<T extends EntityConstructor = EntityConstructor> = {
70
+ /**
71
+ * The Entity class to query.
72
+ */
52
73
  class: T;
74
+ /**
75
+ * The limit of entities to be returned. Not needed when using `getEntity`, as
76
+ * it always returns only one.
77
+ */
53
78
  limit?: number;
79
+ /**
80
+ * The offset from the first matching entity, in order, to start retrieving.
81
+ */
54
82
  offset?: number;
83
+ /**
84
+ * If true, entities will be retrieved from newest to oldest/largest to
85
+ * smallest (with regard to `sort`).
86
+ */
55
87
  reverse?: boolean;
56
- sort?: 'cdate' | 'mdate';
88
+ /**
89
+ * How to sort the entities. Should be "cdate", "mdate", or the name of a
90
+ * property.
91
+ */
92
+ sort?: 'cdate' | 'mdate' | string;
93
+ /**
94
+ * What to return, the entities with their data, just the GUIDs, or just a
95
+ * count.
96
+ */
57
97
  return?: 'entity' | 'guid' | 'count';
98
+ /**
99
+ * If true, Nymph will skip the cache and retrieve the entity from the DB.
100
+ */
58
101
  skipCache?: boolean;
59
102
  };
60
103
 
@@ -81,6 +124,9 @@ type PrimitiveSelector = {
81
124
  contain?: [string, any];
82
125
  '!contain'?: PrimitiveSelector['contain'];
83
126
 
127
+ search?: [string, string];
128
+ '!search'?: PrimitiveSelector['search'];
129
+
84
130
  match?: [string, string];
85
131
  '!match'?: PrimitiveSelector['match'];
86
132
 
@@ -136,6 +182,9 @@ export type Selector = {
136
182
  contain?: Clause<OrWithTime<PrimitiveSelector['contain']>>;
137
183
  '!contain'?: Clause<OrWithTime<PrimitiveSelector['contain']>>;
138
184
 
185
+ search?: Clause<PrimitiveSelector['search']>;
186
+ '!search'?: Clause<PrimitiveSelector['search']>;
187
+
139
188
  match?: Clause<PrimitiveSelector['match']>;
140
189
  '!match'?: Clause<PrimitiveSelector['match']>;
141
190