diff --git a/.changeset/controller-set-value-types.md b/.changeset/controller-set-value-types.md new file mode 100644 index 000000000000..77f94e864b01 --- /dev/null +++ b/.changeset/controller-set-value-types.md @@ -0,0 +1,22 @@ +--- +'@data-client/core': patch +'@data-client/react': patch +'@data-client/vue': patch +--- + +Fix `controller.set()` accepting any value + +Values are now typed by the schema: Entities take their fields, while [Collection](https://dataclient.io/rest/api/Collection) +and [All](https://dataclient.io/rest/api/All) take a list of rows. [Query](https://dataclient.io/rest/api/Query) takes +the input of the schema it wraps, not what its `process()` returns. Updater functions must return the same. + +```ts +// Before: these all typechecked, then failed or wrote nothing at runtime +ctrl.set(new schema.All(Todo), 42); +ctrl.set(TodoResource.getList.schema, 'anything'); +ctrl.set(Todo, { id: '5' }, { id: '5', completed: 'yes' }); + +// After: TypeScript errors on the above; these typecheck +ctrl.set(TodoResource.getList.schema, [{ id: '5', completed: true }]); +ctrl.set(new schema.All(Todo), [{ id: '5', completed: true }]); +``` diff --git a/docs/core/api/Controller.md b/docs/core/api/Controller.md index c9331e6d464e..4080d7c5c36d 100644 --- a/docs/core/api/Controller.md +++ b/docs/core/api/Controller.md @@ -378,6 +378,23 @@ ctrl.set( ); ``` +The value is typed by the schema: an [Entity](/rest/api/Entity) takes its fields (numbers and strings may be either), +while a [Collection](/rest/api/Collection) or [All](/rest/api/All) takes a list of rows. A [Query](/rest/api/Query) +takes the input of the schema it wraps, since `set()` normalizes that schema rather than reversing `process()`. + +```ts +ctrl.set(TodoResource.getList.schema, [{ id: '5', completed: true }]); +``` + +:::note Type checking limits + +To keep type checking fast for large [Unions](/rest/api/Union), a Union row is checked against the +combined fields of all its members rather than against one member. Each field's type is still checked, +but a row that mixes fields from different members (like `{ type: 'first', secondField: 1 }`) is not +an error. Make sure the fields you set belong to the member the row's discriminator selects. + +::: + Functions can be used in the value when derived data is used. This [prevents race conditions](https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state). ```ts diff --git a/packages/core/src/controller/Controller.ts b/packages/core/src/controller/Controller.ts index bf4c0ccd0079..c40811bd602f 100644 --- a/packages/core/src/controller/Controller.ts +++ b/packages/core/src/controller/Controller.ts @@ -33,7 +33,12 @@ import { createSetResponse, } from './actions/index.js'; import ensurePojo from './ensurePojo.js'; -import type { SetManySchema, SetManyValue } from './setManyTypes.js'; +import type { + SkipInfer, + SetManySchema, + SetManyValue, + SetValue, +} from './setManyTypes.js'; import type { EndpointUpdateFunction } from './types.js'; import { ReduxMiddlewareAPI } from '../manager/applyManager.js'; import type { GCInterface } from '../state/GCPolicy.js'; @@ -226,12 +231,13 @@ export default class Controller< */ set( schema: S, - ...rest: readonly [...SchemaArgs, (previousValue: Denormalize) => {}] - ): Promise; - - set( - schema: S, - ...rest: readonly [...SchemaArgs, {}] + ...rest: readonly [ + ...SchemaArgs, + SkipInfer< + SetValue | ((previousValue: Denormalize) => SetValue), + S + >, + ] ): Promise; /** @@ -240,7 +246,7 @@ export default class Controller< */ set( schema: S, - value: SetManyValue, + value: SkipInfer, S>, ): Promise; set( diff --git a/packages/core/src/controller/setManyTypes.ts b/packages/core/src/controller/setManyTypes.ts index 58d8a3021658..751d0868fce7 100644 --- a/packages/core/src/controller/setManyTypes.ts +++ b/packages/core/src/controller/setManyTypes.ts @@ -1,4 +1,4 @@ -/** Types for batch `Controller.set([Entity], rows)` */ +/** Value types for `Controller.set()`, including batch `set([Entity], rows)` */ import type { Denormalize, EntityInterface } from '@data-client/normalizr'; /** What one row normalizes to: a reference to one stored entity */ @@ -37,34 +37,88 @@ type FunctionKeys = { [K in keyof U]: U[K] extends (...args: any) => any ? K : never; }[keyof U]; -/** Raw input for one field: numbers and strings coerce; objects are pre-normalize */ +/** Raw input for one field: numbers and strings coerce (literals stay exact); + * objects are pre-normalize */ type SetField = - T extends number ? T | string - : T extends string ? T | number + T extends number ? + number extends T ? + T | string + : T + : T extends string ? + string extends T ? + T | number + : T : T extends object ? unknown : T; -/** Fields of one row; like EntityFields, but distributive and without key remapping (TS 4.0) */ +/** Non-function keys of any member of U */ +type FieldKeys = + U extends unknown ? Exclude> : never; + +/** Input for field K, from each member of U that has it */ +type MemberField = + U extends unknown ? + K extends keyof U ? + SetField + : never + : never; + +/** Fields of one row (or a coerced primitive); like EntityFields, but without + * key remapping (TS 4.0). A Union's members merge into one object type: checking + * a row against it costs one comparison instead of one per member. */ type SetRow = // EntityMixin and other untyped entities 0 extends 1 & U ? { readonly [k: string]: any } - : U extends unknown ? - { readonly [K in Exclude>]?: SetField } - : never; + : [U] extends [object] ? { readonly [K in FieldKeys]?: MemberField } + : SetField; -/** Polymorphic rows may carry a discriminator that is not an Entity field */ -type SetRowOf = - Sch extends EntityInterface ? SetRow - : SetRow & { readonly [k: string]: unknown }; +/** Keeps S inferred from the schema alone: inferring it from the value too would + * walk the value's type against every conditional in SetValue (TS 5.4 has NoInfer) */ +export type SkipInfer = [T][S extends unknown ? 0 : never]; export type SetManyValue = S extends readonly (infer E)[] ? true extends IsUnion ? readonly { 'Use a Union schema for several Entity types': never }[] - : readonly SetRowOf>[] - : S extends { readonly schema: infer Sch } ? - Denormalize extends readonly (infer U)[] ? readonly SetRowOf[] - : Denormalize extends { readonly [k: string]: infer U } ? - { readonly [k: string]: SetRowOf } - : never + : readonly SetItem>[] + : SetValue; + +/** Raw input `set()` normalizes for a Queryable */ +export type SetValue = + InputSchema>> extends infer N ? + N extends EntityInterface ? + SetRow> + : SetInput> : never; + +/** Query normalizes with its inner schema; its process() output is not input + * + * Applied three times in SetValue to unwrap nested Queries (TS 4.0 has no recursive aliases) + */ +type InputSchema = + S extends ( + { + readonly schema: infer Sch; + process(...args: any): any; + // excludes Entity, whose static schema and process() match the members above + pk?: never; + } + ) ? + Sch + : S; + +/** Raw input for a denormalized value, like a Collection's list or a Union's row */ +type SetInput = + 0 extends 1 & T ? any + : // not distributive, so a Union's members stay together for SetItem + [T] extends [readonly (infer U)[]] ? readonly SetItem[] + : string extends keyof T ? { readonly [k: string]: SetItem } + : SetItem; + +/** + * One member of a list or keyed object. Polymorphic rows may carry a + * discriminator that is not an Entity field. + */ +type SetItem = + true extends IsUnion ? SetRow & { readonly [k: string]: unknown } + : SetRow; diff --git a/packages/react/src/hooks/__tests__/useController/set.tsx b/packages/react/src/hooks/__tests__/useController/set.tsx index c0d0702e49b1..23cd50916864 100644 --- a/packages/react/src/hooks/__tests__/useController/set.tsx +++ b/packages/react/src/hooks/__tests__/useController/set.tsx @@ -3,6 +3,7 @@ import { schema } from '@data-client/rest'; import { ArticleFromMixin, CoolerArticle, + CoolerArticleResource, FirstUnion, SecondUnion, UnionResource, @@ -252,6 +253,9 @@ describe('set', () => { ]); // @ts-expect-error id is a number controller.set([new schema.Invalidate(CoolerArticle)], [{ id: false }]); + controller.set(byKind, { id: '1' }, { id: '1', kind: 'first' }); + // @ts-expect-error body is a string + controller.set(byKind, { id: '1' }, { id: '1', body: false }); // EntityMixin rows controller.set([ArticleFromMixin], [{ id: 5, title: 'mixin' }]); // @ts-expect-error title is a string @@ -259,6 +263,64 @@ describe('set', () => { }; }); + it('should type values by the schema', async () => { + const { controller } = renderDataClient(() => null); + const list = CoolerArticleResource.getList.schema; + const all = new schema.All(CoolerArticle); + const titles = new schema.Query(all, articles => + articles.map(article => article.title), + ); + const titleCount = new schema.Query(titles, list => list.length); + let promise: any; + act(() => { + promise = controller.set(list, [{ id: 5, title: 'listed' }]); + }); + await act(() => promise); + act(() => { + promise = controller.set(titles, [{ id: 6, title: 'queried' }]); + }); + await act(() => promise); + act(() => { + promise = controller.set(titleCount, [{ id: 8, title: 'nested' }]); + }); + await act(() => promise); + const state = controller.getState(); + expect(controller.get(list, state)?.map(({ id }) => id)).toEqual([5]); + expect(controller.get(titles, state)?.sort()).toEqual([ + 'listed', + 'nested', + 'queried', + ]); + expect(controller.get(titleCount, state)).toBe(3); + + // type tests + () => { + controller.set(all, [{ id: '5', title: 'coerced' }]); + controller.set(list, articles => [ + ...articles.map(({ id }) => ({ id })), + { id: 7 }, + ]); + // @ts-expect-error All takes a list of rows + controller.set(all, 42); + // @ts-expect-error title is a string + controller.set(all, [{ id: 5, title: false }]); + // @ts-expect-error Collections take a list of rows + controller.set(list, 'anything'); + // @ts-expect-error unknown field + controller.set(list, [{ id: 5, bogus: 1 }]); + // @ts-expect-error updaters must return rows + controller.set(list, () => 42); + // @ts-expect-error Queries take their schema's input, not process() output + controller.set(titles, ['listed']); + // @ts-expect-error nested Queries take the innermost schema's input + controller.set(titleCount, 3); + // @ts-expect-error title is a string + controller.set(CoolerArticle, { id: 5 }, { id: 5, title: false }); + // @ts-expect-error updaters must return the Entity's fields + controller.set(CoolerArticle, { id: 5 }, () => ({ title: false })); + }; + }); + it('should update store with error', async () => { const { result, controller } = renderDataClient(() => { return useQuery(CoolerArticle, { id: payload.id }); @@ -266,6 +328,7 @@ describe('set', () => { expect(result.current).toBeUndefined(); let promise: any; act(() => { + // @ts-expect-error testing runtime error promise = controller.set(CoolerArticle, { id: 5 }, 5); }); expect(result.current).toBeUndefined(); diff --git a/website/blog/2026-10-03-v0.19-batch-set.md b/website/blog/2026-10-03-v0.19-batch-set.md index 5959e8c6a53f..cf1b4c57df3d 100644 --- a/website/blog/2026-10-03-v0.19-batch-set.md +++ b/website/blog/2026-10-03-v0.19-batch-set.md @@ -8,6 +8,7 @@ draft: true import DiffEditor from '@site/src/components/DiffEditor'; import StackBlitz from '@site/src/components/StackBlitz'; +import TypeScriptEditor from '@site/src/components/TypeScriptEditor'; **New APIs:** @@ -17,6 +18,7 @@ import StackBlitz from '@site/src/components/StackBlitz'; - Fix TypeScript 7 module resolution for package `exports`; imports now resolve to declaration files ([#4019](https://github.com/reactive/data-client/pull/4019)) - [renderDataHook()](/docs/api/renderDataHook) runs provider mount effects when the first render suspends ([#4099](https://github.com/reactive/data-client/pull/4099)) +- [Controller.set() values are typed by the schema](/blog/2026/10/03/v0.19-batch-set#typed-set), so `ctrl.set(new schema.All(Todo), 42)` is a TypeScript error ([#4133](https://github.com/reactive/data-client/pull/4133)) - Vue [useSuspense()](/vue/api/useSuspense) and [useLive()](/vue/api/useLive) keep the previous data while new arguments load, instead of returning `undefined` ([#4131](https://github.com/reactive/data-client/pull/4131)) - Vue [useSuspense()](/vue/api/useSuspense) and [useLive()](/vue/api/useLive) send fetch errors after arguments change to `onErrorCaptured()` instead of an unhandled promise rejection ([#4135](https://github.com/reactive/data-client/pull/4135)) - Vue [useSuspense()](/vue/api/useSuspense), [useDLE()](/vue/api/useDLE) and [useFetch()](/vue/api/useFetch) no longer refetch stale data on every store update, so a `controller.set()` is not overwritten ([#4134](https://github.com/reactive/data-client/pull/4134)) @@ -110,6 +112,70 @@ is the Array schema, so match `action.schema[0]` for `[Ticker]` rather than the ## Other improvements +### Typed set() values {#typed-set} + +[Controller.set()](/docs/api/Controller#set) previously accepted any value for a schema, so a typo or a wrong +field type only surfaced as bad data at runtime. Values are now typed by the schema: an +[Entity](/rest/api/Entity) takes its fields, while a [Collection](/rest/api/Collection), [All](/rest/api/All) or +Array takes a list of rows. Every field is optional, since `set()` merges into what is already stored, and +numbers and strings are interchangeable just like in API responses. +[#4133](https://github.com/reactive/data-client/pull/4133) + +Hover the red underlines to see each error. + + + +```ts title="Todo" collapsed +import { Entity, resource } from '@data-client/rest'; + +export class Todo extends Entity { + id = 0; + userId = 0; + title = ''; + completed = false; + + static key = 'Todo'; +} + +export const TodoResource = resource({ + urlPrefix: 'https://jsonplaceholder.typicode.com', + path: '/todos/:id', + schema: Todo, +}); +``` + +```ts title="updateTodos" +import type { Controller } from '@data-client/react'; +import { schema } from '@data-client/rest'; +import { Todo, TodoResource } from './Todo'; + +export function updateTodos(ctrl: Controller) { + // ✅ rows are partial Todos; ids may be strings or numbers + ctrl.set(TodoResource.getList.schema, [{ id: '5', completed: true }]); + ctrl.set(Todo, { id: 5 }, todo => ({ completed: !todo.completed })); + ctrl.set([Todo], [{ id: 1, title: 'first' }, { id: 2 }]); + + // ❌ All takes a list of rows + ctrl.set(new schema.All(Todo), 42); + // ❌ completed is a boolean + ctrl.set(Todo, { id: 5 }, { id: 5, completed: 'yes' }); + // ❌ Todo has no done field + ctrl.set(TodoResource.getList.schema, [{ id: 5, done: true }]); + // ❌ updaters must return Todo fields + ctrl.set(Todo, { id: 5 }, todo => ({ title: todo.completed })); +} +``` + + + +A [Query](/rest/api/Query) takes the input of the schema it wraps, since `set()` normalizes that schema rather than +reversing `process()`. To keep type checking fast for large [Unions](/rest/api/Union), a Union row is checked +against the combined fields of all its members, so a row mixing fields from different members is not an error; see +[type checking limits](/docs/api/Controller#set). + +If code that previously compiled now fails here, it was writing data its schema doesn't describe. Fix the value, +or widen the Entity's field types if the data really can take that shape. + ### Vue getter arguments {#vue-getter-args} Vue composables like [useSuspense()](/vue/api/useSuspense) and [useLive()](/vue/api/useLive) were typed to accept a diff --git a/website/src/components/Playground/editor-types/@data-client/core.d.ts b/website/src/components/Playground/editor-types/@data-client/core.d.ts index 91d81d7e1479..4ecd94b5f5dd 100644 --- a/website/src/components/Playground/editor-types/@data-client/core.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/core.d.ts @@ -13,10 +13,7 @@ type Serializable = (value: any) => T; interface SchemaSimple { - normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, delegate: { - getEntity: any; - setEntity: any; - }, + normalize(input: any, parent: any, key: any, delegate: INormalizeDelegate, /** The nearest enclosing entity-like schema (one with `pk`), if any. * Tracked automatically by the visit walker. */ parentEntity?: any): any; @@ -53,6 +50,11 @@ interface NormalizedIndex { }; }; } +/** Visits next data + schema while recurisvely normalizing */ +interface Visit { + (schema: any, value: any, parent: any, key: any): any; + creating?: boolean; +} /** Used in denormalize. Lookup to find an entity in the store table */ interface EntityPath { key: string; @@ -101,6 +103,10 @@ interface IDenormalizeDelegate { } /** Helpers during schema.normalize() */ interface INormalizeDelegate { + /** Recursive normalize of nested schemas */ + visit: Visit; + /** Raw endpoint args for this normalize call */ + readonly args: readonly any[]; /** Action meta-data for this normalize call */ readonly meta: { fetchedAt: number; @@ -411,22 +417,94 @@ type FetchFunction = (...args: A) => Pr declare class AbortOptimistic extends Error { } +/** Value types for `Controller.set()`, including batch `set([Entity], rows)` */ + +/** What one row normalizes to: a reference to one stored entity */ +type EntityRef = string | { + readonly id: string; + readonly schema: string; +}; +/** Schemas that write each row to one stored entity: Entity, Union, or Invalidate (batch delete). + * Query, All and Collection don't: they normalize to lists, or Collection keys by args batch set() lacks. */ +type SetEntitySchema = EntityInterface | { + _normalizeNullable(): EntityRef | undefined; + pk?: never; +}; +/** `[Entity]`, `schema.Array(Entity)` or `schema.Values(Entity)` (or of a Union or Invalidate) */ +type SetManySchema = readonly SetEntitySchema[] | { + readonly schema: SetEntitySchema | Record; + schemaKey(): string; + queryKey(...args: any): undefined; + pk?: never; +}; +type IsUnion = T extends unknown ? [ + U +] extends [T] ? false : true : never; +type FunctionKeys = { + [K in keyof U]: U[K] extends (...args: any) => any ? K : never; +}[keyof U]; +/** Raw input for one field: numbers and strings coerce (literals stay exact); + * objects are pre-normalize */ +type SetField = T extends number ? number extends T ? T | string : T : T extends string ? string extends T ? T | number : T : T extends object ? unknown : T; +/** Non-function keys of any member of U */ +type FieldKeys = U extends unknown ? Exclude> : never; +/** Input for field K, from each member of U that has it */ +type MemberField = U extends unknown ? K extends keyof U ? SetField : never : never; +/** Fields of one row (or a coerced primitive); like EntityFields, but without + * key remapping (TS 4.0). A Union's members merge into one object type: checking + * a row against it costs one comparison instead of one per member. */ +type SetRow = 0 extends 1 & U ? { + readonly [k: string]: any; +} : [U] extends [object] ? { + readonly [K in FieldKeys]?: MemberField; +} : SetField; +/** Keeps S inferred from the schema alone: inferring it from the value too would + * walk the value's type against every conditional in SetValue (TS 5.4 has NoInfer) */ +type SkipInfer = [T][S extends unknown ? 0 : never]; +type SetManyValue = S extends readonly (infer E)[] ? true extends IsUnion ? readonly { + 'Use a Union schema for several Entity types': never; +}[] : readonly SetItem>[] : SetValue; +/** Raw input `set()` normalizes for a Queryable */ +type SetValue = InputSchema>> extends infer N ? N extends EntityInterface ? SetRow> : SetInput> : never; +/** Query normalizes with its inner schema; its process() output is not input + * + * Applied three times in SetValue to unwrap nested Queries (TS 4.0 has no recursive aliases) + */ +type InputSchema = S extends ({ + readonly schema: infer Sch; + process(...args: any): any; + pk?: never; +}) ? Sch : S; +/** Raw input for a denormalized value, like a Collection's list or a Union's row */ +type SetInput = 0 extends 1 & T ? any : [ + T +] extends [readonly (infer U)[]] ? readonly SetItem[] : string extends keyof T ? { + readonly [k: string]: SetItem; +} : SetItem; +/** + * One member of a list or keyed object. Polymorphic rows may carry a + * discriminator that is not an Entity field. + */ +type SetItem = true extends IsUnion ? SetRow & { + readonly [k: string]: unknown; +} : SetRow; + type ResultEntry = E['schema'] extends undefined | null ? ResolveType : Normalize; type EndpointUpdateFunction = Record> = (source: ResultEntry, ...args: any) => { [K in keyof Updaters]: (result: Updaters[K]) => Updaters[K]; }; -declare const FETCH: "rdc/fetch"; -declare const SET: "rdc/set"; -declare const SET_RESPONSE: "rdc/setresponse"; -declare const OPTIMISTIC: "rdc/optimistic"; -declare const RESET: "rdc/reset"; -declare const SUBSCRIBE: "rdc/subscribe"; -declare const UNSUBSCRIBE: "rdc/unsubscribe"; -declare const INVALIDATE: "rdc/invalidate"; -declare const INVALIDATEALL: "rdc/invalidateall"; -declare const EXPIREALL: "rdc/expireall"; -declare const GC: "rdc/gc"; +declare const FETCH: 'rdc/fetch'; +declare const SET: 'rdc/set'; +declare const SET_RESPONSE: 'rdc/setresponse'; +declare const OPTIMISTIC: 'rdc/optimistic'; +declare const RESET: 'rdc/reset'; +declare const SUBSCRIBE: 'rdc/subscribe'; +declare const UNSUBSCRIBE: 'rdc/unsubscribe'; +declare const INVALIDATE: 'rdc/invalidate'; +declare const INVALIDATEALL: 'rdc/invalidateall'; +declare const EXPIREALL: 'rdc/expireall'; +declare const GC: 'rdc/gc'; declare const FETCH_TYPE: "rdc/fetch"; declare const SET_TYPE: "rdc/set"; declare const SET_RESPONSE_TYPE: "rdc/setresponse"; @@ -739,14 +817,14 @@ declare class Controller { */ fetch: ; - }>(endpoint: E, ...args: readonly [...Parameters]) => E["schema"] extends undefined | null ? ReturnType : Promise>; + }>(endpoint: E, ...args: readonly [...Parameters]) => E['schema'] extends undefined | null ? ReturnType : Promise>; /** * Fetches only if endpoint is considered 'stale'; otherwise returns undefined * @see https://dataclient.io/docs/api/Controller#fetchIfStale */ fetchIfStale: ; - }>(endpoint: E, ...args: readonly [...Parameters]) => E["schema"] extends undefined | null ? ReturnType | ResolveType : Promise> | Denormalize; + }>(endpoint: E, ...args: readonly [...Parameters]) => E['schema'] extends undefined | null ? ReturnType | ResolveType : Promise> | Denormalize; /** * Forces refetching and suspense on useSuspense with the same Endpoint and parameters. * @see https://dataclient.io/docs/api/Controller#invalidate @@ -777,8 +855,15 @@ declare class Controller { * Sets value for the Queryable and args. * @see https://dataclient.io/docs/api/Controller#set */ - set(schema: S, ...rest: readonly [...SchemaArgs, (previousValue: Denormalize) => {}]): Promise; - set(schema: S, ...rest: readonly [...SchemaArgs, {}]): Promise; + set(schema: S, ...rest: readonly [ + ...SchemaArgs, + SkipInfer | ((previousValue: Denormalize) => SetValue), S> + ]): Promise; + /** + * Sets every row of an Array or Values of one Entity (or Union) in one normalize. + * @see https://dataclient.io/docs/api/Controller#set-array + */ + set(schema: S, value: SkipInfer, S>): Promise; /** * Sets response for the Endpoint and args. * @see https://dataclient.io/docs/api/Controller#setResponse diff --git a/website/src/components/Playground/editor-types/@data-client/endpoint.d.ts b/website/src/components/Playground/editor-types/@data-client/endpoint.d.ts index 63b592809798..811e159e2f8b 100644 --- a/website/src/components/Playground/editor-types/@data-client/endpoint.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/endpoint.d.ts @@ -160,12 +160,16 @@ interface SnapshotInterface { } type ExpiryStatusInterface = 1 | 2 | 3; +/** Removes Rem's length worth of leading elements from Orig */ +type RemoveArray = Rem extends [any, ...infer RestRem] ? Orig extends [any, ...infer RestOrig] ? RemoveArray : never : Orig; + /** Get the Params type for a given Shape */ type EndpointParam = E extends (first: infer A, ...rest: any) => any ? A : E extends { key: (first: infer A, ...rest: any) => any; } ? A : never; /** What the function's promise resolves to */ type ResolveType any> = ReturnType extends Promise ? R : never; + type PartialParameters any> = T extends (...args: infer P) => any ? Partial

: never; type EndpointToFunction Promise> = (this: E, ...args: Parameters) => ReturnType; @@ -208,17 +212,12 @@ interface SchemaSimple { * @param input The value being normalized. * @param parent The parent object/array/dictionary containing `input`. * @param key The key under which `input` lives on `parent`. - * @param args The endpoint args for this normalize call. - * @param visit Recursive visitor for nested schemas. - * @param delegate Store accessors for reading/writing entities. + * @param delegate Recursive visitor, endpoint args, and store accessors. * @param parentEntity Nearest enclosing entity-like schema (one with `pk`), * tracked automatically by the visit walker. `Scalar` * uses this to discover its entity binding. */ - normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, delegate: { - getEntity: any; - setEntity: any; - }, parentEntity?: any): any; + normalize(input: any, parent: any, key: any, delegate: INormalizeDelegate, parentEntity?: any): any; denormalize(input: {}, delegate: IDenormalizeDelegate): T; queryKey(args: Args, unvisit: (...args: any) => any, delegate: { getEntity: any; @@ -270,14 +269,13 @@ interface EntityTable { * Schemas that recurse via `visit` should pass their own * `input` (or the surrounding container) here. * @param key The key under which `value` lives on `parent`. - * @param args The endpoint args for this normalize call. * * The walker internally tracks the nearest enclosing entity-like schema and * forwards it to `schema.normalize` as a trailing `parentEntity` argument — - * see `SchemaSimple.normalize`. Consumers of `visit` don't pass it. + * see `SchemaSimple.normalize`. */ interface Visit { - (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; + (schema: any, value: any, parent: any, key: any): any; creating?: boolean; } /** Used in denormalize. Lookup to find an entity in the store table */ @@ -331,6 +329,10 @@ interface IDenormalizeDelegate { } /** Helpers during schema.normalize() */ interface INormalizeDelegate { + /** Recursive normalize of nested schemas */ + visit: Visit; + /** Raw endpoint args for this normalize call */ + readonly args: readonly any[]; /** Action meta-data for this normalize call */ readonly meta: { fetchedAt: number; @@ -447,7 +449,6 @@ interface ExtendableEndpointConstructor { new & E, params?: any, body?: any) => Promise, S extends Schema | undefined = undefined, M extends boolean | undefined = false, E extends Record = {}>(RestFetch: F, options?: Readonly> & E): EndpointInstanceInterface & E; readonly prototype: Function; } -type RemoveArray = Rem extends [any, ...infer RestRem] ? Orig extends [any, ...infer RestOrig] ? RemoveArray : never : Orig; declare let Endpoint: EndpointConstructor; @@ -498,7 +499,7 @@ interface IEntityClass { * @param [key] When normalizing, the key where this entity was found * @param [args] ...args sent to Endpoint */ - pk IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, value: Partial>, parent?: any, key?: string, args?: any[]): string | number | undefined; + pk IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, value: Partial>, parent?: any, key?: string, args?: readonly any[]): string | number | undefined; /** Return true to merge incoming data; false keeps existing entity * * @see https://dataclient.io/docs/api/Entity#shouldUpdate @@ -559,22 +560,19 @@ interface IEntityClass { * * @param [props] Plain Object of properties to assign. */ - fromJS IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, props?: Partial>): AbstractInstanceType; + fromJS IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, props?: Partial>): AbstractInstanceType; /** Called when denormalizing an entity to create an instance when 'valid' * * @param [props] Plain Object of properties to assign. * @see https://dataclient.io/rest/api/Entity#createIfValid */ - createIfValid IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, props: Partial>): AbstractInstanceType | undefined; + createIfValid IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, props: Partial>): AbstractInstanceType | undefined; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#process */ process(input: any, parent: any, key: string | undefined, args: any[]): any; - normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, snapshot: { - getEntity: any; - setEntity: any; - }): any; + normalize(input: any, parent: any, key: string | undefined, delegate: INormalizeDelegate): any; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#validate @@ -585,7 +583,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#queryKey */ queryKey(args: readonly any[], unvisit: any, delegate: IQueryDelegate): any; - denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, delegate: IDenormalizeDelegate): AbstractInstanceType; + denormalize IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, input: any, delegate: IDenormalizeDelegate): AbstractInstanceType; /** All instance defaults set */ readonly defaults: any; } @@ -600,6 +598,11 @@ interface IEntityInstance { pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; } type Constructor = abstract new (...args: any[]) => {}; +/** InstanceType<> for abstract constructor types, which TypeScript 4.2's InstanceType rejects + * + * Unlike AbstractInstanceType<>, this resolves constructor type aliases (not just classes) + */ +type ConstructorInstance any> = T extends abstract new (...args: any) => infer R ? R : any; type IDClass = abstract new (...args: any[]) => { id: string | number | undefined; }; @@ -624,9 +627,9 @@ interface RequiredPKOptions extends EntityOptions(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase; -declare function EntityMixin(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); -declare function EntityMixin(Base: TBase, opt: RequiredPKOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); +declare function EntityMixin(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase; +declare function EntityMixin(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); +declare function EntityMixin(Base: TBase, opt: RequiredPKOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); declare class PolymorphicSchema { private _schemaAttribute; @@ -637,7 +640,9 @@ declare class PolymorphicSchema { getSchemaAttribute(input: any, parent: any, key: any): any; inferSchema(input: any, parent: any, key: any): any; schemaKey(): string; - normalizeValue(value: any, parent: any, key: any, args: any[], visit: Visit): any; + normalizeValue(value: any, parent: any, key: any, delegate: { + visit: Visit; + }): any; denormalizeValue(value: any, unvisit: any): any; } @@ -667,7 +672,7 @@ declare class Invalidate ? string | ((input: any, parent: any, key: any) => string) : undefined); get key(): string; - normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, delegate: INormalizeDelegate): string | { + normalize(input: any, parent: any, key: string | undefined, delegate: INormalizeDelegate): string | { id: string; schema: string; }; @@ -713,7 +718,7 @@ declare class Lazy implements SchemaSimple { * @param {Schema} schema - The inner schema (e.g., [Building], Building, Collection) */ constructor(schema: S); - normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, _delegate: any): any; + normalize(input: any, parent: any, key: any, delegate: any): any; denormalize(input: {}, _delegate: IDenormalizeDelegate): any; queryKey(_args: readonly any[], _unvisit: (...args: any) => any, _delegate: any): undefined; /** Queryable schema for use with useQuery() to resolve lazy relationships */ @@ -869,7 +874,7 @@ declare class Scalar implements Mergeable { date: number; expiresAt: number; }; - normalize(input: any, parent: any, key: any, args: any[], visit: Visit, delegate: INormalizeDelegate, parentEntity: any): any; + normalize(input: any, parent: any, key: any, delegate: INormalizeDelegate, parentEntity: any): any; denormalize(input: any, delegate: IDenormalizeDelegate): any; /** * Returns the cpks of cells matching the current lens, or undefined. @@ -954,7 +959,7 @@ interface CollectionInterface any, delegate: INormalizeDelegate, parentEntity?: any): string; + normalize(input: any, parent: Parent, key: string, delegate: INormalizeDelegate, parentEntity?: any): string; /** Creates new instance copying over defined values of arguments * * @see https://dataclient.io/docs/api/Collection#merge @@ -1002,7 +1007,7 @@ interface CollectionInterface any | undefined; - denormalize(input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): ReturnType; + denormalize(input: any, delegate: IDenormalizeDelegate): ReturnType; _denormalizeNullable(): ReturnType; _normalizeNullable(): ReturnType; /** Schema to place at the *end* of this Collection @@ -1037,6 +1042,7 @@ interface CollectionConstructor { } type StrategyFunction = (value: any, parent: any, key: string) => T; type SchemaFunction = (value: Args, parent: any, key: string) => K; +type SchemaAttribute = K | SchemaFunction; type MergeFunction = (entityA: any, entityB: any) => any; type SchemaAttributeFunction = (value: any, parent: any, key: string) => S; type UnionResult = { @@ -1069,18 +1075,14 @@ declare class Array$1 implements SchemaClass { input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): - | (S extends EntityMap ? UnionResult : Normalize)[] - | undefined; + (S extends EntityMap ? UnionResult : Normalize)[] | undefined; _denormalizeNullable(): - | (S extends EntityMap ? T : Denormalize)[] - | undefined; + (S extends EntityMap ? T : Denormalize)[] | undefined; denormalize( input: {}, @@ -1123,18 +1125,14 @@ declare class All< input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): - | (S extends EntityMap ? UnionResult : Normalize)[] - | undefined; + (S extends EntityMap ? UnionResult : Normalize)[] | undefined; _denormalizeNullable(): - | (S extends EntityMap ? T : Denormalize)[] - | undefined; + (S extends EntityMap ? T : Denormalize)[] | undefined; denormalize( input: {}, @@ -1167,8 +1165,6 @@ declare class Object$1< input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): NormalizeObject; @@ -1238,8 +1234,8 @@ interface UnionConstructor { */ interface UnionInstance< Choices extends EntityMap = any, - Args extends EntityFields> = - EntityFields>, + // unconstrained: TypeScript 4.2-4.4 can't prove UnionConstructor's Args satisfy EntityFields + Args = EntityFields>, > { readonly _hoistable: true; define(definition: Schema): void; @@ -1251,16 +1247,13 @@ interface UnionInstance< input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): UnionResult; _normalizeNullable(): UnionResult | undefined; _denormalizeNullable(): - | AbstractInstanceType - | undefined; + AbstractInstanceType | undefined; denormalize( input: {}, @@ -1322,8 +1315,6 @@ declare class Values implements SchemaClass { input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): Record< string, @@ -1403,6 +1394,7 @@ type schema_d_Query = SchemaAttribute; type schema_d_SchemaAttributeFunction = SchemaAttributeFunction; type schema_d_SchemaClass = SchemaClass; type schema_d_SchemaFunction = SchemaFunction; @@ -1412,15 +1404,14 @@ type schema_d_Union> = Union; declare const schema_d_Union: typeof Union; type schema_d_UnionConstructor = UnionConstructor; -type schema_d_UnionInstance> = - EntityFields>> = UnionInstance; +type schema_d_UnionInstance>> = UnionInstance; type schema_d_UnionResult = UnionResult; declare const schema_d_UnionRoot: typeof UnionRoot; type schema_d_Values = Values; declare const schema_d_Values: typeof Values; declare const schema_d_unshift: typeof unshift; declare namespace schema_d { - export { schema_d_All as All, Array$1 as Array, schema_d_Collection as Collection, type schema_d_CollectionArrayAdder as CollectionArrayAdder, type schema_d_CollectionArrayOrValuesAdder as CollectionArrayOrValuesAdder, type schema_d_CollectionConstructor as CollectionConstructor, type schema_d_CollectionFromSchema as CollectionFromSchema, type schema_d_CollectionInterface as CollectionInterface, schema_d_CollectionRoot as CollectionRoot, type schema_d_CollectionValuesAdder as CollectionValuesAdder, type schema_d_DefaultArgs as DefaultArgs, EntityMixin as Entity, type schema_d_EntityInterface as EntityInterface, type schema_d_EntityMap as EntityMap, schema_d_EntityMixin as EntityMixin, schema_d_Invalidate as Invalidate, schema_d_Lazy as Lazy, type schema_d_MergeFunction as MergeFunction, Object$1 as Object, schema_d_Query as Query, schema_d_Scalar as Scalar, type schema_d_SchemaAttributeFunction as SchemaAttributeFunction, type schema_d_SchemaClass as SchemaClass, type schema_d_SchemaFunction as SchemaFunction, type schema_d_StrategyFunction as StrategyFunction, schema_d_Union as Union, type schema_d_UnionConstructor as UnionConstructor, type schema_d_UnionInstance as UnionInstance, type schema_d_UnionResult as UnionResult, schema_d_UnionRoot as UnionRoot, schema_d_Values as Values, schema_d_unshift as unshift }; + export { schema_d_All as All, Array$1 as Array, schema_d_Collection as Collection, type schema_d_CollectionArrayAdder as CollectionArrayAdder, type schema_d_CollectionArrayOrValuesAdder as CollectionArrayOrValuesAdder, type schema_d_CollectionConstructor as CollectionConstructor, type schema_d_CollectionFromSchema as CollectionFromSchema, type schema_d_CollectionInterface as CollectionInterface, schema_d_CollectionRoot as CollectionRoot, type schema_d_CollectionValuesAdder as CollectionValuesAdder, type schema_d_DefaultArgs as DefaultArgs, EntityMixin as Entity, type schema_d_EntityInterface as EntityInterface, type schema_d_EntityMap as EntityMap, schema_d_EntityMixin as EntityMixin, schema_d_Invalidate as Invalidate, schema_d_Lazy as Lazy, type schema_d_MergeFunction as MergeFunction, Object$1 as Object, schema_d_Query as Query, schema_d_Scalar as Scalar, type schema_d_SchemaAttribute as SchemaAttribute, type schema_d_SchemaAttributeFunction as SchemaAttributeFunction, type schema_d_SchemaClass as SchemaClass, type schema_d_SchemaFunction as SchemaFunction, type schema_d_StrategyFunction as StrategyFunction, schema_d_Union as Union, type schema_d_UnionConstructor as UnionConstructor, type schema_d_UnionInstance as UnionInstance, type schema_d_UnionResult as UnionResult, schema_d_UnionRoot as UnionRoot, schema_d_Values as Values, schema_d_unshift as unshift }; } declare const Entity_base: IEntityClass { @@ -1457,7 +1448,9 @@ declare abstract class Entity extends Entity_base { * @param [key] When normalizing, the key where this entity was found * @param [args] ...args sent to Endpoint */ - static pk: (this: T, value: Partial>, parent?: any, key?: string, args?: any[]) => string | number | undefined; + static pk: { + pk(this: T, value: Partial>, parent?: any, key?: string, args?: readonly any[]): string | number | undefined; + }['pk']; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#process diff --git a/website/src/components/Playground/editor-types/@data-client/graphql.d.ts b/website/src/components/Playground/editor-types/@data-client/graphql.d.ts index 84ecb353d8a5..4f2d3c6f81df 100644 --- a/website/src/components/Playground/editor-types/@data-client/graphql.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/graphql.d.ts @@ -160,12 +160,16 @@ interface SnapshotInterface { } type ExpiryStatusInterface = 1 | 2 | 3; +/** Removes Rem's length worth of leading elements from Orig */ +type RemoveArray = Rem extends [any, ...infer RestRem] ? Orig extends [any, ...infer RestOrig] ? RemoveArray : never : Orig; + /** Get the Params type for a given Shape */ type EndpointParam = E extends (first: infer A, ...rest: any) => any ? A : E extends { key: (first: infer A, ...rest: any) => any; } ? A : never; /** What the function's promise resolves to */ type ResolveType any> = ReturnType extends Promise ? R : never; + type PartialParameters any> = T extends (...args: infer P) => any ? Partial

: never; type EndpointToFunction Promise> = (this: E, ...args: Parameters) => ReturnType; @@ -208,17 +212,12 @@ interface SchemaSimple { * @param input The value being normalized. * @param parent The parent object/array/dictionary containing `input`. * @param key The key under which `input` lives on `parent`. - * @param args The endpoint args for this normalize call. - * @param visit Recursive visitor for nested schemas. - * @param delegate Store accessors for reading/writing entities. + * @param delegate Recursive visitor, endpoint args, and store accessors. * @param parentEntity Nearest enclosing entity-like schema (one with `pk`), * tracked automatically by the visit walker. `Scalar` * uses this to discover its entity binding. */ - normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, delegate: { - getEntity: any; - setEntity: any; - }, parentEntity?: any): any; + normalize(input: any, parent: any, key: any, delegate: INormalizeDelegate, parentEntity?: any): any; denormalize(input: {}, delegate: IDenormalizeDelegate): T; queryKey(args: Args, unvisit: (...args: any) => any, delegate: { getEntity: any; @@ -270,14 +269,13 @@ interface EntityTable { * Schemas that recurse via `visit` should pass their own * `input` (or the surrounding container) here. * @param key The key under which `value` lives on `parent`. - * @param args The endpoint args for this normalize call. * * The walker internally tracks the nearest enclosing entity-like schema and * forwards it to `schema.normalize` as a trailing `parentEntity` argument — - * see `SchemaSimple.normalize`. Consumers of `visit` don't pass it. + * see `SchemaSimple.normalize`. */ interface Visit { - (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; + (schema: any, value: any, parent: any, key: any): any; creating?: boolean; } /** Used in denormalize. Lookup to find an entity in the store table */ @@ -331,6 +329,10 @@ interface IDenormalizeDelegate { } /** Helpers during schema.normalize() */ interface INormalizeDelegate { + /** Recursive normalize of nested schemas */ + visit: Visit; + /** Raw endpoint args for this normalize call */ + readonly args: readonly any[]; /** Action meta-data for this normalize call */ readonly meta: { fetchedAt: number; @@ -447,7 +449,6 @@ interface ExtendableEndpointConstructor { new & E, params?: any, body?: any) => Promise, S extends Schema | undefined = undefined, M extends boolean | undefined = false, E extends Record = {}>(RestFetch: F, options?: Readonly> & E): EndpointInstanceInterface & E; readonly prototype: Function; } -type RemoveArray = Rem extends [any, ...infer RestRem] ? Orig extends [any, ...infer RestOrig] ? RemoveArray : never : Orig; declare let Endpoint: EndpointConstructor; @@ -498,7 +499,7 @@ interface IEntityClass { * @param [key] When normalizing, the key where this entity was found * @param [args] ...args sent to Endpoint */ - pk IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, value: Partial>, parent?: any, key?: string, args?: any[]): string | number | undefined; + pk IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, value: Partial>, parent?: any, key?: string, args?: readonly any[]): string | number | undefined; /** Return true to merge incoming data; false keeps existing entity * * @see https://dataclient.io/docs/api/Entity#shouldUpdate @@ -559,22 +560,19 @@ interface IEntityClass { * * @param [props] Plain Object of properties to assign. */ - fromJS IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, props?: Partial>): AbstractInstanceType; + fromJS IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, props?: Partial>): AbstractInstanceType; /** Called when denormalizing an entity to create an instance when 'valid' * * @param [props] Plain Object of properties to assign. * @see https://dataclient.io/rest/api/Entity#createIfValid */ - createIfValid IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, props: Partial>): AbstractInstanceType | undefined; + createIfValid IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, props: Partial>): AbstractInstanceType | undefined; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#process */ process(input: any, parent: any, key: string | undefined, args: any[]): any; - normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, snapshot: { - getEntity: any; - setEntity: any; - }): any; + normalize(input: any, parent: any, key: string | undefined, delegate: INormalizeDelegate): any; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#validate @@ -585,7 +583,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#queryKey */ queryKey(args: readonly any[], unvisit: any, delegate: IQueryDelegate): any; - denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, delegate: IDenormalizeDelegate): AbstractInstanceType; + denormalize IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, input: any, delegate: IDenormalizeDelegate): AbstractInstanceType; /** All instance defaults set */ readonly defaults: any; } @@ -600,6 +598,11 @@ interface IEntityInstance { pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; } type Constructor = abstract new (...args: any[]) => {}; +/** InstanceType<> for abstract constructor types, which TypeScript 4.2's InstanceType rejects + * + * Unlike AbstractInstanceType<>, this resolves constructor type aliases (not just classes) + */ +type ConstructorInstance any> = T extends abstract new (...args: any) => infer R ? R : any; type IDClass = abstract new (...args: any[]) => { id: string | number | undefined; }; @@ -624,9 +627,9 @@ interface RequiredPKOptions extends EntityOptions(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase; -declare function EntityMixin(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); -declare function EntityMixin(Base: TBase, opt: RequiredPKOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); +declare function EntityMixin(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase; +declare function EntityMixin(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); +declare function EntityMixin(Base: TBase, opt: RequiredPKOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); declare class PolymorphicSchema { private _schemaAttribute; @@ -637,7 +640,9 @@ declare class PolymorphicSchema { getSchemaAttribute(input: any, parent: any, key: any): any; inferSchema(input: any, parent: any, key: any): any; schemaKey(): string; - normalizeValue(value: any, parent: any, key: any, args: any[], visit: Visit): any; + normalizeValue(value: any, parent: any, key: any, delegate: { + visit: Visit; + }): any; denormalizeValue(value: any, unvisit: any): any; } @@ -667,7 +672,7 @@ declare class Invalidate ? string | ((input: any, parent: any, key: any) => string) : undefined); get key(): string; - normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, delegate: INormalizeDelegate): string | { + normalize(input: any, parent: any, key: string | undefined, delegate: INormalizeDelegate): string | { id: string; schema: string; }; @@ -713,7 +718,7 @@ declare class Lazy implements SchemaSimple { * @param {Schema} schema - The inner schema (e.g., [Building], Building, Collection) */ constructor(schema: S); - normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, _delegate: any): any; + normalize(input: any, parent: any, key: any, delegate: any): any; denormalize(input: {}, _delegate: IDenormalizeDelegate): any; queryKey(_args: readonly any[], _unvisit: (...args: any) => any, _delegate: any): undefined; /** Queryable schema for use with useQuery() to resolve lazy relationships */ @@ -869,7 +874,7 @@ declare class Scalar implements Mergeable { date: number; expiresAt: number; }; - normalize(input: any, parent: any, key: any, args: any[], visit: Visit, delegate: INormalizeDelegate, parentEntity: any): any; + normalize(input: any, parent: any, key: any, delegate: INormalizeDelegate, parentEntity: any): any; denormalize(input: any, delegate: IDenormalizeDelegate): any; /** * Returns the cpks of cells matching the current lens, or undefined. @@ -954,7 +959,7 @@ interface CollectionInterface any, delegate: INormalizeDelegate, parentEntity?: any): string; + normalize(input: any, parent: Parent, key: string, delegate: INormalizeDelegate, parentEntity?: any): string; /** Creates new instance copying over defined values of arguments * * @see https://dataclient.io/docs/api/Collection#merge @@ -1002,7 +1007,7 @@ interface CollectionInterface any | undefined; - denormalize(input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): ReturnType; + denormalize(input: any, delegate: IDenormalizeDelegate): ReturnType; _denormalizeNullable(): ReturnType; _normalizeNullable(): ReturnType; /** Schema to place at the *end* of this Collection @@ -1037,6 +1042,7 @@ interface CollectionConstructor { } type StrategyFunction = (value: any, parent: any, key: string) => T; type SchemaFunction = (value: Args, parent: any, key: string) => K; +type SchemaAttribute = K | SchemaFunction; type MergeFunction = (entityA: any, entityB: any) => any; type SchemaAttributeFunction = (value: any, parent: any, key: string) => S; type UnionResult = { @@ -1069,18 +1075,14 @@ declare class Array$1 implements SchemaClass { input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): - | (S extends EntityMap ? UnionResult : Normalize)[] - | undefined; + (S extends EntityMap ? UnionResult : Normalize)[] | undefined; _denormalizeNullable(): - | (S extends EntityMap ? T : Denormalize)[] - | undefined; + (S extends EntityMap ? T : Denormalize)[] | undefined; denormalize( input: {}, @@ -1123,18 +1125,14 @@ declare class All< input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): - | (S extends EntityMap ? UnionResult : Normalize)[] - | undefined; + (S extends EntityMap ? UnionResult : Normalize)[] | undefined; _denormalizeNullable(): - | (S extends EntityMap ? T : Denormalize)[] - | undefined; + (S extends EntityMap ? T : Denormalize)[] | undefined; denormalize( input: {}, @@ -1167,8 +1165,6 @@ declare class Object$1< input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): NormalizeObject; @@ -1238,8 +1234,8 @@ interface UnionConstructor { */ interface UnionInstance< Choices extends EntityMap = any, - Args extends EntityFields> = - EntityFields>, + // unconstrained: TypeScript 4.2-4.4 can't prove UnionConstructor's Args satisfy EntityFields + Args = EntityFields>, > { readonly _hoistable: true; define(definition: Schema): void; @@ -1251,16 +1247,13 @@ interface UnionInstance< input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): UnionResult; _normalizeNullable(): UnionResult | undefined; _denormalizeNullable(): - | AbstractInstanceType - | undefined; + AbstractInstanceType | undefined; denormalize( input: {}, @@ -1322,8 +1315,6 @@ declare class Values implements SchemaClass { input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): Record< string, @@ -1403,6 +1394,7 @@ type schema_d_Query = SchemaAttribute; type schema_d_SchemaAttributeFunction = SchemaAttributeFunction; type schema_d_SchemaClass = SchemaClass; type schema_d_SchemaFunction = SchemaFunction; @@ -1412,15 +1404,14 @@ type schema_d_Union> = Union; declare const schema_d_Union: typeof Union; type schema_d_UnionConstructor = UnionConstructor; -type schema_d_UnionInstance> = - EntityFields>> = UnionInstance; +type schema_d_UnionInstance>> = UnionInstance; type schema_d_UnionResult = UnionResult; declare const schema_d_UnionRoot: typeof UnionRoot; type schema_d_Values = Values; declare const schema_d_Values: typeof Values; declare const schema_d_unshift: typeof unshift; declare namespace schema_d { - export { schema_d_All as All, Array$1 as Array, schema_d_Collection as Collection, type schema_d_CollectionArrayAdder as CollectionArrayAdder, type schema_d_CollectionArrayOrValuesAdder as CollectionArrayOrValuesAdder, type schema_d_CollectionConstructor as CollectionConstructor, type schema_d_CollectionFromSchema as CollectionFromSchema, type schema_d_CollectionInterface as CollectionInterface, schema_d_CollectionRoot as CollectionRoot, type schema_d_CollectionValuesAdder as CollectionValuesAdder, type schema_d_DefaultArgs as DefaultArgs, EntityMixin as Entity, type schema_d_EntityInterface as EntityInterface, type schema_d_EntityMap as EntityMap, schema_d_EntityMixin as EntityMixin, schema_d_Invalidate as Invalidate, schema_d_Lazy as Lazy, type schema_d_MergeFunction as MergeFunction, Object$1 as Object, schema_d_Query as Query, schema_d_Scalar as Scalar, type schema_d_SchemaAttributeFunction as SchemaAttributeFunction, type schema_d_SchemaClass as SchemaClass, type schema_d_SchemaFunction as SchemaFunction, type schema_d_StrategyFunction as StrategyFunction, schema_d_Union as Union, type schema_d_UnionConstructor as UnionConstructor, type schema_d_UnionInstance as UnionInstance, type schema_d_UnionResult as UnionResult, schema_d_UnionRoot as UnionRoot, schema_d_Values as Values, schema_d_unshift as unshift }; + export { schema_d_All as All, Array$1 as Array, schema_d_Collection as Collection, type schema_d_CollectionArrayAdder as CollectionArrayAdder, type schema_d_CollectionArrayOrValuesAdder as CollectionArrayOrValuesAdder, type schema_d_CollectionConstructor as CollectionConstructor, type schema_d_CollectionFromSchema as CollectionFromSchema, type schema_d_CollectionInterface as CollectionInterface, schema_d_CollectionRoot as CollectionRoot, type schema_d_CollectionValuesAdder as CollectionValuesAdder, type schema_d_DefaultArgs as DefaultArgs, EntityMixin as Entity, type schema_d_EntityInterface as EntityInterface, type schema_d_EntityMap as EntityMap, schema_d_EntityMixin as EntityMixin, schema_d_Invalidate as Invalidate, schema_d_Lazy as Lazy, type schema_d_MergeFunction as MergeFunction, Object$1 as Object, schema_d_Query as Query, schema_d_Scalar as Scalar, type schema_d_SchemaAttribute as SchemaAttribute, type schema_d_SchemaAttributeFunction as SchemaAttributeFunction, type schema_d_SchemaClass as SchemaClass, type schema_d_SchemaFunction as SchemaFunction, type schema_d_StrategyFunction as StrategyFunction, schema_d_Union as Union, type schema_d_UnionConstructor as UnionConstructor, type schema_d_UnionInstance as UnionInstance, type schema_d_UnionResult as UnionResult, schema_d_UnionRoot as UnionRoot, schema_d_Values as Values, schema_d_unshift as unshift }; } declare const Entity_base: IEntityClass { @@ -1457,7 +1448,9 @@ declare abstract class Entity extends Entity_base { * @param [key] When normalizing, the key where this entity was found * @param [args] ...args sent to Endpoint */ - static pk: (this: T, value: Partial>, parent?: any, key?: string, args?: any[]) => string | number | undefined; + static pk: { + pk(this: T, value: Partial>, parent?: any, key?: string, args?: readonly any[]): string | number | undefined; + }['pk']; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#process diff --git a/website/src/components/Playground/editor-types/@data-client/normalizr.d.ts b/website/src/components/Playground/editor-types/@data-client/normalizr.d.ts index cdfd4b43f051..dfb483735410 100644 --- a/website/src/components/Playground/editor-types/@data-client/normalizr.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/normalizr.d.ts @@ -13,10 +13,7 @@ type Serializable = (value: any) => T; interface SchemaSimple { - normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, delegate: { - getEntity: any; - setEntity: any; - }, + normalize(input: any, parent: any, key: any, delegate: INormalizeDelegate, /** The nearest enclosing entity-like schema (one with `pk`), if any. * Tracked automatically by the visit walker. */ parentEntity?: any): any; @@ -60,7 +57,8 @@ interface EntityTable { } /** Visits next data + schema while recurisvely normalizing */ interface Visit { - (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; + (schema: any, value: any, parent: any, key: any): any; + creating?: boolean; } /** Used in denormalize. Lookup to find an entity in the store table */ interface EntityPath { @@ -114,6 +112,10 @@ interface IDenormalizeDelegate { } /** Helpers during schema.normalize() */ interface INormalizeDelegate { + /** Recursive normalize of nested schemas */ + visit: Visit; + /** Raw endpoint args for this normalize call */ + readonly args: readonly any[]; /** Action meta-data for this normalize call */ readonly meta: { fetchedAt: number; diff --git a/website/src/components/Playground/editor-types/@data-client/react.d.ts b/website/src/components/Playground/editor-types/@data-client/react.d.ts index 117ee2e0b3d5..218667c8cb94 100644 --- a/website/src/components/Playground/editor-types/@data-client/react.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/react.d.ts @@ -1,19 +1,19 @@ import * as _data_client_core from '@data-client/core'; import { NetworkManager, Manager, State, Controller, GCInterface, DevToolsManager, DevToolsConfig, SubscriptionManager, EndpointInterface, FetchFunction, Schema, ResolveType, Denormalize, DenormalizeNullable, Queryable, NI, SchemaArgs, NetworkError, UnknownError, ErrorTypes as ErrorTypes$1, __INTERNAL__, actions, applyManager, createReducer, initManager } from '@data-client/core'; export { AbstractInstanceType, ActionTypes, Controller, CreateCountRef, DataClientDispatch, DefaultConnectionListener, Denormalize, DenormalizeNullable, DevToolsManager, Dispatch, EndpointExtraOptions, EndpointInterface, EntityInterface, ErrorTypes, ExpiryStatus, FetchAction, FetchFunction, GCInterface, GCOptions, GCPolicy, GenericDispatch, InvalidateAction, LogoutManager, Manager, Middleware, MiddlewareAPI, NetworkError, NetworkManager, Normalize, NormalizeNullable, PK, PollingSubscription, Queryable, ResetAction, ResolveType, Schema, SchemaArgs, SchemaClass, SetAction, SetResponseAction, State, SubscribeAction, SubscriptionManager, UnknownError, UnsubscribeAction, UpdateFunction, actionTypes } from '@data-client/core'; -import * as react_jsx_runtime from 'react/jsx-runtime'; -import React, { JSX, Context } from 'react'; +import * as React from 'react'; +import React__default, { JSX, Context } from 'react'; /** Can help prevent stuttering by waiting for idle for sideEffect free fetches */ declare class WebIdlingNetworkManager extends NetworkManager { } -declare function BackupLoading(): react_jsx_runtime.JSX.Element; +declare function BackupLoading(): React__default.JSX.Element; type DevToolsPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; interface ProviderProps { - children: React.ReactNode; + children: React__default.ReactNode; managers?: Manager[]; initialState?: State; Controller?: typeof Controller; @@ -44,18 +44,25 @@ type ConstructorArgs; //# sourceMappingURL=UniversalSuspense.d.ts.map +declare const ErrorFallback: ({ error, className, }: { + error: Error; + resetErrorBoundary: () => void; + className?: string; +}) => React.JSX.Element; +//# sourceMappingURL=ErrorFallback.d.ts.map + interface ErrorBoundaryProps { - children: React.ReactNode; + children: React__default.ReactNode; /** className prop sent to fallbackComponent */ className?: string; /** Renders when an error is caught */ - fallbackComponent: React.ComponentType<{ + fallbackComponent: React__default.ComponentType<{ error: E; resetErrorBoundary: () => void; className?: string; @@ -70,13 +77,9 @@ interface ErrorState { * Reusable React error boundary component * @see https://dataclient.io/docs/api/ErrorBoundary */ -declare class ErrorBoundary extends React.Component, ErrorState> { +declare class ErrorBoundary extends React__default.Component, ErrorState> { static defaultProps: { - fallbackComponent: ({ error, className, }: { - error: Error; - resetErrorBoundary: () => void; - className?: string; - }) => react_jsx_runtime.JSX.Element; + fallbackComponent: typeof ErrorFallback; }; static getDerivedStateFromError(error: Error): { error: Error; @@ -99,8 +102,8 @@ declare function AsyncBoundary({ children, errorComponent, fallback, ...errorPro declare const _default: typeof AsyncBoundary; interface Props { - children: React.ReactNode; - fallback?: React.ReactNode; + children: React__default.ReactNode; + fallback?: React__default.ReactNode; errorClassName?: string; /** Renders when an error is caught */ errorComponent?: ErrorBoundaryProps['fallbackComponent']; @@ -295,9 +298,9 @@ declare namespace internal_d { export { internal_d_INVALID as INVALID, internal_d_MemoCache as MemoCache, internal_d_actions as actions, internal_d_applyManager as applyManager, internal_d_createReducer as createReducer, internal_d_initManager as initManager, internal_d_initialState as initialState, internal_d_useCacheState as useCacheState }; } -type ReducerAction> = R extends React.Reducer ? A : never; +type ReducerAction> = R extends React__default.Reducer ? A : never; /** Turns a dispatch function into one that resolves once its been commited */ -declare function usePromisifiedDispatch>(dispatch: React.Dispatch>, state: React.ReducerState): (action: ReducerAction) => Promise; +declare function usePromisifiedDispatch>(dispatch: React__default.Dispatch>, state: React__default.ReducerState): (action: ReducerAction) => Promise; export { _default as AsyncBoundary, BackupLoading, DataProvider as CacheProvider, ControllerContext, DataProvider, type DevToolsPosition, ErrorBoundary, WebIdlingNetworkManager as IdlingNetworkManager, ErrorBoundary as NetworkErrorBoundary, type ProviderProps, StateContext, type Store, StoreContext, UniversalSuspense, internal_d as __INTERNAL__, getDefaultManagers, useCache, useCancelling, useController, useDLE, useDebounce, useError, useFetch, useLive, useLoading, usePromisifiedDispatch, useQuery, useSubscription, useSuspense }; diff --git a/website/src/components/Playground/editor-types/@data-client/react/nextjs.d.ts b/website/src/components/Playground/editor-types/@data-client/react/nextjs.d.ts index a6997a7743d4..0afbf7d12dd0 100644 --- a/website/src/components/Playground/editor-types/@data-client/react/nextjs.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/react/nextjs.d.ts @@ -1,10 +1,10 @@ -import React$1, { JSX, ComponentProps } from 'react'; +import react, { JSX, ComponentProps } from 'react'; import { Manager, State, Controller, GCInterface } from '@data-client/core'; type DevToolsPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; interface ProviderProps$1 { - children: React$1.ReactNode; + children: react.ReactNode; managers?: Manager[]; initialState?: State; Controller?: typeof Controller; diff --git a/website/src/components/Playground/editor-types/@data-client/react/redux.d.ts b/website/src/components/Playground/editor-types/@data-client/react/redux.d.ts index 09cb15c4020f..78e23d10b455 100644 --- a/website/src/components/Playground/editor-types/@data-client/react/redux.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/react/redux.d.ts @@ -1,7 +1,6 @@ import * as _data_client_core from '@data-client/core'; import { ActionTypes, State, Manager, Controller, GCInterface, Dispatch as Dispatch$1 } from '@data-client/core'; export { applyManager, createReducer, initialState } from '@data-client/core'; -import * as react_jsx_runtime from 'react/jsx-runtime'; import React from 'react'; /** @@ -151,7 +150,7 @@ type ReducersMapObject { * Like DataProvider, but for an external store * @see https://dataclient.io/docs/api/ExternalDataProvider */ -declare function ExternalDataProvider({ children, store, selector, controller, devButton, hasDevManager, }: Props): react_jsx_runtime.JSX.Element; +declare function ExternalDataProvider({ children, store, selector, controller, devButton, hasDevManager, }: Props): React.JSX.Element; declare const mapMiddleware: (selector: (state: any) => State) => (...middlewares: Middleware[]) => M; //# sourceMappingURL=mapMiddleware.d.ts.map diff --git a/website/src/components/Playground/editor-types/@data-client/react/ssr.d.ts b/website/src/components/Playground/editor-types/@data-client/react/ssr.d.ts index f6408e597dbe..936482a8fd81 100644 --- a/website/src/components/Playground/editor-types/@data-client/react/ssr.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/react/ssr.d.ts @@ -1,6 +1,6 @@ import * as _data_client_core from '@data-client/core'; import { Manager, State, Controller } from '@data-client/core'; -import * as react_jsx_runtime from 'react/jsx-runtime'; +import * as react from 'react'; /** * An *action* is a plain object that represents an intention to change the @@ -156,7 +156,7 @@ interface Store { declare function createPersistedStore(managers?: Manager[], hasDevManager?: boolean): readonly [({ children }: { children: React.ReactNode; -}) => react_jsx_runtime.JSX.Element, () => State, Controller<_data_client_core.DataClientDispatch>, Store, _data_client_core.ActionTypes, unknown> & { +}) => react.JSX.Element, () => State, Controller<_data_client_core.DataClientDispatch>, Store, _data_client_core.ActionTypes, unknown> & { dispatch: unknown; }]; @@ -165,12 +165,12 @@ declare const getInitialData: (id?: string) => any; declare function createServerDataComponent(useReadyCacheState: () => State, id?: string): ({ nonce }: { nonce?: string | undefined; -}) => react_jsx_runtime.JSX.Element; +}) => react.JSX.Element; declare const ServerData: ({ data, nonce, id, }: { data: State; id?: string; nonce?: string | undefined; -}) => react_jsx_runtime.JSX.Element | null; +}) => react.JSX.Element | null; export { ServerData, awaitInitialData, createPersistedStore, createServerDataComponent, getInitialData }; diff --git a/website/src/components/Playground/editor-types/@data-client/rest.d.ts b/website/src/components/Playground/editor-types/@data-client/rest.d.ts index 49875188a970..73b26c94c2b2 100644 --- a/website/src/components/Playground/editor-types/@data-client/rest.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/rest.d.ts @@ -162,12 +162,16 @@ interface SnapshotInterface { } type ExpiryStatusInterface = 1 | 2 | 3; +/** Removes Rem's length worth of leading elements from Orig */ +type RemoveArray = Rem extends [any, ...infer RestRem] ? Orig extends [any, ...infer RestOrig] ? RemoveArray : never : Orig; + /** Get the Params type for a given Shape */ type EndpointParam = E extends (first: infer A, ...rest: any) => any ? A : E extends { key: (first: infer A, ...rest: any) => any; } ? A : never; /** What the function's promise resolves to */ type ResolveType any> = ReturnType extends Promise ? R : never; + type PartialParameters any> = T extends (...args: infer P) => any ? Partial

: never; type EndpointToFunction Promise> = (this: E, ...args: Parameters) => ReturnType; @@ -210,17 +214,12 @@ interface SchemaSimple { * @param input The value being normalized. * @param parent The parent object/array/dictionary containing `input`. * @param key The key under which `input` lives on `parent`. - * @param args The endpoint args for this normalize call. - * @param visit Recursive visitor for nested schemas. - * @param delegate Store accessors for reading/writing entities. + * @param delegate Recursive visitor, endpoint args, and store accessors. * @param parentEntity Nearest enclosing entity-like schema (one with `pk`), * tracked automatically by the visit walker. `Scalar` * uses this to discover its entity binding. */ - normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, delegate: { - getEntity: any; - setEntity: any; - }, parentEntity?: any): any; + normalize(input: any, parent: any, key: any, delegate: INormalizeDelegate, parentEntity?: any): any; denormalize(input: {}, delegate: IDenormalizeDelegate): T; queryKey(args: Args, unvisit: (...args: any) => any, delegate: { getEntity: any; @@ -272,14 +271,13 @@ interface EntityTable { * Schemas that recurse via `visit` should pass their own * `input` (or the surrounding container) here. * @param key The key under which `value` lives on `parent`. - * @param args The endpoint args for this normalize call. * * The walker internally tracks the nearest enclosing entity-like schema and * forwards it to `schema.normalize` as a trailing `parentEntity` argument — - * see `SchemaSimple.normalize`. Consumers of `visit` don't pass it. + * see `SchemaSimple.normalize`. */ interface Visit { - (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; + (schema: any, value: any, parent: any, key: any): any; creating?: boolean; } /** Used in denormalize. Lookup to find an entity in the store table */ @@ -333,6 +331,10 @@ interface IDenormalizeDelegate { } /** Helpers during schema.normalize() */ interface INormalizeDelegate { + /** Recursive normalize of nested schemas */ + visit: Visit; + /** Raw endpoint args for this normalize call */ + readonly args: readonly any[]; /** Action meta-data for this normalize call */ readonly meta: { fetchedAt: number; @@ -445,7 +447,6 @@ interface ExtendableEndpointConstructor { new & E, params?: any, body?: any) => Promise, S extends Schema | undefined = undefined, M extends boolean | undefined = false, E extends Record = {}>(RestFetch: F, options?: Readonly> & E): EndpointInstanceInterface & E; readonly prototype: Function; } -type RemoveArray = Rem extends [any, ...infer RestRem] ? Orig extends [any, ...infer RestOrig] ? RemoveArray : never : Orig; declare let Endpoint: EndpointConstructor; @@ -496,7 +497,7 @@ interface IEntityClass { * @param [key] When normalizing, the key where this entity was found * @param [args] ...args sent to Endpoint */ - pk IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, value: Partial>, parent?: any, key?: string, args?: any[]): string | number | undefined; + pk IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, value: Partial>, parent?: any, key?: string, args?: readonly any[]): string | number | undefined; /** Return true to merge incoming data; false keeps existing entity * * @see https://dataclient.io/docs/api/Entity#shouldUpdate @@ -557,22 +558,19 @@ interface IEntityClass { * * @param [props] Plain Object of properties to assign. */ - fromJS IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, props?: Partial>): AbstractInstanceType; + fromJS IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, props?: Partial>): AbstractInstanceType; /** Called when denormalizing an entity to create an instance when 'valid' * * @param [props] Plain Object of properties to assign. * @see https://dataclient.io/rest/api/Entity#createIfValid */ - createIfValid IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, props: Partial>): AbstractInstanceType | undefined; + createIfValid IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, props: Partial>): AbstractInstanceType | undefined; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#process */ process(input: any, parent: any, key: string | undefined, args: any[]): any; - normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, snapshot: { - getEntity: any; - setEntity: any; - }): any; + normalize(input: any, parent: any, key: string | undefined, delegate: INormalizeDelegate): any; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#validate @@ -583,7 +581,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#queryKey */ queryKey(args: readonly any[], unvisit: any, delegate: IQueryDelegate): any; - denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, delegate: IDenormalizeDelegate): AbstractInstanceType; + denormalize IEntityInstance & ConstructorInstance) & IEntityClass & TBase>(this: T, input: any, delegate: IDenormalizeDelegate): AbstractInstanceType; /** All instance defaults set */ readonly defaults: any; } @@ -598,6 +596,11 @@ interface IEntityInstance { pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; } type Constructor = abstract new (...args: any[]) => {}; +/** InstanceType<> for abstract constructor types, which TypeScript 4.2's InstanceType rejects + * + * Unlike AbstractInstanceType<>, this resolves constructor type aliases (not just classes) + */ +type ConstructorInstance any> = T extends abstract new (...args: any) => infer R ? R : any; type IDClass = abstract new (...args: any[]) => { id: string | number | undefined; }; @@ -622,9 +625,9 @@ interface RequiredPKOptions extends EntityOptions(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase; -declare function EntityMixin(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); -declare function EntityMixin(Base: TBase, opt: RequiredPKOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); +declare function EntityMixin(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase; +declare function EntityMixin(Base: TBase, opt?: EntityOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); +declare function EntityMixin(Base: TBase, opt: RequiredPKOptions>): IEntityClass & TBase & (new (...args: any[]) => IEntityInstance); declare class PolymorphicSchema { private _schemaAttribute; @@ -635,7 +638,9 @@ declare class PolymorphicSchema { getSchemaAttribute(input: any, parent: any, key: any): any; inferSchema(input: any, parent: any, key: any): any; schemaKey(): string; - normalizeValue(value: any, parent: any, key: any, args: any[], visit: Visit): any; + normalizeValue(value: any, parent: any, key: any, delegate: { + visit: Visit; + }): any; denormalizeValue(value: any, unvisit: any): any; } @@ -665,7 +670,7 @@ declare class Invalidate ? string | ((input: any, parent: any, key: any) => string) : undefined); get key(): string; - normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, delegate: INormalizeDelegate): string | { + normalize(input: any, parent: any, key: string | undefined, delegate: INormalizeDelegate): string | { id: string; schema: string; }; @@ -711,7 +716,7 @@ declare class Lazy implements SchemaSimple { * @param {Schema} schema - The inner schema (e.g., [Building], Building, Collection) */ constructor(schema: S); - normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, _delegate: any): any; + normalize(input: any, parent: any, key: any, delegate: any): any; denormalize(input: {}, _delegate: IDenormalizeDelegate): any; queryKey(_args: readonly any[], _unvisit: (...args: any) => any, _delegate: any): undefined; /** Queryable schema for use with useQuery() to resolve lazy relationships */ @@ -867,7 +872,7 @@ declare class Scalar implements Mergeable { date: number; expiresAt: number; }; - normalize(input: any, parent: any, key: any, args: any[], visit: Visit, delegate: INormalizeDelegate, parentEntity: any): any; + normalize(input: any, parent: any, key: any, delegate: INormalizeDelegate, parentEntity: any): any; denormalize(input: any, delegate: IDenormalizeDelegate): any; /** * Returns the cpks of cells matching the current lens, or undefined. @@ -952,7 +957,7 @@ interface CollectionInterface any, delegate: INormalizeDelegate, parentEntity?: any): string; + normalize(input: any, parent: Parent, key: string, delegate: INormalizeDelegate, parentEntity?: any): string; /** Creates new instance copying over defined values of arguments * * @see https://dataclient.io/docs/api/Collection#merge @@ -1000,7 +1005,7 @@ interface CollectionInterface any | undefined; - denormalize(input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): ReturnType; + denormalize(input: any, delegate: IDenormalizeDelegate): ReturnType; _denormalizeNullable(): ReturnType; _normalizeNullable(): ReturnType; /** Schema to place at the *end* of this Collection @@ -1035,6 +1040,7 @@ interface CollectionConstructor { } type StrategyFunction = (value: any, parent: any, key: string) => T; type SchemaFunction = (value: Args, parent: any, key: string) => K; +type SchemaAttribute = K | SchemaFunction; type MergeFunction = (entityA: any, entityB: any) => any; type SchemaAttributeFunction = (value: any, parent: any, key: string) => S; type UnionResult = { @@ -1067,18 +1073,14 @@ declare class Array$1 implements SchemaClass { input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): - | (S extends EntityMap ? UnionResult : Normalize)[] - | undefined; + (S extends EntityMap ? UnionResult : Normalize)[] | undefined; _denormalizeNullable(): - | (S extends EntityMap ? T : Denormalize)[] - | undefined; + (S extends EntityMap ? T : Denormalize)[] | undefined; denormalize( input: {}, @@ -1121,18 +1123,14 @@ declare class All< input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): - | (S extends EntityMap ? UnionResult : Normalize)[] - | undefined; + (S extends EntityMap ? UnionResult : Normalize)[] | undefined; _denormalizeNullable(): - | (S extends EntityMap ? T : Denormalize)[] - | undefined; + (S extends EntityMap ? T : Denormalize)[] | undefined; denormalize( input: {}, @@ -1165,8 +1163,6 @@ declare class Object$1< input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): NormalizeObject; @@ -1236,8 +1232,8 @@ interface UnionConstructor { */ interface UnionInstance< Choices extends EntityMap = any, - Args extends EntityFields> = - EntityFields>, + // unconstrained: TypeScript 4.2-4.4 can't prove UnionConstructor's Args satisfy EntityFields + Args = EntityFields>, > { readonly _hoistable: true; define(definition: Schema): void; @@ -1249,16 +1245,13 @@ interface UnionInstance< input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): UnionResult; _normalizeNullable(): UnionResult | undefined; _denormalizeNullable(): - | AbstractInstanceType - | undefined; + AbstractInstanceType | undefined; denormalize( input: {}, @@ -1320,8 +1313,6 @@ declare class Values implements SchemaClass { input: any, parent: any, key: any, - args: any[], - visit: (...args: any) => any, delegate: INormalizeDelegate, ): Record< string, @@ -1401,6 +1392,7 @@ type schema_d_Query = SchemaAttribute; type schema_d_SchemaAttributeFunction = SchemaAttributeFunction; type schema_d_SchemaClass = SchemaClass; type schema_d_SchemaFunction = SchemaFunction; @@ -1410,15 +1402,14 @@ type schema_d_Union> = Union; declare const schema_d_Union: typeof Union; type schema_d_UnionConstructor = UnionConstructor; -type schema_d_UnionInstance> = - EntityFields>> = UnionInstance; +type schema_d_UnionInstance>> = UnionInstance; type schema_d_UnionResult = UnionResult; declare const schema_d_UnionRoot: typeof UnionRoot; type schema_d_Values = Values; declare const schema_d_Values: typeof Values; declare const schema_d_unshift: typeof unshift; declare namespace schema_d { - export { schema_d_All as All, Array$1 as Array, schema_d_Collection as Collection, type schema_d_CollectionArrayAdder as CollectionArrayAdder, type schema_d_CollectionArrayOrValuesAdder as CollectionArrayOrValuesAdder, type schema_d_CollectionConstructor as CollectionConstructor, type schema_d_CollectionFromSchema as CollectionFromSchema, type schema_d_CollectionInterface as CollectionInterface, schema_d_CollectionRoot as CollectionRoot, type schema_d_CollectionValuesAdder as CollectionValuesAdder, type schema_d_DefaultArgs as DefaultArgs, EntityMixin as Entity, type schema_d_EntityInterface as EntityInterface, type schema_d_EntityMap as EntityMap, schema_d_EntityMixin as EntityMixin, schema_d_Invalidate as Invalidate, schema_d_Lazy as Lazy, type schema_d_MergeFunction as MergeFunction, Object$1 as Object, schema_d_Query as Query, schema_d_Scalar as Scalar, type schema_d_SchemaAttributeFunction as SchemaAttributeFunction, type schema_d_SchemaClass as SchemaClass, type schema_d_SchemaFunction as SchemaFunction, type schema_d_StrategyFunction as StrategyFunction, schema_d_Union as Union, type schema_d_UnionConstructor as UnionConstructor, type schema_d_UnionInstance as UnionInstance, type schema_d_UnionResult as UnionResult, schema_d_UnionRoot as UnionRoot, schema_d_Values as Values, schema_d_unshift as unshift }; + export { schema_d_All as All, Array$1 as Array, schema_d_Collection as Collection, type schema_d_CollectionArrayAdder as CollectionArrayAdder, type schema_d_CollectionArrayOrValuesAdder as CollectionArrayOrValuesAdder, type schema_d_CollectionConstructor as CollectionConstructor, type schema_d_CollectionFromSchema as CollectionFromSchema, type schema_d_CollectionInterface as CollectionInterface, schema_d_CollectionRoot as CollectionRoot, type schema_d_CollectionValuesAdder as CollectionValuesAdder, type schema_d_DefaultArgs as DefaultArgs, EntityMixin as Entity, type schema_d_EntityInterface as EntityInterface, type schema_d_EntityMap as EntityMap, schema_d_EntityMixin as EntityMixin, schema_d_Invalidate as Invalidate, schema_d_Lazy as Lazy, type schema_d_MergeFunction as MergeFunction, Object$1 as Object, schema_d_Query as Query, schema_d_Scalar as Scalar, type schema_d_SchemaAttribute as SchemaAttribute, type schema_d_SchemaAttributeFunction as SchemaAttributeFunction, type schema_d_SchemaClass as SchemaClass, type schema_d_SchemaFunction as SchemaFunction, type schema_d_StrategyFunction as StrategyFunction, schema_d_Union as Union, type schema_d_UnionConstructor as UnionConstructor, type schema_d_UnionInstance as UnionInstance, type schema_d_UnionResult as UnionResult, schema_d_UnionRoot as UnionRoot, schema_d_Values as Values, schema_d_unshift as unshift }; } declare const Entity_base: IEntityClass { @@ -1455,7 +1446,9 @@ declare abstract class Entity extends Entity_base { * @param [key] When normalizing, the key where this entity was found * @param [args] ...args sent to Endpoint */ - static pk: (this: T, value: Partial>, parent?: any, key?: string, args?: any[]) => string | number | undefined; + static pk: { + pk(this: T, value: Partial>, parent?: any, key?: string, args?: readonly any[]): string | number | undefined; + }['pk']; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#process @@ -1630,7 +1623,7 @@ interface RestInstance : undefined : undefined; + } & O, Extract> : undefined : undefined; /** Create a new item (POST) and `push` to the end * @see https://dataclient.io/rest/api/RestEndpoint#push */