@data-client/core 0.18.0 → 0.18.2

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 (58) hide show
  1. package/CHANGELOG.md +147 -1
  2. package/dist/index.js +19 -11
  3. package/dist/index.umd.min.js +1 -1
  4. package/dist/mock.js +19 -11
  5. package/legacy/actions.js +1 -1
  6. package/legacy/controller/Controller.js +11 -4
  7. package/legacy/controller/setManyTypes.js +2 -0
  8. package/legacy/manager/DevtoolsManager.js +9 -9
  9. package/legacy/mock/MockController.js +1 -1
  10. package/legacy/mock/fixtureTypes.js +1 -1
  11. package/legacy/mock/mockState.js +1 -1
  12. package/lib/actionTypes.d.ts +11 -11
  13. package/lib/actions.d.ts +1 -1
  14. package/lib/actions.d.ts.map +1 -1
  15. package/lib/actions.js +1 -1
  16. package/lib/controller/Controller.d.ts +13 -4
  17. package/lib/controller/Controller.d.ts.map +1 -1
  18. package/lib/controller/Controller.js +11 -4
  19. package/lib/controller/ensurePojo.d.ts.map +1 -1
  20. package/lib/controller/setManyTypes.d.ts +69 -0
  21. package/lib/controller/setManyTypes.d.ts.map +1 -0
  22. package/lib/controller/setManyTypes.js +2 -0
  23. package/lib/manager/DefaultConnectionListener.d.ts.map +1 -1
  24. package/lib/manager/DevtoolsManager.d.ts.map +1 -1
  25. package/lib/manager/DevtoolsManager.js +9 -9
  26. package/lib/manager/LogoutManager.d.ts.map +1 -1
  27. package/lib/manager/NetworkManager.d.ts.map +1 -1
  28. package/lib/manager/PollingSubscription.d.ts.map +1 -1
  29. package/lib/manager/SubscriptionManager.d.ts.map +1 -1
  30. package/lib/mock/MockController.d.ts +1 -1
  31. package/lib/mock/MockController.d.ts.map +1 -1
  32. package/lib/mock/MockController.js +1 -1
  33. package/lib/mock/fixtureTypes.d.ts +2 -2
  34. package/lib/mock/fixtureTypes.d.ts.map +1 -1
  35. package/lib/mock/fixtureTypes.js +1 -1
  36. package/lib/mock/mockState.js +1 -1
  37. package/lib/state/GCPolicy.d.ts.map +1 -1
  38. package/lib/state/reducer/expireReducer.d.ts +11 -11
  39. package/lib/state/reducer/invalidateReducer.d.ts +14 -14
  40. package/package.json +6 -5
  41. package/src/actions.ts +1 -1
  42. package/src/controller/Controller.ts +26 -6
  43. package/src/controller/__tests__/getResponse.ts +105 -0
  44. package/src/controller/setManyTypes.ts +114 -0
  45. package/src/manager/DevtoolsManager.ts +11 -8
  46. package/src/manager/__tests__/networkManager.ts +8 -1
  47. package/src/mock/MockController.ts +5 -2
  48. package/src/mock/__tests__/MockController.ts +15 -0
  49. package/src/mock/fixtureTypes.ts +3 -4
  50. package/src/mock/mockState.ts +1 -1
  51. package/ts3.4/actionTypes.d.ts +11 -11
  52. package/ts3.4/actions.d.ts +1 -1
  53. package/ts3.4/controller/Controller.d.ts +10 -7
  54. package/ts3.4/controller/setManyTypes.d.ts +75 -0
  55. package/ts3.4/mock/MockController.d.ts +1 -1
  56. package/ts3.4/mock/fixtureTypes.d.ts +2 -2
  57. package/ts3.4/state/reducer/expireReducer.d.ts +11 -11
  58. package/ts3.4/state/reducer/invalidateReducer.d.ts +14 -14
@@ -1,4 +1,5 @@
1
1
  import { Endpoint, Entity, Scalar, schema } from '@data-client/endpoint';
2
+ import { INVALID } from '@data-client/normalizr';
2
3
 
3
4
  import { ExpiryStatus } from '../..';
4
5
  import { initialState } from '../../state/reducer/createReducer';
@@ -195,6 +196,63 @@ describe('Controller.getResponse()', () => {
195
196
  });
196
197
  });
197
198
 
199
+ describe('Controller.getResponse() with deleted entities', () => {
200
+ class Tacos extends Entity {
201
+ type = '';
202
+ id = '';
203
+ }
204
+ const ep = new Endpoint(({ id }: { id: string }) => Promise.resolve(), {
205
+ key: ({ id }) => `taco ${id}`,
206
+ schema: Tacos,
207
+ });
208
+ const key = ep.key({ id: '1' });
209
+ const state = {
210
+ ...initialState,
211
+ entities: { Tacos: { 1: INVALID } },
212
+ endpoints: { [key]: '1' },
213
+ };
214
+ // error meta keeps the response from counting as expired
215
+ const erroredState = {
216
+ ...state,
217
+ meta: {
218
+ [key]: {
219
+ date: 0,
220
+ fetchedAt: 0,
221
+ expiresAt: Infinity,
222
+ error: new Error('failed'),
223
+ },
224
+ },
225
+ };
226
+
227
+ it('returns undefined data instead of the INVALID symbol', () => {
228
+ const controller = new Contoller();
229
+ const { data, expiryStatus } = controller.getResponse(
230
+ ep,
231
+ { id: '1' },
232
+ state,
233
+ );
234
+ expect(data).toBeUndefined();
235
+ expect(expiryStatus).toBe(ExpiryStatus.Invalid);
236
+ });
237
+
238
+ it('returns undefined data when the last fetch errored', () => {
239
+ const controller = new Contoller();
240
+ const { data, expiryStatus } = controller.getResponse(
241
+ ep,
242
+ { id: '1' },
243
+ erroredState,
244
+ );
245
+ expect(data).toBeUndefined();
246
+ expect(expiryStatus).toBe(ExpiryStatus.InvalidIfStale);
247
+ });
248
+
249
+ it('fetchIfStale() resolves errored deleted entities to undefined', () => {
250
+ const controller = new Contoller();
251
+ controller.getState = () => erroredState as any;
252
+ expect(controller.fetchIfStale(ep, { id: '1' })).toBeUndefined();
253
+ });
254
+ });
255
+
198
256
  describe('Controller.getResponse() with Scalar', () => {
199
257
  // Regression: `requiresDenormalize` (formerly `schemaHasEntity`) previously
200
258
  // walked `Object.values(scalar)` and recursed into the `lensSelector`
@@ -318,6 +376,53 @@ describe('Controller.getResponse() with Scalar', () => {
318
376
  );
319
377
  });
320
378
 
379
+ describe('Controller.getResponse() with schema-less endpoints', () => {
380
+ it.each([
381
+ ['empty string', ''],
382
+ ['zero', 0],
383
+ ['false', false],
384
+ ['null', null],
385
+ ])('treats cached %s as valid data', (_name, cachedValue) => {
386
+ const controller = new Contoller();
387
+ const endpoint = new Endpoint(() => Promise.resolve(), {
388
+ key: () => 'falsy-endpoint',
389
+ });
390
+ const key = endpoint.key();
391
+ const fetchedAt = Date.now();
392
+ const state = {
393
+ ...initialState,
394
+ endpoints: {
395
+ [key]: cachedValue,
396
+ },
397
+ meta: {
398
+ [key]: {
399
+ date: fetchedAt,
400
+ fetchedAt,
401
+ expiresAt: fetchedAt + 1000,
402
+ },
403
+ },
404
+ };
405
+
406
+ const { data, expiryStatus } = controller.getResponse(endpoint, state);
407
+ expect(data).toBe(cachedValue);
408
+ expect(expiryStatus).toBe(ExpiryStatus.Valid);
409
+ });
410
+
411
+ it('treats undefined cache entries as invalid', () => {
412
+ const controller = new Contoller();
413
+ const endpoint = new Endpoint(() => Promise.resolve(), {
414
+ key: () => 'missing-endpoint',
415
+ });
416
+
417
+ const { data, expiryStatus } = controller.getResponse(
418
+ endpoint,
419
+ initialState,
420
+ );
421
+ expect(data).toBeUndefined();
422
+ expect(expiryStatus).toBe(ExpiryStatus.Invalid);
423
+ });
424
+ });
425
+
321
426
  describe('Snapshot.getResponseMeta()', () => {
322
427
  it('denormalizes schema with extra members but not set', () => {
323
428
  const controller = new Contoller();
@@ -0,0 +1,114 @@
1
+ /** Value types for `Controller.set()`, including batch `set([Entity], rows)` */
2
+ import type { Denormalize, EntityInterface } from '@data-client/normalizr';
3
+
4
+ /** What one row normalizes to: a reference to one stored entity */
5
+ type EntityRef = string | { readonly id: string; readonly schema: string };
6
+
7
+ /** Schemas that write each row to one stored entity: Entity, Union, or Invalidate (batch delete).
8
+ * Query, All and Collection don't: they normalize to lists, or Collection keys by args batch set() lacks. */
9
+ type SetEntitySchema = EntityInterface | EntityRefSchema;
10
+
11
+ /** Union or Invalidate */
12
+ type EntityRefSchema = {
13
+ _normalizeNullable(): EntityRef | undefined;
14
+ // excludes Entity and Collection
15
+ pk?: never;
16
+ };
17
+
18
+ /** `[Entity]`, `schema.Array(Entity)` or `schema.Values(Entity)` (or of a Union or Invalidate) */
19
+ export type SetManySchema =
20
+ | readonly SetEntitySchema[]
21
+ | {
22
+ readonly schema: SetEntitySchema | Record<string, EntityInterface>;
23
+ // Array and Values; excludes schema.Object, whose queryKey() returns any
24
+ schemaKey(): string;
25
+ queryKey(...args: any): undefined;
26
+ // excludes Entity, whose `any` returns match the members above
27
+ pk?: never;
28
+ };
29
+
30
+ /** `new Invalidate(Entity)` (or of a Union): not Queryable, since its queryKey() returns undefined */
31
+ export type SetInvalidateSchema = EntityRefSchema & {
32
+ // excludes Union
33
+ queryKey(...args: any): undefined;
34
+ // excludes Lazy
35
+ query?: never;
36
+ };
37
+
38
+ type IsUnion<T, U = T> =
39
+ T extends unknown ?
40
+ [U] extends [T] ?
41
+ false
42
+ : true
43
+ : never;
44
+
45
+ type FunctionKeys<U> = {
46
+ [K in keyof U]: U[K] extends (...args: any) => any ? K : never;
47
+ }[keyof U];
48
+
49
+ /** Raw input for one field: numbers and strings coerce (literals stay exact);
50
+ * objects are pre-normalize */
51
+ type SetField<T> =
52
+ T extends number ?
53
+ number extends T ?
54
+ T | string
55
+ : T
56
+ : T extends string ?
57
+ string extends T ?
58
+ T | number
59
+ : T
60
+ : T extends object ? unknown
61
+ : T;
62
+
63
+ /** Fields of one row (or a coerced primitive); like EntityFields, but distributive
64
+ * and without key remapping (TS 4.0). A Union gets one row per member, so a
65
+ * discriminator like `type` selects the member the other fields are checked against. */
66
+ type SetRow<U> =
67
+ // EntityMixin and other untyped entities
68
+ 0 extends 1 & U ? { readonly [k: string]: any }
69
+ : U extends object ?
70
+ { readonly [K in Exclude<keyof U, FunctionKeys<U>>]?: SetField<U[K]> }
71
+ : SetField<U>;
72
+
73
+ /** Keeps S inferred from the schema alone: inferring it from the value too would
74
+ * walk the value's type against every conditional in SetValue (TS 5.4 has NoInfer) */
75
+ export type SkipInfer<T, S> = [T][S extends unknown ? 0 : never];
76
+
77
+ export type SetManyValue<S> =
78
+ S extends readonly (infer E)[] ?
79
+ true extends IsUnion<E> ?
80
+ readonly { 'Use a Union schema for several Entity types': never }[]
81
+ : readonly SetRow<Denormalize<E>>[]
82
+ : SetValue<S>;
83
+
84
+ /** Raw input `set()` normalizes for a Queryable */
85
+ export type SetValue<S> =
86
+ InputSchema<InputSchema<InputSchema<S>>> extends infer N ?
87
+ N extends EntityInterface ?
88
+ SetRow<Denormalize<N>>
89
+ : SetInput<Denormalize<N>>
90
+ : never;
91
+
92
+ /** Query normalizes with its inner schema; its process() output is not input
93
+ *
94
+ * Applied three times in SetValue to unwrap nested Queries (TS 4.0 has no recursive aliases)
95
+ */
96
+ type InputSchema<S> =
97
+ S extends (
98
+ {
99
+ readonly schema: infer Sch;
100
+ process(...args: any): any;
101
+ // excludes Entity, whose static schema and process() match the members above
102
+ pk?: never;
103
+ }
104
+ ) ?
105
+ Sch
106
+ : S;
107
+
108
+ /** Raw input for a denormalized value, like a Collection's list or a Union's row */
109
+ type SetInput<T> =
110
+ 0 extends 1 & T ? any
111
+ : // not distributive: SetRow splits a Union into its members itself
112
+ [T] extends [readonly (infer U)[]] ? readonly SetRow<U>[]
113
+ : string extends keyof T ? { readonly [k: string]: SetRow<T[keyof T]> }
114
+ : SetRow<T>;
@@ -32,7 +32,15 @@ if (process.env.NODE_ENV !== 'production') {
32
32
  return serial;
33
33
  }
34
34
 
35
- const HASINTL = typeof Intl !== 'undefined';
35
+ const dateFormat =
36
+ typeof Intl !== 'undefined' ?
37
+ new Intl.DateTimeFormat('en-US', {
38
+ hour: 'numeric',
39
+ minute: 'numeric',
40
+ second: 'numeric',
41
+ fractionalSecondDigits: 3,
42
+ })
43
+ : undefined;
36
44
  DEFAULT_CONFIG = {
37
45
  name: `Data Client: ${globalThis.document?.title}`,
38
46
  autoPause: true,
@@ -59,7 +67,7 @@ if (process.env.NODE_ENV !== 'production') {
59
67
  options: undefined,
60
68
  /* istanbul ignore next */
61
69
  replacer:
62
- HASINTL ?
70
+ dateFormat ?
63
71
  (key: string | number | symbol, value: unknown) => {
64
72
  if (
65
73
  typeof value === 'number' &&
@@ -67,12 +75,7 @@ if (process.env.NODE_ENV !== 'production') {
67
75
  isFinite(value) &&
68
76
  (key === 'date' || key.endsWith('At'))
69
77
  ) {
70
- return Intl.DateTimeFormat('en-US', {
71
- hour: 'numeric',
72
- minute: 'numeric',
73
- second: 'numeric',
74
- fractionalSecondDigits: 3,
75
- }).format(value);
78
+ return dateFormat.format(value);
76
79
  }
77
80
  return value;
78
81
  }
@@ -128,7 +128,7 @@ describe('NetworkManager', () => {
128
128
  const fetchRejectAction = createFetch(errorUpdateShape, {
129
129
  args: [{ id: 5 }, { id: 5, title: 'hi' }],
130
130
  });
131
- (fetchRejectAction.meta.promise as any).catch((e: unknown) => {});
131
+ fetchRejectAction.meta.promise.catch(() => {});
132
132
 
133
133
  let NM: NetworkManager;
134
134
  beforeEach(() => {
@@ -138,6 +138,13 @@ describe('NetworkManager', () => {
138
138
  NM.cleanup();
139
139
  });
140
140
 
141
+ it('meta.promise is a Promise managers can chain with finally()', async () => {
142
+ const action = createFetch(detailEndpoint, { args: [{ id: 5 }] });
143
+ const done = action.meta.promise.finally(() => {});
144
+ action.meta.resolve('result');
145
+ await expect(done).resolves.toBe('result');
146
+ });
147
+
141
148
  it('should handle fetch actions and dispatch on success', async () => {
142
149
  const next = jest.fn();
143
150
  const dispatch = jest.fn();
@@ -9,7 +9,10 @@ import { createFixtureMap } from './createFixtureMap.js';
9
9
  import type { Fixture, Interceptor } from './fixtureTypes.js';
10
10
  import { MockProps } from './mockTypes.js';
11
11
 
12
- export function MockController<TBase extends typeof Controller, T>(
12
+ export function MockController<
13
+ TBase extends new (...args: any[]) => Controller<any>,
14
+ T,
15
+ >(
13
16
  Base: TBase,
14
17
  {
15
18
  fixtures = [],
@@ -22,7 +25,7 @@ export function MockController<TBase extends typeof Controller, T>(
22
25
  // @ts-ignore
23
26
  return class MockedController<
24
27
  D extends GenericDispatch = DataClientDispatch,
25
- > extends Base<D> {
28
+ > extends (Base as typeof Controller)<D> {
26
29
  // legacy compatibility (re-declaration)
27
30
  // TODO: drop when drop support for destructuring (0.14 and below)
28
31
  declare protected _dispatch: D;
@@ -0,0 +1,15 @@
1
+ import Controller from '../../controller/Controller';
2
+ import { MockController } from '../MockController';
3
+
4
+ describe('MockController', () => {
5
+ it('accepts a Controller subclass with its own members', () => {
6
+ class MyController extends Controller {
7
+ doSomething = () => 'hi';
8
+ }
9
+ const Mocked = MockController(MyController, {});
10
+ const controller = new Mocked();
11
+
12
+ expect(controller).toBeInstanceOf(MyController);
13
+ expect(controller.doSomething()).toBe('hi');
14
+ });
15
+ });
@@ -9,10 +9,9 @@ export interface SuccessFixtureEndpoint<
9
9
  E extends EndpointInterface & { update?: Updater } = EndpointInterface,
10
10
  > {
11
11
  readonly endpoint: E;
12
- readonly args: Parameters<E>;
12
+ readonly args: Readonly<Parameters<E>>;
13
13
  readonly response:
14
- | ResolveType<E>
15
- | ((...args: Parameters<E>) => ResolveType<E>);
14
+ ResolveType<E> | ((...args: Parameters<E>) => ResolveType<E>);
16
15
  readonly error?: false;
17
16
  /** Number of miliseconds to wait before resolving */
18
17
  readonly delay?: number;
@@ -85,7 +84,7 @@ export interface ErrorFixtureEndpoint<
85
84
  E extends EndpointInterface & { update?: Updater } = EndpointInterface,
86
85
  > {
87
86
  readonly endpoint: E;
88
- readonly args: Parameters<E>;
87
+ readonly args: Readonly<Parameters<E>>;
89
88
  readonly response: any;
90
89
  readonly error: true;
91
90
  /** Number of miliseconds to wait before resolving */
@@ -32,7 +32,7 @@ export default function mockInitialState(
32
32
 
33
33
  function dispatchFixture(
34
34
  fixture: Fixture,
35
- args: any[],
35
+ args: readonly any[],
36
36
  controller: Controller,
37
37
  fetchedAt?: number,
38
38
  ) {
@@ -1,14 +1,14 @@
1
- export declare const FETCH: "rdc/fetch";
2
- export declare const SET: "rdc/set";
3
- export declare const SET_RESPONSE: "rdc/setresponse";
4
- export declare const OPTIMISTIC: "rdc/optimistic";
5
- export declare const RESET: "rdc/reset";
6
- export declare const SUBSCRIBE: "rdc/subscribe";
7
- export declare const UNSUBSCRIBE: "rdc/unsubscribe";
8
- export declare const INVALIDATE: "rdc/invalidate";
9
- export declare const INVALIDATEALL: "rdc/invalidateall";
10
- export declare const EXPIREALL: "rdc/expireall";
11
- export declare const GC: "rdc/gc";
1
+ export declare const FETCH: 'rdc/fetch';
2
+ export declare const SET: 'rdc/set';
3
+ export declare const SET_RESPONSE: 'rdc/setresponse';
4
+ export declare const OPTIMISTIC: 'rdc/optimistic';
5
+ export declare const RESET: 'rdc/reset';
6
+ export declare const SUBSCRIBE: 'rdc/subscribe';
7
+ export declare const UNSUBSCRIBE: 'rdc/unsubscribe';
8
+ export declare const INVALIDATE: 'rdc/invalidate';
9
+ export declare const INVALIDATEALL: 'rdc/invalidateall';
10
+ export declare const EXPIREALL: 'rdc/expireall';
11
+ export declare const GC: 'rdc/gc';
12
12
  export declare const FETCH_TYPE: "rdc/fetch";
13
13
  export declare const SET_TYPE: "rdc/set";
14
14
  export declare const SET_RESPONSE_TYPE: "rdc/setresponse";
@@ -42,7 +42,7 @@ export interface FetchMeta {
42
42
  fetchedAt: number;
43
43
  resolve: (value?: any | PromiseLike<any>) => void;
44
44
  reject: (reason?: any) => void;
45
- promise: PromiseLike<any>;
45
+ promise: Promise<any>;
46
46
  }
47
47
  /** Action for Controller.fetch() */
48
48
  export interface FetchAction<E extends EndpointAndUpdate<E> = EndpointDefault> {
@@ -1,6 +1,7 @@
1
1
  import { ErrorTypes, SnapshotInterface, Schema, Denormalize, Queryable, SchemaArgs } from '@data-client/normalizr';
2
2
  import { ExpiryStatus, EndpointInterface, FetchFunction, ResolveType, DenormalizeNullable, MemoCache } from '@data-client/normalizr';
3
3
  import AbortOptimistic from './AbortOptimistic.js';
4
+ import { SkipInfer, SetInvalidateSchema, SetManySchema, SetManyValue, SetValue } from './setManyTypes.js';
4
5
  import { EndpointUpdateFunction } from './types.js';
5
6
  import { ReduxMiddlewareAPI } from '../manager/applyManager.js';
6
7
  import { GCInterface } from '../state/GCPolicy.js';
@@ -55,7 +56,7 @@ export default class Controller<D extends GenericDispatch = DataClientDispatch>
55
56
  update?: EndpointUpdateFunction<E>;
56
57
  }>(endpoint: E, ...args: readonly [
57
58
  ...Parameters<E>
58
- ]) => E["schema"] extends undefined | null ? ReturnType<E> : Promise<Denormalize<E["schema"]>>;
59
+ ]) => E['schema'] extends undefined | null ? ReturnType<E> : Promise<Denormalize<E['schema']>>;
59
60
  /**
60
61
  * Fetches only if endpoint is considered 'stale'; otherwise returns undefined
61
62
  * @see https://dataclient.io/docs/api/Controller#fetchIfStale
@@ -64,7 +65,7 @@ export default class Controller<D extends GenericDispatch = DataClientDispatch>
64
65
  update?: EndpointUpdateFunction<E>;
65
66
  }>(endpoint: E, ...args: readonly [
66
67
  ...Parameters<E>
67
- ]) => E["schema"] extends undefined | null ? ReturnType<E> | ResolveType<E> : Promise<Denormalize<E["schema"]>> | Denormalize<E["schema"]>;
68
+ ]) => E['schema'] extends undefined | null ? ReturnType<E> | ResolveType<E> : Promise<Denormalize<E['schema']>> | Denormalize<E['schema']>;
68
69
  /**
69
70
  * Forces refetching and suspense on useSuspense with the same Endpoint and parameters.
70
71
  * @see https://dataclient.io/docs/api/Controller#invalidate
@@ -101,12 +102,14 @@ export default class Controller<D extends GenericDispatch = DataClientDispatch>
101
102
  */
102
103
  set<S extends Queryable>(schema: S, ...rest: readonly [
103
104
  ...SchemaArgs<S>,
104
- (previousValue: Denormalize<S>) => {}
105
- ]): Promise<void>;
106
- set<S extends Queryable>(schema: S, ...rest: readonly [
107
- ...SchemaArgs<S>,
108
- {}
105
+ SkipInfer<SetValue<S> | ((previousValue: Denormalize<S>) => SetValue<S>), S>
109
106
  ]): Promise<void>;
107
+ /**
108
+ * Sets every row of an Array or Values of one Entity (or Union) in one normalize,
109
+ * or invalidates the one Entity an Invalidate schema's value identifies.
110
+ * @see https://dataclient.io/docs/api/Controller#set
111
+ */
112
+ set<S extends SetManySchema | SetInvalidateSchema>(schema: S, value: SkipInfer<SetManyValue<S>, S>): Promise<void>;
110
113
  /**
111
114
  * Sets response for the Endpoint and args.
112
115
  * @see https://dataclient.io/docs/api/Controller#setResponse
@@ -0,0 +1,75 @@
1
+ /** Value types for `Controller.set()`, including batch `set([Entity], rows)` */
2
+ import { Denormalize, EntityInterface } from '@data-client/normalizr';
3
+ /** What one row normalizes to: a reference to one stored entity */
4
+ type EntityRef = string | {
5
+ readonly id: string;
6
+ readonly schema: string;
7
+ };
8
+ /** Schemas that write each row to one stored entity: Entity, Union, or Invalidate (batch delete).
9
+ * Query, All and Collection don't: they normalize to lists, or Collection keys by args batch set() lacks. */
10
+ type SetEntitySchema = EntityInterface | EntityRefSchema;
11
+ /** Union or Invalidate */
12
+ type EntityRefSchema = {
13
+ _normalizeNullable(): EntityRef | undefined;
14
+ pk?: never;
15
+ };
16
+ /** `[Entity]`, `schema.Array(Entity)` or `schema.Values(Entity)` (or of a Union or Invalidate) */
17
+ export type SetManySchema = readonly SetEntitySchema[] | {
18
+ readonly schema: SetEntitySchema | Record<string, EntityInterface>;
19
+ schemaKey(): string;
20
+ queryKey(...args: any): undefined;
21
+ pk?: never;
22
+ };
23
+ /** `new Invalidate(Entity)` (or of a Union): not Queryable, since its queryKey() returns undefined */
24
+ export type SetInvalidateSchema = EntityRefSchema & {
25
+ queryKey(...args: any): undefined;
26
+ query?: never;
27
+ };
28
+ type IsUnion<T, U = T> = T extends unknown ? [
29
+ U
30
+ ] extends [
31
+ T
32
+ ] ? false : true : never;
33
+ type FunctionKeys<U> = {
34
+ [K in keyof U]: U[K] extends (...args: any) => any ? K : never;
35
+ }[keyof U];
36
+ /** Raw input for one field: numbers and strings coerce (literals stay exact);
37
+ * objects are pre-normalize */
38
+ type SetField<T> = T extends number ? number extends T ? T | string : T : T extends string ? string extends T ? T | number : T : T extends object ? unknown : T;
39
+ /** Fields of one row (or a coerced primitive); like EntityFields, but distributive
40
+ * and without key remapping (TS 4.0). A Union gets one row per member, so a
41
+ * discriminator like `type` selects the member the other fields are checked against. */
42
+ type SetRow<U> = 0 extends 1 & U ? {
43
+ readonly [k: string]: any;
44
+ } : U extends object ? {
45
+ readonly [K in Exclude<keyof U, FunctionKeys<U>>]?: SetField<U[K]>;
46
+ } : SetField<U>;
47
+ /** Keeps S inferred from the schema alone: inferring it from the value too would
48
+ * walk the value's type against every conditional in SetValue (TS 5.4 has NoInfer) */
49
+ export type SkipInfer<T, S> = [
50
+ T
51
+ ][S extends unknown ? 0 : never];
52
+ export type SetManyValue<S> = S extends readonly (infer E)[] ? true extends IsUnion<E> ? readonly {
53
+ 'Use a Union schema for several Entity types': never;
54
+ }[] : readonly SetRow<Denormalize<E>>[] : SetValue<S>;
55
+ /** Raw input `set()` normalizes for a Queryable */
56
+ export type SetValue<S> = InputSchema<InputSchema<InputSchema<S>>> extends infer N ? N extends EntityInterface ? SetRow<Denormalize<N>> : SetInput<Denormalize<N>> : never;
57
+ /** Query normalizes with its inner schema; its process() output is not input
58
+ *
59
+ * Applied three times in SetValue to unwrap nested Queries (TS 4.0 has no recursive aliases)
60
+ */
61
+ type InputSchema<S> = S extends ({
62
+ readonly schema: infer Sch;
63
+ process(...args: any): any;
64
+ pk?: never;
65
+ }) ? Sch : S;
66
+ /** Raw input for a denormalized value, like a Collection's list or a Union's row */
67
+ type SetInput<T> = 0 extends 1 & T ? any : [
68
+ T
69
+ ] extends [
70
+ readonly (infer U)[]
71
+ ] ? readonly SetRow<U>[] : string extends keyof T ? {
72
+ readonly [k: string]: SetRow<T[keyof T]>;
73
+ } : SetRow<T>;
74
+ export {};
75
+ //# sourceMappingURL=setManyTypes.d.ts.map
@@ -1,4 +1,4 @@
1
1
  import { Controller } from '../index.js';
2
2
  import { MockProps } from './mockTypes.js';
3
- export declare function MockController<TBase extends typeof Controller, T>(Base: TBase, { fixtures, getInitialInterceptorData, }: MockProps<T>): TBase;
3
+ export declare function MockController<TBase extends new (...args: any[]) => Controller<any>, T>(Base: TBase, { fixtures, getInitialInterceptorData, }: MockProps<T>): TBase;
4
4
  //# sourceMappingURL=MockController.d.ts.map
@@ -4,7 +4,7 @@ export interface SuccessFixtureEndpoint<E extends EndpointInterface & {
4
4
  update?: Updater;
5
5
  } = EndpointInterface> {
6
6
  readonly endpoint: E;
7
- readonly args: Parameters<E>;
7
+ readonly args: Readonly<Parameters<E>>;
8
8
  readonly response: ResolveType<E> | ((...args: Parameters<E>) => ResolveType<E>);
9
9
  readonly error?: false;
10
10
  /** Number of miliseconds to wait before resolving */
@@ -63,7 +63,7 @@ export interface ErrorFixtureEndpoint<E extends EndpointInterface & {
63
63
  update?: Updater;
64
64
  } = EndpointInterface> {
65
65
  readonly endpoint: E;
66
- readonly args: Parameters<E>;
66
+ readonly args: Readonly<Parameters<E>>;
67
67
  readonly response: any;
68
68
  readonly error: true;
69
69
  /** Number of miliseconds to wait before resolving */
@@ -1,16 +1,5 @@
1
1
  import { State, ExpireAllAction } from '../../types.js';
2
2
  export declare function expireReducer(state: State<unknown>, action: ExpireAllAction): {
3
- meta: {
4
- [key: string]: {
5
- readonly date: number;
6
- readonly fetchedAt: number;
7
- readonly expiresAt: number;
8
- readonly prevExpiresAt?: number;
9
- readonly error?: import("@data-client/normalizr").ErrorTypes;
10
- readonly invalidated?: boolean;
11
- readonly errorPolicy?: "hard" | "soft" | undefined;
12
- };
13
- };
14
3
  entities: {
15
4
  readonly [entityKey: string]: {
16
5
  readonly [pk: string]: unknown;
@@ -31,5 +20,16 @@ export declare function expireReducer(state: State<unknown>, action: ExpireAllAc
31
20
  };
32
21
  optimistic: (import("../../actions.js").SetResponseAction | import("../../actions.js").OptimisticAction)[];
33
22
  lastReset: number;
23
+ meta: {
24
+ [key: string]: {
25
+ readonly date: number;
26
+ readonly fetchedAt: number;
27
+ readonly expiresAt: number;
28
+ readonly prevExpiresAt?: number;
29
+ readonly error?: import("@data-client/normalizr").ErrorTypes;
30
+ readonly invalidated?: boolean;
31
+ readonly errorPolicy?: 'hard' | 'soft' | undefined;
32
+ };
33
+ };
34
34
  };
35
35
  //# sourceMappingURL=expireReducer.d.ts.map
@@ -1,19 +1,5 @@
1
1
  import { State, InvalidateAllAction, InvalidateAction } from '../../types.js';
2
2
  export declare function invalidateReducer(state: State<unknown>, action: InvalidateAction | InvalidateAllAction): {
3
- endpoints: {
4
- [key: string]: unknown;
5
- };
6
- meta: {
7
- [key: string]: {
8
- readonly date: number;
9
- readonly fetchedAt: number;
10
- readonly expiresAt: number;
11
- readonly prevExpiresAt?: number;
12
- readonly error?: import("@data-client/normalizr").ErrorTypes;
13
- readonly invalidated?: boolean;
14
- readonly errorPolicy?: "hard" | "soft" | undefined;
15
- };
16
- };
17
3
  entities: {
18
4
  readonly [entityKey: string]: {
19
5
  readonly [pk: string]: unknown;
@@ -31,5 +17,19 @@ export declare function invalidateReducer(state: State<unknown>, action: Invalid
31
17
  };
32
18
  optimistic: (import("../../actions.js").SetResponseAction | import("../../actions.js").OptimisticAction)[];
33
19
  lastReset: number;
20
+ endpoints: {
21
+ [key: string]: unknown;
22
+ };
23
+ meta: {
24
+ [key: string]: {
25
+ readonly date: number;
26
+ readonly fetchedAt: number;
27
+ readonly expiresAt: number;
28
+ readonly prevExpiresAt?: number;
29
+ readonly error?: import("@data-client/normalizr").ErrorTypes;
30
+ readonly invalidated?: boolean;
31
+ readonly errorPolicy?: 'hard' | 'soft' | undefined;
32
+ };
33
+ };
34
34
  };
35
35
  //# sourceMappingURL=invalidateReducer.d.ts.map