@data-client/core 0.9.7 → 0.11.0

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 (128) hide show
  1. package/README.md +24 -1
  2. package/dist/index.js +132 -96
  3. package/dist/index.umd.min.js +1 -1
  4. package/legacy/actionTypes.js +1 -1
  5. package/legacy/actions.js +1 -1
  6. package/legacy/controller/AbortOptimistic.js +2 -0
  7. package/legacy/controller/Controller.js +110 -78
  8. package/legacy/controller/createExpireAll.js +1 -1
  9. package/legacy/controller/createFetch.js +1 -1
  10. package/legacy/controller/createInvalidate.js +1 -1
  11. package/legacy/controller/createInvalidateAll.js +1 -1
  12. package/legacy/controller/createOptimistic.js +1 -1
  13. package/legacy/controller/createReset.js +1 -1
  14. package/legacy/controller/createSet.js +1 -1
  15. package/legacy/controller/createSubscription.js +1 -1
  16. package/legacy/controller/ensurePojo.js +2 -3
  17. package/legacy/controller/types.js +1 -1
  18. package/legacy/index.js +1 -1
  19. package/legacy/internal.js +2 -2
  20. package/legacy/manager/ConnectionListener.js +1 -1
  21. package/legacy/manager/DefaultConnectionListener.js +1 -1
  22. package/legacy/manager/DevtoolsManager.js +6 -2
  23. package/legacy/manager/LogoutManager.js +1 -1
  24. package/legacy/manager/NetworkManager.js +1 -1
  25. package/legacy/manager/PollingSubscription.js +1 -1
  26. package/legacy/manager/SubscriptionManager.js +1 -1
  27. package/legacy/manager/applyManager.js +1 -1
  28. package/legacy/manager/devtoolsTypes.js +1 -1
  29. package/legacy/manager/index.js +1 -1
  30. package/legacy/middlewareTypes.js +1 -1
  31. package/legacy/next/index.js +1 -1
  32. package/legacy/state/RIC.js +1 -1
  33. package/legacy/state/reducer/createReducer.js +6 -6
  34. package/legacy/state/reducer/expireReducer.js +1 -1
  35. package/legacy/state/reducer/fetchReducer.js +1 -1
  36. package/legacy/state/reducer/invalidateReducer.js +5 -5
  37. package/legacy/state/reducer/setReducer.js +7 -7
  38. package/legacy/state/selectMeta.js +1 -1
  39. package/legacy/types.js +1 -1
  40. package/lib/actionTypes.js +1 -1
  41. package/lib/actions.d.ts +1 -1
  42. package/lib/actions.d.ts.map +1 -1
  43. package/lib/actions.js +1 -1
  44. package/lib/controller/AbortOptimistic.d.ts +3 -0
  45. package/lib/controller/AbortOptimistic.d.ts.map +1 -0
  46. package/lib/controller/AbortOptimistic.js +2 -0
  47. package/lib/controller/Controller.d.ts +36 -11
  48. package/lib/controller/Controller.d.ts.map +1 -1
  49. package/lib/controller/Controller.js +110 -78
  50. package/lib/controller/createExpireAll.js +1 -1
  51. package/lib/controller/createFetch.js +1 -1
  52. package/lib/controller/createInvalidate.js +1 -1
  53. package/lib/controller/createInvalidateAll.js +1 -1
  54. package/lib/controller/createOptimistic.js +1 -1
  55. package/lib/controller/createReset.js +1 -1
  56. package/lib/controller/createSet.js +1 -1
  57. package/lib/controller/createSubscription.js +1 -1
  58. package/lib/controller/ensurePojo.d.ts.map +1 -1
  59. package/lib/controller/ensurePojo.js +2 -3
  60. package/lib/controller/types.d.ts.map +1 -1
  61. package/lib/controller/types.js +1 -1
  62. package/lib/index.d.ts +1 -1
  63. package/lib/index.d.ts.map +1 -1
  64. package/lib/index.js +1 -1
  65. package/lib/internal.d.ts +1 -1
  66. package/lib/internal.d.ts.map +1 -1
  67. package/lib/internal.js +2 -2
  68. package/lib/manager/ConnectionListener.js +1 -1
  69. package/lib/manager/DefaultConnectionListener.js +1 -1
  70. package/lib/manager/DevtoolsManager.d.ts +1 -0
  71. package/lib/manager/DevtoolsManager.d.ts.map +1 -1
  72. package/lib/manager/DevtoolsManager.js +6 -2
  73. package/lib/manager/LogoutManager.js +1 -1
  74. package/lib/manager/NetworkManager.js +1 -1
  75. package/lib/manager/PollingSubscription.js +1 -1
  76. package/lib/manager/SubscriptionManager.js +1 -1
  77. package/lib/manager/applyManager.js +1 -1
  78. package/lib/manager/devtoolsTypes.js +1 -1
  79. package/lib/manager/index.js +1 -1
  80. package/lib/middlewareTypes.d.ts.map +1 -1
  81. package/lib/middlewareTypes.js +1 -1
  82. package/lib/next/index.js +1 -1
  83. package/lib/state/RIC.d.ts.map +1 -1
  84. package/lib/state/RIC.js +1 -1
  85. package/lib/state/reducer/createReducer.js +6 -6
  86. package/lib/state/reducer/expireReducer.d.ts +1 -1
  87. package/lib/state/reducer/expireReducer.js +1 -1
  88. package/lib/state/reducer/fetchReducer.js +1 -1
  89. package/lib/state/reducer/invalidateReducer.d.ts +1 -1
  90. package/lib/state/reducer/invalidateReducer.js +6 -6
  91. package/lib/state/reducer/setReducer.d.ts.map +1 -1
  92. package/lib/state/reducer/setReducer.js +8 -8
  93. package/lib/state/selectMeta.js +1 -1
  94. package/lib/types.d.ts +1 -1
  95. package/lib/types.d.ts.map +1 -1
  96. package/lib/types.js +1 -1
  97. package/package.json +3 -3
  98. package/src/actions.ts +1 -1
  99. package/src/controller/AbortOptimistic.ts +1 -0
  100. package/src/controller/Controller.ts +215 -96
  101. package/src/controller/__tests__/Controller.ts +2 -4
  102. package/src/controller/__tests__/__snapshots__/get.ts.snap +120 -0
  103. package/src/controller/__tests__/get.ts +285 -0
  104. package/src/controller/__tests__/getResponse.ts +2 -2
  105. package/src/controller/createSet.ts +3 -2
  106. package/src/controller/ensurePojo.ts +6 -7
  107. package/src/controller/types.ts +2 -4
  108. package/src/index.ts +2 -1
  109. package/src/internal.ts +1 -1
  110. package/src/manager/DevtoolsManager.ts +8 -2
  111. package/src/manager/__tests__/subscriptionManager.ts +3 -2
  112. package/src/middlewareTypes.ts +4 -12
  113. package/src/state/RIC.ts +3 -4
  114. package/src/state/__tests__/__snapshots__/reducer.ts.snap +4 -4
  115. package/src/state/__tests__/reducer.ts +32 -34
  116. package/src/state/reducer/createReducer.ts +3 -3
  117. package/src/state/reducer/invalidateReducer.ts +4 -4
  118. package/src/state/reducer/setReducer.ts +10 -9
  119. package/src/types.ts +3 -1
  120. package/ts3.4/actions.d.ts +1 -1
  121. package/ts3.4/controller/AbortOptimistic.d.ts +3 -0
  122. package/ts3.4/controller/Controller.d.ts +47 -19
  123. package/ts3.4/index.d.ts +1 -1
  124. package/ts3.4/internal.d.ts +1 -1
  125. package/ts3.4/manager/DevtoolsManager.d.ts +1 -0
  126. package/ts3.4/state/reducer/expireReducer.d.ts +1 -1
  127. package/ts3.4/state/reducer/invalidateReducer.d.ts +1 -1
  128. package/ts3.4/types.d.ts +1 -1
@@ -1,24 +1,25 @@
1
1
  import type {
2
2
  ErrorTypes,
3
3
  SnapshotInterface,
4
- DenormalizeCache,
5
4
  Schema,
6
5
  Denormalize,
6
+ Queryable,
7
+ SchemaArgs,
7
8
  } from '@data-client/normalizr';
8
9
  import {
9
- WeakEntityMap,
10
10
  ExpiryStatus,
11
11
  EndpointInterface,
12
12
  FetchFunction,
13
13
  ResolveType,
14
14
  DenormalizeNullable,
15
- Path,
16
- denormalizeCached,
15
+ EntityPath,
16
+ MemoCache,
17
17
  isEntity,
18
18
  denormalize,
19
+ validateQueryKey,
19
20
  } from '@data-client/normalizr';
20
- import { inferResults, validateInference } from '@data-client/normalizr';
21
21
 
22
+ import AbortOptimistic from './AbortOptimistic.js';
22
23
  import createExpireAll from './createExpireAll.js';
23
24
  import createFetch from './createFetch.js';
24
25
  import createInvalidate from './createInvalidate.js';
@@ -41,7 +42,7 @@ export type DataClientDispatch = (value: ActionTypes) => Promise<void>;
41
42
  interface ConstructorProps<D extends GenericDispatch = DataClientDispatch> {
42
43
  dispatch?: D;
43
44
  getState?: () => State<unknown>;
44
- globalCache?: DenormalizeCache;
45
+ memo?: Pick<MemoCache, 'denormalize' | 'query' | 'buildQueryKey'>;
45
46
  }
46
47
 
47
48
  const unsetDispatch = (action: unknown): Promise<void> => {
@@ -77,19 +78,22 @@ export default class Controller<
77
78
  * @see https://dataclient.io/docs/api/Controller#getState
78
79
  */
79
80
  declare readonly getState: () => State<unknown>;
80
- declare readonly globalCache: DenormalizeCache;
81
+ /**
82
+ * Singleton to maintain referential equality between calls
83
+ */
84
+ declare readonly memo: Pick<
85
+ MemoCache,
86
+ 'denormalize' | 'query' | 'buildQueryKey'
87
+ >;
81
88
 
82
89
  constructor({
83
90
  dispatch = unsetDispatch as any,
84
91
  getState = unsetState,
85
- globalCache = {
86
- entities: {},
87
- results: {},
88
- },
92
+ memo = new MemoCache(),
89
93
  }: ConstructorProps<D> = {}) {
90
94
  this.dispatch = dispatch;
91
95
  this.getState = getState;
92
- this.globalCache = globalCache;
96
+ this.memo = memo;
93
97
  }
94
98
 
95
99
  /*************** Action Dispatchers ***************/
@@ -103,9 +107,8 @@ export default class Controller<
103
107
  >(
104
108
  endpoint: E,
105
109
  ...args: readonly [...Parameters<E>]
106
- ): E['schema'] extends undefined | null
107
- ? ReturnType<E>
108
- : Promise<Denormalize<E['schema']>> => {
110
+ ): E['schema'] extends undefined | null ? ReturnType<E>
111
+ : Promise<Denormalize<E['schema']>> => {
109
112
  const action = createFetch(endpoint, {
110
113
  args,
111
114
  });
@@ -128,9 +131,8 @@ export default class Controller<
128
131
  >(
129
132
  endpoint: E,
130
133
  ...args: readonly [...Parameters<E>]
131
- ): E['schema'] extends undefined | null
132
- ? ReturnType<E> | ResolveType<E>
133
- : Promise<Denormalize<E['schema']>> | Denormalize<E['schema']> => {
134
+ ): E['schema'] extends undefined | null ? ReturnType<E> | ResolveType<E>
135
+ : Promise<Denormalize<E['schema']>> | Denormalize<E['schema']> => {
134
136
  const { data, expiresAt, expiryStatus } = this.getResponse(
135
137
  endpoint,
136
138
  ...args,
@@ -149,13 +151,13 @@ export default class Controller<
149
151
  endpoint: E,
150
152
  ...args: readonly [...Parameters<E>] | readonly [null]
151
153
  ): Promise<void> =>
152
- args[0] !== null
153
- ? this.dispatch(
154
- createInvalidate(endpoint, {
155
- args: args as readonly [...Parameters<E>],
156
- }),
157
- )
158
- : Promise.resolve();
154
+ args[0] !== null ?
155
+ this.dispatch(
156
+ createInvalidate(endpoint, {
157
+ args: args as readonly [...Parameters<E>],
158
+ }),
159
+ )
160
+ : Promise.resolve();
159
161
 
160
162
  /**
161
163
  * Forces refetching and suspense on useSuspense on all matching endpoint result keys.
@@ -241,7 +243,7 @@ export default class Controller<
241
243
  args: readonly [...Parameters<E>];
242
244
  response: any;
243
245
  fetchedAt: number;
244
- error?: false;
246
+ error?: false | undefined;
245
247
  },
246
248
  ): Promise<void> => {
247
249
  return this.dispatch(createSet(endpoint, meta as any));
@@ -261,13 +263,13 @@ export default class Controller<
261
263
  endpoint: E,
262
264
  ...args: readonly [...Parameters<E>] | readonly [null]
263
265
  ): Promise<void> =>
264
- args[0] !== null
265
- ? this.dispatch(
266
- createSubscription(endpoint, {
267
- args: args as readonly [...Parameters<E>],
268
- }),
269
- )
270
- : Promise.resolve();
266
+ args[0] !== null ?
267
+ this.dispatch(
268
+ createSubscription(endpoint, {
269
+ args: args as readonly [...Parameters<E>],
270
+ }),
271
+ )
272
+ : Promise.resolve();
271
273
 
272
274
  /**
273
275
  * Marks completion of subscription to a given Endpoint.
@@ -283,13 +285,13 @@ export default class Controller<
283
285
  endpoint: E,
284
286
  ...args: readonly [...Parameters<E>] | readonly [null]
285
287
  ): Promise<void> =>
286
- args[0] !== null
287
- ? this.dispatch(
288
- createUnsubscription(endpoint, {
289
- args: args as readonly [...Parameters<E>],
290
- }),
291
- )
292
- : Promise.resolve();
288
+ args[0] !== null ?
289
+ this.dispatch(
290
+ createUnsubscription(endpoint, {
291
+ args: args as readonly [...Parameters<E>],
292
+ }),
293
+ )
294
+ : Promise.resolve();
293
295
 
294
296
  /*************** More ***************/
295
297
 
@@ -326,9 +328,9 @@ export default class Controller<
326
328
  const key = endpoint.key(...args);
327
329
 
328
330
  const meta = selectMeta(state, key);
329
- const results = state.results[key];
331
+ const error = state.endpoints[key];
330
332
 
331
- if (results !== undefined && meta?.errorPolicy === 'soft') return;
333
+ if (error !== undefined && meta?.errorPolicy === 'soft') return;
332
334
 
333
335
  return meta?.error as any;
334
336
  };
@@ -337,106 +339,183 @@ export default class Controller<
337
339
  * Gets the (globally referentially stable) response for a given endpoint/args pair from state given.
338
340
  * @see https://dataclient.io/docs/api/Controller#getResponse
339
341
  */
340
- getResponse = <
342
+ getResponse<E extends EndpointInterface>(
343
+ endpoint: E,
344
+ ...rest: readonly [null, State<unknown>]
345
+ ): {
346
+ data: DenormalizeNullable<E['schema']>;
347
+ expiryStatus: ExpiryStatus;
348
+ expiresAt: number;
349
+ };
350
+
351
+ getResponse<E extends EndpointInterface>(
352
+ endpoint: E,
353
+ ...rest: readonly [...Parameters<E>, State<unknown>]
354
+ ): {
355
+ data: DenormalizeNullable<E['schema']>;
356
+ expiryStatus: ExpiryStatus;
357
+ expiresAt: number;
358
+ };
359
+
360
+ getResponse<
341
361
  E extends Pick<EndpointInterface, 'key' | 'schema' | 'invalidIfStale'>,
342
- Args extends readonly [...Parameters<E['key']>] | readonly [null],
343
362
  >(
344
363
  endpoint: E,
345
- ...rest: [...Args, State<unknown>]
364
+ ...rest: readonly [
365
+ ...(readonly [...Parameters<E['key']>] | readonly [null]),
366
+ State<unknown>,
367
+ ]
346
368
  ): {
347
369
  data: DenormalizeNullable<E['schema']>;
348
370
  expiryStatus: ExpiryStatus;
349
371
  expiresAt: number;
350
- } => {
372
+ };
373
+
374
+ getResponse(
375
+ endpoint: EndpointInterface,
376
+ ...rest: readonly [...unknown[], State<unknown>]
377
+ ): {
378
+ data: unknown;
379
+ expiryStatus: ExpiryStatus;
380
+ expiresAt: number;
381
+ } {
351
382
  const state = rest[rest.length - 1] as State<unknown>;
352
383
  // this is typescript generics breaking
353
384
  const args: any = rest
354
385
  .slice(0, rest.length - 1)
355
- .map(ensurePojo) as Parameters<E['key']>;
386
+ // handle FormData
387
+ .map(ensurePojo);
356
388
  const isActive = args.length !== 1 || args[0] !== null;
357
389
  const key = isActive ? endpoint.key(...args) : '';
358
- const cacheResults = isActive ? state.results[key] : undefined;
390
+ const cacheEndpoints = isActive ? state.endpoints[key] : undefined;
359
391
  const schema = endpoint.schema;
360
392
  const meta = selectMeta(state, key);
361
393
  let expiresAt = meta?.expiresAt;
362
-
363
- let invalidResults = false;
364
- let results;
365
- if (cacheResults === undefined && endpoint.schema !== undefined) {
366
- results = inferResults(
367
- endpoint.schema,
368
- args,
369
- state.indexes,
370
- state.entities,
371
- );
372
- invalidResults = !validateInference(results);
373
- if (!expiresAt && invalidResults) expiresAt = 1;
374
- } else {
375
- results = cacheResults;
376
- }
394
+ // if we have no endpoint entry, and our endpoint has a schema - try querying the store
395
+ const shouldQuery = cacheEndpoints === undefined && schema !== undefined;
396
+
397
+ const input =
398
+ shouldQuery ?
399
+ // nothing in endpoints cache, so try querying if we have a schema to do so
400
+ this.memo.buildQueryKey(
401
+ key,
402
+ schema,
403
+ args,
404
+ state.entities as any,
405
+ state.indexes,
406
+ )
407
+ : cacheEndpoints;
377
408
 
378
409
  if (!isActive) {
410
+ // when not active simply return the query input without denormalizing
379
411
  return {
380
- data: results as any,
412
+ data: input as any,
381
413
  expiryStatus: ExpiryStatus.Valid,
382
414
  expiresAt: Infinity,
383
415
  };
384
416
  }
385
417
 
386
- if (!endpoint.schema || !schemaHasEntity(endpoint.schema)) {
418
+ let isInvalid = false;
419
+ if (shouldQuery) {
420
+ isInvalid = !validateQueryKey(input);
421
+ } else if (!schema || !schemaHasEntity(schema)) {
387
422
  return {
388
- data: results,
389
- expiryStatus: meta?.invalidated
390
- ? ExpiryStatus.Invalid
391
- : cacheResults && !endpoint.invalidIfStale
392
- ? ExpiryStatus.Valid
423
+ data: cacheEndpoints,
424
+ expiryStatus:
425
+ meta?.invalidated ? ExpiryStatus.Invalid
426
+ : cacheEndpoints && !endpoint.invalidIfStale ? ExpiryStatus.Valid
393
427
  : ExpiryStatus.InvalidIfStale,
394
428
  expiresAt: expiresAt || 0,
395
- } as {
396
- data: DenormalizeNullable<E['schema']>;
397
- expiryStatus: ExpiryStatus;
398
- expiresAt: number;
399
429
  };
400
430
  }
401
431
 
402
- if (!this.globalCache.results[key])
403
- this.globalCache.results[key] = new WeakEntityMap();
404
-
405
432
  // second argument is false if any entities are missing
406
433
  // eslint-disable-next-line prefer-const
407
- const { data, paths } = denormalizeCached(
408
- results,
434
+ const { data, paths } = this.memo.denormalize(
435
+ input,
409
436
  schema,
410
437
  state.entities,
411
- this.globalCache.entities,
412
- this.globalCache.results[key],
413
438
  args,
414
- ) as { data: DenormalizeNullable<E['schema']>; paths: Path[] };
439
+ ) as { data: any; paths: EntityPath[] };
440
+
441
+ // note: isInvalid can only be true if shouldQuery is true
442
+ if (!expiresAt && isInvalid) expiresAt = 1;
443
+
444
+ return this.getSchemaResponse(
445
+ data,
446
+ paths,
447
+ state.entityMeta,
448
+ expiresAt,
449
+ endpoint.invalidIfStale || isInvalid,
450
+ meta,
451
+ );
452
+ }
453
+
454
+ /**
455
+ * Queries the store for a Querable schema
456
+ * @see https://dataclient.io/docs/api/Controller#get
457
+ */
458
+ get<S extends Queryable>(
459
+ schema: S,
460
+ ...rest: readonly [
461
+ ...SchemaArgs<S>,
462
+ Pick<State<unknown>, 'entities' | 'entityMeta'>,
463
+ ]
464
+ ): DenormalizeNullable<S> | undefined {
465
+ const state = rest[rest.length - 1] as State<any>;
466
+ // this is typescript generics breaking
467
+ const args: any = rest
468
+ .slice(0, rest.length - 1)
469
+ .map(ensurePojo) as SchemaArgs<S>;
470
+
471
+ // NOTE: different orders can result in cache busting here; but since it's just a perf penalty we will allow for now
472
+ const key = JSON.stringify(args);
473
+
474
+ return this.memo.query(
475
+ key,
476
+ schema,
477
+ args,
478
+ state.entities as any,
479
+ state.indexes,
480
+ );
481
+ }
482
+
483
+ private getSchemaResponse<T>(
484
+ data: T,
485
+ paths: EntityPath[],
486
+ entityMeta: State<unknown>['entityMeta'],
487
+ expiresAt: number,
488
+ invalidIfStale: boolean,
489
+ meta: { error?: unknown; invalidated?: unknown } = {},
490
+ ): {
491
+ data: T;
492
+ expiryStatus: ExpiryStatus;
493
+ expiresAt: number;
494
+ } {
415
495
  const invalidDenormalize = typeof data === 'symbol';
416
496
 
417
497
  // fallback to entity expiry time
418
498
  if (!expiresAt) {
419
- expiresAt = entityExpiresAt(paths, state.entityMeta);
499
+ expiresAt = entityExpiresAt(paths, entityMeta);
420
500
  }
421
501
 
422
502
  // https://dataclient.io/docs/concepts/expiry-policy#expiry-status
423
503
  // we don't track the difference between stale or fresh because that is tied to triggering
424
504
  // conditions
425
505
  const expiryStatus =
426
- meta?.invalidated || (invalidDenormalize && !meta?.error)
427
- ? ExpiryStatus.Invalid
428
- : invalidDenormalize || endpoint.invalidIfStale || invalidResults
429
- ? ExpiryStatus.InvalidIfStale
430
- : ExpiryStatus.Valid;
506
+ meta?.invalidated || (invalidDenormalize && !meta?.error) ?
507
+ ExpiryStatus.Invalid
508
+ : invalidDenormalize || invalidIfStale ? ExpiryStatus.InvalidIfStale
509
+ : ExpiryStatus.Valid;
431
510
 
432
511
  return { data, expiryStatus, expiresAt };
433
- };
512
+ }
434
513
  }
435
514
 
436
515
  // benchmark: https://www.measurethat.net/Benchmarks/Show/24691/0/min-reducer-vs-imperative-with-paths
437
516
  // earliest expiry dictates age
438
517
  function entityExpiresAt(
439
- paths: Path[],
518
+ paths: EntityPath[],
440
519
  entityMeta: {
441
520
  readonly [entityKey: string]: {
442
521
  readonly [pk: string]: {
@@ -458,7 +537,7 @@ function entityExpiresAt(
458
537
 
459
538
  /** Determine whether the schema has any entities.
460
539
  *
461
- * Without entities, denormalization is not needed, and results should not be inferred.
540
+ * Without entities, denormalization is not needed, and results should not be queried.
462
541
  */
463
542
  function schemaHasEntity(schema: Schema): boolean {
464
543
  if (isEntity(schema)) return true;
@@ -481,6 +560,7 @@ class Snapshot<T = unknown> implements SnapshotInterface {
481
560
  private state: State<T>;
482
561
  private controller: Controller;
483
562
  readonly fetchedAt: number;
563
+ readonly abort = new AbortOptimistic();
484
564
 
485
565
  constructor(controller: Controller, state: State<T>, fetchedAt = 0) {
486
566
  this.state = state;
@@ -490,20 +570,48 @@ class Snapshot<T = unknown> implements SnapshotInterface {
490
570
 
491
571
  /*************** Data Access ***************/
492
572
  /** @see https://dataclient.io/docs/api/Snapshot#getResponse */
493
- getResponse = <
573
+ getResponse<E extends EndpointInterface>(
574
+ endpoint: E,
575
+ ...args: readonly [null]
576
+ ): {
577
+ data: DenormalizeNullable<E['schema']>;
578
+ expiryStatus: ExpiryStatus;
579
+ expiresAt: number;
580
+ };
581
+
582
+ getResponse<E extends EndpointInterface>(
583
+ endpoint: E,
584
+ ...args: readonly [...Parameters<E>]
585
+ ): {
586
+ data: DenormalizeNullable<E['schema']>;
587
+ expiryStatus: ExpiryStatus;
588
+ expiresAt: number;
589
+ };
590
+
591
+ getResponse<
494
592
  E extends Pick<EndpointInterface, 'key' | 'schema' | 'invalidIfStale'>,
495
- Args extends readonly [...Parameters<E['key']>],
496
593
  >(
497
594
  endpoint: E,
498
- ...args: Args
595
+ ...args: readonly [...Parameters<E['key']>] | readonly [null]
499
596
  ): {
500
597
  data: DenormalizeNullable<E['schema']>;
501
598
  expiryStatus: ExpiryStatus;
502
599
  expiresAt: number;
503
- } => {
504
- return this.controller.getResponse(endpoint, ...args, this.state);
505
600
  };
506
601
 
602
+ getResponse<
603
+ E extends Pick<EndpointInterface, 'key' | 'schema' | 'invalidIfStale'>,
604
+ >(
605
+ endpoint: E,
606
+ ...args: readonly [...Parameters<E['key']>] | readonly [null]
607
+ ): {
608
+ data: DenormalizeNullable<E['schema']>;
609
+ expiryStatus: ExpiryStatus;
610
+ expiresAt: number;
611
+ } {
612
+ return this.controller.getResponse(endpoint, ...args, this.state);
613
+ }
614
+
507
615
  /** @see https://dataclient.io/docs/api/Snapshot#getError */
508
616
  getError = <
509
617
  E extends Pick<EndpointInterface, 'key'>,
@@ -514,4 +622,15 @@ class Snapshot<T = unknown> implements SnapshotInterface {
514
622
  ): ErrorTypes | undefined => {
515
623
  return this.controller.getError(endpoint, ...args, this.state);
516
624
  };
625
+
626
+ /**
627
+ * Retrieved memoized value for any Querable schema
628
+ * @see https://dataclient.io/docs/api/Snapshot#get
629
+ */
630
+ get<S extends Queryable>(
631
+ schema: S,
632
+ ...args: SchemaArgs<S>
633
+ ): DenormalizeNullable<S> | undefined {
634
+ return this.controller.get(schema, ...args, this.state);
635
+ }
517
636
  }
@@ -1,9 +1,7 @@
1
- import { Endpoint, Entity } from '@data-client/endpoint';
2
1
  import { normalize } from '@data-client/normalizr';
3
2
  import { CoolerArticleResource } from '__tests__/new';
4
3
  import { createEntityMeta } from '__tests__/utils';
5
4
 
6
- import { ExpiryStatus } from '../..';
7
5
  import { initialState } from '../../state/reducer/createReducer';
8
6
  import Controller from '../Controller';
9
7
 
@@ -45,7 +43,7 @@ describe('Controller', () => {
45
43
  const state = {
46
44
  ...initialState,
47
45
  entities,
48
- results: {
46
+ endpoints: {
49
47
  [fetchKey]: result,
50
48
  },
51
49
  entityMeta: createEntityMeta(entities),
@@ -82,7 +80,7 @@ describe('Controller', () => {
82
80
  const state = {
83
81
  ...initialState,
84
82
  entities,
85
- results: {
83
+ endpoints: {
86
84
  [fetchKey]: result,
87
85
  },
88
86
  entityMeta: createEntityMeta(entities),
@@ -0,0 +1,120 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`Controller.get() Query+All based on args 1`] = `
4
+ [
5
+ Tacos {
6
+ "id": "1",
7
+ "type": "foo",
8
+ },
9
+ ]
10
+ `;
11
+
12
+ exports[`Controller.get() Query+All based on args 2`] = `
13
+ [
14
+ Tacos {
15
+ "id": "1",
16
+ "type": "foo",
17
+ },
18
+ Tacos {
19
+ "id": "2",
20
+ "type": "bar",
21
+ },
22
+ ]
23
+ `;
24
+
25
+ exports[`Controller.get() Query+All based on args 3`] = `
26
+ [
27
+ Tacos {
28
+ "id": "2",
29
+ "type": "bar",
30
+ },
31
+ Tacos {
32
+ "id": "3",
33
+ "name": "thing3",
34
+ "type": "bar",
35
+ },
36
+ ]
37
+ `;
38
+
39
+ exports[`Controller.get() Union based on args 1`] = `
40
+ User {
41
+ "id": "1",
42
+ "type": "users",
43
+ "username": "bob",
44
+ }
45
+ `;
46
+
47
+ exports[`Controller.get() Union based on args 2`] = `
48
+ Group {
49
+ "groupname": "fast",
50
+ "id": "2",
51
+ "memberCount": 5,
52
+ "type": "groups",
53
+ }
54
+ `;
55
+
56
+ exports[`Controller.get() query All should get all entities 1`] = `
57
+ [
58
+ Tacos {
59
+ "id": "1",
60
+ "type": "foo",
61
+ },
62
+ Tacos {
63
+ "id": "2",
64
+ "type": "bar",
65
+ },
66
+ ]
67
+ `;
68
+
69
+ exports[`Controller.get() query All should include new entities when added 1`] = `
70
+ [
71
+ Tacos {
72
+ "id": "1",
73
+ "type": "foo",
74
+ },
75
+ Tacos {
76
+ "id": "2",
77
+ "type": "bar",
78
+ },
79
+ Tacos {
80
+ "id": "3",
81
+ "type": "extra",
82
+ },
83
+ ]
84
+ `;
85
+
86
+ exports[`Controller.get() query Collection based on args 1`] = `
87
+ [
88
+ Tacos {
89
+ "id": "1",
90
+ "type": "foo",
91
+ },
92
+ ]
93
+ `;
94
+
95
+ exports[`Controller.get() query Collection based on args 2`] = `
96
+ [
97
+ Tacos {
98
+ "id": "1",
99
+ "type": "foo",
100
+ },
101
+ Tacos {
102
+ "id": "2",
103
+ "type": "bar",
104
+ },
105
+ ]
106
+ `;
107
+
108
+ exports[`Controller.get() query Entity based on index 1`] = `
109
+ User {
110
+ "id": "1",
111
+ "username": "bob",
112
+ }
113
+ `;
114
+
115
+ exports[`Controller.get() query Entity based on pk 1`] = `
116
+ Tacos {
117
+ "id": "1",
118
+ "type": "foo",
119
+ }
120
+ `;