@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.
- package/README.md +24 -1
- package/dist/index.js +132 -96
- package/dist/index.umd.min.js +1 -1
- package/legacy/actionTypes.js +1 -1
- package/legacy/actions.js +1 -1
- package/legacy/controller/AbortOptimistic.js +2 -0
- package/legacy/controller/Controller.js +110 -78
- package/legacy/controller/createExpireAll.js +1 -1
- package/legacy/controller/createFetch.js +1 -1
- package/legacy/controller/createInvalidate.js +1 -1
- package/legacy/controller/createInvalidateAll.js +1 -1
- package/legacy/controller/createOptimistic.js +1 -1
- package/legacy/controller/createReset.js +1 -1
- package/legacy/controller/createSet.js +1 -1
- package/legacy/controller/createSubscription.js +1 -1
- package/legacy/controller/ensurePojo.js +2 -3
- package/legacy/controller/types.js +1 -1
- package/legacy/index.js +1 -1
- package/legacy/internal.js +2 -2
- package/legacy/manager/ConnectionListener.js +1 -1
- package/legacy/manager/DefaultConnectionListener.js +1 -1
- package/legacy/manager/DevtoolsManager.js +6 -2
- package/legacy/manager/LogoutManager.js +1 -1
- package/legacy/manager/NetworkManager.js +1 -1
- package/legacy/manager/PollingSubscription.js +1 -1
- package/legacy/manager/SubscriptionManager.js +1 -1
- package/legacy/manager/applyManager.js +1 -1
- package/legacy/manager/devtoolsTypes.js +1 -1
- package/legacy/manager/index.js +1 -1
- package/legacy/middlewareTypes.js +1 -1
- package/legacy/next/index.js +1 -1
- package/legacy/state/RIC.js +1 -1
- package/legacy/state/reducer/createReducer.js +6 -6
- package/legacy/state/reducer/expireReducer.js +1 -1
- package/legacy/state/reducer/fetchReducer.js +1 -1
- package/legacy/state/reducer/invalidateReducer.js +5 -5
- package/legacy/state/reducer/setReducer.js +7 -7
- package/legacy/state/selectMeta.js +1 -1
- package/legacy/types.js +1 -1
- package/lib/actionTypes.js +1 -1
- package/lib/actions.d.ts +1 -1
- package/lib/actions.d.ts.map +1 -1
- package/lib/actions.js +1 -1
- package/lib/controller/AbortOptimistic.d.ts +3 -0
- package/lib/controller/AbortOptimistic.d.ts.map +1 -0
- package/lib/controller/AbortOptimistic.js +2 -0
- package/lib/controller/Controller.d.ts +36 -11
- package/lib/controller/Controller.d.ts.map +1 -1
- package/lib/controller/Controller.js +110 -78
- package/lib/controller/createExpireAll.js +1 -1
- package/lib/controller/createFetch.js +1 -1
- package/lib/controller/createInvalidate.js +1 -1
- package/lib/controller/createInvalidateAll.js +1 -1
- package/lib/controller/createOptimistic.js +1 -1
- package/lib/controller/createReset.js +1 -1
- package/lib/controller/createSet.js +1 -1
- package/lib/controller/createSubscription.js +1 -1
- package/lib/controller/ensurePojo.d.ts.map +1 -1
- package/lib/controller/ensurePojo.js +2 -3
- package/lib/controller/types.d.ts.map +1 -1
- package/lib/controller/types.js +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/internal.d.ts +1 -1
- package/lib/internal.d.ts.map +1 -1
- package/lib/internal.js +2 -2
- package/lib/manager/ConnectionListener.js +1 -1
- package/lib/manager/DefaultConnectionListener.js +1 -1
- package/lib/manager/DevtoolsManager.d.ts +1 -0
- package/lib/manager/DevtoolsManager.d.ts.map +1 -1
- package/lib/manager/DevtoolsManager.js +6 -2
- package/lib/manager/LogoutManager.js +1 -1
- package/lib/manager/NetworkManager.js +1 -1
- package/lib/manager/PollingSubscription.js +1 -1
- package/lib/manager/SubscriptionManager.js +1 -1
- package/lib/manager/applyManager.js +1 -1
- package/lib/manager/devtoolsTypes.js +1 -1
- package/lib/manager/index.js +1 -1
- package/lib/middlewareTypes.d.ts.map +1 -1
- package/lib/middlewareTypes.js +1 -1
- package/lib/next/index.js +1 -1
- package/lib/state/RIC.d.ts.map +1 -1
- package/lib/state/RIC.js +1 -1
- package/lib/state/reducer/createReducer.js +6 -6
- package/lib/state/reducer/expireReducer.d.ts +1 -1
- package/lib/state/reducer/expireReducer.js +1 -1
- package/lib/state/reducer/fetchReducer.js +1 -1
- package/lib/state/reducer/invalidateReducer.d.ts +1 -1
- package/lib/state/reducer/invalidateReducer.js +6 -6
- package/lib/state/reducer/setReducer.d.ts.map +1 -1
- package/lib/state/reducer/setReducer.js +8 -8
- package/lib/state/selectMeta.js +1 -1
- package/lib/types.d.ts +1 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +1 -1
- package/package.json +3 -3
- package/src/actions.ts +1 -1
- package/src/controller/AbortOptimistic.ts +1 -0
- package/src/controller/Controller.ts +215 -96
- package/src/controller/__tests__/Controller.ts +2 -4
- package/src/controller/__tests__/__snapshots__/get.ts.snap +120 -0
- package/src/controller/__tests__/get.ts +285 -0
- package/src/controller/__tests__/getResponse.ts +2 -2
- package/src/controller/createSet.ts +3 -2
- package/src/controller/ensurePojo.ts +6 -7
- package/src/controller/types.ts +2 -4
- package/src/index.ts +2 -1
- package/src/internal.ts +1 -1
- package/src/manager/DevtoolsManager.ts +8 -2
- package/src/manager/__tests__/subscriptionManager.ts +3 -2
- package/src/middlewareTypes.ts +4 -12
- package/src/state/RIC.ts +3 -4
- package/src/state/__tests__/__snapshots__/reducer.ts.snap +4 -4
- package/src/state/__tests__/reducer.ts +32 -34
- package/src/state/reducer/createReducer.ts +3 -3
- package/src/state/reducer/invalidateReducer.ts +4 -4
- package/src/state/reducer/setReducer.ts +10 -9
- package/src/types.ts +3 -1
- package/ts3.4/actions.d.ts +1 -1
- package/ts3.4/controller/AbortOptimistic.d.ts +3 -0
- package/ts3.4/controller/Controller.d.ts +47 -19
- package/ts3.4/index.d.ts +1 -1
- package/ts3.4/internal.d.ts +1 -1
- package/ts3.4/manager/DevtoolsManager.d.ts +1 -0
- package/ts3.4/state/reducer/expireReducer.d.ts +1 -1
- package/ts3.4/state/reducer/invalidateReducer.d.ts +1 -1
- 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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
331
|
+
const error = state.endpoints[key];
|
|
330
332
|
|
|
331
|
-
if (
|
|
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: [
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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:
|
|
412
|
+
data: input as any,
|
|
381
413
|
expiryStatus: ExpiryStatus.Valid,
|
|
382
414
|
expiresAt: Infinity,
|
|
383
415
|
};
|
|
384
416
|
}
|
|
385
417
|
|
|
386
|
-
|
|
418
|
+
let isInvalid = false;
|
|
419
|
+
if (shouldQuery) {
|
|
420
|
+
isInvalid = !validateQueryKey(input);
|
|
421
|
+
} else if (!schema || !schemaHasEntity(schema)) {
|
|
387
422
|
return {
|
|
388
|
-
data:
|
|
389
|
-
expiryStatus:
|
|
390
|
-
? ExpiryStatus.Invalid
|
|
391
|
-
:
|
|
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 } =
|
|
408
|
-
|
|
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:
|
|
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,
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
`;
|