@nymphjs/client 1.0.0-alpha.2 → 1.0.0-alpha.23

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/Nymph.ts CHANGED
@@ -1,10 +1,13 @@
1
+ import Entity from './Entity';
1
2
  import {
2
3
  EntityConstructor,
4
+ EntityData,
3
5
  EntityInterface,
4
6
  EntityJson,
5
7
  ServerCallResponse,
6
8
  ServerCallStaticResponse,
7
9
  } from './Entity.types';
10
+ import EntityWeakCache from './EntityWeakCache';
8
11
  import HttpRequester from './HttpRequester';
9
12
  import {
10
13
  EventType,
@@ -14,34 +17,42 @@ import {
14
17
  ResponseCallback,
15
18
  Selector,
16
19
  } from './Nymph.types';
20
+ import PubSub from './PubSub';
17
21
  import { entitiesToReferences, entityConstructorsToClassNames } from './utils';
18
22
 
19
23
  let requester: HttpRequester;
20
24
 
21
25
  export default class Nymph {
22
- private static entityClasses: { [k: string]: EntityConstructor } = {};
23
- private static requestCallbacks: RequestCallback[] = [];
24
- private static responseCallbacks: ResponseCallback[] = [];
25
- private static restUrl: string;
26
-
27
- public static setEntityClass(
28
- className: string,
29
- entityClass: EntityConstructor
30
- ) {
31
- this.entityClasses[className] = entityClass;
32
- }
33
-
34
- public static getEntityClass(className: string) {
35
- if (this.entityClasses.hasOwnProperty(className)) {
36
- return this.entityClasses[className];
37
- }
38
- throw new ClassNotAvailableError(
39
- "Tried to get class that's not available: " + className
40
- );
41
- }
42
-
43
- public static init(NymphOptions: NymphOptions) {
26
+ /**
27
+ * And optional PubSub client instance.
28
+ */
29
+ public pubsub: PubSub | undefined = undefined;
30
+
31
+ /**
32
+ * A simple map of names to Entity classes.
33
+ */
34
+ private entityClasses: { [k: string]: EntityConstructor } = {};
35
+
36
+ /**
37
+ * The entity class for this instance of Nymph.
38
+ */
39
+ public Entity: typeof Entity = Entity;
40
+
41
+ private requestCallbacks: RequestCallback[] = [];
42
+ private responseCallbacks: ResponseCallback[] = [];
43
+ private restUrl: string = '';
44
+ private weakCache = false;
45
+ public cache = new EntityWeakCache();
46
+
47
+ public constructor(NymphOptions: NymphOptions) {
44
48
  this.restUrl = NymphOptions.restUrl;
49
+ // @ts-ignore TS doesn't know about WeakRef.
50
+ this.weakCache = !!NymphOptions.weakCache && typeof WeakRef !== 'undefined';
51
+
52
+ class NymphEntity<T extends EntityData = EntityData> extends Entity<T> {}
53
+ NymphEntity.nymph = this;
54
+ this.Entity = NymphEntity;
55
+ this.addEntityClass(NymphEntity);
45
56
 
46
57
  requester = new HttpRequester(
47
58
  'fetch' in NymphOptions ? NymphOptions.fetch : undefined
@@ -60,7 +71,23 @@ export default class Nymph {
60
71
  });
61
72
  }
62
73
 
63
- public static async newUID(name: string) {
74
+ public addEntityClass(entityClass: EntityConstructor) {
75
+ this.entityClasses[entityClass.class] = entityClass;
76
+ entityClass.nymph = this;
77
+ }
78
+
79
+ public getEntityClass(className: string) {
80
+ if (this.entityClasses.hasOwnProperty(className)) {
81
+ const EntityClass = this.entityClasses[className];
82
+ EntityClass.nymph = this;
83
+ return EntityClass;
84
+ }
85
+ throw new ClassNotAvailableError(
86
+ "Tried to get class that's not available: " + className
87
+ );
88
+ }
89
+
90
+ public async newUID(name: string) {
64
91
  const data = await requester.POST({
65
92
  url: this.restUrl,
66
93
  dataType: 'text',
@@ -69,7 +96,7 @@ export default class Nymph {
69
96
  return Number(data);
70
97
  }
71
98
 
72
- public static async setUID(name: string, value: number) {
99
+ public async setUID(name: string, value: number) {
73
100
  return await requester.PUT({
74
101
  url: this.restUrl,
75
102
  dataType: 'json',
@@ -77,7 +104,7 @@ export default class Nymph {
77
104
  });
78
105
  }
79
106
 
80
- public static async getUID(name: string) {
107
+ public async getUID(name: string) {
81
108
  const data = await requester.GET({
82
109
  url: this.restUrl,
83
110
  dataType: 'text',
@@ -86,7 +113,7 @@ export default class Nymph {
86
113
  return Number(data);
87
114
  }
88
115
 
89
- public static async deleteUID(name: string) {
116
+ public async deleteUID(name: string) {
90
117
  return await requester.DELETE({
91
118
  url: this.restUrl,
92
119
  dataType: 'text',
@@ -94,12 +121,12 @@ export default class Nymph {
94
121
  });
95
122
  }
96
123
 
97
- public static async saveEntity(entity: EntityInterface) {
124
+ public async saveEntity(entity: EntityInterface) {
98
125
  let method: 'POST' | 'PUT' = entity.guid == null ? 'POST' : 'PUT';
99
126
  return await this.requestWithMethod(entity, method, entity, false);
100
127
  }
101
128
 
102
- public static async saveEntities(entities: EntityInterface[]) {
129
+ public async saveEntities(entities: EntityInterface[]) {
103
130
  if (!entities.length) {
104
131
  return Promise.resolve(false);
105
132
  }
@@ -119,7 +146,7 @@ export default class Nymph {
119
146
  return await this.requestWithMethod(entities, method, entities, true);
120
147
  }
121
148
 
122
- public static async patchEntity(entity: EntityInterface) {
149
+ public async patchEntity(entity: EntityInterface) {
123
150
  if (entity.guid == null) {
124
151
  throw new InvalidRequestError(
125
152
  "You can't patch an entity that hasn't yet been saved."
@@ -130,7 +157,7 @@ export default class Nymph {
130
157
  return await this.requestWithMethod(entity, 'PATCH', patch, false);
131
158
  }
132
159
 
133
- public static async patchEntities(entities: EntityInterface[]) {
160
+ public async patchEntities(entities: EntityInterface[]) {
134
161
  if (!entities.length) {
135
162
  return Promise.resolve(false);
136
163
  }
@@ -147,19 +174,19 @@ export default class Nymph {
147
174
  return await this.requestWithMethod(entities, 'PATCH', patch, true);
148
175
  }
149
176
 
150
- private static async requestWithMethod<T extends EntityInterface>(
177
+ private async requestWithMethod<T extends EntityInterface>(
151
178
  entity: T,
152
179
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
153
180
  data: { [k: string]: any },
154
181
  plural: false
155
182
  ): Promise<T>;
156
- private static async requestWithMethod<T extends EntityInterface>(
183
+ private async requestWithMethod<T extends EntityInterface>(
157
184
  entity: T[],
158
185
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
159
186
  data: { [k: string]: any },
160
187
  plural: true
161
188
  ): Promise<T[]>;
162
- private static async requestWithMethod<T extends EntityInterface>(
189
+ private async requestWithMethod<T extends EntityInterface>(
163
190
  entity: T | T[],
164
191
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
165
192
  data: { [k: string]: any },
@@ -187,33 +214,23 @@ export default class Nymph {
187
214
  throw new Error('Server error');
188
215
  }
189
216
 
190
- public static async getEntity<
191
- T extends EntityConstructor = EntityConstructor
192
- >(
217
+ public async getEntity<T extends EntityConstructor = EntityConstructor>(
193
218
  options: Options<T> & { return: 'guid' },
194
219
  ...selectors: Selector[]
195
220
  ): Promise<string | null>;
196
- public static async getEntity<
197
- T extends EntityConstructor = EntityConstructor
198
- >(
221
+ public async getEntity<T extends EntityConstructor = EntityConstructor>(
199
222
  options: Options<T>,
200
223
  ...selectors: Selector[]
201
224
  ): Promise<ReturnType<T['factorySync']> | null>;
202
- public static async getEntity<
203
- T extends EntityConstructor = EntityConstructor
204
- >(
225
+ public async getEntity<T extends EntityConstructor = EntityConstructor>(
205
226
  options: Options<T> & { return: 'guid' },
206
227
  guid: string
207
228
  ): Promise<string | null>;
208
- public static async getEntity<
209
- T extends EntityConstructor = EntityConstructor
210
- >(
229
+ public async getEntity<T extends EntityConstructor = EntityConstructor>(
211
230
  options: Options<T>,
212
231
  guid: string
213
232
  ): Promise<ReturnType<T['factorySync']> | null>;
214
- public static async getEntity<
215
- T extends EntityConstructor = EntityConstructor
216
- >(
233
+ public async getEntity<T extends EntityConstructor = EntityConstructor>(
217
234
  options: Options<T>,
218
235
  ...selectors: Selector[] | string[]
219
236
  ): Promise<ReturnType<T['factorySync']> | string | null> {
@@ -233,30 +250,23 @@ export default class Nymph {
233
250
  return null;
234
251
  }
235
252
 
236
- public static async getEntityData<
237
- T extends EntityConstructor = EntityConstructor
238
- >(
253
+ public async getEntityData<T extends EntityConstructor = EntityConstructor>(
239
254
  options: Options<T> & { return: 'guid' },
240
255
  ...selectors: Selector[]
241
256
  ): Promise<string | null>;
242
- public static async getEntityData<
243
- T extends EntityConstructor = EntityConstructor
244
- >(
257
+ public async getEntityData<T extends EntityConstructor = EntityConstructor>(
245
258
  options: Options<T>,
246
259
  ...selectors: Selector[]
247
260
  ): Promise<EntityJson<T> | null>;
248
- public static async getEntityData<
249
- T extends EntityConstructor = EntityConstructor
250
- >(
261
+ public async getEntityData<T extends EntityConstructor = EntityConstructor>(
251
262
  options: Options<T> & { return: 'guid' },
252
263
  guid: string
253
264
  ): Promise<string | null>;
254
- public static async getEntityData<
255
- T extends EntityConstructor = EntityConstructor
256
- >(options: Options<T>, guid: string): Promise<EntityJson<T> | null>;
257
- public static async getEntityData<
258
- T extends EntityConstructor = EntityConstructor
259
- >(
265
+ public async getEntityData<T extends EntityConstructor = EntityConstructor>(
266
+ options: Options<T>,
267
+ guid: string
268
+ ): Promise<EntityJson<T> | null>;
269
+ public async getEntityData<T extends EntityConstructor = EntityConstructor>(
260
270
  options: Options<T>,
261
271
  ...selectors: Selector[] | string[]
262
272
  ): Promise<EntityJson<T> | string | null> {
@@ -287,21 +297,15 @@ export default class Nymph {
287
297
  return null;
288
298
  }
289
299
 
290
- public static async getEntities<
291
- T extends EntityConstructor = EntityConstructor
292
- >(
300
+ public async getEntities<T extends EntityConstructor = EntityConstructor>(
293
301
  options: Options<T> & { return: 'guid' },
294
302
  ...selectors: Selector[]
295
303
  ): Promise<string[]>;
296
- public static async getEntities<
297
- T extends EntityConstructor = EntityConstructor
298
- >(
304
+ public async getEntities<T extends EntityConstructor = EntityConstructor>(
299
305
  options: Options<T>,
300
306
  ...selectors: Selector[]
301
307
  ): Promise<ReturnType<T['factorySync']>[]>;
302
- public static async getEntities<
303
- T extends EntityConstructor = EntityConstructor
304
- >(
308
+ public async getEntities<T extends EntityConstructor = EntityConstructor>(
305
309
  options: Options<T>,
306
310
  ...selectors: Selector[]
307
311
  ): Promise<ReturnType<T['factorySync']>[] | string[]> {
@@ -323,7 +327,7 @@ export default class Nymph {
323
327
  return data.map((e: EntityJson<T>) => this.initEntity(e));
324
328
  }
325
329
 
326
- public static initEntity<T extends EntityConstructor = EntityConstructor>(
330
+ public initEntity<T extends EntityConstructor = EntityConstructor>(
327
331
  entityJSON: EntityJson<T>
328
332
  ): ReturnType<T['factorySync']> {
329
333
  const EntityClass = this.getEntityClass(entityJSON.class);
@@ -332,11 +336,43 @@ export default class Nymph {
332
336
  entityJSON.class + ' class cannot be found.'
333
337
  );
334
338
  }
335
- const entity = EntityClass.factorySync();
339
+ let entity = EntityClass.factorySync();
340
+ if (this.weakCache) {
341
+ // Try to get it from cache.
342
+ const entityFromCache = this.cache.get(
343
+ EntityClass,
344
+ entityJSON.guid || ''
345
+ );
346
+ if (entityFromCache != null) {
347
+ entity = entityFromCache;
348
+ }
349
+ }
336
350
  return entity.$init(entityJSON) as ReturnType<T['factorySync']>;
337
351
  }
338
352
 
339
- public static initEntitiesFromData<T extends any>(item: T): T {
353
+ public getEntityFromCache<T extends EntityConstructor = EntityConstructor>(
354
+ EntityClass: EntityConstructor,
355
+ guid: string
356
+ ): ReturnType<T['factorySync']> | null {
357
+ if (!this.weakCache) {
358
+ return null;
359
+ }
360
+ return this.cache.get(EntityClass, guid) as ReturnType<
361
+ T['factorySync']
362
+ > | null;
363
+ }
364
+
365
+ public setEntityToCache(
366
+ EntityClass: EntityConstructor,
367
+ entity: EntityInterface
368
+ ) {
369
+ if (!this.weakCache) {
370
+ return;
371
+ }
372
+ return this.cache.set(EntityClass, entity);
373
+ }
374
+
375
+ public initEntitiesFromData<T extends any>(item: T): T {
340
376
  if (Array.isArray(item)) {
341
377
  // Recurse into lower arrays.
342
378
  return item.map((entry) => this.initEntitiesFromData(entry)) as T;
@@ -365,7 +401,7 @@ export default class Nymph {
365
401
  return item;
366
402
  }
367
403
 
368
- public static async deleteEntity(
404
+ public async deleteEntity(
369
405
  entity: EntityInterface | EntityInterface[],
370
406
  _plural = false
371
407
  ) {
@@ -388,11 +424,11 @@ export default class Nymph {
388
424
  });
389
425
  }
390
426
 
391
- public static async deleteEntities(entities: EntityInterface[]) {
427
+ public async deleteEntities(entities: EntityInterface[]) {
392
428
  return await this.deleteEntity(entities, true);
393
429
  }
394
430
 
395
- public static async serverCall(
431
+ public async serverCall(
396
432
  entity: EntityInterface,
397
433
  method: string,
398
434
  params: any[],
@@ -418,7 +454,7 @@ export default class Nymph {
418
454
  };
419
455
  }
420
456
 
421
- public static async serverCallStatic(
457
+ public async serverCallStatic(
422
458
  className: string,
423
459
  method: string,
424
460
  params: any[]
@@ -440,7 +476,7 @@ export default class Nymph {
440
476
  return this.initEntitiesFromData(data);
441
477
  }
442
478
 
443
- public static on<T extends EventType>(
479
+ public on<T extends EventType>(
444
480
  event: T,
445
481
  callback: T extends 'request'
446
482
  ? RequestCallback
@@ -461,7 +497,7 @@ export default class Nymph {
461
497
  return () => this.off(event, callback);
462
498
  }
463
499
 
464
- public static off<T extends EventType>(
500
+ public off<T extends EventType>(
465
501
  event: T,
466
502
  callback: T extends 'request'
467
503
  ? RequestCallback
@@ -486,7 +522,7 @@ export default class Nymph {
486
522
  return true;
487
523
  }
488
524
 
489
- public static setXsrfToken(token: string | null) {
525
+ public setXsrfToken(token: string | null) {
490
526
  requester.setXsrfToken(token);
491
527
  }
492
528
  }
@@ -504,19 +540,3 @@ export class InvalidRequestError extends Error {
504
540
  this.name = 'InvalidRequestError';
505
541
  }
506
542
  }
507
-
508
- ((global) => {
509
- if (
510
- typeof global !== 'undefined' &&
511
- typeof (global as any as { NymphOptions: NymphOptions }).NymphOptions !==
512
- 'undefined'
513
- ) {
514
- Nymph.init((global as any as { NymphOptions: NymphOptions }).NymphOptions);
515
- }
516
- })(
517
- typeof window === 'undefined'
518
- ? typeof self === 'undefined'
519
- ? undefined
520
- : self
521
- : window
522
- );
@@ -21,6 +21,25 @@ export type NymphOptions = {
21
21
  * Whether to not output status messages to the console.
22
22
  */
23
23
  noConsole?: boolean;
24
+ /**
25
+ * Use a WeakRef based cache of entities.
26
+ *
27
+ * This ensures all entities returned are the same instance if they have the
28
+ * same class and GUID. This also means that whenever an entity is returned
29
+ * from the server, the single instance in memory will be refreshed. This
30
+ * could have annoying results, like destroying dirty data (the dreaded
31
+ * triple-D).
32
+ *
33
+ * This could also be a potential source of memory leaks. Although the
34
+ * entities themselves are referenced weakly so they get garbage collected,
35
+ * the GUID used as a key and the WeakRef object itself are not weak
36
+ * references, so not destroyed when the instance is garbage collected.
37
+ *
38
+ * However, even with these caveats, this might help you if you have a big app
39
+ * with the same entities stored in several different places in memory. This
40
+ * can help to synchronize them correctly and avoid data conflicts.
41
+ */
42
+ weakCache?: boolean;
24
43
  };
25
44
 
26
45
  export type EventType = 'request' | 'response';