From a50fc6fc0c68e2f5ff81ce27949f51fbd1daeff1 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Thu, 10 Apr 2025 15:07:37 +0100 Subject: [PATCH 1/9] enhance: ImmutableJS support moved to /immutable export --- packages/core/src/controller/Controller.ts | 2 - .../src/schemas/__tests__/All.test.ts | 23 +- .../src/schemas/__tests__/Query.test.ts | 29 +- packages/normalizr/package.json | 4 + packages/normalizr/src/__tests__/MemoCache.ts | 29 +- .../normalizr/src/denormalize/denormalize.ts | 2 - packages/normalizr/src/denormalize/unvisit.ts | 10 +- packages/normalizr/src/immutable.ts | 1 + packages/normalizr/src/index.ts | 1 + packages/normalizr/src/interface.ts | 14 + .../normalizr/src/memo/Delegate.immutable.ts | 7 +- packages/normalizr/src/memo/Delegate.ts | 13 +- packages/normalizr/src/memo/MemoCache.ts | 32 +- .../editor-types/@data-client/core.d.ts | 249 ++++++------- .../editor-types/@data-client/normalizr.d.ts | 327 ++++++++++-------- 15 files changed, 412 insertions(+), 331 deletions(-) create mode 100644 packages/normalizr/src/immutable.ts diff --git a/packages/core/src/controller/Controller.ts b/packages/core/src/controller/Controller.ts index abd48df7fca3..71b03a7b6a26 100644 --- a/packages/core/src/controller/Controller.ts +++ b/packages/core/src/controller/Controller.ts @@ -544,8 +544,6 @@ export default class Controller< }; } - // second argument is false if any entities are missing - const { data, paths } = this.memo.denormalize( schema, input, diff --git a/packages/endpoint/src/schemas/__tests__/All.test.ts b/packages/endpoint/src/schemas/__tests__/All.test.ts index 54955301c2de..5944886f3a1b 100644 --- a/packages/endpoint/src/schemas/__tests__/All.test.ts +++ b/packages/endpoint/src/schemas/__tests__/All.test.ts @@ -5,7 +5,9 @@ import { MemoCache, denormalize, INVALID, + BaseDelegate, } from '@data-client/normalizr'; +import { DelegateImmutable } from '@data-client/normalizr/immutable'; import { IDEntity } from '__tests__/new'; import { schema } from '../..'; @@ -101,15 +103,16 @@ describe.each([[]])(`${schema.All.name} normalization (%s)`, () => { }); describe.each([ - ['direct', (data: T) => data, (data: T) => data], + ['direct', (data: T) => data, (data: T) => data, BaseDelegate], [ 'immutable', fromJSState, (v: any) => (typeof v?.toJS === 'function' ? v.toJS() : v), + DelegateImmutable, ], ])( `${schema.Array.name} denormalization (%s)`, - (_, createInput, createOutput) => { + (_, createInput, createOutput, Delegate) => { test('denormalizes a single entity', () => { class Cat extends IDEntity {} const state: State = createInput({ @@ -123,7 +126,9 @@ describe.each([ indexes: {}, }) as any; const sch = new schema.All(Cat); - expect(new Controller().get(sch, state)).toMatchSnapshot(); + expect( + new Controller({ memo: new MemoCache(Delegate) }).get(sch, state), + ).toMatchSnapshot(); }); test('denormalizes nested in object', () => { @@ -140,7 +145,7 @@ describe.each([ }); // use memocache because we don't support 'object' schemas in controller yet expect( - new MemoCache().query(catSchema, [], state).data, + new MemoCache(Delegate).query(catSchema, [], state).data, ).toMatchSnapshot(); }); @@ -156,7 +161,7 @@ describe.each([ }, indexes: {}, }); - const value = new MemoCache().query(catSchema, [], state).data; + const value = new MemoCache(Delegate).query(catSchema, [], state).data; expect(value).not.toEqual(expect.any(Symbol)); if (typeof value === 'symbol' || value === undefined) return; expect(createOutput(value.results)).toMatchSnapshot(); @@ -177,7 +182,7 @@ describe.each([ }, indexes: {}, }); - const value = new MemoCache().query(catSchema, [], state).data; + const value = new MemoCache(Delegate).query(catSchema, [], state).data; expect(value).not.toEqual(expect.any(Symbol)); if (typeof value === 'symbol' || value === undefined) return; expect(createOutput(value.results).length).toBe(2); @@ -244,7 +249,7 @@ describe.each([ }, indexes: {}, }); - const value = new MemoCache().query(catSchema, [], state).data; + const value = new MemoCache(Delegate).query(catSchema, [], state).data; expect(createOutput(value)).toEqual(expect.any(Symbol)); }); @@ -276,7 +281,7 @@ describe.each([ }, indexes: {}, }); - const value = new MemoCache().query(listSchema, [], state).data; + const value = new MemoCache(Delegate).query(listSchema, [], state).data; expect(createOutput(value)).toEqual(expect.any(Symbol)); }); @@ -339,7 +344,7 @@ describe.each([ }, indexes: {}, }); - const value = new MemoCache().query(listSchema, [], state).data; + const value = new MemoCache(Delegate).query(listSchema, [], state).data; expect(value).not.toEqual(expect.any(Symbol)); if (typeof value === 'symbol') return; expect(value).toMatchSnapshot(); diff --git a/packages/endpoint/src/schemas/__tests__/Query.test.ts b/packages/endpoint/src/schemas/__tests__/Query.test.ts index 583ac302123c..58ad1190d279 100644 --- a/packages/endpoint/src/schemas/__tests__/Query.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Query.test.ts @@ -1,5 +1,6 @@ // eslint-env jest -import { MemoCache } from '@data-client/normalizr'; +import { MemoCache, BaseDelegate } from '@data-client/normalizr'; +import { DelegateImmutable } from '@data-client/normalizr/immutable'; import { useQuery, useSuspense, __INTERNAL__ } from '@data-client/react'; import { RestEndpoint } from '@data-client/rest'; import { IDEntity } from '__tests__/new'; @@ -26,13 +27,14 @@ class User extends IDEntity { } describe.each([ - ['direct', (data: T) => data, (data: T) => data], + ['direct', (data: T) => data, (data: T) => data, BaseDelegate], [ 'immutable', fromJSState, (v: any) => (typeof v?.toJS === 'function' ? v.toJS() : v), + DelegateImmutable, ], -])(`input (%s)`, (_, createInput, createOutput) => { +])(`input (%s)`, (_, createInput, createOutput, Delegate) => { const SCHEMA_CASES = [ ['All', new schema.Object({ results: new schema.All(User) })], [ @@ -76,7 +78,7 @@ describe.each([ }, }); const users: DenormalizeNullable | symbol = - new MemoCache().query(sortedUsers, [], state).data; + new MemoCache(Delegate).query(sortedUsers, [], state).data; expect(users).not.toEqual(expect.any(Symbol)); if (typeof users === 'symbol') return; expect(users && users[0].name).toBe('Zeta'); @@ -101,12 +103,13 @@ describe.each([ }, }); expect( - new MemoCache().query(sortedUsers, [{ asc: true }], state).data, + new MemoCache(Delegate).query(sortedUsers, [{ asc: true }], state) + .data, ).toMatchSnapshot(); }); test('denormalizes should not be found when no entities are present', () => { - const state = { + const state = createInput({ ...initialState, entities: { DOG: { @@ -114,8 +117,8 @@ describe.each([ 2: { id: '2', name: 'Jake' }, }, }, - }; - const { data } = new MemoCache().query(sortedUsers, [], state); + }); + const { data } = new MemoCache(Delegate).query(sortedUsers, [], state); expect(createOutput(data)).not.toEqual(expect.any(Array)); }); @@ -152,12 +155,16 @@ describe.each([ }); const totalCount: | DenormalizeNullable - | symbol = new MemoCache().query(userCountByAdmin, [], state).data; + | symbol = new MemoCache(Delegate).query( + userCountByAdmin, + [], + state, + ).data; expect(totalCount).toBe(4); const nonAdminCount: | DenormalizeNullable - | symbol = new MemoCache().query( + | symbol = new MemoCache(Delegate).query( userCountByAdmin, [{ isAdmin: false }], state, @@ -165,7 +172,7 @@ describe.each([ expect(nonAdminCount).toBe(3); const adminCount: | DenormalizeNullable - | symbol = new MemoCache().query( + | symbol = new MemoCache(Delegate).query( userCountByAdmin, [{ isAdmin: true }], state, diff --git a/packages/normalizr/package.json b/packages/normalizr/package.json index 2b5a6a76eadb..705b62146d54 100644 --- a/packages/normalizr/package.json +++ b/packages/normalizr/package.json @@ -72,6 +72,10 @@ "react-native": "./lib/index.js", "default": "./lib/index.js" }, + "./immutable": { + "types": "./lib/immutable.d.ts", + "default": "./lib/immutable.js" + }, "./package.json": "./package.json" }, "type": "module", diff --git a/packages/normalizr/src/__tests__/MemoCache.ts b/packages/normalizr/src/__tests__/MemoCache.ts index e654cd3e4af5..13e98285978b 100644 --- a/packages/normalizr/src/__tests__/MemoCache.ts +++ b/packages/normalizr/src/__tests__/MemoCache.ts @@ -10,6 +10,8 @@ import { import { fromJSState } from './immutable.test'; import { IQueryDelegate } from '../interface'; +import { BaseDelegate } from '../memo/Delegate'; +import { DelegateImmutable } from '../memo/Delegate.immutable'; import MemoCache from '../memo/MemoCache'; class IDEntity extends Entity { @@ -1009,13 +1011,9 @@ describe('MemoCache', () => { }); describe.each([ - ['direct', (data: T) => data, (data: T) => data], - [ - 'immutable', - fromJSState, - (v: any) => (typeof v?.toJS === 'function' ? v.toJS() : v), - ], - ])(`query (%s)`, (_, createInput, createOutput) => { + ['direct', (data: T) => data, BaseDelegate], + ['immutable', fromJSState, DelegateImmutable], + ])(`query (%s)`, (_, createInput, Delegate) => { class Cat extends IDEntity { id = '0'; name = ''; @@ -1038,20 +1036,29 @@ describe('MemoCache', () => { }); test('works with indexes', () => { - const m = new MemoCache().query(Cat, [{ username: 'm' }], state).data; + const m = new MemoCache(Delegate).query( + Cat, + [{ username: 'm' }], + state, + ).data; expect(m).toBeDefined(); expect(m).toMatchSnapshot(); expect( - new MemoCache().query(Cat, [{ username: 'doesnotexist' }], state).data, + new MemoCache(Delegate).query( + Cat, + [{ username: 'doesnotexist' }], + state, + ).data, ).toBeUndefined(); }); test('works with pk', () => { - const m = new MemoCache().query(Cat, [{ id: '1' }], state); + const m = new MemoCache(Delegate).query(Cat, [{ id: '1' }], state); expect(m).toBeDefined(); expect(m).toMatchSnapshot(); expect( - new MemoCache().query(Cat, [{ id: 'doesnotexist' }], state).data, + new MemoCache(Delegate).query(Cat, [{ id: 'doesnotexist' }], state) + .data, ).toBeUndefined(); }); }); diff --git a/packages/normalizr/src/denormalize/denormalize.ts b/packages/normalizr/src/denormalize/denormalize.ts index b1813be57cad..9a322c8795cc 100644 --- a/packages/normalizr/src/denormalize/denormalize.ts +++ b/packages/normalizr/src/denormalize/denormalize.ts @@ -2,7 +2,6 @@ import { getEntities } from './getEntities.js'; import LocalCache from './localCache.js'; import getUnvisit from './unvisit.js'; import type { Schema } from '../interface.js'; -import { isImmutable } from '../schemas/ImmutableUtils.js'; import type { DenormalizeNullable } from '../types.js'; import type { INVALID } from './symbol.js'; @@ -21,6 +20,5 @@ export function denormalize( getEntities(entities), new LocalCache(), args, - isImmutable(entities), )(schema, input).data; } diff --git a/packages/normalizr/src/denormalize/unvisit.ts b/packages/normalizr/src/denormalize/unvisit.ts index dafaedfb012b..c5bae15e48d6 100644 --- a/packages/normalizr/src/denormalize/unvisit.ts +++ b/packages/normalizr/src/denormalize/unvisit.ts @@ -12,7 +12,6 @@ const getUnvisitEntity = ( getEntity: GetEntity, cache: Cache, args: readonly any[], - isImmutable: boolean, unvisit: (schema: any, input: any) => any, ) => { return function unvisitEntity( @@ -103,16 +102,9 @@ const getUnvisit = ( getEntity: GetEntity, cache: Cache, args: readonly any[], - isImmutable: boolean, ) => { // we don't inline this as making this function too big inhibits v8's JIT - const unvisitEntity = getUnvisitEntity( - getEntity, - cache, - args, - isImmutable, - unvisit, - ); + const unvisitEntity = getUnvisitEntity(getEntity, cache, args, unvisit); function unvisit(schema: any, input: any): any { if (!schema) return input; diff --git a/packages/normalizr/src/immutable.ts b/packages/normalizr/src/immutable.ts new file mode 100644 index 000000000000..77f0c2809652 --- /dev/null +++ b/packages/normalizr/src/immutable.ts @@ -0,0 +1 @@ +export { DelegateImmutable } from './memo/Delegate.immutable.js'; diff --git a/packages/normalizr/src/index.ts b/packages/normalizr/src/index.ts index c2401c835f32..90c872f79590 100644 --- a/packages/normalizr/src/index.ts +++ b/packages/normalizr/src/index.ts @@ -4,6 +4,7 @@ import WeakDependencyMap from './memo/WeakDependencyMap.js'; import { normalize } from './normalize/normalize.js'; export { default as MemoCache } from './memo/MemoCache.js'; +export { BaseDelegate } from './memo/Delegate.js'; export type { AbstractInstanceType, NormalizeReturnType, diff --git a/packages/normalizr/src/interface.ts b/packages/normalizr/src/interface.ts index ba1e8a0cc624..5c7bfd8e2851 100644 --- a/packages/normalizr/src/interface.ts +++ b/packages/normalizr/src/interface.ts @@ -1,3 +1,6 @@ +import type { QueryPath } from './memo/types.js'; +import type { Dep } from './memo/WeakDependencyMap.js'; + export type Schema = | null | string @@ -123,6 +126,17 @@ export interface IQueryDelegate { INVALID: symbol; } +export interface IBaseDelegate { + entities: any; + indexes: any; + + getEntity(entityKey: string | symbol, pk?: string): any; + getIndex(key: string, field: string): any; + tracked( + schema: any, + ): IQueryDelegate & { readonly dependencies: Dep[] }; +} + /** Helpers during schema.normalize() */ export interface INormalizeDelegate { /** Action meta-data for this normalize call */ diff --git a/packages/normalizr/src/memo/Delegate.immutable.ts b/packages/normalizr/src/memo/Delegate.immutable.ts index 84eacd7a59a8..ece4ee5d034f 100644 --- a/packages/normalizr/src/memo/Delegate.immutable.ts +++ b/packages/normalizr/src/memo/Delegate.immutable.ts @@ -1,4 +1,5 @@ -import { IBaseDelegate, TrackingQueryDelegate } from './Delegate.js'; +import { IBaseDelegate } from '../interface.js'; +import { TrackingQueryDelegate } from './Delegate.js'; import { IndexPath } from './types.js'; type ImmutableJSEntityTable = { @@ -31,6 +32,10 @@ export class DelegateImmutable implements IBaseDelegate { getIndex(key: string, field: string) { return this.indexes.getIn([key, field]); } + + tracked(schema: any) { + return new TrackingQueryDelegateImmutable(this, schema); + } } export class TrackingQueryDelegateImmutable extends TrackingQueryDelegate { diff --git a/packages/normalizr/src/memo/Delegate.ts b/packages/normalizr/src/memo/Delegate.ts index e4fbd3fbd519..2c24250edc7b 100644 --- a/packages/normalizr/src/memo/Delegate.ts +++ b/packages/normalizr/src/memo/Delegate.ts @@ -3,6 +3,7 @@ import type { EntityTable, NormalizedIndex, IQueryDelegate, + IBaseDelegate, } from '../interface.js'; import { QueryPath, IndexPath } from './types.js'; import { INVALID } from '../denormalize/symbol.js'; @@ -15,14 +16,6 @@ export const getDependency = delegate.getIndex(args[0], args[1]) : delegate.getEntity(...(args as [any])); -export interface IBaseDelegate { - entities: any; - indexes: any; - - getEntity(entityKey: string | symbol, pk?: string): any; - getIndex(key: string, field: string): any; -} - export class BaseDelegate implements IBaseDelegate { declare entities: EntityTable; declare indexes: { @@ -56,6 +49,10 @@ export class BaseDelegate implements IBaseDelegate { getIndex(key: string, field: string) { return this.indexes[key]?.[field]; } + + tracked(schema: any) { + return new TrackingQueryDelegate(this, schema); + } } export class TrackingQueryDelegate implements IQueryDelegate { diff --git a/packages/normalizr/src/memo/MemoCache.ts b/packages/normalizr/src/memo/MemoCache.ts index 5568bf87ac29..89f986db2661 100644 --- a/packages/normalizr/src/memo/MemoCache.ts +++ b/packages/normalizr/src/memo/MemoCache.ts @@ -1,29 +1,21 @@ import GlobalCache from './globalCache.js'; import WeakDependencyMap from './WeakDependencyMap.js'; import buildQueryKey from '../buildQueryKey.js'; -import { - DelegateImmutable, - TrackingQueryDelegateImmutable, -} from './Delegate.immutable.js'; import { getEntities } from '../denormalize/getEntities.js'; import getUnvisit from '../denormalize/unvisit.js'; -import type { NormalizedIndex, Schema } from '../interface.js'; +import type { IBaseDelegate, NormalizedIndex, Schema } from '../interface.js'; import { isImmutable } from '../schemas/ImmutableUtils.js'; import type { DenormalizeNullable, EntityPath, NormalizeNullable, } from '../types.js'; -import { - getDependency, - BaseDelegate, - TrackingQueryDelegate, -} from './Delegate.js'; +import { getDependency, BaseDelegate } from './Delegate.js'; import { EndpointsCache, EntityCache } from './types.js'; import { QueryPath } from './types.js'; import type { INVALID } from '../denormalize/symbol.js'; -//TODO: make immutable distinction occur when initilizing MemoCache +type DelegateClass = new (v: { entities: any; indexes: any }) => IBaseDelegate; /** Singleton to store the memoization cache for denormalization methods */ export default class MemoCache { @@ -33,6 +25,11 @@ export default class MemoCache { protected endpoints: EndpointsCache = new WeakDependencyMap(); /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Map> = new Map(); + declare protected Delegate: DelegateClass; + + constructor(Delegate: DelegateClass = BaseDelegate) { + this.Delegate = Delegate; + } /** Compute denormalized form maintaining referential equality for same inputs */ denormalize( @@ -61,7 +58,6 @@ export default class MemoCache { getEntity, new GlobalCache(getEntity, this.entities, this.endpoints), args, - isImmutable(entities), )(schema, input); } @@ -112,12 +108,7 @@ export default class MemoCache { any >; - const imm = isImmutable(state.entities); - - // TODO: remove casting when we split this to immutable vs plain implementations - const baseDelegate = new (imm ? DelegateImmutable : BaseDelegate)( - state as any, - ); + const baseDelegate = new this.Delegate(state); // eslint-disable-next-line prefer-const let [value, paths] = queryCache.get( schema as any, @@ -126,10 +117,7 @@ export default class MemoCache { // paths undefined is the only way to truly tell nothing was found (the value could have actually been undefined) if (!paths) { - const tracked = new ( - imm ? - TrackingQueryDelegateImmutable - : TrackingQueryDelegate)(baseDelegate, schema); + const tracked = baseDelegate.tracked(schema); value = buildQueryKey(tracked)(schema, args); queryCache.set(tracked.dependencies, value); 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 79f0a906d75d..a0ba714d7580 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 @@ -1,3 +1,120 @@ +/** Maps a (ordered) list of dependencies to a value. + * + * Useful as a memoization cache for flat/normalized stores. + * + * All dependencies are only weakly referenced, allowing automatic garbage collection + * when any dependencies are no longer used. + */ +declare class WeakDependencyMap { + private readonly next; + private nextPath; + get(entity: K, getDependency: GetDependency): readonly [undefined, undefined] | readonly [V, Path[]]; + set(dependencies: Dep[], value: V): void; +} +type GetDependency = (lookup: Path) => K | undefined; +interface Dep { + path: Path; + entity: K; +} + +/** Attempts to infer reasonable input type to construct an Entity */ +type EntityFields = { + readonly [K in keyof U as U[K] extends (...args: any) => any ? never : K]?: U[K] extends number ? U[K] | string : U[K] extends string ? U[K] | number : U[K]; +}; + +type SchemaArgs = S extends { + createIfValid: any; + pk: any; + key: string; + prototype: infer U; +} ? [ + EntityFields +] : S extends ({ + queryKey(args: infer Args, ...rest: any): any; +}) ? Args : S extends { + [K: string]: any; +} ? ObjectArgs : never; +type ObjectArgs> = { + [K in keyof S]: S[K] extends Schema ? SchemaArgs : never; +}[keyof S]; + +interface EntityPath { + key: string; + pk: string; +} +type AbstractInstanceType = T extends new (...args: any) => infer U ? U : T extends { + prototype: infer U; +} ? U : never; +type DenormalizeObject> = { + [K in keyof S]: S[K] extends Schema ? Denormalize : S[K]; +}; +type DenormalizeNullableObject> = { + [K in keyof S]: S[K] extends Schema ? DenormalizeNullable : S[K]; +}; +type NormalizeObject> = { + [K in keyof S]: S[K] extends Schema ? Normalize : S[K]; +}; +type NormalizedNullableObject> = { + [K in keyof S]: S[K] extends Schema ? NormalizeNullable : S[K]; +}; +interface NestedSchemaClass { + schema: Record; + prototype: T; +} +interface RecordClass extends NestedSchemaClass { + fromJS: (...args: any) => AbstractInstanceType; +} +type DenormalizeNullableNestedSchema = keyof S['schema'] extends never ? S['prototype'] : string extends keyof S['schema'] ? S['prototype'] : S['prototype']; +type NormalizeReturnType = T extends (...args: any) => infer R ? R : never; +type Denormalize = S extends { + createIfValid: any; + pk: any; + key: string; + prototype: infer U; +} ? U : S extends RecordClass ? AbstractInstanceType : S extends { + denormalize: (...args: any) => any; +} ? ReturnType : S extends Serializable ? T : S extends Array ? Denormalize[] : S extends { + [K: string]: any; +} ? DenormalizeObject : S; +type DenormalizeNullable = S extends ({ + createIfValid: any; + pk: any; + key: string; + prototype: any; + schema: any; +}) ? DenormalizeNullableNestedSchema | undefined : S extends RecordClass ? DenormalizeNullableNestedSchema : S extends { + _denormalizeNullable: (...args: any) => any; +} ? ReturnType : S extends Serializable ? T : S extends Array ? Denormalize[] | undefined : S extends { + [K: string]: any; +} ? DenormalizeNullableObject : S; +type Normalize = S extends { + createIfValid: any; + pk: any; + key: string; + prototype: {}; +} ? string : S extends RecordClass ? NormalizeObject : S extends { + normalize: (...args: any) => any; +} ? NormalizeReturnType : S extends Serializable ? T : S extends Array ? Normalize[] : S extends { + [K: string]: any; +} ? NormalizeObject : S; +type NormalizeNullable = S extends { + createIfValid: any; + pk: any; + key: string; + prototype: {}; +} ? string | undefined : S extends RecordClass ? NormalizedNullableObject : S extends { + _normalizeNullable: (...args: any) => any; +} ? NormalizeReturnType : S extends Serializable ? T : S extends Array ? Normalize[] | undefined : S extends { + [K: string]: any; +} ? NormalizedNullableObject : S; + +interface EntityCache extends Map>>> { +} +type EndpointsCache = WeakDependencyMap; +type IndexPath = [key: string, field: string, value: string]; +type EntitySchemaPath = [key: string] | [key: string, pk: string]; +type QueryPath = IndexPath | EntitySchemaPath; + type Schema = null | string | { [K: string]: any; } | Schema[] | SchemaSimple | Serializable; @@ -68,6 +185,15 @@ interface IQueryDelegate { /** Return to consider results invalid */ INVALID: symbol; } +interface IBaseDelegate { + entities: any; + indexes: any; + getEntity(entityKey: string | symbol, pk?: string): any; + getIndex(key: string, field: string): any; + tracked(schema: any): IQueryDelegate & { + readonly dependencies: Dep[]; + }; +} /** Helpers during schema.normalize() */ interface INormalizeDelegate { /** Action meta-data for this normalize call */ @@ -100,125 +226,12 @@ interface INormalizeDelegate { checkLoop(key: string, pk: string, input: object): boolean; } -/** Attempts to infer reasonable input type to construct an Entity */ -type EntityFields = { - readonly [K in keyof U as U[K] extends (...args: any) => any ? never : K]?: U[K] extends number ? U[K] | string : U[K] extends string ? U[K] | number : U[K]; -}; - -type SchemaArgs = S extends { - createIfValid: any; - pk: any; - key: string; - prototype: infer U; -} ? [ - EntityFields -] : S extends ({ - queryKey(args: infer Args, ...rest: any): any; -}) ? Args : S extends { - [K: string]: any; -} ? ObjectArgs : never; -type ObjectArgs> = { - [K in keyof S]: S[K] extends Schema ? SchemaArgs : never; -}[keyof S]; - -interface EntityPath { - key: string; - pk: string; -} -type AbstractInstanceType = T extends new (...args: any) => infer U ? U : T extends { - prototype: infer U; -} ? U : never; -type DenormalizeObject> = { - [K in keyof S]: S[K] extends Schema ? Denormalize : S[K]; -}; -type DenormalizeNullableObject> = { - [K in keyof S]: S[K] extends Schema ? DenormalizeNullable : S[K]; -}; -type NormalizeObject> = { - [K in keyof S]: S[K] extends Schema ? Normalize : S[K]; -}; -type NormalizedNullableObject> = { - [K in keyof S]: S[K] extends Schema ? NormalizeNullable : S[K]; -}; -interface NestedSchemaClass { - schema: Record; - prototype: T; -} -interface RecordClass extends NestedSchemaClass { - fromJS: (...args: any) => AbstractInstanceType; -} -type DenormalizeNullableNestedSchema = keyof S['schema'] extends never ? S['prototype'] : string extends keyof S['schema'] ? S['prototype'] : S['prototype']; -type NormalizeReturnType = T extends (...args: any) => infer R ? R : never; -type Denormalize = S extends { - createIfValid: any; - pk: any; - key: string; - prototype: infer U; -} ? U : S extends RecordClass ? AbstractInstanceType : S extends { - denormalize: (...args: any) => any; -} ? ReturnType : S extends Serializable ? T : S extends Array ? Denormalize[] : S extends { - [K: string]: any; -} ? DenormalizeObject : S; -type DenormalizeNullable = S extends ({ - createIfValid: any; - pk: any; - key: string; - prototype: any; - schema: any; -}) ? DenormalizeNullableNestedSchema | undefined : S extends RecordClass ? DenormalizeNullableNestedSchema : S extends { - _denormalizeNullable: (...args: any) => any; -} ? ReturnType : S extends Serializable ? T : S extends Array ? Denormalize[] | undefined : S extends { - [K: string]: any; -} ? DenormalizeNullableObject : S; -type Normalize = S extends { - createIfValid: any; - pk: any; - key: string; - prototype: {}; -} ? string : S extends RecordClass ? NormalizeObject : S extends { - normalize: (...args: any) => any; -} ? NormalizeReturnType : S extends Serializable ? T : S extends Array ? Normalize[] : S extends { - [K: string]: any; -} ? NormalizeObject : S; -type NormalizeNullable = S extends { - createIfValid: any; - pk: any; - key: string; - prototype: {}; -} ? string | undefined : S extends RecordClass ? NormalizedNullableObject : S extends { - _normalizeNullable: (...args: any) => any; -} ? NormalizeReturnType : S extends Serializable ? T : S extends Array ? Normalize[] | undefined : S extends { - [K: string]: any; -} ? NormalizedNullableObject : S; - declare const INVALID: unique symbol; -/** Maps a (ordered) list of dependencies to a value. - * - * Useful as a memoization cache for flat/normalized stores. - * - * All dependencies are only weakly referenced, allowing automatic garbage collection - * when any dependencies are no longer used. - */ -declare class WeakDependencyMap { - private readonly next; - private nextPath; - get(entity: K, getDependency: GetDependency): readonly [undefined, undefined] | readonly [V, Path[]]; - set(dependencies: Dep[], value: V): void; -} -type GetDependency = (lookup: Path) => K | undefined; -interface Dep { - path: Path; - entity: K; -} - -interface EntityCache extends Map>>> { -} -type EndpointsCache = WeakDependencyMap; -type IndexPath = [key: string, field: string, value: string]; -type EntitySchemaPath = [key: string] | [key: string, pk: string]; -type QueryPath = IndexPath | EntitySchemaPath; - +type DelegateClass = new (v: { + entities: any; + indexes: any; +}) => IBaseDelegate; /** Singleton to store the memoization cache for denormalization methods */ declare class MemoCache { /** Cache for every entity based on its dependencies and its own input */ @@ -227,6 +240,8 @@ declare class MemoCache { protected endpoints: EndpointsCache; /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Map>; + protected Delegate: DelegateClass; + constructor(Delegate?: DelegateClass); /** Compute denormalized form maintaining referential equality for same inputs */ denormalize(schema: S | undefined, input: unknown, entities: any, args?: readonly any[]): { data: DenormalizeNullable | typeof INVALID; 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 3ab661d43886..a5024ec4688c 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 @@ -1,116 +1,20 @@ -type Schema = null | string | { - [K: string]: any; -} | Schema[] | SchemaSimple | Serializable; -interface Queryable { - queryKey(args: Args, unvisit: (...args: any) => any, delegate: { - getEntity: any; - getIndex: any; - }): {}; -} -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; - }): any; - denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; - queryKey(args: Args, unvisit: (...args: any) => any, delegate: { - getEntity: any; - getIndex: any; - }): any; -} -interface SchemaClass extends SchemaSimple { - _normalizeNullable(): any; - _denormalizeNullable(): any; -} -interface EntityInterface extends SchemaSimple { - createIfValid(props: any): any; - pk(params: any, parent: any, key: string | undefined, args: readonly any[]): string | number | undefined; - readonly key: string; - indexes?: any; - schema: Record; - prototype: T; - cacheWith?: object; -} -interface Mergeable { - key: string; - merge(existing: any, incoming: any): any; - mergeWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; - mergeMetaWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; -} -interface NormalizedIndex { - readonly [entityKey: string]: { - readonly [indexName: string]: { - readonly [lookup: string]: string; - }; - }; -} -interface EntityTable { - [entityKey: string]: { - [pk: string]: unknown; - } | undefined; -} -/** Visits next data + schema while recurisvely normalizing */ -interface Visit { - (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; -} -/** Returns true if a circular reference is found */ -interface CheckLoop { - (entityKey: string, pk: string, input: object): boolean; -} -/** Get Array of entities with map function applied */ -interface GetEntity { - (entityKey: string | symbol): { - readonly [pk: string]: any; - } | undefined; - (entityKey: string | symbol, pk: string | number): any; -} -/** Get PK using an Entity Index */ -interface GetIndex { - /** getIndex('User', 'username', 'ntucker') */ - (entityKey: string, field: string, value: string): string | undefined; -} -/** Accessors to the currently processing state while building query */ -interface IQueryDelegate { - getEntity: GetEntity; - getIndex: GetIndex; - /** Return to consider results invalid */ - INVALID: symbol; +/** Maps a (ordered) list of dependencies to a value. + * + * Useful as a memoization cache for flat/normalized stores. + * + * All dependencies are only weakly referenced, allowing automatic garbage collection + * when any dependencies are no longer used. + */ +declare class WeakDependencyMap { + private readonly next; + private nextPath; + get(entity: K, getDependency: GetDependency): readonly [undefined, undefined] | readonly [V, Path[]]; + set(dependencies: Dep[], value: V): void; } -/** Helpers during schema.normalize() */ -interface INormalizeDelegate { - /** Action meta-data for this normalize call */ - readonly meta: { - fetchedAt: number; - date: number; - expiresAt: number; - }; - /** Gets any previously normalized entity from store */ - getEntity: GetEntity; - /** Updates an entity using merge lifecycles when it has previously been set */ - mergeEntity(schema: Mergeable & { - indexes?: any; - }, pk: string, incomingEntity: any): void; - /** Sets an entity overwriting any previously set values */ - setEntity(schema: { - key: string; - indexes?: any; - }, pk: string, entity: any, meta?: { - fetchedAt: number; - date: number; - expiresAt: number; - }): void; - /** Invalidates an entity, potentially triggering suspense */ - invalidate(schema: { - key: string; - indexes?: any; - }, pk: string): void; - /** Returns true when we're in a cycle, so we should not continue recursing */ - checkLoop(key: string, pk: string, input: object): boolean; +type GetDependency = (lookup: Path) => K | undefined; +interface Dep { + path: Path; + entity: K; } /** Attempts to infer reasonable input type to construct an Entity */ @@ -225,40 +129,149 @@ interface NormalizeMeta { fetchedAt: number; } +interface EntityCache extends Map>>> { +} +type EndpointsCache = WeakDependencyMap; +type IndexPath = [key: string, field: string, value: string]; +type EntitySchemaPath = [key: string] | [key: string, pk: string]; +type QueryPath = IndexPath | EntitySchemaPath; + +type Schema = null | string | { + [K: string]: any; +} | Schema[] | SchemaSimple | Serializable; +interface Queryable { + queryKey(args: Args, unvisit: (...args: any) => any, delegate: { + getEntity: any; + getIndex: any; + }): {}; +} +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; + }): any; + denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; + queryKey(args: Args, unvisit: (...args: any) => any, delegate: { + getEntity: any; + getIndex: any; + }): any; +} +interface SchemaClass extends SchemaSimple { + _normalizeNullable(): any; + _denormalizeNullable(): any; +} +interface EntityInterface extends SchemaSimple { + createIfValid(props: any): any; + pk(params: any, parent: any, key: string | undefined, args: readonly any[]): string | number | undefined; + readonly key: string; + indexes?: any; + schema: Record; + prototype: T; + cacheWith?: object; +} +interface Mergeable { + key: string; + merge(existing: any, incoming: any): any; + mergeWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; + mergeMetaWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; +} +interface NormalizedIndex { + readonly [entityKey: string]: { + readonly [indexName: string]: { + readonly [lookup: string]: string; + }; + }; +} +interface EntityTable { + [entityKey: string]: { + [pk: string]: unknown; + } | undefined; +} +/** Visits next data + schema while recurisvely normalizing */ +interface Visit { + (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; +} +/** Returns true if a circular reference is found */ +interface CheckLoop { + (entityKey: string, pk: string, input: object): boolean; +} +/** Get Array of entities with map function applied */ +interface GetEntity { + (entityKey: string | symbol): { + readonly [pk: string]: any; + } | undefined; + (entityKey: string | symbol, pk: string | number): any; +} +/** Get PK using an Entity Index */ +interface GetIndex { + /** getIndex('User', 'username', 'ntucker') */ + (entityKey: string, field: string, value: string): string | undefined; +} +/** Accessors to the currently processing state while building query */ +interface IQueryDelegate { + getEntity: GetEntity; + getIndex: GetIndex; + /** Return to consider results invalid */ + INVALID: symbol; +} +interface IBaseDelegate { + entities: any; + indexes: any; + getEntity(entityKey: string | symbol, pk?: string): any; + getIndex(key: string, field: string): any; + tracked(schema: any): IQueryDelegate & { + readonly dependencies: Dep[]; + }; +} +/** Helpers during schema.normalize() */ +interface INormalizeDelegate { + /** Action meta-data for this normalize call */ + readonly meta: { + fetchedAt: number; + date: number; + expiresAt: number; + }; + /** Gets any previously normalized entity from store */ + getEntity: GetEntity; + /** Updates an entity using merge lifecycles when it has previously been set */ + mergeEntity(schema: Mergeable & { + indexes?: any; + }, pk: string, incomingEntity: any): void; + /** Sets an entity overwriting any previously set values */ + setEntity(schema: { + key: string; + indexes?: any; + }, pk: string, entity: any, meta?: { + fetchedAt: number; + date: number; + expiresAt: number; + }): void; + /** Invalidates an entity, potentially triggering suspense */ + invalidate(schema: { + key: string; + indexes?: any; + }, pk: string): void; + /** Returns true when we're in a cycle, so we should not continue recursing */ + checkLoop(key: string, pk: string, input: object): boolean; +} + declare const INVALID: unique symbol; declare function denormalize(schema: S | undefined, input: any, entities: any, args?: readonly any[]): DenormalizeNullable | typeof INVALID; declare function isEntity(schema: Schema): schema is EntityInterface; -/** Maps a (ordered) list of dependencies to a value. - * - * Useful as a memoization cache for flat/normalized stores. - * - * All dependencies are only weakly referenced, allowing automatic garbage collection - * when any dependencies are no longer used. - */ -declare class WeakDependencyMap { - private readonly next; - private nextPath; - get(entity: K, getDependency: GetDependency): readonly [undefined, undefined] | readonly [V, Path[]]; - set(dependencies: Dep[], value: V): void; -} -type GetDependency = (lookup: Path) => K | undefined; -interface Dep { - path: Path; - entity: K; -} - declare const normalize: | undefined> = Record>, R = NormalizeNullable>(schema: S | undefined, input: any, args?: readonly any[], { entities, indexes, entitiesMeta }?: StoreData, meta?: NormalizeMeta) => NormalizedSchema; -interface EntityCache extends Map>>> { -} -type EndpointsCache = WeakDependencyMap; -type IndexPath = [key: string, field: string, value: string]; -type EntitySchemaPath = [key: string] | [key: string, pk: string]; -type QueryPath = IndexPath | EntitySchemaPath; - +type DelegateClass = new (v: { + entities: any; + indexes: any; +}) => IBaseDelegate; /** Singleton to store the memoization cache for denormalization methods */ declare class MemoCache { /** Cache for every entity based on its dependencies and its own input */ @@ -267,6 +280,8 @@ declare class MemoCache { protected endpoints: EndpointsCache; /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Map>; + protected Delegate: DelegateClass; + constructor(Delegate?: DelegateClass); /** Compute denormalized form maintaining referential equality for same inputs */ denormalize(schema: S | undefined, input: unknown, entities: any, args?: readonly any[]): { data: DenormalizeNullable | typeof INVALID; @@ -288,6 +303,40 @@ type StateInterface = { }; }; +declare class BaseDelegate implements IBaseDelegate { + entities: EntityTable; + indexes: { + [entityKey: string]: { + [indexName: string]: { + [lookup: string]: string; + }; + }; + }; + constructor({ entities, indexes, }: { + entities: EntityTable; + indexes: NormalizedIndex; + }); + getEntity(entityKey: string | symbol): { + readonly [pk: string]: any; + } | undefined; + getEntity(entityKey: string | symbol, pk: string | number): any; + getIndex(key: string, field: string): { + [lookup: string]: string; + }; + tracked(schema: any): TrackingQueryDelegate; +} +declare class TrackingQueryDelegate implements IQueryDelegate { + readonly INVALID: symbol; + protected snap: IBaseDelegate; + readonly dependencies: Dep[]; + constructor(snap: IBaseDelegate, schema: any); + getIndex(...path: IndexPath): string | undefined; + getEntity(entityKey: string | symbol): { + readonly [pk: string]: any; + } | undefined; + getEntity(entityKey: string | symbol, pk: string | number): any; +} + /** https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#the-noinfer-utility-type */ type NI = NoInfer; @@ -402,4 +451,4 @@ type FetchFunction = (...args: A) => Pr declare function validateQueryKey(queryKey: unknown): boolean; -export { type AbstractInstanceType, type ArrayElement, type CheckLoop, type Denormalize, type DenormalizeNullable, type EndpointExtraOptions, type EndpointInterface, type EntityInterface, type EntityPath, type EntityTable, type ErrorTypes, ExpiryStatus, type ExpiryStatusInterface, type FetchFunction, type GetEntity, type GetIndex, INVALID, type INormalizeDelegate, type IQueryDelegate, type IndexInterface, type IndexParams, type InferReturn, MemoCache, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeReturnType, type NormalizedIndex, type NormalizedSchema, type OptimisticUpdateParams, type Queryable, type ReadEndpoint, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type UpdateFunction, type Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; +export { type AbstractInstanceType, type ArrayElement, BaseDelegate, type CheckLoop, type Denormalize, type DenormalizeNullable, type EndpointExtraOptions, type EndpointInterface, type EntityInterface, type EntityPath, type EntityTable, type ErrorTypes, ExpiryStatus, type ExpiryStatusInterface, type FetchFunction, type GetEntity, type GetIndex, type IBaseDelegate, INVALID, type INormalizeDelegate, type IQueryDelegate, type IndexInterface, type IndexParams, type InferReturn, MemoCache, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeReturnType, type NormalizedIndex, type NormalizedSchema, type OptimisticUpdateParams, type Queryable, type ReadEndpoint, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type UpdateFunction, type Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; From b72ec2a40e2be19d683efa43e22d9b6285a2676e Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Sat, 12 Apr 2025 16:09:30 +0100 Subject: [PATCH 2/9] enhance: Use {} style args for delegates - just like denormalize.getEntity --- packages/endpoint/src/index.ts | 16 +- packages/endpoint/src/interface.ts | 27 +- packages/endpoint/src/schemas/All.ts | 4 +- packages/endpoint/src/schemas/Collection.ts | 6 +- packages/endpoint/src/schemas/EntityMixin.ts | 12 +- .../src/schemas/__tests__/All.test.ts | 20 +- .../src/schemas/__tests__/Query.test.ts | 22 +- packages/normalizr/src/__tests__/MemoCache.ts | 20 +- .../src/__tests__/WeakDependencyMap.test.ts | 2 +- .../src/__tests__/normalizerMerge.test.tsx | 2 +- packages/normalizr/src/denormalize/UNDEF.ts | 2 +- packages/normalizr/src/denormalize/cache.ts | 3 +- .../normalizr/src/denormalize/getEntities.ts | 4 +- .../normalizr/src/denormalize/localCache.ts | 3 +- packages/normalizr/src/denormalize/unvisit.ts | 3 +- packages/normalizr/src/index.ts | 4 +- packages/normalizr/src/interface.ts | 42 ++- packages/normalizr/src/memo/BaseDelegate.ts | 58 +++ .../normalizr/src/memo/Delegate.immutable.ts | 43 +-- packages/normalizr/src/memo/Delegate.ts | 79 +--- packages/normalizr/src/memo/MemoCache.ts | 30 +- .../normalizr/src/memo/WeakDependencyMap.ts | 4 +- packages/normalizr/src/memo/globalCache.ts | 3 +- packages/normalizr/src/memo/types.ts | 7 +- .../src/normalize/NormalizeDelegate.ts | 6 +- packages/normalizr/src/types.ts | 5 - .../editor-types/@data-client/core.d.ts | 284 +++++++------- .../editor-types/@data-client/endpoint.d.ts | 51 ++- .../editor-types/@data-client/graphql.d.ts | 51 ++- .../editor-types/@data-client/normalizr.d.ts | 353 +++++++++--------- .../editor-types/@data-client/rest.d.ts | 51 ++- .../Playground/editor-types/globals.d.ts | 51 ++- 32 files changed, 727 insertions(+), 541 deletions(-) create mode 100644 packages/normalizr/src/memo/BaseDelegate.ts diff --git a/packages/endpoint/src/index.ts b/packages/endpoint/src/index.ts index 5801d24d489c..9b280194a56f 100644 --- a/packages/endpoint/src/index.ts +++ b/packages/endpoint/src/index.ts @@ -17,21 +17,7 @@ export { default as Entity } from './schemas/Entity.js'; export { default as EntityMixin } from './schemas/EntityMixin.js'; export type { IEntityClass, IEntityInstance } from './schemas/EntityTypes.js'; export { default as validateRequired } from './validateRequired.js'; -export type { - EndpointInterface, - ReadEndpoint, - MutateEndpoint, - Schema, - IQueryDelegate, - INormalizeDelegate, - SnapshotInterface, - ExpiryStatusInterface, - SchemaSimple, - SchemaClass, - PolymorphicInterface, - Queryable, - Mergeable, -} from './interface.js'; +export * from './interface.js'; export type { EntityFields } from './schemas/EntityFields.js'; export type { AbstractInstanceType, diff --git a/packages/endpoint/src/interface.ts b/packages/endpoint/src/interface.ts index 95c45abeba4f..e2e895917f89 100644 --- a/packages/endpoint/src/interface.ts +++ b/packages/endpoint/src/interface.ts @@ -113,24 +113,41 @@ export interface Visit { creating?: boolean; } +export interface EntityPath { + key: string; + pk: string; +} +export interface IndexPath { + key: string; + field: string; + value: string; +} +export interface EntitiesPath { + key: string; +} + /** Returns true if a circular reference is found */ export interface CheckLoop { (entityKey: string, pk: string, input: object): boolean; } -/** Get Array of entities with map function applied */ +/** Get all normalized entities of one type from store */ +export interface GetEntities { + (path: EntitiesPath): { readonly [pk: string]: any } | undefined; +} +/** Get normalized Entity from store */ export interface GetEntity { - (entityKey: string | symbol): { readonly [pk: string]: any } | undefined; - (entityKey: string | symbol, pk: string | number): any; + (path: EntityPath): any; } /** Get PK using an Entity Index */ export interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (entityKey: string, field: string, value: string): string | undefined; + (path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ export interface IQueryDelegate { + getEntities: GetEntities; getEntity: GetEntity; getIndex: GetIndex; /** Return to consider results invalid */ @@ -141,6 +158,8 @@ export interface IQueryDelegate { export interface INormalizeDelegate { /** Action meta-data for this normalize call */ readonly meta: { fetchedAt: number; date: number; expiresAt: number }; + /** Get all normalized entities of one type from store */ + getEntities: GetEntities; /** Gets any previously normalized entity from store */ getEntity: GetEntity; /** Updates an entity using merge lifecycles when it has previously been set */ diff --git a/packages/endpoint/src/schemas/All.ts b/packages/endpoint/src/schemas/All.ts index 214f9c0ce66f..945f7af7363b 100644 --- a/packages/endpoint/src/schemas/All.ts +++ b/packages/endpoint/src/schemas/All.ts @@ -26,7 +26,7 @@ export default class AllSchema< queryKey(args: any, unvisit: any, delegate: IQueryDelegate): any { if (this.isSingleSchema) { - const entitiesEntry = delegate.getEntity(this.schema.key); + const entitiesEntry = delegate.getEntities({ key: this.schema.key }); // we must wait until there are entries for any 'All' query to be Valid if (entitiesEntry === undefined) return delegate.INVALID; return Object.values(entitiesEntry).map( @@ -36,7 +36,7 @@ export default class AllSchema< let found = false; const list = Object.values(this.schema as Record).flatMap( (schema: EntityInterface) => { - const entitiesEntry = delegate.getEntity(schema.key); + const entitiesEntry = delegate.getEntities({ key: schema.key }); if (entitiesEntry === undefined) return []; found = true; return Object.entries(entitiesEntry).map(([key, entity]) => ({ diff --git a/packages/endpoint/src/schemas/Collection.ts b/packages/endpoint/src/schemas/Collection.ts index 30ebabd9a8a3..a57f651ecb75 100644 --- a/packages/endpoint/src/schemas/Collection.ts +++ b/packages/endpoint/src/schemas/Collection.ts @@ -219,9 +219,9 @@ export default class CollectionSchema< queryKey(args: Args, unvisit: unknown, delegate: IQueryDelegate): any { if (this.argsKey) { - const id = this.pk(undefined, undefined, '', args); + const pk = this.pk(undefined, undefined, '', args); // ensure this actually has entity or we shouldn't try to use it in our query - if (delegate.getEntity(this.key, id)) return id; + if (delegate.getEntity({ key: this.key, pk })) return pk; } } @@ -326,7 +326,7 @@ function normalizeCreate( // parent is args when not nested const filterCollections = (this.createCollectionFilter as any)(...args); // add to any collections that match this - const entities = delegate.getEntity(this.key); + const entities = delegate.getEntities({ key: this.key }); if (entities) Object.keys(entities).forEach(collectionPk => { if (!filterCollections(JSON.parse(collectionPk))) return; diff --git a/packages/endpoint/src/schemas/EntityMixin.ts b/packages/endpoint/src/schemas/EntityMixin.ts index 41d0db022002..fde6d3b9d886 100644 --- a/packages/endpoint/src/schemas/EntityMixin.ts +++ b/packages/endpoint/src/schemas/EntityMixin.ts @@ -327,9 +327,9 @@ export default function EntityMixin( delegate: IQueryDelegate, ): any { if (!args[0]) return; - const id = queryKeyCandidate(this, args, delegate); + const pk = queryKeyCandidate(this, args, delegate); // ensure this actually has entity or we shouldn't try to use it in our query - if (id && delegate.getEntity(this.key, id)) return id; + if (pk && delegate.getEntity({ key: this.key, pk })) return pk; } static denormalize( @@ -484,8 +484,8 @@ function queryKeyCandidate( // Was able to infer the entity's primary key from params if (id !== undefined && id !== '') return id; // now attempt lookup in indexes - const indexName = indexFromParams(args[0], schema.indexes); - if (!indexName) return; - const value = (args[0] as Record)[indexName]; - return delegate.getIndex(schema.key, indexName, value); + const field = indexFromParams(args[0], schema.indexes); + if (!field) return; + const value = (args[0] as Record)[field]; + return delegate.getIndex({ key: schema.key, field, value }); } diff --git a/packages/endpoint/src/schemas/__tests__/All.test.ts b/packages/endpoint/src/schemas/__tests__/All.test.ts index 5944886f3a1b..85a1907088d2 100644 --- a/packages/endpoint/src/schemas/__tests__/All.test.ts +++ b/packages/endpoint/src/schemas/__tests__/All.test.ts @@ -5,7 +5,7 @@ import { MemoCache, denormalize, INVALID, - BaseDelegate, + Delegate, } from '@data-client/normalizr'; import { DelegateImmutable } from '@data-client/normalizr/immutable'; import { IDEntity } from '__tests__/new'; @@ -103,7 +103,7 @@ describe.each([[]])(`${schema.All.name} normalization (%s)`, () => { }); describe.each([ - ['direct', (data: T) => data, (data: T) => data, BaseDelegate], + ['direct', (data: T) => data, (data: T) => data, Delegate], [ 'immutable', fromJSState, @@ -112,7 +112,7 @@ describe.each([ ], ])( `${schema.Array.name} denormalization (%s)`, - (_, createInput, createOutput, Delegate) => { + (_, createInput, createOutput, MyDelegate) => { test('denormalizes a single entity', () => { class Cat extends IDEntity {} const state: State = createInput({ @@ -127,7 +127,7 @@ describe.each([ }) as any; const sch = new schema.All(Cat); expect( - new Controller({ memo: new MemoCache(Delegate) }).get(sch, state), + new Controller({ memo: new MemoCache(MyDelegate) }).get(sch, state), ).toMatchSnapshot(); }); @@ -145,7 +145,7 @@ describe.each([ }); // use memocache because we don't support 'object' schemas in controller yet expect( - new MemoCache(Delegate).query(catSchema, [], state).data, + new MemoCache(MyDelegate).query(catSchema, [], state).data, ).toMatchSnapshot(); }); @@ -161,7 +161,7 @@ describe.each([ }, indexes: {}, }); - const value = new MemoCache(Delegate).query(catSchema, [], state).data; + const value = new MemoCache(MyDelegate).query(catSchema, [], state).data; expect(value).not.toEqual(expect.any(Symbol)); if (typeof value === 'symbol' || value === undefined) return; expect(createOutput(value.results)).toMatchSnapshot(); @@ -182,7 +182,7 @@ describe.each([ }, indexes: {}, }); - const value = new MemoCache(Delegate).query(catSchema, [], state).data; + const value = new MemoCache(MyDelegate).query(catSchema, [], state).data; expect(value).not.toEqual(expect.any(Symbol)); if (typeof value === 'symbol' || value === undefined) return; expect(createOutput(value.results).length).toBe(2); @@ -249,7 +249,7 @@ describe.each([ }, indexes: {}, }); - const value = new MemoCache(Delegate).query(catSchema, [], state).data; + const value = new MemoCache(MyDelegate).query(catSchema, [], state).data; expect(createOutput(value)).toEqual(expect.any(Symbol)); }); @@ -281,7 +281,7 @@ describe.each([ }, indexes: {}, }); - const value = new MemoCache(Delegate).query(listSchema, [], state).data; + const value = new MemoCache(MyDelegate).query(listSchema, [], state).data; expect(createOutput(value)).toEqual(expect.any(Symbol)); }); @@ -344,7 +344,7 @@ describe.each([ }, indexes: {}, }); - const value = new MemoCache(Delegate).query(listSchema, [], state).data; + const value = new MemoCache(MyDelegate).query(listSchema, [], state).data; expect(value).not.toEqual(expect.any(Symbol)); if (typeof value === 'symbol') return; expect(value).toMatchSnapshot(); diff --git a/packages/endpoint/src/schemas/__tests__/Query.test.ts b/packages/endpoint/src/schemas/__tests__/Query.test.ts index 58ad1190d279..4fd520188538 100644 --- a/packages/endpoint/src/schemas/__tests__/Query.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Query.test.ts @@ -1,5 +1,5 @@ // eslint-env jest -import { MemoCache, BaseDelegate } from '@data-client/normalizr'; +import { MemoCache, Delegate } from '@data-client/normalizr'; import { DelegateImmutable } from '@data-client/normalizr/immutable'; import { useQuery, useSuspense, __INTERNAL__ } from '@data-client/react'; import { RestEndpoint } from '@data-client/rest'; @@ -27,14 +27,14 @@ class User extends IDEntity { } describe.each([ - ['direct', (data: T) => data, (data: T) => data, BaseDelegate], + ['direct', (data: T) => data, (data: T) => data, Delegate], [ 'immutable', fromJSState, (v: any) => (typeof v?.toJS === 'function' ? v.toJS() : v), DelegateImmutable, ], -])(`input (%s)`, (_, createInput, createOutput, Delegate) => { +])(`input (%s)`, (_, createInput, createOutput, MyDelegate) => { const SCHEMA_CASES = [ ['All', new schema.Object({ results: new schema.All(User) })], [ @@ -78,7 +78,7 @@ describe.each([ }, }); const users: DenormalizeNullable | symbol = - new MemoCache(Delegate).query(sortedUsers, [], state).data; + new MemoCache(MyDelegate).query(sortedUsers, [], state).data; expect(users).not.toEqual(expect.any(Symbol)); if (typeof users === 'symbol') return; expect(users && users[0].name).toBe('Zeta'); @@ -103,7 +103,7 @@ describe.each([ }, }); expect( - new MemoCache(Delegate).query(sortedUsers, [{ asc: true }], state) + new MemoCache(MyDelegate).query(sortedUsers, [{ asc: true }], state) .data, ).toMatchSnapshot(); }); @@ -118,7 +118,11 @@ describe.each([ }, }, }); - const { data } = new MemoCache(Delegate).query(sortedUsers, [], state); + const { data } = new MemoCache(MyDelegate).query( + sortedUsers, + [], + state, + ); expect(createOutput(data)).not.toEqual(expect.any(Array)); }); @@ -155,7 +159,7 @@ describe.each([ }); const totalCount: | DenormalizeNullable - | symbol = new MemoCache(Delegate).query( + | symbol = new MemoCache(MyDelegate).query( userCountByAdmin, [], state, @@ -164,7 +168,7 @@ describe.each([ expect(totalCount).toBe(4); const nonAdminCount: | DenormalizeNullable - | symbol = new MemoCache(Delegate).query( + | symbol = new MemoCache(MyDelegate).query( userCountByAdmin, [{ isAdmin: false }], state, @@ -172,7 +176,7 @@ describe.each([ expect(nonAdminCount).toBe(3); const adminCount: | DenormalizeNullable - | symbol = new MemoCache(Delegate).query( + | symbol = new MemoCache(MyDelegate).query( userCountByAdmin, [{ isAdmin: true }], state, diff --git a/packages/normalizr/src/__tests__/MemoCache.ts b/packages/normalizr/src/__tests__/MemoCache.ts index 13e98285978b..ec2ad023fb95 100644 --- a/packages/normalizr/src/__tests__/MemoCache.ts +++ b/packages/normalizr/src/__tests__/MemoCache.ts @@ -10,7 +10,7 @@ import { import { fromJSState } from './immutable.test'; import { IQueryDelegate } from '../interface'; -import { BaseDelegate } from '../memo/Delegate'; +import { PlainDelegate } from '../memo/Delegate'; import { DelegateImmutable } from '../memo/Delegate.immutable'; import MemoCache from '../memo/MemoCache'; @@ -894,17 +894,21 @@ describe('MemoCache', () => { describe('legacy schema', () => { class MyEntity extends CoolerArticle { - static queryKey(args: any[], unvisit: any, snapshot: IQueryDelegate) { + static queryKey(args: any[], unvisit: any, delegate: IQueryDelegate) { if (!args[0]) return; - let id: undefined | number | string; + let pk: any; if (['string', 'number'].includes(typeof args[0])) { - id = `${args[0]}`; + pk = `${args[0]}`; } else { - id = this.pk(args[0], undefined, '', args); + pk = this.pk(args[0], undefined, '', args); } // Was able to infer the entity's primary key from params - if (id !== undefined && id !== '' && snapshot.getEntity(this.key, id)) - return id; + if ( + pk !== undefined && + pk !== '' && + delegate.getEntity({ key: this.key, pk }) + ) + return pk; } } @@ -1011,7 +1015,7 @@ describe('MemoCache', () => { }); describe.each([ - ['direct', (data: T) => data, BaseDelegate], + ['direct', (data: T) => data, PlainDelegate], ['immutable', fromJSState, DelegateImmutable], ])(`query (%s)`, (_, createInput, Delegate) => { class Cat extends IDEntity { diff --git a/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts b/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts index cc1d9b28b7f6..2a6a603a3d39 100644 --- a/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts +++ b/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts @@ -1,8 +1,8 @@ import { Temporal } from '@js-temporal/polyfill'; import { getEntities } from '../denormalize/getEntities'; +import { EntityPath } from '../interface'; import WeakDependencyMap from '../memo/WeakDependencyMap'; -import { EntityPath } from '../types'; describe('WeakDependencyMap', () => { const a = { hi: '5' }; diff --git a/packages/normalizr/src/__tests__/normalizerMerge.test.tsx b/packages/normalizr/src/__tests__/normalizerMerge.test.tsx index 66d3c529cf57..f0292121dc30 100644 --- a/packages/normalizr/src/__tests__/normalizerMerge.test.tsx +++ b/packages/normalizr/src/__tests__/normalizerMerge.test.tsx @@ -1,5 +1,5 @@ import { schema } from '@data-client/endpoint'; -import { Article, IDEntity } from '__tests__/new'; +import { Article } from '__tests__/new'; import { denormalize } from '../denormalize/denormalize'; import { normalize } from '../normalize/normalize'; diff --git a/packages/normalizr/src/denormalize/UNDEF.ts b/packages/normalizr/src/denormalize/UNDEF.ts index fae2e20f71cf..d40bb931d312 100644 --- a/packages/normalizr/src/denormalize/UNDEF.ts +++ b/packages/normalizr/src/denormalize/UNDEF.ts @@ -1 +1 @@ -export const UNDEF = {}; +export const UNDEF = {} as any; diff --git a/packages/normalizr/src/denormalize/cache.ts b/packages/normalizr/src/denormalize/cache.ts index 29e1a962ab4d..594ff05f455e 100644 --- a/packages/normalizr/src/denormalize/cache.ts +++ b/packages/normalizr/src/denormalize/cache.ts @@ -1,5 +1,4 @@ -import type { EntityInterface } from '../interface.js'; -import { EntityPath } from '../types.js'; +import type { EntityInterface, EntityPath } from '../interface.js'; import type { INVALID } from './symbol.js'; export default interface Cache { diff --git a/packages/normalizr/src/denormalize/getEntities.ts b/packages/normalizr/src/denormalize/getEntities.ts index 267137fde7cc..c19827b31345 100644 --- a/packages/normalizr/src/denormalize/getEntities.ts +++ b/packages/normalizr/src/denormalize/getEntities.ts @@ -1,6 +1,6 @@ -import { GetDependency } from '../memo/WeakDependencyMap.js'; +import type { EntityPath } from '../interface.js'; +import type { GetDependency } from '../memo/WeakDependencyMap.js'; import { isImmutable } from '../schemas/ImmutableUtils.js'; -import { EntityPath } from '../types.js'; export function getEntities( state: State, diff --git a/packages/normalizr/src/denormalize/localCache.ts b/packages/normalizr/src/denormalize/localCache.ts index ef7fa09b23df..890ad7967e22 100644 --- a/packages/normalizr/src/denormalize/localCache.ts +++ b/packages/normalizr/src/denormalize/localCache.ts @@ -1,6 +1,5 @@ import type Cache from './cache.js'; -import type { EntityInterface } from '../interface.js'; -import type { EntityPath } from '../types.js'; +import type { EntityInterface, EntityPath } from '../interface.js'; import type { INVALID } from './symbol.js'; export default class LocalCache implements Cache { diff --git a/packages/normalizr/src/denormalize/unvisit.ts b/packages/normalizr/src/denormalize/unvisit.ts index c5bae15e48d6..234fb1626871 100644 --- a/packages/normalizr/src/denormalize/unvisit.ts +++ b/packages/normalizr/src/denormalize/unvisit.ts @@ -2,11 +2,10 @@ import type Cache from './cache.js'; import { type GetEntity } from './getEntities.js'; import { INVALID } from './symbol.js'; import { UNDEF } from './UNDEF.js'; -import type { EntityInterface } from '../interface.js'; +import type { EntityInterface, EntityPath } from '../interface.js'; import { isEntity } from '../isEntity.js'; import { denormalize as arrayDenormalize } from '../schemas/Array.js'; import { denormalize as objectDenormalize } from '../schemas/Object.js'; -import type { EntityPath } from '../types.js'; const getUnvisitEntity = ( getEntity: GetEntity, diff --git a/packages/normalizr/src/index.ts b/packages/normalizr/src/index.ts index 90c872f79590..d0616bd769ce 100644 --- a/packages/normalizr/src/index.ts +++ b/packages/normalizr/src/index.ts @@ -4,12 +4,12 @@ import WeakDependencyMap from './memo/WeakDependencyMap.js'; import { normalize } from './normalize/normalize.js'; export { default as MemoCache } from './memo/MemoCache.js'; -export { BaseDelegate } from './memo/Delegate.js'; +export { BaseDelegate } from './memo/BaseDelegate.js'; +export { PlainDelegate as Delegate } from './memo/Delegate.js'; export type { AbstractInstanceType, NormalizeReturnType, NormalizedSchema, - EntityPath, Denormalize, DenormalizeNullable, Normalize, diff --git a/packages/normalizr/src/interface.ts b/packages/normalizr/src/interface.ts index 5c7bfd8e2851..2c3a3f183ef4 100644 --- a/packages/normalizr/src/interface.ts +++ b/packages/normalizr/src/interface.ts @@ -1,6 +1,3 @@ -import type { QueryPath } from './memo/types.js'; -import type { Dep } from './memo/WeakDependencyMap.js'; - export type Schema = | null | string @@ -102,45 +99,54 @@ export interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; } +export interface EntityPath { + key: string; + pk: string; +} +export interface IndexPath { + key: string; + field: string; + value: string; +} +export interface EntitiesPath { + key: string; +} +export type QueryPath = IndexPath | EntityPath | EntitiesPath; + /** Returns true if a circular reference is found */ export interface CheckLoop { (entityKey: string, pk: string, input: object): boolean; } -/** Get Array of entities with map function applied */ +/** Get all normalized entities of one type from store */ +export interface GetEntities { + (path: EntitiesPath): { readonly [pk: string]: any } | undefined; +} +/** Get normalized Entity from store */ export interface GetEntity { - (entityKey: string | symbol): { readonly [pk: string]: any } | undefined; - (entityKey: string | symbol, pk: string | number): any; + (path: EntityPath): any; } /** Get PK using an Entity Index */ export interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (entityKey: string, field: string, value: string): string | undefined; + (path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ export interface IQueryDelegate { + getEntities: GetEntities; getEntity: GetEntity; getIndex: GetIndex; /** Return to consider results invalid */ INVALID: symbol; } -export interface IBaseDelegate { - entities: any; - indexes: any; - - getEntity(entityKey: string | symbol, pk?: string): any; - getIndex(key: string, field: string): any; - tracked( - schema: any, - ): IQueryDelegate & { readonly dependencies: Dep[] }; -} - /** Helpers during schema.normalize() */ export interface INormalizeDelegate { /** Action meta-data for this normalize call */ readonly meta: { fetchedAt: number; date: number; expiresAt: number }; + /** Get all normalized entities of one type from store */ + getEntities: GetEntities; /** Gets any previously normalized entity from store */ getEntity: GetEntity; /** Updates an entity using merge lifecycles when it has previously been set */ diff --git a/packages/normalizr/src/memo/BaseDelegate.ts b/packages/normalizr/src/memo/BaseDelegate.ts new file mode 100644 index 000000000000..8a38186f5f85 --- /dev/null +++ b/packages/normalizr/src/memo/BaseDelegate.ts @@ -0,0 +1,58 @@ +import type { Dep } from './WeakDependencyMap.js'; +import { INVALID } from '../denormalize/symbol.js'; +import type { + EntityPath, + QueryPath, + IndexPath, + EntitiesPath, + IQueryDelegate, +} from '../interface.js'; + +export abstract class BaseDelegate { + declare entities: any; + declare indexes: any; + + constructor({ entities, indexes }: { entities: any; indexes: any }) { + this.entities = entities; + this.indexes = indexes; + } + + abstract getEntities(path: EntitiesPath): object | undefined; + abstract getEntity(path: EntityPath): object | undefined; + abstract getIndex(path: IndexPath): object | undefined; + abstract getIndexEnd(entity: any, value: string): string | undefined; + + getDependency = (path: QueryPath): object | undefined => + 'pk' in path ? this.getEntity(path) + : 'field' in path ? this.getIndex(path) + : this.getEntities(path); + + tracked( + schema: any, + ): [delegate: IQueryDelegate, dependencies: Dep[]] { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const base = this; + const dependencies: Dep[] = [ + { path: { key: '' }, entity: schema }, + ]; + const delegate = { + INVALID, + getIndex(path: IndexPath): string | undefined { + const entity = base.getIndex(path); + dependencies.push({ path, entity }); + return base.getIndexEnd(entity, path.value); + }, + getEntity(path: EntityPath) { + const entity = base.getEntity(path); + dependencies.push({ path, entity }); + return entity; + }, + getEntities(path: EntitiesPath) { + const entity = base.getEntities(path); + dependencies.push({ path, entity }); + return entity; + }, + }; + return [delegate, dependencies]; + } +} diff --git a/packages/normalizr/src/memo/Delegate.immutable.ts b/packages/normalizr/src/memo/Delegate.immutable.ts index ece4ee5d034f..fdf049afaf1e 100644 --- a/packages/normalizr/src/memo/Delegate.immutable.ts +++ b/packages/normalizr/src/memo/Delegate.immutable.ts @@ -1,47 +1,40 @@ -import { IBaseDelegate } from '../interface.js'; -import { TrackingQueryDelegate } from './Delegate.js'; -import { IndexPath } from './types.js'; +import type { EntitiesPath, EntityPath, IndexPath } from '../interface.js'; +import { BaseDelegate } from './BaseDelegate.js'; type ImmutableJSEntityTable = { + get(key: string): { toJS(): any } | undefined; getIn(k: [key: string, pk: string]): { toJS(): any } | undefined; setIn(k: [key: string, pk: string], value: any); }; -export class DelegateImmutable implements IBaseDelegate { +export class DelegateImmutable extends BaseDelegate { declare entities: ImmutableJSEntityTable; declare indexes: ImmutableJSEntityTable; - constructor({ - entities, - indexes, - }: { + constructor(state: { entities: ImmutableJSEntityTable; indexes: ImmutableJSEntityTable; }) { - this.entities = entities; - this.indexes = indexes; + super(state); } - getEntity(...args: [entityKey: string, pk?: string]): any { - // TODO: Don't make consumer depend on this going toJS() - return args.length === 1 ? - this.entities.getIn(args as any)?.toJS() - : this.entities.getIn(args as any); + getEntities({ key }: EntitiesPath): any { + return this.entities.get(key)?.toJS(); } - getIndex(key: string, field: string) { - return this.indexes.getIn([key, field]); + getEntity({ key, pk }: EntityPath): any { + return this.entities.getIn([key, pk]); } - tracked(schema: any) { - return new TrackingQueryDelegateImmutable(this, schema); + // this is different return value than QuerySnapshot + getIndex({ key, field }: IndexPath): object | undefined { + return this.indexes.getIn([key, field]); } -} -export class TrackingQueryDelegateImmutable extends TrackingQueryDelegate { - getIndex(...path: IndexPath): string | undefined { - const entity = this.snap.getIndex(path[0], path[1]); - this.dependencies.push({ path, entity }); - return entity?.get?.(path[2]); + getIndexEnd( + entity: { get(k: string): string | undefined } | undefined, + value: string, + ) { + return entity?.get?.(value); } } diff --git a/packages/normalizr/src/memo/Delegate.ts b/packages/normalizr/src/memo/Delegate.ts index 2c24250edc7b..7c7d67c48b35 100644 --- a/packages/normalizr/src/memo/Delegate.ts +++ b/packages/normalizr/src/memo/Delegate.ts @@ -1,22 +1,13 @@ -import { Dep } from './WeakDependencyMap.js'; import type { EntityTable, NormalizedIndex, - IQueryDelegate, - IBaseDelegate, + EntityPath, + IndexPath, + EntitiesPath, } from '../interface.js'; -import { QueryPath, IndexPath } from './types.js'; -import { INVALID } from '../denormalize/symbol.js'; +import { BaseDelegate } from './BaseDelegate.js'; -export const getDependency = - (delegate: IBaseDelegate) => - (args: QueryPath): QueryPath | undefined => - // ignore third arg so we only track - args.length === 3 ? - delegate.getIndex(args[0], args[1]) - : delegate.getEntity(...(args as [any])); - -export class BaseDelegate implements IBaseDelegate { +export class PlainDelegate extends BaseDelegate { declare entities: EntityTable; declare indexes: { [entityKey: string]: { @@ -24,64 +15,24 @@ export class BaseDelegate implements IBaseDelegate { }; }; - constructor({ - entities, - indexes, - }: { - entities: EntityTable; - indexes: NormalizedIndex; - }) { - this.entities = entities; - this.indexes = indexes; + constructor(state: { entities: EntityTable; indexes: NormalizedIndex }) { + super(state); } - getEntity( - entityKey: string | symbol, - ): { readonly [pk: string]: any } | undefined; - - getEntity(entityKey: string | symbol, pk: string | number): any; + getEntities({ key }: EntitiesPath): any { + return this.entities[key]; + } - getEntity(entityKey: string, pk?: any): any { - return pk ? this.entities[entityKey]?.[pk] : this.entities[entityKey]; + getEntity({ key, pk }: EntityPath): any { + return this.entities[key]?.[pk]; } // this is different return value than QuerySnapshot - getIndex(key: string, field: string) { + getIndex({ key, field }: IndexPath): object | undefined { return this.indexes[key]?.[field]; } - tracked(schema: any) { - return new TrackingQueryDelegate(this, schema); - } -} - -export class TrackingQueryDelegate implements IQueryDelegate { - readonly INVALID = INVALID; - declare protected snap: IBaseDelegate; - // first dep path is ignored - // we start with schema object, then lookup any 'touched' members and their paths - declare readonly dependencies: Dep[]; - - constructor(snap: IBaseDelegate, schema: any) { - this.snap = snap; - this.dependencies = [{ path: [''], entity: schema }]; - } - - getIndex(...path: IndexPath): string | undefined { - const entity = this.snap.getIndex(path[0], path[1]); - this.dependencies.push({ path, entity }); - return entity?.[path[2]]; - } - - getEntity( - entityKey: string | symbol, - ): { readonly [pk: string]: any } | undefined; - - getEntity(entityKey: string | symbol, pk: string | number): any; - - getEntity(...path: any): any { - const entity = this.snap.getEntity(...(path as [any])); - this.dependencies.push({ path, entity }); - return entity; + getIndexEnd(entity: object | undefined, value: string) { + return entity?.[value]; } } diff --git a/packages/normalizr/src/memo/MemoCache.ts b/packages/normalizr/src/memo/MemoCache.ts index 89f986db2661..45e6aa8c8c29 100644 --- a/packages/normalizr/src/memo/MemoCache.ts +++ b/packages/normalizr/src/memo/MemoCache.ts @@ -1,21 +1,23 @@ import GlobalCache from './globalCache.js'; import WeakDependencyMap from './WeakDependencyMap.js'; import buildQueryKey from '../buildQueryKey.js'; +import type { BaseDelegate } from './BaseDelegate.js'; +import { PlainDelegate } from './Delegate.js'; import { getEntities } from '../denormalize/getEntities.js'; import getUnvisit from '../denormalize/unvisit.js'; -import type { IBaseDelegate, NormalizedIndex, Schema } from '../interface.js'; -import { isImmutable } from '../schemas/ImmutableUtils.js'; import type { - DenormalizeNullable, EntityPath, - NormalizeNullable, -} from '../types.js'; -import { getDependency, BaseDelegate } from './Delegate.js'; + NormalizedIndex, + QueryPath, + Schema, +} from '../interface.js'; +import type { DenormalizeNullable, NormalizeNullable } from '../types.js'; import { EndpointsCache, EntityCache } from './types.js'; -import { QueryPath } from './types.js'; import type { INVALID } from '../denormalize/symbol.js'; -type DelegateClass = new (v: { entities: any; indexes: any }) => IBaseDelegate; +type DelegateClass = new (v: { entities: any; indexes: any }) => BaseDelegate; + +// TODO: make MemoCache generic on the arguments sent to Delegate constructor /** Singleton to store the memoization cache for denormalization methods */ export default class MemoCache { @@ -27,8 +29,8 @@ export default class MemoCache { protected queryKeys: Map> = new Map(); declare protected Delegate: DelegateClass; - constructor(Delegate: DelegateClass = BaseDelegate) { - this.Delegate = Delegate; + constructor(D: DelegateClass = PlainDelegate) { + this.Delegate = D; } /** Compute denormalized form maintaining referential equality for same inputs */ @@ -112,15 +114,15 @@ export default class MemoCache { // eslint-disable-next-line prefer-const let [value, paths] = queryCache.get( schema as any, - getDependency(baseDelegate), + baseDelegate.getDependency, ); // paths undefined is the only way to truly tell nothing was found (the value could have actually been undefined) if (!paths) { - const tracked = baseDelegate.tracked(schema); + const [delegate, dependencies] = baseDelegate.tracked(schema); - value = buildQueryKey(tracked)(schema, args); - queryCache.set(tracked.dependencies, value); + value = buildQueryKey(delegate)(schema, args); + queryCache.set(dependencies, value); } return value; } diff --git a/packages/normalizr/src/memo/WeakDependencyMap.ts b/packages/normalizr/src/memo/WeakDependencyMap.ts index d7508ab638a5..c6469443491b 100644 --- a/packages/normalizr/src/memo/WeakDependencyMap.ts +++ b/packages/normalizr/src/memo/WeakDependencyMap.ts @@ -32,7 +32,7 @@ export default class WeakDependencyMap< if (dependencies.length < 1) throw new KeySize(); let curLink: Link = this as any; for (const { path, entity } of dependencies) { - let nextLink = curLink.next.get(entity); + let nextLink = curLink.next.get(entity as K); if (!nextLink) { nextLink = new Link(); // void members are represented as a symbol so we can lookup @@ -55,7 +55,7 @@ export type GetDependency = ( export interface Dep { path: Path; - entity: K; + entity: K | undefined; } const EMPTY = [undefined, undefined] as const; diff --git a/packages/normalizr/src/memo/globalCache.ts b/packages/normalizr/src/memo/globalCache.ts index 93ff7e17d4e3..5aca2a12044e 100644 --- a/packages/normalizr/src/memo/globalCache.ts +++ b/packages/normalizr/src/memo/globalCache.ts @@ -3,8 +3,7 @@ import WeakDependencyMap, { type Dep } from './WeakDependencyMap.js'; import type Cache from '../denormalize/cache.js'; import type { GetEntity } from '../denormalize/getEntities.js'; import type { INVALID } from '../denormalize/symbol.js'; -import type { EntityInterface } from '../interface.js'; -import type { EntityPath } from '../types.js'; +import type { EntityInterface, EntityPath } from '../interface.js'; export default class GlobalCache implements Cache { private dependencies: Dep[] = []; diff --git a/packages/normalizr/src/memo/types.ts b/packages/normalizr/src/memo/types.ts index 7e4a609acc1c..64a6fd34963e 100644 --- a/packages/normalizr/src/memo/types.ts +++ b/packages/normalizr/src/memo/types.ts @@ -1,6 +1,5 @@ import WeakDependencyMap from './WeakDependencyMap.js'; -import { EntityInterface } from '../interface.js'; -import { EntityPath } from '../types.js'; +import type { EntityInterface, EntityPath } from '../interface.js'; export interface EntityCache extends Map< @@ -12,7 +11,3 @@ export interface EntityCache > {} export type EndpointsCache = WeakDependencyMap; - -export type IndexPath = [key: string, field: string, value: string]; -export type EntitySchemaPath = [key: string] | [key: string, pk: string]; -export type QueryPath = IndexPath | EntitySchemaPath; diff --git a/packages/normalizr/src/normalize/NormalizeDelegate.ts b/packages/normalizr/src/normalize/NormalizeDelegate.ts index deb4b72d867c..37be99408e2e 100644 --- a/packages/normalizr/src/normalize/NormalizeDelegate.ts +++ b/packages/normalizr/src/normalize/NormalizeDelegate.ts @@ -6,10 +6,10 @@ import { } from '../interface.js'; import { getCheckLoop } from './getCheckLoop.js'; import { INVALID } from '../denormalize/symbol.js'; -import { BaseDelegate } from '../memo/Delegate.js'; +import { PlainDelegate } from '../memo/Delegate.js'; export class NormalizeDelegate - extends BaseDelegate + extends PlainDelegate implements INormalizeDelegate { declare readonly entitiesMeta: { @@ -96,7 +96,7 @@ export class NormalizeDelegate nextEntity = schema.merge(entity, incomingEntity); } else { // if we find it in the store - entity = this.getEntity(key, pk); + entity = this.getEntity({ key, pk }); if (entity) { const meta = this.getMeta(key, pk); nextEntity = schema.mergeWithStore( diff --git a/packages/normalizr/src/types.ts b/packages/normalizr/src/types.ts index ffe4804905aa..8daf3f07bb2e 100644 --- a/packages/normalizr/src/types.ts +++ b/packages/normalizr/src/types.ts @@ -6,11 +6,6 @@ import type { } from './interface.js'; export * from './schemaArgs.js'; -export interface EntityPath { - key: string; - pk: string; -} - // TypeScript <4.2 InstanceType<> does not work on abstract classes export type AbstractInstanceType = T extends new (...args: any) => infer U ? U 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 a0ba714d7580..a54363f9adf0 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 @@ -1,120 +1,3 @@ -/** Maps a (ordered) list of dependencies to a value. - * - * Useful as a memoization cache for flat/normalized stores. - * - * All dependencies are only weakly referenced, allowing automatic garbage collection - * when any dependencies are no longer used. - */ -declare class WeakDependencyMap { - private readonly next; - private nextPath; - get(entity: K, getDependency: GetDependency): readonly [undefined, undefined] | readonly [V, Path[]]; - set(dependencies: Dep[], value: V): void; -} -type GetDependency = (lookup: Path) => K | undefined; -interface Dep { - path: Path; - entity: K; -} - -/** Attempts to infer reasonable input type to construct an Entity */ -type EntityFields = { - readonly [K in keyof U as U[K] extends (...args: any) => any ? never : K]?: U[K] extends number ? U[K] | string : U[K] extends string ? U[K] | number : U[K]; -}; - -type SchemaArgs = S extends { - createIfValid: any; - pk: any; - key: string; - prototype: infer U; -} ? [ - EntityFields -] : S extends ({ - queryKey(args: infer Args, ...rest: any): any; -}) ? Args : S extends { - [K: string]: any; -} ? ObjectArgs : never; -type ObjectArgs> = { - [K in keyof S]: S[K] extends Schema ? SchemaArgs : never; -}[keyof S]; - -interface EntityPath { - key: string; - pk: string; -} -type AbstractInstanceType = T extends new (...args: any) => infer U ? U : T extends { - prototype: infer U; -} ? U : never; -type DenormalizeObject> = { - [K in keyof S]: S[K] extends Schema ? Denormalize : S[K]; -}; -type DenormalizeNullableObject> = { - [K in keyof S]: S[K] extends Schema ? DenormalizeNullable : S[K]; -}; -type NormalizeObject> = { - [K in keyof S]: S[K] extends Schema ? Normalize : S[K]; -}; -type NormalizedNullableObject> = { - [K in keyof S]: S[K] extends Schema ? NormalizeNullable : S[K]; -}; -interface NestedSchemaClass { - schema: Record; - prototype: T; -} -interface RecordClass extends NestedSchemaClass { - fromJS: (...args: any) => AbstractInstanceType; -} -type DenormalizeNullableNestedSchema = keyof S['schema'] extends never ? S['prototype'] : string extends keyof S['schema'] ? S['prototype'] : S['prototype']; -type NormalizeReturnType = T extends (...args: any) => infer R ? R : never; -type Denormalize = S extends { - createIfValid: any; - pk: any; - key: string; - prototype: infer U; -} ? U : S extends RecordClass ? AbstractInstanceType : S extends { - denormalize: (...args: any) => any; -} ? ReturnType : S extends Serializable ? T : S extends Array ? Denormalize[] : S extends { - [K: string]: any; -} ? DenormalizeObject : S; -type DenormalizeNullable = S extends ({ - createIfValid: any; - pk: any; - key: string; - prototype: any; - schema: any; -}) ? DenormalizeNullableNestedSchema | undefined : S extends RecordClass ? DenormalizeNullableNestedSchema : S extends { - _denormalizeNullable: (...args: any) => any; -} ? ReturnType : S extends Serializable ? T : S extends Array ? Denormalize[] | undefined : S extends { - [K: string]: any; -} ? DenormalizeNullableObject : S; -type Normalize = S extends { - createIfValid: any; - pk: any; - key: string; - prototype: {}; -} ? string : S extends RecordClass ? NormalizeObject : S extends { - normalize: (...args: any) => any; -} ? NormalizeReturnType : S extends Serializable ? T : S extends Array ? Normalize[] : S extends { - [K: string]: any; -} ? NormalizeObject : S; -type NormalizeNullable = S extends { - createIfValid: any; - pk: any; - key: string; - prototype: {}; -} ? string | undefined : S extends RecordClass ? NormalizedNullableObject : S extends { - _normalizeNullable: (...args: any) => any; -} ? NormalizeReturnType : S extends Serializable ? T : S extends Array ? Normalize[] | undefined : S extends { - [K: string]: any; -} ? NormalizedNullableObject : S; - -interface EntityCache extends Map>>> { -} -type EndpointsCache = WeakDependencyMap; -type IndexPath = [key: string, field: string, value: string]; -type EntitySchemaPath = [key: string] | [key: string, pk: string]; -type QueryPath = IndexPath | EntitySchemaPath; - type Schema = null | string | { [K: string]: any; } | Schema[] | SchemaSimple | Serializable; @@ -166,34 +49,42 @@ interface NormalizedIndex { }; }; } -/** Get Array of entities with map function applied */ -interface GetEntity { - (entityKey: string | symbol): { +interface EntityPath { + key: string; + pk: string; +} +interface IndexPath { + key: string; + field: string; + value: string; +} +interface EntitiesPath { + key: string; +} +type QueryPath = IndexPath | EntityPath | EntitiesPath; +/** Get all normalized entities of one type from store */ +interface GetEntities { + (path: EntitiesPath): { readonly [pk: string]: any; } | undefined; - (entityKey: string | symbol, pk: string | number): any; +} +/** Get normalized Entity from store */ +interface GetEntity { + (path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (entityKey: string, field: string, value: string): string | undefined; + (path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { + getEntities: GetEntities; getEntity: GetEntity; getIndex: GetIndex; /** Return to consider results invalid */ INVALID: symbol; } -interface IBaseDelegate { - entities: any; - indexes: any; - getEntity(entityKey: string | symbol, pk?: string): any; - getIndex(key: string, field: string): any; - tracked(schema: any): IQueryDelegate & { - readonly dependencies: Dep[]; - }; -} /** Helpers during schema.normalize() */ interface INormalizeDelegate { /** Action meta-data for this normalize call */ @@ -202,6 +93,8 @@ interface INormalizeDelegate { date: number; expiresAt: number; }; + /** Get all normalized entities of one type from store */ + getEntities: GetEntities; /** Gets any previously normalized entity from store */ getEntity: GetEntity; /** Updates an entity using merge lifecycles when it has previously been set */ @@ -226,12 +119,137 @@ interface INormalizeDelegate { checkLoop(key: string, pk: string, input: object): boolean; } +/** Attempts to infer reasonable input type to construct an Entity */ +type EntityFields = { + readonly [K in keyof U as U[K] extends (...args: any) => any ? never : K]?: U[K] extends number ? U[K] | string : U[K] extends string ? U[K] | number : U[K]; +}; + +type SchemaArgs = S extends { + createIfValid: any; + pk: any; + key: string; + prototype: infer U; +} ? [ + EntityFields +] : S extends ({ + queryKey(args: infer Args, ...rest: any): any; +}) ? Args : S extends { + [K: string]: any; +} ? ObjectArgs : never; +type ObjectArgs> = { + [K in keyof S]: S[K] extends Schema ? SchemaArgs : never; +}[keyof S]; + +type AbstractInstanceType = T extends new (...args: any) => infer U ? U : T extends { + prototype: infer U; +} ? U : never; +type DenormalizeObject> = { + [K in keyof S]: S[K] extends Schema ? Denormalize : S[K]; +}; +type DenormalizeNullableObject> = { + [K in keyof S]: S[K] extends Schema ? DenormalizeNullable : S[K]; +}; +type NormalizeObject> = { + [K in keyof S]: S[K] extends Schema ? Normalize : S[K]; +}; +type NormalizedNullableObject> = { + [K in keyof S]: S[K] extends Schema ? NormalizeNullable : S[K]; +}; +interface NestedSchemaClass { + schema: Record; + prototype: T; +} +interface RecordClass extends NestedSchemaClass { + fromJS: (...args: any) => AbstractInstanceType; +} +type DenormalizeNullableNestedSchema = keyof S['schema'] extends never ? S['prototype'] : string extends keyof S['schema'] ? S['prototype'] : S['prototype']; +type NormalizeReturnType = T extends (...args: any) => infer R ? R : never; +type Denormalize = S extends { + createIfValid: any; + pk: any; + key: string; + prototype: infer U; +} ? U : S extends RecordClass ? AbstractInstanceType : S extends { + denormalize: (...args: any) => any; +} ? ReturnType : S extends Serializable ? T : S extends Array ? Denormalize[] : S extends { + [K: string]: any; +} ? DenormalizeObject : S; +type DenormalizeNullable = S extends ({ + createIfValid: any; + pk: any; + key: string; + prototype: any; + schema: any; +}) ? DenormalizeNullableNestedSchema | undefined : S extends RecordClass ? DenormalizeNullableNestedSchema : S extends { + _denormalizeNullable: (...args: any) => any; +} ? ReturnType : S extends Serializable ? T : S extends Array ? Denormalize[] | undefined : S extends { + [K: string]: any; +} ? DenormalizeNullableObject : S; +type Normalize = S extends { + createIfValid: any; + pk: any; + key: string; + prototype: {}; +} ? string : S extends RecordClass ? NormalizeObject : S extends { + normalize: (...args: any) => any; +} ? NormalizeReturnType : S extends Serializable ? T : S extends Array ? Normalize[] : S extends { + [K: string]: any; +} ? NormalizeObject : S; +type NormalizeNullable = S extends { + createIfValid: any; + pk: any; + key: string; + prototype: {}; +} ? string | undefined : S extends RecordClass ? NormalizedNullableObject : S extends { + _normalizeNullable: (...args: any) => any; +} ? NormalizeReturnType : S extends Serializable ? T : S extends Array ? Normalize[] | undefined : S extends { + [K: string]: any; +} ? NormalizedNullableObject : S; + declare const INVALID: unique symbol; +/** Maps a (ordered) list of dependencies to a value. + * + * Useful as a memoization cache for flat/normalized stores. + * + * All dependencies are only weakly referenced, allowing automatic garbage collection + * when any dependencies are no longer used. + */ +declare class WeakDependencyMap { + private readonly next; + private nextPath; + get(entity: K, getDependency: GetDependency): readonly [undefined, undefined] | readonly [V, Path[]]; + set(dependencies: Dep[], value: V): void; +} +type GetDependency = (lookup: Path) => K | undefined; +interface Dep { + path: Path; + entity: K | undefined; +} + +declare abstract class BaseDelegate { + entities: any; + indexes: any; + constructor({ entities, indexes }: { + entities: any; + indexes: any; + }); + abstract getEntities(path: EntitiesPath): object | undefined; + abstract getEntity(path: EntityPath): object | undefined; + abstract getIndex(path: IndexPath): object | undefined; + abstract getIndexEnd(entity: any, value: string): string | undefined; + getDependency: (path: QueryPath) => object | undefined; + tracked(schema: any): [delegate: IQueryDelegate, dependencies: Dep[]]; +} + +interface EntityCache extends Map>>> { +} +type EndpointsCache = WeakDependencyMap; + type DelegateClass = new (v: { entities: any; indexes: any; -}) => IBaseDelegate; +}) => BaseDelegate; /** Singleton to store the memoization cache for denormalization methods */ declare class MemoCache { /** Cache for every entity based on its dependencies and its own input */ @@ -241,7 +259,7 @@ declare class MemoCache { /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Map>; protected Delegate: DelegateClass; - constructor(Delegate?: DelegateClass); + constructor(D?: DelegateClass); /** Compute denormalized form maintaining referential equality for same inputs */ denormalize(schema: S | undefined, input: unknown, entities: any, args?: readonly any[]): { data: DenormalizeNullable | typeof INVALID; 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 d049ea436c25..3d0d748904e2 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 @@ -236,20 +236,57 @@ interface PolymorphicInterface extends Sche _normalizeNullable(): any; _denormalizeNullable(): any; } -/** Get Array of entities with map function applied */ -interface GetEntity { - (entityKey: string | symbol): { +interface NormalizedIndex { + readonly [entityKey: string]: { + readonly [indexName: string]: { + readonly [lookup: string]: string; + }; + }; +} +interface EntityTable { + [entityKey: string]: { + [pk: string]: unknown; + } | undefined; +} +/** Visits next data + schema while recurisvely normalizing */ +interface Visit { + (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; + creating?: boolean; +} +interface EntityPath { + key: string; + pk: string; +} +interface IndexPath { + key: string; + field: string; + value: string; +} +interface EntitiesPath { + key: string; +} +/** Returns true if a circular reference is found */ +interface CheckLoop { + (entityKey: string, pk: string, input: object): boolean; +} +/** Get all normalized entities of one type from store */ +interface GetEntities { + (path: EntitiesPath): { readonly [pk: string]: any; } | undefined; - (entityKey: string | symbol, pk: string | number): any; +} +/** Get normalized Entity from store */ +interface GetEntity { + (path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (entityKey: string, field: string, value: string): string | undefined; + (path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { + getEntities: GetEntities; getEntity: GetEntity; getIndex: GetIndex; /** Return to consider results invalid */ @@ -263,6 +300,8 @@ interface INormalizeDelegate { date: number; expiresAt: number; }; + /** Get all normalized entities of one type from store */ + getEntities: GetEntities; /** Gets any previously normalized entity from store */ getEntity: GetEntity; /** Updates an entity using merge lifecycles when it has previously been set */ @@ -1160,4 +1199,4 @@ declare function validateRequired(processedEntity: any, requiredDefaults: Record /** https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#the-noinfer-utility-type */ type NI = NoInfer; -export { type AbstractInstanceType, Array$1 as Array, Collection, type DefaultArgs, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, Entity, type EntityFields, type EntityMap, EntityMixin, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type FetchFunction, type INormalizeDelegate, type IQueryDelegate, Invalidate, type KeyofEndpointInstance, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedNullableObject, type ObjectArgs, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type SnapshotInterface, type UnknownError, schema_d as schema, validateRequired }; +export { type AbstractInstanceType, Array$1 as Array, type CheckLoop, Collection, type DefaultArgs, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type FetchFunction, type GetEntities, type GetEntity, type GetIndex, type INormalizeDelegate, type IQueryDelegate, type IndexPath, Invalidate, type KeyofEndpointInstance, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type Visit, schema_d as schema, validateRequired }; 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 d2e77cafa90f..2919496e7fb0 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 @@ -236,20 +236,57 @@ interface PolymorphicInterface extends Sche _normalizeNullable(): any; _denormalizeNullable(): any; } -/** Get Array of entities with map function applied */ -interface GetEntity { - (entityKey: string | symbol): { +interface NormalizedIndex { + readonly [entityKey: string]: { + readonly [indexName: string]: { + readonly [lookup: string]: string; + }; + }; +} +interface EntityTable { + [entityKey: string]: { + [pk: string]: unknown; + } | undefined; +} +/** Visits next data + schema while recurisvely normalizing */ +interface Visit { + (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; + creating?: boolean; +} +interface EntityPath { + key: string; + pk: string; +} +interface IndexPath { + key: string; + field: string; + value: string; +} +interface EntitiesPath { + key: string; +} +/** Returns true if a circular reference is found */ +interface CheckLoop { + (entityKey: string, pk: string, input: object): boolean; +} +/** Get all normalized entities of one type from store */ +interface GetEntities { + (path: EntitiesPath): { readonly [pk: string]: any; } | undefined; - (entityKey: string | symbol, pk: string | number): any; +} +/** Get normalized Entity from store */ +interface GetEntity { + (path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (entityKey: string, field: string, value: string): string | undefined; + (path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { + getEntities: GetEntities; getEntity: GetEntity; getIndex: GetIndex; /** Return to consider results invalid */ @@ -263,6 +300,8 @@ interface INormalizeDelegate { date: number; expiresAt: number; }; + /** Get all normalized entities of one type from store */ + getEntities: GetEntities; /** Gets any previously normalized entity from store */ getEntity: GetEntity; /** Updates an entity using merge lifecycles when it has previously been set */ @@ -1201,4 +1240,4 @@ interface GQLError { path: (string | number)[]; } -export { type AbstractInstanceType, Array$1 as Array, Collection, type DefaultArgs, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, Entity, type EntityFields, type EntityMap, EntityMixin, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type FetchFunction, GQLEndpoint, GQLEntity, type GQLError, GQLNetworkError, type GQLOptions, type INormalizeDelegate, type IQueryDelegate, Invalidate, type KeyofEndpointInstance, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedNullableObject, type ObjectArgs, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type SnapshotInterface, type UnknownError, schema_d as schema, validateRequired }; +export { type AbstractInstanceType, Array$1 as Array, type CheckLoop, Collection, type DefaultArgs, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type FetchFunction, GQLEndpoint, GQLEntity, type GQLError, GQLNetworkError, type GQLOptions, type GetEntities, type GetEntity, type GetIndex, type INormalizeDelegate, type IQueryDelegate, type IndexPath, Invalidate, type KeyofEndpointInstance, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type Visit, schema_d as schema, validateRequired }; 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 a5024ec4688c..9b8f556d603b 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 @@ -1,20 +1,135 @@ -/** Maps a (ordered) list of dependencies to a value. - * - * Useful as a memoization cache for flat/normalized stores. - * - * All dependencies are only weakly referenced, allowing automatic garbage collection - * when any dependencies are no longer used. - */ -declare class WeakDependencyMap { - private readonly next; - private nextPath; - get(entity: K, getDependency: GetDependency): readonly [undefined, undefined] | readonly [V, Path[]]; - set(dependencies: Dep[], value: V): void; +type Schema = null | string | { + [K: string]: any; +} | Schema[] | SchemaSimple | Serializable; +interface Queryable { + queryKey(args: Args, unvisit: (...args: any) => any, delegate: { + getEntity: any; + getIndex: any; + }): {}; } -type GetDependency = (lookup: Path) => K | undefined; -interface Dep { - path: Path; - entity: K; +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; + }): any; + denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; + queryKey(args: Args, unvisit: (...args: any) => any, delegate: { + getEntity: any; + getIndex: any; + }): any; +} +interface SchemaClass extends SchemaSimple { + _normalizeNullable(): any; + _denormalizeNullable(): any; +} +interface EntityInterface extends SchemaSimple { + createIfValid(props: any): any; + pk(params: any, parent: any, key: string | undefined, args: readonly any[]): string | number | undefined; + readonly key: string; + indexes?: any; + schema: Record; + prototype: T; + cacheWith?: object; +} +interface Mergeable { + key: string; + merge(existing: any, incoming: any): any; + mergeWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; + mergeMetaWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; +} +interface NormalizedIndex { + readonly [entityKey: string]: { + readonly [indexName: string]: { + readonly [lookup: string]: string; + }; + }; +} +interface EntityTable { + [entityKey: string]: { + [pk: string]: unknown; + } | undefined; +} +/** Visits next data + schema while recurisvely normalizing */ +interface Visit { + (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; +} +interface EntityPath { + key: string; + pk: string; +} +interface IndexPath { + key: string; + field: string; + value: string; +} +interface EntitiesPath { + key: string; +} +type QueryPath = IndexPath | EntityPath | EntitiesPath; +/** Returns true if a circular reference is found */ +interface CheckLoop { + (entityKey: string, pk: string, input: object): boolean; +} +/** Get all normalized entities of one type from store */ +interface GetEntities { + (path: EntitiesPath): { + readonly [pk: string]: any; + } | undefined; +} +/** Get normalized Entity from store */ +interface GetEntity { + (path: EntityPath): any; +} +/** Get PK using an Entity Index */ +interface GetIndex { + /** getIndex('User', 'username', 'ntucker') */ + (path: IndexPath): string | undefined; +} +/** Accessors to the currently processing state while building query */ +interface IQueryDelegate { + getEntities: GetEntities; + getEntity: GetEntity; + getIndex: GetIndex; + /** Return to consider results invalid */ + INVALID: symbol; +} +/** Helpers during schema.normalize() */ +interface INormalizeDelegate { + /** Action meta-data for this normalize call */ + readonly meta: { + fetchedAt: number; + date: number; + expiresAt: number; + }; + /** Get all normalized entities of one type from store */ + getEntities: GetEntities; + /** Gets any previously normalized entity from store */ + getEntity: GetEntity; + /** Updates an entity using merge lifecycles when it has previously been set */ + mergeEntity(schema: Mergeable & { + indexes?: any; + }, pk: string, incomingEntity: any): void; + /** Sets an entity overwriting any previously set values */ + setEntity(schema: { + key: string; + indexes?: any; + }, pk: string, entity: any, meta?: { + fetchedAt: number; + date: number; + expiresAt: number; + }): void; + /** Invalidates an entity, potentially triggering suspense */ + invalidate(schema: { + key: string; + indexes?: any; + }, pk: string): void; + /** Returns true when we're in a cycle, so we should not continue recursing */ + checkLoop(key: string, pk: string, input: object): boolean; } /** Attempts to infer reasonable input type to construct an Entity */ @@ -38,10 +153,6 @@ type ObjectArgs> = { [K in keyof S]: S[K] extends Schema ? SchemaArgs : never; }[keyof S]; -interface EntityPath { - key: string; - pk: string; -} type AbstractInstanceType = T extends new (...args: any) => infer U ? U : T extends { prototype: infer U; } ? U : never; @@ -129,149 +240,56 @@ interface NormalizeMeta { fetchedAt: number; } -interface EntityCache extends Map>>> { -} -type EndpointsCache = WeakDependencyMap; -type IndexPath = [key: string, field: string, value: string]; -type EntitySchemaPath = [key: string] | [key: string, pk: string]; -type QueryPath = IndexPath | EntitySchemaPath; - -type Schema = null | string | { - [K: string]: any; -} | Schema[] | SchemaSimple | Serializable; -interface Queryable { - queryKey(args: Args, unvisit: (...args: any) => any, delegate: { - getEntity: any; - getIndex: any; - }): {}; -} -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; - }): any; - denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; - queryKey(args: Args, unvisit: (...args: any) => any, delegate: { - getEntity: any; - getIndex: any; - }): any; -} -interface SchemaClass extends SchemaSimple { - _normalizeNullable(): any; - _denormalizeNullable(): any; -} -interface EntityInterface extends SchemaSimple { - createIfValid(props: any): any; - pk(params: any, parent: any, key: string | undefined, args: readonly any[]): string | number | undefined; - readonly key: string; - indexes?: any; - schema: Record; - prototype: T; - cacheWith?: object; -} -interface Mergeable { - key: string; - merge(existing: any, incoming: any): any; - mergeWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; - mergeMetaWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; -} -interface NormalizedIndex { - readonly [entityKey: string]: { - readonly [indexName: string]: { - readonly [lookup: string]: string; - }; - }; -} -interface EntityTable { - [entityKey: string]: { - [pk: string]: unknown; - } | undefined; -} -/** Visits next data + schema while recurisvely normalizing */ -interface Visit { - (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; -} -/** Returns true if a circular reference is found */ -interface CheckLoop { - (entityKey: string, pk: string, input: object): boolean; -} -/** Get Array of entities with map function applied */ -interface GetEntity { - (entityKey: string | symbol): { - readonly [pk: string]: any; - } | undefined; - (entityKey: string | symbol, pk: string | number): any; -} -/** Get PK using an Entity Index */ -interface GetIndex { - /** getIndex('User', 'username', 'ntucker') */ - (entityKey: string, field: string, value: string): string | undefined; -} -/** Accessors to the currently processing state while building query */ -interface IQueryDelegate { - getEntity: GetEntity; - getIndex: GetIndex; - /** Return to consider results invalid */ - INVALID: symbol; -} -interface IBaseDelegate { - entities: any; - indexes: any; - getEntity(entityKey: string | symbol, pk?: string): any; - getIndex(key: string, field: string): any; - tracked(schema: any): IQueryDelegate & { - readonly dependencies: Dep[]; - }; -} -/** Helpers during schema.normalize() */ -interface INormalizeDelegate { - /** Action meta-data for this normalize call */ - readonly meta: { - fetchedAt: number; - date: number; - expiresAt: number; - }; - /** Gets any previously normalized entity from store */ - getEntity: GetEntity; - /** Updates an entity using merge lifecycles when it has previously been set */ - mergeEntity(schema: Mergeable & { - indexes?: any; - }, pk: string, incomingEntity: any): void; - /** Sets an entity overwriting any previously set values */ - setEntity(schema: { - key: string; - indexes?: any; - }, pk: string, entity: any, meta?: { - fetchedAt: number; - date: number; - expiresAt: number; - }): void; - /** Invalidates an entity, potentially triggering suspense */ - invalidate(schema: { - key: string; - indexes?: any; - }, pk: string): void; - /** Returns true when we're in a cycle, so we should not continue recursing */ - checkLoop(key: string, pk: string, input: object): boolean; -} - declare const INVALID: unique symbol; declare function denormalize(schema: S | undefined, input: any, entities: any, args?: readonly any[]): DenormalizeNullable | typeof INVALID; declare function isEntity(schema: Schema): schema is EntityInterface; +/** Maps a (ordered) list of dependencies to a value. + * + * Useful as a memoization cache for flat/normalized stores. + * + * All dependencies are only weakly referenced, allowing automatic garbage collection + * when any dependencies are no longer used. + */ +declare class WeakDependencyMap { + private readonly next; + private nextPath; + get(entity: K, getDependency: GetDependency): readonly [undefined, undefined] | readonly [V, Path[]]; + set(dependencies: Dep[], value: V): void; +} +type GetDependency = (lookup: Path) => K | undefined; +interface Dep { + path: Path; + entity: K | undefined; +} + declare const normalize: | undefined> = Record>, R = NormalizeNullable>(schema: S | undefined, input: any, args?: readonly any[], { entities, indexes, entitiesMeta }?: StoreData, meta?: NormalizeMeta) => NormalizedSchema; +declare abstract class BaseDelegate { + entities: any; + indexes: any; + constructor({ entities, indexes }: { + entities: any; + indexes: any; + }); + abstract getEntities(path: EntitiesPath): object | undefined; + abstract getEntity(path: EntityPath): object | undefined; + abstract getIndex(path: IndexPath): object | undefined; + abstract getIndexEnd(entity: any, value: string): string | undefined; + getDependency: (path: QueryPath) => object | undefined; + tracked(schema: any): [delegate: IQueryDelegate, dependencies: Dep[]]; +} + +interface EntityCache extends Map>>> { +} +type EndpointsCache = WeakDependencyMap; + type DelegateClass = new (v: { entities: any; indexes: any; -}) => IBaseDelegate; +}) => BaseDelegate; /** Singleton to store the memoization cache for denormalization methods */ declare class MemoCache { /** Cache for every entity based on its dependencies and its own input */ @@ -281,7 +299,7 @@ declare class MemoCache { /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Map>; protected Delegate: DelegateClass; - constructor(Delegate?: DelegateClass); + constructor(D?: DelegateClass); /** Compute denormalized form maintaining referential equality for same inputs */ denormalize(schema: S | undefined, input: unknown, entities: any, args?: readonly any[]): { data: DenormalizeNullable | typeof INVALID; @@ -303,7 +321,7 @@ type StateInterface = { }; }; -declare class BaseDelegate implements IBaseDelegate { +declare class Delegate extends BaseDelegate { entities: EntityTable; indexes: { [entityKey: string]: { @@ -312,29 +330,14 @@ declare class BaseDelegate implements IBaseDelegate { }; }; }; - constructor({ entities, indexes, }: { + constructor(state: { entities: EntityTable; indexes: NormalizedIndex; }); - getEntity(entityKey: string | symbol): { - readonly [pk: string]: any; - } | undefined; - getEntity(entityKey: string | symbol, pk: string | number): any; - getIndex(key: string, field: string): { - [lookup: string]: string; - }; - tracked(schema: any): TrackingQueryDelegate; -} -declare class TrackingQueryDelegate implements IQueryDelegate { - readonly INVALID: symbol; - protected snap: IBaseDelegate; - readonly dependencies: Dep[]; - constructor(snap: IBaseDelegate, schema: any); - getIndex(...path: IndexPath): string | undefined; - getEntity(entityKey: string | symbol): { - readonly [pk: string]: any; - } | undefined; - getEntity(entityKey: string | symbol, pk: string | number): any; + getEntities({ key }: EntitiesPath): any; + getEntity({ key, pk }: EntityPath): any; + getIndex({ key, field }: IndexPath): object | undefined; + getIndexEnd(entity: object | undefined, value: string): any; } /** https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#the-noinfer-utility-type */ @@ -451,4 +454,4 @@ type FetchFunction = (...args: A) => Pr declare function validateQueryKey(queryKey: unknown): boolean; -export { type AbstractInstanceType, type ArrayElement, BaseDelegate, type CheckLoop, type Denormalize, type DenormalizeNullable, type EndpointExtraOptions, type EndpointInterface, type EntityInterface, type EntityPath, type EntityTable, type ErrorTypes, ExpiryStatus, type ExpiryStatusInterface, type FetchFunction, type GetEntity, type GetIndex, type IBaseDelegate, INVALID, type INormalizeDelegate, type IQueryDelegate, type IndexInterface, type IndexParams, type InferReturn, MemoCache, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeReturnType, type NormalizedIndex, type NormalizedSchema, type OptimisticUpdateParams, type Queryable, type ReadEndpoint, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type UpdateFunction, type Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; +export { type AbstractInstanceType, type ArrayElement, BaseDelegate, type CheckLoop, Delegate, type Denormalize, type DenormalizeNullable, type EndpointExtraOptions, type EndpointInterface, type EntitiesPath, type EntityInterface, type EntityPath, type EntityTable, type ErrorTypes, ExpiryStatus, type ExpiryStatusInterface, type FetchFunction, type GetEntities, type GetEntity, type GetIndex, INVALID, type INormalizeDelegate, type IQueryDelegate, type IndexInterface, type IndexParams, type IndexPath, type InferReturn, MemoCache, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeReturnType, type NormalizedIndex, type NormalizedSchema, type OptimisticUpdateParams, type QueryPath, type Queryable, type ReadEndpoint, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type UpdateFunction, type Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; 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 a0e6cdb4f671..a2ae4d415e65 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 @@ -238,20 +238,57 @@ interface PolymorphicInterface extends Sche _normalizeNullable(): any; _denormalizeNullable(): any; } -/** Get Array of entities with map function applied */ -interface GetEntity { - (entityKey: string | symbol): { +interface NormalizedIndex { + readonly [entityKey: string]: { + readonly [indexName: string]: { + readonly [lookup: string]: string; + }; + }; +} +interface EntityTable { + [entityKey: string]: { + [pk: string]: unknown; + } | undefined; +} +/** Visits next data + schema while recurisvely normalizing */ +interface Visit { + (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; + creating?: boolean; +} +interface EntityPath { + key: string; + pk: string; +} +interface IndexPath { + key: string; + field: string; + value: string; +} +interface EntitiesPath { + key: string; +} +/** Returns true if a circular reference is found */ +interface CheckLoop { + (entityKey: string, pk: string, input: object): boolean; +} +/** Get all normalized entities of one type from store */ +interface GetEntities { + (path: EntitiesPath): { readonly [pk: string]: any; } | undefined; - (entityKey: string | symbol, pk: string | number): any; +} +/** Get normalized Entity from store */ +interface GetEntity { + (path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (entityKey: string, field: string, value: string): string | undefined; + (path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { + getEntities: GetEntities; getEntity: GetEntity; getIndex: GetIndex; /** Return to consider results invalid */ @@ -265,6 +302,8 @@ interface INormalizeDelegate { date: number; expiresAt: number; }; + /** Get all normalized entities of one type from store */ + getEntities: GetEntities; /** Gets any previously normalized entity from store */ getEntity: GetEntity; /** Updates an entity using merge lifecycles when it has previously been set */ @@ -1759,4 +1798,4 @@ declare class NetworkError extends Error { constructor(response: Response); } -export { type AbstractInstanceType, type AddEndpoint, Array$1 as Array, Collection, type CustomResource, type DefaultArgs, type Defaults, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, Entity, type EntityFields, type EntityMap, EntityMixin, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type ExtendedResource, type FetchFunction, type FetchGet, type FetchMutate, type FromFallBack, type GetEndpoint, type HookResource, type HookableEndpointInterface, type INormalizeDelegate, type IQueryDelegate, type RestEndpoint$1 as IRestEndpoint, Invalidate, type KeyofEndpointInstance, type KeyofRestEndpoint, type KeysToArgs, type Mergeable, type MethodToSide, type MutateEndpoint, type NI, NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedNullableObject, type ObjectArgs, type OptionsToFunction, type PaginationEndpoint, type PaginationFieldEndpoint, type ParamFetchNoBody, type ParamFetchWithBody, type ParamToArgs, type PartialRestGenerics, type PathArgs, type PathArgsAndSearch, type PathKeys, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Resource, type ResourceEndpointExtensions, type ResourceExtension, type ResourceGenerics, type ResourceInterface, type ResourceOptions, RestEndpoint, type RestEndpointConstructor, type RestEndpointConstructorOptions, type RestEndpointExtendOptions, type RestEndpointOptions, type RestExtendedEndpoint, type RestFetch, type RestGenerics, type RestInstance, type RestInstanceBase, type RestType, type RestTypeNoBody, type RestTypeWithBody, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type ShortenPath, type SnapshotInterface, type UnknownError, resource as createResource, getUrlBase, getUrlTokens, hookifyResource, resource, schema_d as schema, validateRequired }; +export { type AbstractInstanceType, type AddEndpoint, Array$1 as Array, type CheckLoop, Collection, type CustomResource, type DefaultArgs, type Defaults, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type ExtendedResource, type FetchFunction, type FetchGet, type FetchMutate, type FromFallBack, type GetEndpoint, type GetEntities, type GetEntity, type GetIndex, type HookResource, type HookableEndpointInterface, type INormalizeDelegate, type IQueryDelegate, type RestEndpoint$1 as IRestEndpoint, type IndexPath, Invalidate, type KeyofEndpointInstance, type KeyofRestEndpoint, type KeysToArgs, type Mergeable, type MethodToSide, type MutateEndpoint, type NI, NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type OptionsToFunction, type PaginationEndpoint, type PaginationFieldEndpoint, type ParamFetchNoBody, type ParamFetchWithBody, type ParamToArgs, type PartialRestGenerics, type PathArgs, type PathArgsAndSearch, type PathKeys, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Resource, type ResourceEndpointExtensions, type ResourceExtension, type ResourceGenerics, type ResourceInterface, type ResourceOptions, RestEndpoint, type RestEndpointConstructor, type RestEndpointConstructorOptions, type RestEndpointExtendOptions, type RestEndpointOptions, type RestExtendedEndpoint, type RestFetch, type RestGenerics, type RestInstance, type RestInstanceBase, type RestType, type RestTypeNoBody, type RestTypeWithBody, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type ShortenPath, type SnapshotInterface, type UnknownError, type Visit, resource as createResource, getUrlBase, getUrlTokens, hookifyResource, resource, schema_d as schema, validateRequired }; diff --git a/website/src/components/Playground/editor-types/globals.d.ts b/website/src/components/Playground/editor-types/globals.d.ts index e432a127da88..fd734ab0edb8 100644 --- a/website/src/components/Playground/editor-types/globals.d.ts +++ b/website/src/components/Playground/editor-types/globals.d.ts @@ -242,20 +242,57 @@ interface PolymorphicInterface extends Sche _normalizeNullable(): any; _denormalizeNullable(): any; } -/** Get Array of entities with map function applied */ -interface GetEntity { - (entityKey: string | symbol): { +interface NormalizedIndex { + readonly [entityKey: string]: { + readonly [indexName: string]: { + readonly [lookup: string]: string; + }; + }; +} +interface EntityTable { + [entityKey: string]: { + [pk: string]: unknown; + } | undefined; +} +/** Visits next data + schema while recurisvely normalizing */ +interface Visit { + (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; + creating?: boolean; +} +interface EntityPath { + key: string; + pk: string; +} +interface IndexPath { + key: string; + field: string; + value: string; +} +interface EntitiesPath { + key: string; +} +/** Returns true if a circular reference is found */ +interface CheckLoop { + (entityKey: string, pk: string, input: object): boolean; +} +/** Get all normalized entities of one type from store */ +interface GetEntities { + (path: EntitiesPath): { readonly [pk: string]: any; } | undefined; - (entityKey: string | symbol, pk: string | number): any; +} +/** Get normalized Entity from store */ +interface GetEntity { + (path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (entityKey: string, field: string, value: string): string | undefined; + (path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { + getEntities: GetEntities; getEntity: GetEntity; getIndex: GetIndex; /** Return to consider results invalid */ @@ -269,6 +306,8 @@ interface INormalizeDelegate { date: number; expiresAt: number; }; + /** Get all normalized entities of one type from store */ + getEntities: GetEntities; /** Gets any previously normalized entity from store */ getEntity: GetEntity; /** Updates an entity using merge lifecycles when it has previously been set */ @@ -1939,4 +1978,4 @@ declare function useController(): Controller; declare function useLive>(endpoint: E, ...args: readonly [...Parameters]): E['schema'] extends undefined | null ? ResolveType$1 : Denormalize$1; declare function useLive>(endpoint: E, ...args: readonly [...Parameters] | readonly [null]): E['schema'] extends undefined | null ? ResolveType$1 | undefined : DenormalizeNullable$1; -export { type AbstractInstanceType, type AddEndpoint, Array$1 as Array, _default as AsyncBoundary, Collection, type CustomResource, DataProvider, type DefaultArgs, type Defaults, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, Entity, type EntityFields, type EntityMap, EntityMixin, type ErrorTypes$1 as ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type ExtendedResource, type FetchFunction, type FetchGet, type FetchMutate, type FromFallBack, type GetEndpoint, type HookResource, type HookableEndpointInterface, type INormalizeDelegate, type IQueryDelegate, type RestEndpoint$1 as IRestEndpoint, Invalidate, type KeyofEndpointInstance, type KeyofRestEndpoint, type KeysToArgs, type Mergeable, type MethodToSide, type MutateEndpoint, type NI, NetworkError, ErrorBoundary as NetworkErrorBoundary, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedNullableObject, type ObjectArgs, type OptionsToFunction, type PaginationEndpoint, type PaginationFieldEndpoint, type ParamFetchNoBody, type ParamFetchWithBody, type ParamToArgs, type PartialRestGenerics, type PathArgs, type PathArgsAndSearch, type PathKeys, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Resource, type ResourceEndpointExtensions, type ResourceExtension, type ResourceGenerics, type ResourceInterface, type ResourceOptions, RestEndpoint, type RestEndpointConstructor, type RestEndpointConstructorOptions, type RestEndpointExtendOptions, type RestEndpointOptions, type RestExtendedEndpoint, type RestFetch, type RestGenerics, type RestInstance, type RestInstanceBase, type RestType, type RestTypeNoBody, type RestTypeWithBody, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type ShortenPath, type SnapshotInterface, type UnknownError, resource as createResource, getUrlBase, getUrlTokens, hookifyResource, resource, schema_d as schema, useCache, useController, useDLE, useError, useFetch, useLive, useQuery, useSubscription, useSuspense, validateRequired }; +export { type AbstractInstanceType, type AddEndpoint, Array$1 as Array, _default as AsyncBoundary, type CheckLoop, Collection, type CustomResource, DataProvider, type DefaultArgs, type Defaults, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes$1 as ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type ExtendedResource, type FetchFunction, type FetchGet, type FetchMutate, type FromFallBack, type GetEndpoint, type GetEntities, type GetEntity, type GetIndex, type HookResource, type HookableEndpointInterface, type INormalizeDelegate, type IQueryDelegate, type RestEndpoint$1 as IRestEndpoint, type IndexPath, Invalidate, type KeyofEndpointInstance, type KeyofRestEndpoint, type KeysToArgs, type Mergeable, type MethodToSide, type MutateEndpoint, type NI, NetworkError, ErrorBoundary as NetworkErrorBoundary, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type OptionsToFunction, type PaginationEndpoint, type PaginationFieldEndpoint, type ParamFetchNoBody, type ParamFetchWithBody, type ParamToArgs, type PartialRestGenerics, type PathArgs, type PathArgsAndSearch, type PathKeys, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Resource, type ResourceEndpointExtensions, type ResourceExtension, type ResourceGenerics, type ResourceInterface, type ResourceOptions, RestEndpoint, type RestEndpointConstructor, type RestEndpointConstructorOptions, type RestEndpointExtendOptions, type RestEndpointOptions, type RestExtendedEndpoint, type RestFetch, type RestGenerics, type RestInstance, type RestInstanceBase, type RestType, type RestTypeNoBody, type RestTypeWithBody, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type ShortenPath, type SnapshotInterface, type UnknownError, type Visit, resource as createResource, getUrlBase, getUrlTokens, hookifyResource, resource, schema_d as schema, useCache, useController, useDLE, useError, useFetch, useLive, useQuery, useSubscription, useSuspense, validateRequired }; From 5a0e3ca7834cfb9a23aa1e0e0f6eb4fa22699fd6 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Sat, 12 Apr 2025 18:59:45 +0100 Subject: [PATCH 3/9] enhance: Change entity path arguments from object to tuple --- packages/core/src/controller/Controller.ts | 2 +- packages/core/src/state/GCPolicy.ts | 24 ++++++------ .../core/src/state/__tests__/GCPolicy.test.ts | 20 +++++----- packages/core/src/state/__tests__/reducer.ts | 8 ++-- .../core/src/state/reducer/createReducer.ts | 2 +- packages/endpoint/src/interface.ts | 21 +++-------- packages/endpoint/src/schemas/All.ts | 4 +- packages/endpoint/src/schemas/Collection.ts | 4 +- packages/endpoint/src/schemas/EntityMixin.ts | 4 +- packages/normalizr/src/__tests__/MemoCache.ts | 6 +-- .../src/__tests__/WeakDependencyMap.test.ts | 6 +-- .../__tests__/__snapshots__/MemoCache.ts.snap | 16 ++++---- .../normalizr/src/denormalize/getEntities.ts | 4 +- packages/normalizr/src/denormalize/unvisit.ts | 3 +- packages/normalizr/src/interface.ts | 21 +++-------- packages/normalizr/src/memo/BaseDelegate.ts | 28 ++++++-------- .../normalizr/src/memo/Delegate.immutable.ts | 8 ++-- packages/normalizr/src/memo/Delegate.ts | 14 ++----- packages/normalizr/src/memo/globalCache.ts | 6 +-- .../src/normalize/NormalizeDelegate.ts | 2 +- .../editor-types/@data-client/core.d.ts | 27 +++++--------- .../editor-types/@data-client/endpoint.d.ts | 21 +++-------- .../editor-types/@data-client/graphql.d.ts | 21 +++-------- .../editor-types/@data-client/normalizr.d.ts | 37 +++++++------------ .../editor-types/@data-client/rest.d.ts | 21 +++-------- .../Playground/editor-types/globals.d.ts | 21 +++-------- 26 files changed, 133 insertions(+), 218 deletions(-) diff --git a/packages/core/src/controller/Controller.ts b/packages/core/src/controller/Controller.ts index 71b03a7b6a26..0f5dc6ec16b7 100644 --- a/packages/core/src/controller/Controller.ts +++ b/packages/core/src/controller/Controller.ts @@ -650,7 +650,7 @@ function entityExpiresAt( }, ) { let expiresAt = Infinity; - for (const { pk, key } of paths) { + for (const [key, pk] of paths) { const entityExpiry = entitiesMeta[key]?.[pk]?.expiresAt; // expiresAt will always resolve to false with any comparison if (entityExpiry < expiresAt) expiresAt = entityExpiry; diff --git a/packages/core/src/state/GCPolicy.ts b/packages/core/src/state/GCPolicy.ts index 08c5cf72e699..fa284527e6e6 100644 --- a/packages/core/src/state/GCPolicy.ts +++ b/packages/core/src/state/GCPolicy.ts @@ -46,11 +46,12 @@ export class GCPolicy implements GCInterface { if (key) this.endpointCount.set(key, (this.endpointCount.get(key) ?? 0) + 1); paths.forEach(path => { - if (!this.entityCount.has(path.key)) { - this.entityCount.set(path.key, new Map()); + const [key, pk] = path; + if (!this.entityCount.has(key)) { + this.entityCount.set(key, new Map()); } - const instanceCount = this.entityCount.get(path.key)!; - instanceCount.set(path.pk, (instanceCount.get(path.pk) ?? 0) + 1); + const instanceCount = this.entityCount.get(key)!; + instanceCount.set(pk, (instanceCount.get(pk) ?? 0) + 1); }); // decrement @@ -68,18 +69,19 @@ export class GCPolicy implements GCInterface { } } paths.forEach(path => { - if (!this.entityCount.has(path.key)) { + const [key, pk] = path; + if (!this.entityCount.has(key)) { return; } - const instanceCount = this.entityCount.get(path.key)!; - const entityCount = instanceCount.get(path.pk)!; + const instanceCount = this.entityCount.get(key)!; + const entityCount = instanceCount.get(pk)!; if (entityCount !== undefined) { if (entityCount <= 1) { - instanceCount.delete(path.pk); + instanceCount.delete(pk); // queue for cleanup this.entitiesQ.push(path); } else { - instanceCount.set(path.pk, entityCount - 1); + instanceCount.set(pk, entityCount - 1); } } }); @@ -131,9 +133,9 @@ export class GCPolicy implements GCInterface { const nextEntitiesQ: EntityPath[] = []; for (const path of this.entitiesQ) { if ( - !this.entityCount.get(path.key)?.has(path.pk) && + !this.entityCount.get(path[0])?.has(path[1]) && this.expiresAt( - state.entitiesMeta[path.key]?.[path.pk] ?? { + state.entitiesMeta[path[0]]?.[path[1]] ?? { fetchedAt: 0, date: 0, expiresAt: 0, diff --git a/packages/core/src/state/__tests__/GCPolicy.test.ts b/packages/core/src/state/__tests__/GCPolicy.test.ts index 1ee755a68890..f42a015c7195 100644 --- a/packages/core/src/state/__tests__/GCPolicy.test.ts +++ b/packages/core/src/state/__tests__/GCPolicy.test.ts @@ -25,7 +25,7 @@ describe('GCPolicy', () => { it('should increment and decrement endpoint and entity counts', () => { const key = 'testEndpoint'; - const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; + const paths: EntityPath[] = [['testEntity', '1']]; const countRef = gcPolicy.createCountRef({ key, paths }); const decrement = countRef(); @@ -39,7 +39,7 @@ describe('GCPolicy', () => { it('should dispatch GC action once no ref counts and is expired', () => { const key = 'testEndpoint'; - const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; + const paths: EntityPath[] = [['testEntity', '1']]; const state = { meta: { testEndpoint: { @@ -74,14 +74,14 @@ describe('GCPolicy', () => { gcPolicy['runSweep'](); expect(controller.dispatch).toHaveBeenCalledWith({ type: GC, - entities: [{ key: 'testEntity', pk: '1' }], + entities: [['testEntity', '1']], endpoints: ['testEndpoint'], }); }); it('should dispatch GC action once no ref counts and is expired with extra decrements', () => { const key = 'testEndpoint'; - const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; + const paths: EntityPath[] = [['testEntity', '1']]; const state = { meta: { testEndpoint: { @@ -117,14 +117,14 @@ describe('GCPolicy', () => { gcPolicy['runSweep'](); expect(controller.dispatch).toHaveBeenCalledWith({ type: GC, - entities: [{ key: 'testEntity', pk: '1' }], + entities: [['testEntity', '1']], endpoints: ['testEndpoint'], }); }); it('should dispatch GC action once no ref counts and no expiry state', () => { const key = 'testEndpoint'; - const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; + const paths: EntityPath[] = [['testEntity', '1']]; const state = { meta: {}, entitiesMeta: {}, @@ -145,7 +145,7 @@ describe('GCPolicy', () => { gcPolicy['runSweep'](); expect(controller.dispatch).toHaveBeenCalledWith({ type: GC, - entities: [{ key: 'testEntity', pk: '1' }], + entities: [['testEntity', '1']], endpoints: ['testEndpoint'], }); }); @@ -153,7 +153,7 @@ describe('GCPolicy', () => { it('should not dispatch GC action if expiresAt has not passed, but dispatch later when it has', () => { jest.useFakeTimers(); const key = 'testEndpoint'; - const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; + const paths: EntityPath[] = [['testEntity', '1']]; const futureTime = Date.now() + 1000; const state = { meta: { @@ -211,7 +211,7 @@ describe('GCPolicy', () => { expect(controller.dispatch).toHaveBeenCalledWith({ type: GC, - entities: [{ key: 'testEntity', pk: '1' }], + entities: [['testEntity', '1']], endpoints: ['testEndpoint'], }); @@ -223,7 +223,7 @@ describe('GCPolicy', () => { gcPolicy = new GCPolicy({ expiresAt: () => 0 }); gcPolicy.init(controller); const key = 'testEndpoint'; - const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; + const paths: EntityPath[] = [['testEntity', '1']]; const futureTime = Date.now() + 1000; const state = { meta: { diff --git a/packages/core/src/state/__tests__/reducer.ts b/packages/core/src/state/__tests__/reducer.ts index 367dfe652a45..1778d5aa9e0a 100644 --- a/packages/core/src/state/__tests__/reducer.ts +++ b/packages/core/src/state/__tests__/reducer.ts @@ -720,8 +720,8 @@ describe('reducer', () => { const action: GCAction = { type: GC, entities: [ - { key: Article.key, pk: '10' }, - { key: Article.key, pk: '250' }, + [Article.key, '10'], + [Article.key, '250'], ], endpoints: ['abc'], }; @@ -739,8 +739,8 @@ describe('reducer', () => { const action: GCAction = { type: GC, entities: [ - { key: Article.key, pk: '100000000' }, - { key: 'sillythings', pk: '10' }, + [Article.key, '100000000'], + ['sillythings', '10'], ], endpoints: [], }; diff --git a/packages/core/src/state/reducer/createReducer.ts b/packages/core/src/state/reducer/createReducer.ts index 45411c37139a..e4ca79169034 100644 --- a/packages/core/src/state/reducer/createReducer.ts +++ b/packages/core/src/state/reducer/createReducer.ts @@ -26,7 +26,7 @@ export default function createReducer(controller: Controller): ReducerType { switch (action.type) { case GC: // inline deletes are fine as these should have 0 refcounts - action.entities.forEach(({ key, pk }) => { + action.entities.forEach(([key, pk]) => { delete (state as any).entities[key]?.[pk]; delete (state as any).entitiesMeta[key]?.[pk]; }); diff --git a/packages/endpoint/src/interface.ts b/packages/endpoint/src/interface.ts index e2e895917f89..412b6b96c055 100644 --- a/packages/endpoint/src/interface.ts +++ b/packages/endpoint/src/interface.ts @@ -113,18 +113,9 @@ export interface Visit { creating?: boolean; } -export interface EntityPath { - key: string; - pk: string; -} -export interface IndexPath { - key: string; - field: string; - value: string; -} -export interface EntitiesPath { - key: string; -} +export type EntityPath = [key: string, pk: string]; +export type IndexPath = [key: string, index: string, value: string]; +export type EntitiesPath = [key: string]; /** Returns true if a circular reference is found */ export interface CheckLoop { @@ -133,16 +124,16 @@ export interface CheckLoop { /** Get all normalized entities of one type from store */ export interface GetEntities { - (path: EntitiesPath): { readonly [pk: string]: any } | undefined; + (...path: EntitiesPath): { readonly [pk: string]: any } | undefined; } /** Get normalized Entity from store */ export interface GetEntity { - (path: EntityPath): any; + (...path: EntityPath): any; } /** Get PK using an Entity Index */ export interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (path: IndexPath): string | undefined; + (...path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ diff --git a/packages/endpoint/src/schemas/All.ts b/packages/endpoint/src/schemas/All.ts index 945f7af7363b..48251b81a051 100644 --- a/packages/endpoint/src/schemas/All.ts +++ b/packages/endpoint/src/schemas/All.ts @@ -26,7 +26,7 @@ export default class AllSchema< queryKey(args: any, unvisit: any, delegate: IQueryDelegate): any { if (this.isSingleSchema) { - const entitiesEntry = delegate.getEntities({ key: this.schema.key }); + const entitiesEntry = delegate.getEntities(this.schema.key); // we must wait until there are entries for any 'All' query to be Valid if (entitiesEntry === undefined) return delegate.INVALID; return Object.values(entitiesEntry).map( @@ -36,7 +36,7 @@ export default class AllSchema< let found = false; const list = Object.values(this.schema as Record).flatMap( (schema: EntityInterface) => { - const entitiesEntry = delegate.getEntities({ key: schema.key }); + const entitiesEntry = delegate.getEntities(schema.key); if (entitiesEntry === undefined) return []; found = true; return Object.entries(entitiesEntry).map(([key, entity]) => ({ diff --git a/packages/endpoint/src/schemas/Collection.ts b/packages/endpoint/src/schemas/Collection.ts index a57f651ecb75..058c83dc4483 100644 --- a/packages/endpoint/src/schemas/Collection.ts +++ b/packages/endpoint/src/schemas/Collection.ts @@ -221,7 +221,7 @@ export default class CollectionSchema< if (this.argsKey) { const pk = this.pk(undefined, undefined, '', args); // ensure this actually has entity or we shouldn't try to use it in our query - if (delegate.getEntity({ key: this.key, pk })) return pk; + if (delegate.getEntity(this.key, pk)) return pk; } } @@ -326,7 +326,7 @@ function normalizeCreate( // parent is args when not nested const filterCollections = (this.createCollectionFilter as any)(...args); // add to any collections that match this - const entities = delegate.getEntities({ key: this.key }); + const entities = delegate.getEntities(this.key); if (entities) Object.keys(entities).forEach(collectionPk => { if (!filterCollections(JSON.parse(collectionPk))) return; diff --git a/packages/endpoint/src/schemas/EntityMixin.ts b/packages/endpoint/src/schemas/EntityMixin.ts index fde6d3b9d886..48fb68153afd 100644 --- a/packages/endpoint/src/schemas/EntityMixin.ts +++ b/packages/endpoint/src/schemas/EntityMixin.ts @@ -329,7 +329,7 @@ export default function EntityMixin( if (!args[0]) return; const pk = queryKeyCandidate(this, args, delegate); // ensure this actually has entity or we shouldn't try to use it in our query - if (pk && delegate.getEntity({ key: this.key, pk })) return pk; + if (pk && delegate.getEntity(this.key, pk)) return pk; } static denormalize( @@ -487,5 +487,5 @@ function queryKeyCandidate( const field = indexFromParams(args[0], schema.indexes); if (!field) return; const value = (args[0] as Record)[field]; - return delegate.getIndex({ key: schema.key, field, value }); + return delegate.getIndex(schema.key, field, value); } diff --git a/packages/normalizr/src/__tests__/MemoCache.ts b/packages/normalizr/src/__tests__/MemoCache.ts index ec2ad023fb95..f1dfe3214787 100644 --- a/packages/normalizr/src/__tests__/MemoCache.ts +++ b/packages/normalizr/src/__tests__/MemoCache.ts @@ -903,11 +903,7 @@ describe('MemoCache', () => { pk = this.pk(args[0], undefined, '', args); } // Was able to infer the entity's primary key from params - if ( - pk !== undefined && - pk !== '' && - delegate.getEntity({ key: this.key, pk }) - ) + if (pk !== undefined && pk !== '' && delegate.getEntity(this.key, pk)) return pk; } } diff --git a/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts b/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts index 2a6a603a3d39..8df2b036abc8 100644 --- a/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts +++ b/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts @@ -20,9 +20,9 @@ describe('WeakDependencyMap', () => { }, }; const getEntity = getEntities(state); - const depA = { path: { key: 'A', pk: '1' }, entity: a }; - const depB = { path: { key: 'B', pk: '2' }, entity: b }; - const depC = { path: { key: 'C', pk: '3' }, entity: c }; + const depA = { path: ['A', '1'] as EntityPath, entity: a }; + const depB = { path: ['B', '2'] as EntityPath, entity: b }; + const depC = { path: ['C', '3'] as EntityPath, entity: c }; it('should construct', () => { const wem = new WeakDependencyMap(); diff --git a/packages/normalizr/src/__tests__/__snapshots__/MemoCache.ts.snap b/packages/normalizr/src/__tests__/__snapshots__/MemoCache.ts.snap index 16f58d463274..31a031ea560f 100644 --- a/packages/normalizr/src/__tests__/__snapshots__/MemoCache.ts.snap +++ b/packages/normalizr/src/__tests__/__snapshots__/MemoCache.ts.snap @@ -16,10 +16,10 @@ exports[`MemoCache query (direct) works with pk 1`] = ` "username": "m", }, "paths": [ - { - "key": "Cat", - "pk": "1", - }, + [ + "Cat", + "1", + ], ], } `; @@ -40,10 +40,10 @@ exports[`MemoCache query (immutable) works with pk 1`] = ` "username": "m", }, "paths": [ - { - "key": "Cat", - "pk": "1", - }, + [ + "Cat", + "1", + ], ], } `; diff --git a/packages/normalizr/src/denormalize/getEntities.ts b/packages/normalizr/src/denormalize/getEntities.ts index c19827b31345..c9c39498e610 100644 --- a/packages/normalizr/src/denormalize/getEntities.ts +++ b/packages/normalizr/src/denormalize/getEntities.ts @@ -8,9 +8,9 @@ export function getEntities( const entityIsImmutable = isImmutable(state); if (entityIsImmutable) { - return ({ key, pk }: EntityPath) => state.getIn([key, pk]); + return (path: EntityPath) => state.getIn(path); } else { - return ({ key, pk }: EntityPath) => state[key]?.[pk]; + return ([key, pk]: EntityPath) => state[key]?.[pk]; } } diff --git a/packages/normalizr/src/denormalize/unvisit.ts b/packages/normalizr/src/denormalize/unvisit.ts index 234fb1626871..62ca7b9d0c69 100644 --- a/packages/normalizr/src/denormalize/unvisit.ts +++ b/packages/normalizr/src/denormalize/unvisit.ts @@ -18,8 +18,7 @@ const getUnvisitEntity = ( entityOrId: Record | string, ): object | undefined | typeof INVALID { const inputIsId = typeof entityOrId !== 'object'; - const entity = - inputIsId ? getEntity({ key: schema.key, pk: entityOrId }) : entityOrId; + const entity = inputIsId ? getEntity([schema.key, entityOrId]) : entityOrId; if (typeof entity === 'symbol') { return schema.denormalize(entity, args, unvisit); } diff --git a/packages/normalizr/src/interface.ts b/packages/normalizr/src/interface.ts index 2c3a3f183ef4..a456c05be72c 100644 --- a/packages/normalizr/src/interface.ts +++ b/packages/normalizr/src/interface.ts @@ -99,18 +99,9 @@ export interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; } -export interface EntityPath { - key: string; - pk: string; -} -export interface IndexPath { - key: string; - field: string; - value: string; -} -export interface EntitiesPath { - key: string; -} +export type EntityPath = [key: string, pk: string]; +export type IndexPath = [key: string, index: string, value: string]; +export type EntitiesPath = [key: string]; export type QueryPath = IndexPath | EntityPath | EntitiesPath; /** Returns true if a circular reference is found */ @@ -120,16 +111,16 @@ export interface CheckLoop { /** Get all normalized entities of one type from store */ export interface GetEntities { - (path: EntitiesPath): { readonly [pk: string]: any } | undefined; + (...path: EntitiesPath): { readonly [pk: string]: any } | undefined; } /** Get normalized Entity from store */ export interface GetEntity { - (path: EntityPath): any; + (...path: EntityPath): any; } /** Get PK using an Entity Index */ export interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (path: IndexPath): string | undefined; + (...path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ diff --git a/packages/normalizr/src/memo/BaseDelegate.ts b/packages/normalizr/src/memo/BaseDelegate.ts index 8a38186f5f85..19f5ad4fbc57 100644 --- a/packages/normalizr/src/memo/BaseDelegate.ts +++ b/packages/normalizr/src/memo/BaseDelegate.ts @@ -17,38 +17,34 @@ export abstract class BaseDelegate { this.indexes = indexes; } - abstract getEntities(path: EntitiesPath): object | undefined; - abstract getEntity(path: EntityPath): object | undefined; - abstract getIndex(path: IndexPath): object | undefined; + abstract getEntities(...path: EntitiesPath): object | undefined; + abstract getEntity(...path: EntityPath): object | undefined; + abstract getIndex(...path: IndexPath): object | undefined; abstract getIndexEnd(entity: any, value: string): string | undefined; getDependency = (path: QueryPath): object | undefined => - 'pk' in path ? this.getEntity(path) - : 'field' in path ? this.getIndex(path) - : this.getEntities(path); + this[['', 'getEntities', 'getEntity', 'getIndex'][path.length]](...path); tracked( schema: any, ): [delegate: IQueryDelegate, dependencies: Dep[]] { // eslint-disable-next-line @typescript-eslint/no-this-alias const base = this; - const dependencies: Dep[] = [ - { path: { key: '' }, entity: schema }, - ]; + const dependencies: Dep[] = [{ path: [''], entity: schema }]; const delegate = { INVALID, - getIndex(path: IndexPath): string | undefined { - const entity = base.getIndex(path); + getIndex(...path: IndexPath): string | undefined { + const entity = base.getIndex(...path); dependencies.push({ path, entity }); - return base.getIndexEnd(entity, path.value); + return base.getIndexEnd(entity, path[2]); }, - getEntity(path: EntityPath) { - const entity = base.getEntity(path); + getEntity(...path: EntityPath) { + const entity = base.getEntity(...path); dependencies.push({ path, entity }); return entity; }, - getEntities(path: EntitiesPath) { - const entity = base.getEntities(path); + getEntities(...path: EntitiesPath) { + const entity = base.getEntities(...path); dependencies.push({ path, entity }); return entity; }, diff --git a/packages/normalizr/src/memo/Delegate.immutable.ts b/packages/normalizr/src/memo/Delegate.immutable.ts index fdf049afaf1e..0faa7dd1003e 100644 --- a/packages/normalizr/src/memo/Delegate.immutable.ts +++ b/packages/normalizr/src/memo/Delegate.immutable.ts @@ -18,16 +18,16 @@ export class DelegateImmutable extends BaseDelegate { super(state); } - getEntities({ key }: EntitiesPath): any { + getEntities(key: string): any { return this.entities.get(key)?.toJS(); } - getEntity({ key, pk }: EntityPath): any { - return this.entities.getIn([key, pk]); + getEntity(...path: EntityPath): any { + return this.entities.getIn(path); } // this is different return value than QuerySnapshot - getIndex({ key, field }: IndexPath): object | undefined { + getIndex(key: string, field: string): object | undefined { return this.indexes.getIn([key, field]); } diff --git a/packages/normalizr/src/memo/Delegate.ts b/packages/normalizr/src/memo/Delegate.ts index 7c7d67c48b35..54f2270609c4 100644 --- a/packages/normalizr/src/memo/Delegate.ts +++ b/packages/normalizr/src/memo/Delegate.ts @@ -1,10 +1,4 @@ -import type { - EntityTable, - NormalizedIndex, - EntityPath, - IndexPath, - EntitiesPath, -} from '../interface.js'; +import type { EntityTable, NormalizedIndex } from '../interface.js'; import { BaseDelegate } from './BaseDelegate.js'; export class PlainDelegate extends BaseDelegate { @@ -19,16 +13,16 @@ export class PlainDelegate extends BaseDelegate { super(state); } - getEntities({ key }: EntitiesPath): any { + getEntities(key: string): any { return this.entities[key]; } - getEntity({ key, pk }: EntityPath): any { + getEntity(key: string, pk: string): any { return this.entities[key]?.[pk]; } // this is different return value than QuerySnapshot - getIndex({ key, field }: IndexPath): object | undefined { + getIndex(key: string, field: string): object | undefined { return this.indexes[key]?.[field]; } diff --git a/packages/normalizr/src/memo/globalCache.ts b/packages/normalizr/src/memo/globalCache.ts index 5aca2a12044e..8d48d0e9a542 100644 --- a/packages/normalizr/src/memo/globalCache.ts +++ b/packages/normalizr/src/memo/globalCache.ts @@ -58,7 +58,7 @@ export default class GlobalCache implements Cache { else { const trackingIndex = this.dependencies.length; cycleCacheKey.set(pk, trackingIndex); - this.dependencies.push({ path: { key, pk }, entity }); + this.dependencies.push({ path: [key, pk], entity }); /** NON-GLOBAL_CACHE CODE */ computeValue(localCacheKey); @@ -87,7 +87,7 @@ export default class GlobalCache implements Cache { this.cycleIndex = cycleCacheKey.get(pk)!; } else { // with no cycle, globalCacheEntry will have already been set - this.dependencies.push({ path: { key, pk }, entity }); + this.dependencies.push({ path: [key, pk], entity }); } } return localCacheKey.get(pk); @@ -125,7 +125,7 @@ export default class GlobalCache implements Cache { // we want to do this before we add our 'input' entry paths = this.paths(); // for the first entry, `path` is ignored so empty members is fine - this.dependencies.unshift({ path: { key: '', pk: '' }, entity: input }); + this.dependencies.unshift({ path: ['', ''], entity: input }); this.resultCache.set(this.dependencies, data); } else { paths.shift(); diff --git a/packages/normalizr/src/normalize/NormalizeDelegate.ts b/packages/normalizr/src/normalize/NormalizeDelegate.ts index 37be99408e2e..de76a1254b62 100644 --- a/packages/normalizr/src/normalize/NormalizeDelegate.ts +++ b/packages/normalizr/src/normalize/NormalizeDelegate.ts @@ -96,7 +96,7 @@ export class NormalizeDelegate nextEntity = schema.merge(entity, incomingEntity); } else { // if we find it in the store - entity = this.getEntity({ key, pk }); + entity = this.getEntity(key, pk); if (entity) { const meta = this.getMeta(key, pk); nextEntity = schema.mergeWithStore( 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 a54363f9adf0..cf5a070c8514 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 @@ -49,33 +49,24 @@ interface NormalizedIndex { }; }; } -interface EntityPath { - key: string; - pk: string; -} -interface IndexPath { - key: string; - field: string; - value: string; -} -interface EntitiesPath { - key: string; -} +type EntityPath = [key: string, pk: string]; +type IndexPath = [key: string, index: string, value: string]; +type EntitiesPath = [key: string]; type QueryPath = IndexPath | EntityPath | EntitiesPath; /** Get all normalized entities of one type from store */ interface GetEntities { - (path: EntitiesPath): { + (...path: EntitiesPath): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (path: EntityPath): any; + (...path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (path: IndexPath): string | undefined; + (...path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { @@ -234,9 +225,9 @@ declare abstract class BaseDelegate { entities: any; indexes: any; }); - abstract getEntities(path: EntitiesPath): object | undefined; - abstract getEntity(path: EntityPath): object | undefined; - abstract getIndex(path: IndexPath): object | undefined; + abstract getEntities(...path: EntitiesPath): object | undefined; + abstract getEntity(...path: EntityPath): object | undefined; + abstract getIndex(...path: IndexPath): object | undefined; abstract getIndexEnd(entity: any, value: string): string | undefined; getDependency: (path: QueryPath) => object | undefined; tracked(schema: any): [delegate: IQueryDelegate, dependencies: Dep[]]; 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 3d0d748904e2..0bbf62914b19 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 @@ -253,36 +253,27 @@ interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; creating?: boolean; } -interface EntityPath { - key: string; - pk: string; -} -interface IndexPath { - key: string; - field: string; - value: string; -} -interface EntitiesPath { - key: string; -} +type EntityPath = [key: string, pk: string]; +type IndexPath = [key: string, index: string, value: string]; +type EntitiesPath = [key: string]; /** Returns true if a circular reference is found */ interface CheckLoop { (entityKey: string, pk: string, input: object): boolean; } /** Get all normalized entities of one type from store */ interface GetEntities { - (path: EntitiesPath): { + (...path: EntitiesPath): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (path: EntityPath): any; + (...path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (path: IndexPath): string | undefined; + (...path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { 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 2919496e7fb0..a1009e5cb144 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 @@ -253,36 +253,27 @@ interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; creating?: boolean; } -interface EntityPath { - key: string; - pk: string; -} -interface IndexPath { - key: string; - field: string; - value: string; -} -interface EntitiesPath { - key: string; -} +type EntityPath = [key: string, pk: string]; +type IndexPath = [key: string, index: string, value: string]; +type EntitiesPath = [key: string]; /** Returns true if a circular reference is found */ interface CheckLoop { (entityKey: string, pk: string, input: object): boolean; } /** Get all normalized entities of one type from store */ interface GetEntities { - (path: EntitiesPath): { + (...path: EntitiesPath): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (path: EntityPath): any; + (...path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (path: IndexPath): string | undefined; + (...path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { 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 9b8f556d603b..4c3d80759808 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 @@ -58,18 +58,9 @@ interface EntityTable { interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; } -interface EntityPath { - key: string; - pk: string; -} -interface IndexPath { - key: string; - field: string; - value: string; -} -interface EntitiesPath { - key: string; -} +type EntityPath = [key: string, pk: string]; +type IndexPath = [key: string, index: string, value: string]; +type EntitiesPath = [key: string]; type QueryPath = IndexPath | EntityPath | EntitiesPath; /** Returns true if a circular reference is found */ interface CheckLoop { @@ -77,18 +68,18 @@ interface CheckLoop { } /** Get all normalized entities of one type from store */ interface GetEntities { - (path: EntitiesPath): { + (...path: EntitiesPath): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (path: EntityPath): any; + (...path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (path: IndexPath): string | undefined; + (...path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { @@ -274,9 +265,9 @@ declare abstract class BaseDelegate { entities: any; indexes: any; }); - abstract getEntities(path: EntitiesPath): object | undefined; - abstract getEntity(path: EntityPath): object | undefined; - abstract getIndex(path: IndexPath): object | undefined; + abstract getEntities(...path: EntitiesPath): object | undefined; + abstract getEntity(...path: EntityPath): object | undefined; + abstract getIndex(...path: IndexPath): object | undefined; abstract getIndexEnd(entity: any, value: string): string | undefined; getDependency: (path: QueryPath) => object | undefined; tracked(schema: any): [delegate: IQueryDelegate, dependencies: Dep[]]; @@ -321,7 +312,7 @@ type StateInterface = { }; }; -declare class Delegate extends BaseDelegate { +declare class PlainDelegate extends BaseDelegate { entities: EntityTable; indexes: { [entityKey: string]: { @@ -334,9 +325,9 @@ declare class Delegate extends BaseDelegate { entities: EntityTable; indexes: NormalizedIndex; }); - getEntities({ key }: EntitiesPath): any; - getEntity({ key, pk }: EntityPath): any; - getIndex({ key, field }: IndexPath): object | undefined; + getEntities(key: string): any; + getEntity(key: string, pk: string): any; + getIndex(key: string, field: string): object | undefined; getIndexEnd(entity: object | undefined, value: string): any; } @@ -454,4 +445,4 @@ type FetchFunction = (...args: A) => Pr declare function validateQueryKey(queryKey: unknown): boolean; -export { type AbstractInstanceType, type ArrayElement, BaseDelegate, type CheckLoop, Delegate, type Denormalize, type DenormalizeNullable, type EndpointExtraOptions, type EndpointInterface, type EntitiesPath, type EntityInterface, type EntityPath, type EntityTable, type ErrorTypes, ExpiryStatus, type ExpiryStatusInterface, type FetchFunction, type GetEntities, type GetEntity, type GetIndex, INVALID, type INormalizeDelegate, type IQueryDelegate, type IndexInterface, type IndexParams, type IndexPath, type InferReturn, MemoCache, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeReturnType, type NormalizedIndex, type NormalizedSchema, type OptimisticUpdateParams, type QueryPath, type Queryable, type ReadEndpoint, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type UpdateFunction, type Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; +export { type AbstractInstanceType, type ArrayElement, BaseDelegate, type CheckLoop, PlainDelegate as Delegate, type Denormalize, type DenormalizeNullable, type EndpointExtraOptions, type EndpointInterface, type EntitiesPath, type EntityInterface, type EntityPath, type EntityTable, type ErrorTypes, ExpiryStatus, type ExpiryStatusInterface, type FetchFunction, type GetEntities, type GetEntity, type GetIndex, INVALID, type INormalizeDelegate, type IQueryDelegate, type IndexInterface, type IndexParams, type IndexPath, type InferReturn, MemoCache, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeReturnType, type NormalizedIndex, type NormalizedSchema, type OptimisticUpdateParams, type QueryPath, type Queryable, type ReadEndpoint, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type UpdateFunction, type Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; 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 a2ae4d415e65..53e49034b5d1 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 @@ -255,36 +255,27 @@ interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; creating?: boolean; } -interface EntityPath { - key: string; - pk: string; -} -interface IndexPath { - key: string; - field: string; - value: string; -} -interface EntitiesPath { - key: string; -} +type EntityPath = [key: string, pk: string]; +type IndexPath = [key: string, index: string, value: string]; +type EntitiesPath = [key: string]; /** Returns true if a circular reference is found */ interface CheckLoop { (entityKey: string, pk: string, input: object): boolean; } /** Get all normalized entities of one type from store */ interface GetEntities { - (path: EntitiesPath): { + (...path: EntitiesPath): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (path: EntityPath): any; + (...path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (path: IndexPath): string | undefined; + (...path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { diff --git a/website/src/components/Playground/editor-types/globals.d.ts b/website/src/components/Playground/editor-types/globals.d.ts index fd734ab0edb8..4b374f9ae48a 100644 --- a/website/src/components/Playground/editor-types/globals.d.ts +++ b/website/src/components/Playground/editor-types/globals.d.ts @@ -259,36 +259,27 @@ interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; creating?: boolean; } -interface EntityPath { - key: string; - pk: string; -} -interface IndexPath { - key: string; - field: string; - value: string; -} -interface EntitiesPath { - key: string; -} +type EntityPath = [key: string, pk: string]; +type IndexPath = [key: string, index: string, value: string]; +type EntitiesPath = [key: string]; /** Returns true if a circular reference is found */ interface CheckLoop { (entityKey: string, pk: string, input: object): boolean; } /** Get all normalized entities of one type from store */ interface GetEntities { - (path: EntitiesPath): { + (...path: EntitiesPath): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (path: EntityPath): any; + (...path: EntityPath): any; } /** Get PK using an Entity Index */ interface GetIndex { /** getIndex('User', 'username', 'ntucker') */ - (path: IndexPath): string | undefined; + (...path: IndexPath): string | undefined; } /** Accessors to the currently processing state while building query */ interface IQueryDelegate { From d2704736da297fc42f122492da592772fd1207f5 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Mon, 14 Apr 2025 13:20:55 +0100 Subject: [PATCH 4/9] enhance: Fix performance regression --- packages/normalizr/src/memo/BaseDelegate.ts | 7 +++++-- packages/normalizr/src/memo/MemoCache.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/normalizr/src/memo/BaseDelegate.ts b/packages/normalizr/src/memo/BaseDelegate.ts index 19f5ad4fbc57..8ee7d6651aea 100644 --- a/packages/normalizr/src/memo/BaseDelegate.ts +++ b/packages/normalizr/src/memo/BaseDelegate.ts @@ -22,8 +22,11 @@ export abstract class BaseDelegate { abstract getIndex(...path: IndexPath): object | undefined; abstract getIndexEnd(entity: any, value: string): string | undefined; - getDependency = (path: QueryPath): object | undefined => - this[['', 'getEntities', 'getEntity', 'getIndex'][path.length]](...path); + getDependency(path: QueryPath): object | undefined { + return this[['', 'getEntities', 'getEntity', 'getIndex'][path.length]]( + ...path, + ); + } tracked( schema: any, diff --git a/packages/normalizr/src/memo/MemoCache.ts b/packages/normalizr/src/memo/MemoCache.ts index 45e6aa8c8c29..9d58717daed7 100644 --- a/packages/normalizr/src/memo/MemoCache.ts +++ b/packages/normalizr/src/memo/MemoCache.ts @@ -114,7 +114,7 @@ export default class MemoCache { // eslint-disable-next-line prefer-const let [value, paths] = queryCache.get( schema as any, - baseDelegate.getDependency, + baseDelegate.getDependency.bind(baseDelegate), ); // paths undefined is the only way to truly tell nothing was found (the value could have actually been undefined) From 14f58e36325938bd341bc639b42c54df9f58d94c Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Mon, 14 Apr 2025 13:29:13 +0100 Subject: [PATCH 5/9] enhance: getDependency back to own function --- packages/normalizr/src/memo/BaseDelegate.ts | 13 +++++++------ packages/normalizr/src/memo/MemoCache.ts | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/normalizr/src/memo/BaseDelegate.ts b/packages/normalizr/src/memo/BaseDelegate.ts index 8ee7d6651aea..1b574b2800a8 100644 --- a/packages/normalizr/src/memo/BaseDelegate.ts +++ b/packages/normalizr/src/memo/BaseDelegate.ts @@ -8,6 +8,13 @@ import type { IQueryDelegate, } from '../interface.js'; +export const getDependency = + (delegate: BaseDelegate) => + (path: QueryPath): object | undefined => + delegate[['', 'getEntities', 'getEntity', 'getIndex'][path.length]]( + ...path, + ); + export abstract class BaseDelegate { declare entities: any; declare indexes: any; @@ -22,12 +29,6 @@ export abstract class BaseDelegate { abstract getIndex(...path: IndexPath): object | undefined; abstract getIndexEnd(entity: any, value: string): string | undefined; - getDependency(path: QueryPath): object | undefined { - return this[['', 'getEntities', 'getEntity', 'getIndex'][path.length]]( - ...path, - ); - } - tracked( schema: any, ): [delegate: IQueryDelegate, dependencies: Dep[]] { diff --git a/packages/normalizr/src/memo/MemoCache.ts b/packages/normalizr/src/memo/MemoCache.ts index 9d58717daed7..68d33fdbe238 100644 --- a/packages/normalizr/src/memo/MemoCache.ts +++ b/packages/normalizr/src/memo/MemoCache.ts @@ -1,7 +1,7 @@ import GlobalCache from './globalCache.js'; import WeakDependencyMap from './WeakDependencyMap.js'; import buildQueryKey from '../buildQueryKey.js'; -import type { BaseDelegate } from './BaseDelegate.js'; +import { getDependency, type BaseDelegate } from './BaseDelegate.js'; import { PlainDelegate } from './Delegate.js'; import { getEntities } from '../denormalize/getEntities.js'; import getUnvisit from '../denormalize/unvisit.js'; @@ -114,7 +114,7 @@ export default class MemoCache { // eslint-disable-next-line prefer-const let [value, paths] = queryCache.get( schema as any, - baseDelegate.getDependency.bind(baseDelegate), + getDependency(baseDelegate), ); // paths undefined is the only way to truly tell nothing was found (the value could have actually been undefined) From 1e463dc60d3b706c3432d5e188dc403d3411ca8a Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Mon, 14 Apr 2025 14:36:36 +0100 Subject: [PATCH 6/9] fix: Export --- packages/normalizr/src/index.ts | 2 +- .../components/Playground/editor-types/@data-client/core.d.ts | 1 - .../Playground/editor-types/@data-client/normalizr.d.ts | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/normalizr/src/index.ts b/packages/normalizr/src/index.ts index d0616bd769ce..ebd02750337b 100644 --- a/packages/normalizr/src/index.ts +++ b/packages/normalizr/src/index.ts @@ -5,7 +5,7 @@ import { normalize } from './normalize/normalize.js'; export { default as MemoCache } from './memo/MemoCache.js'; export { BaseDelegate } from './memo/BaseDelegate.js'; -export { PlainDelegate as Delegate } from './memo/Delegate.js'; +export { PlainDelegate } from './memo/Delegate.js'; export type { AbstractInstanceType, NormalizeReturnType, 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 cf5a070c8514..2ef7ff095e9c 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 @@ -229,7 +229,6 @@ declare abstract class BaseDelegate { abstract getEntity(...path: EntityPath): object | undefined; abstract getIndex(...path: IndexPath): object | undefined; abstract getIndexEnd(entity: any, value: string): string | undefined; - getDependency: (path: QueryPath) => object | undefined; tracked(schema: any): [delegate: IQueryDelegate, dependencies: Dep[]]; } 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 4c3d80759808..35b404dc382c 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 @@ -269,7 +269,6 @@ declare abstract class BaseDelegate { abstract getEntity(...path: EntityPath): object | undefined; abstract getIndex(...path: IndexPath): object | undefined; abstract getIndexEnd(entity: any, value: string): string | undefined; - getDependency: (path: QueryPath) => object | undefined; tracked(schema: any): [delegate: IQueryDelegate, dependencies: Dep[]]; } @@ -445,4 +444,4 @@ type FetchFunction = (...args: A) => Pr declare function validateQueryKey(queryKey: unknown): boolean; -export { type AbstractInstanceType, type ArrayElement, BaseDelegate, type CheckLoop, PlainDelegate as Delegate, type Denormalize, type DenormalizeNullable, type EndpointExtraOptions, type EndpointInterface, type EntitiesPath, type EntityInterface, type EntityPath, type EntityTable, type ErrorTypes, ExpiryStatus, type ExpiryStatusInterface, type FetchFunction, type GetEntities, type GetEntity, type GetIndex, INVALID, type INormalizeDelegate, type IQueryDelegate, type IndexInterface, type IndexParams, type IndexPath, type InferReturn, MemoCache, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeReturnType, type NormalizedIndex, type NormalizedSchema, type OptimisticUpdateParams, type QueryPath, type Queryable, type ReadEndpoint, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type UpdateFunction, type Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; +export { type AbstractInstanceType, type ArrayElement, BaseDelegate, type CheckLoop, type Denormalize, type DenormalizeNullable, type EndpointExtraOptions, type EndpointInterface, type EntitiesPath, type EntityInterface, type EntityPath, type EntityTable, type ErrorTypes, ExpiryStatus, type ExpiryStatusInterface, type FetchFunction, type GetEntities, type GetEntity, type GetIndex, INVALID, type INormalizeDelegate, type IQueryDelegate, type IndexInterface, type IndexParams, type IndexPath, type InferReturn, MemoCache, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeReturnType, type NormalizedIndex, type NormalizedSchema, type OptimisticUpdateParams, PlainDelegate, type QueryPath, type Queryable, type ReadEndpoint, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type UpdateFunction, type Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; From 9732403b0bb7c10f7305e391b2a28d0e5c421d2d Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Mon, 14 Apr 2025 12:06:07 +0100 Subject: [PATCH 7/9] enhance: Switch denormalize paths back to object --- packages/core/src/controller/Controller.ts | 2 +- packages/core/src/state/GCPolicy.ts | 8 ++++---- .../core/src/state/__tests__/GCPolicy.test.ts | 20 +++++++++---------- packages/core/src/state/__tests__/reducer.ts | 8 ++++---- .../core/src/state/reducer/createReducer.ts | 2 +- packages/endpoint/src/interface.ts | 11 +++++++--- .../src/schemas/__tests__/All.test.ts | 4 ++-- .../src/schemas/__tests__/Query.test.ts | 4 ++-- .../src/__tests__/WeakDependencyMap.test.ts | 6 +++--- .../__tests__/__snapshots__/MemoCache.ts.snap | 16 +++++++-------- .../normalizr/src/denormalize/getEntities.ts | 4 ++-- packages/normalizr/src/denormalize/unvisit.ts | 3 ++- packages/normalizr/src/interface.ts | 13 ++++++++---- packages/normalizr/src/memo/BaseDelegate.ts | 5 ++--- .../normalizr/src/memo/Delegate.immutable.ts | 2 +- packages/normalizr/src/memo/globalCache.ts | 6 +++--- 16 files changed, 62 insertions(+), 52 deletions(-) diff --git a/packages/core/src/controller/Controller.ts b/packages/core/src/controller/Controller.ts index 0f5dc6ec16b7..fdc08c6a5f28 100644 --- a/packages/core/src/controller/Controller.ts +++ b/packages/core/src/controller/Controller.ts @@ -650,7 +650,7 @@ function entityExpiresAt( }, ) { let expiresAt = Infinity; - for (const [key, pk] of paths) { + for (const { key, pk } of paths) { const entityExpiry = entitiesMeta[key]?.[pk]?.expiresAt; // expiresAt will always resolve to false with any comparison if (entityExpiry < expiresAt) expiresAt = entityExpiry; diff --git a/packages/core/src/state/GCPolicy.ts b/packages/core/src/state/GCPolicy.ts index fa284527e6e6..d63c8ff98549 100644 --- a/packages/core/src/state/GCPolicy.ts +++ b/packages/core/src/state/GCPolicy.ts @@ -46,7 +46,7 @@ export class GCPolicy implements GCInterface { if (key) this.endpointCount.set(key, (this.endpointCount.get(key) ?? 0) + 1); paths.forEach(path => { - const [key, pk] = path; + const { key, pk } = path; if (!this.entityCount.has(key)) { this.entityCount.set(key, new Map()); } @@ -69,7 +69,7 @@ export class GCPolicy implements GCInterface { } } paths.forEach(path => { - const [key, pk] = path; + const { key, pk } = path; if (!this.entityCount.has(key)) { return; } @@ -133,9 +133,9 @@ export class GCPolicy implements GCInterface { const nextEntitiesQ: EntityPath[] = []; for (const path of this.entitiesQ) { if ( - !this.entityCount.get(path[0])?.has(path[1]) && + !this.entityCount.get(path.key)?.has(path.pk) && this.expiresAt( - state.entitiesMeta[path[0]]?.[path[1]] ?? { + state.entitiesMeta[path.key]?.[path.pk] ?? { fetchedAt: 0, date: 0, expiresAt: 0, diff --git a/packages/core/src/state/__tests__/GCPolicy.test.ts b/packages/core/src/state/__tests__/GCPolicy.test.ts index f42a015c7195..1ee755a68890 100644 --- a/packages/core/src/state/__tests__/GCPolicy.test.ts +++ b/packages/core/src/state/__tests__/GCPolicy.test.ts @@ -25,7 +25,7 @@ describe('GCPolicy', () => { it('should increment and decrement endpoint and entity counts', () => { const key = 'testEndpoint'; - const paths: EntityPath[] = [['testEntity', '1']]; + const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; const countRef = gcPolicy.createCountRef({ key, paths }); const decrement = countRef(); @@ -39,7 +39,7 @@ describe('GCPolicy', () => { it('should dispatch GC action once no ref counts and is expired', () => { const key = 'testEndpoint'; - const paths: EntityPath[] = [['testEntity', '1']]; + const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; const state = { meta: { testEndpoint: { @@ -74,14 +74,14 @@ describe('GCPolicy', () => { gcPolicy['runSweep'](); expect(controller.dispatch).toHaveBeenCalledWith({ type: GC, - entities: [['testEntity', '1']], + entities: [{ key: 'testEntity', pk: '1' }], endpoints: ['testEndpoint'], }); }); it('should dispatch GC action once no ref counts and is expired with extra decrements', () => { const key = 'testEndpoint'; - const paths: EntityPath[] = [['testEntity', '1']]; + const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; const state = { meta: { testEndpoint: { @@ -117,14 +117,14 @@ describe('GCPolicy', () => { gcPolicy['runSweep'](); expect(controller.dispatch).toHaveBeenCalledWith({ type: GC, - entities: [['testEntity', '1']], + entities: [{ key: 'testEntity', pk: '1' }], endpoints: ['testEndpoint'], }); }); it('should dispatch GC action once no ref counts and no expiry state', () => { const key = 'testEndpoint'; - const paths: EntityPath[] = [['testEntity', '1']]; + const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; const state = { meta: {}, entitiesMeta: {}, @@ -145,7 +145,7 @@ describe('GCPolicy', () => { gcPolicy['runSweep'](); expect(controller.dispatch).toHaveBeenCalledWith({ type: GC, - entities: [['testEntity', '1']], + entities: [{ key: 'testEntity', pk: '1' }], endpoints: ['testEndpoint'], }); }); @@ -153,7 +153,7 @@ describe('GCPolicy', () => { it('should not dispatch GC action if expiresAt has not passed, but dispatch later when it has', () => { jest.useFakeTimers(); const key = 'testEndpoint'; - const paths: EntityPath[] = [['testEntity', '1']]; + const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; const futureTime = Date.now() + 1000; const state = { meta: { @@ -211,7 +211,7 @@ describe('GCPolicy', () => { expect(controller.dispatch).toHaveBeenCalledWith({ type: GC, - entities: [['testEntity', '1']], + entities: [{ key: 'testEntity', pk: '1' }], endpoints: ['testEndpoint'], }); @@ -223,7 +223,7 @@ describe('GCPolicy', () => { gcPolicy = new GCPolicy({ expiresAt: () => 0 }); gcPolicy.init(controller); const key = 'testEndpoint'; - const paths: EntityPath[] = [['testEntity', '1']]; + const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }]; const futureTime = Date.now() + 1000; const state = { meta: { diff --git a/packages/core/src/state/__tests__/reducer.ts b/packages/core/src/state/__tests__/reducer.ts index 1778d5aa9e0a..367dfe652a45 100644 --- a/packages/core/src/state/__tests__/reducer.ts +++ b/packages/core/src/state/__tests__/reducer.ts @@ -720,8 +720,8 @@ describe('reducer', () => { const action: GCAction = { type: GC, entities: [ - [Article.key, '10'], - [Article.key, '250'], + { key: Article.key, pk: '10' }, + { key: Article.key, pk: '250' }, ], endpoints: ['abc'], }; @@ -739,8 +739,8 @@ describe('reducer', () => { const action: GCAction = { type: GC, entities: [ - [Article.key, '100000000'], - ['sillythings', '10'], + { key: Article.key, pk: '100000000' }, + { key: 'sillythings', pk: '10' }, ], endpoints: [], }; diff --git a/packages/core/src/state/reducer/createReducer.ts b/packages/core/src/state/reducer/createReducer.ts index e4ca79169034..45411c37139a 100644 --- a/packages/core/src/state/reducer/createReducer.ts +++ b/packages/core/src/state/reducer/createReducer.ts @@ -26,7 +26,7 @@ export default function createReducer(controller: Controller): ReducerType { switch (action.type) { case GC: // inline deletes are fine as these should have 0 refcounts - action.entities.forEach(([key, pk]) => { + action.entities.forEach(({ key, pk }) => { delete (state as any).entities[key]?.[pk]; delete (state as any).entitiesMeta[key]?.[pk]; }); diff --git a/packages/endpoint/src/interface.ts b/packages/endpoint/src/interface.ts index 412b6b96c055..a67820d28218 100644 --- a/packages/endpoint/src/interface.ts +++ b/packages/endpoint/src/interface.ts @@ -113,7 +113,12 @@ export interface Visit { creating?: boolean; } -export type EntityPath = [key: string, pk: string]; +/** Used in denormalize. Lookup to find an entity in the store table */ +export interface EntityPath { + key: string; + pk: string; +} + export type IndexPath = [key: string, index: string, value: string]; export type EntitiesPath = [key: string]; @@ -124,11 +129,11 @@ export interface CheckLoop { /** Get all normalized entities of one type from store */ export interface GetEntities { - (...path: EntitiesPath): { readonly [pk: string]: any } | undefined; + (key: string): { readonly [pk: string]: any } | undefined; } /** Get normalized Entity from store */ export interface GetEntity { - (...path: EntityPath): any; + (key: string, pk: string): any; } /** Get PK using an Entity Index */ export interface GetIndex { diff --git a/packages/endpoint/src/schemas/__tests__/All.test.ts b/packages/endpoint/src/schemas/__tests__/All.test.ts index 85a1907088d2..d6d718cd3d4f 100644 --- a/packages/endpoint/src/schemas/__tests__/All.test.ts +++ b/packages/endpoint/src/schemas/__tests__/All.test.ts @@ -5,7 +5,7 @@ import { MemoCache, denormalize, INVALID, - Delegate, + PlainDelegate, } from '@data-client/normalizr'; import { DelegateImmutable } from '@data-client/normalizr/immutable'; import { IDEntity } from '__tests__/new'; @@ -103,7 +103,7 @@ describe.each([[]])(`${schema.All.name} normalization (%s)`, () => { }); describe.each([ - ['direct', (data: T) => data, (data: T) => data, Delegate], + ['direct', (data: T) => data, (data: T) => data, PlainDelegate], [ 'immutable', fromJSState, diff --git a/packages/endpoint/src/schemas/__tests__/Query.test.ts b/packages/endpoint/src/schemas/__tests__/Query.test.ts index 4fd520188538..928f2d23a57b 100644 --- a/packages/endpoint/src/schemas/__tests__/Query.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Query.test.ts @@ -1,5 +1,5 @@ // eslint-env jest -import { MemoCache, Delegate } from '@data-client/normalizr'; +import { MemoCache, PlainDelegate } from '@data-client/normalizr'; import { DelegateImmutable } from '@data-client/normalizr/immutable'; import { useQuery, useSuspense, __INTERNAL__ } from '@data-client/react'; import { RestEndpoint } from '@data-client/rest'; @@ -27,7 +27,7 @@ class User extends IDEntity { } describe.each([ - ['direct', (data: T) => data, (data: T) => data, Delegate], + ['direct', (data: T) => data, (data: T) => data, PlainDelegate], [ 'immutable', fromJSState, diff --git a/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts b/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts index 8df2b036abc8..2a6a603a3d39 100644 --- a/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts +++ b/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts @@ -20,9 +20,9 @@ describe('WeakDependencyMap', () => { }, }; const getEntity = getEntities(state); - const depA = { path: ['A', '1'] as EntityPath, entity: a }; - const depB = { path: ['B', '2'] as EntityPath, entity: b }; - const depC = { path: ['C', '3'] as EntityPath, entity: c }; + const depA = { path: { key: 'A', pk: '1' }, entity: a }; + const depB = { path: { key: 'B', pk: '2' }, entity: b }; + const depC = { path: { key: 'C', pk: '3' }, entity: c }; it('should construct', () => { const wem = new WeakDependencyMap(); diff --git a/packages/normalizr/src/__tests__/__snapshots__/MemoCache.ts.snap b/packages/normalizr/src/__tests__/__snapshots__/MemoCache.ts.snap index 31a031ea560f..16f58d463274 100644 --- a/packages/normalizr/src/__tests__/__snapshots__/MemoCache.ts.snap +++ b/packages/normalizr/src/__tests__/__snapshots__/MemoCache.ts.snap @@ -16,10 +16,10 @@ exports[`MemoCache query (direct) works with pk 1`] = ` "username": "m", }, "paths": [ - [ - "Cat", - "1", - ], + { + "key": "Cat", + "pk": "1", + }, ], } `; @@ -40,10 +40,10 @@ exports[`MemoCache query (immutable) works with pk 1`] = ` "username": "m", }, "paths": [ - [ - "Cat", - "1", - ], + { + "key": "Cat", + "pk": "1", + }, ], } `; diff --git a/packages/normalizr/src/denormalize/getEntities.ts b/packages/normalizr/src/denormalize/getEntities.ts index c9c39498e610..c19827b31345 100644 --- a/packages/normalizr/src/denormalize/getEntities.ts +++ b/packages/normalizr/src/denormalize/getEntities.ts @@ -8,9 +8,9 @@ export function getEntities( const entityIsImmutable = isImmutable(state); if (entityIsImmutable) { - return (path: EntityPath) => state.getIn(path); + return ({ key, pk }: EntityPath) => state.getIn([key, pk]); } else { - return ([key, pk]: EntityPath) => state[key]?.[pk]; + return ({ key, pk }: EntityPath) => state[key]?.[pk]; } } diff --git a/packages/normalizr/src/denormalize/unvisit.ts b/packages/normalizr/src/denormalize/unvisit.ts index 62ca7b9d0c69..234fb1626871 100644 --- a/packages/normalizr/src/denormalize/unvisit.ts +++ b/packages/normalizr/src/denormalize/unvisit.ts @@ -18,7 +18,8 @@ const getUnvisitEntity = ( entityOrId: Record | string, ): object | undefined | typeof INVALID { const inputIsId = typeof entityOrId !== 'object'; - const entity = inputIsId ? getEntity([schema.key, entityOrId]) : entityOrId; + const entity = + inputIsId ? getEntity({ key: schema.key, pk: entityOrId }) : entityOrId; if (typeof entity === 'symbol') { return schema.denormalize(entity, args, unvisit); } diff --git a/packages/normalizr/src/interface.ts b/packages/normalizr/src/interface.ts index a456c05be72c..e815179a2163 100644 --- a/packages/normalizr/src/interface.ts +++ b/packages/normalizr/src/interface.ts @@ -99,10 +99,15 @@ export interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; } -export type EntityPath = [key: string, pk: string]; +/** Used in denormalize. Lookup to find an entity in the store table */ +export interface EntityPath { + key: string; + pk: string; +} + export type IndexPath = [key: string, index: string, value: string]; export type EntitiesPath = [key: string]; -export type QueryPath = IndexPath | EntityPath | EntitiesPath; +export type QueryPath = IndexPath | [key: string, pk: string] | EntitiesPath; /** Returns true if a circular reference is found */ export interface CheckLoop { @@ -111,11 +116,11 @@ export interface CheckLoop { /** Get all normalized entities of one type from store */ export interface GetEntities { - (...path: EntitiesPath): { readonly [pk: string]: any } | undefined; + (key: string): { readonly [pk: string]: any } | undefined; } /** Get normalized Entity from store */ export interface GetEntity { - (...path: EntityPath): any; + (key: string, pk: string): any; } /** Get PK using an Entity Index */ export interface GetIndex { diff --git a/packages/normalizr/src/memo/BaseDelegate.ts b/packages/normalizr/src/memo/BaseDelegate.ts index 1b574b2800a8..276a730a6287 100644 --- a/packages/normalizr/src/memo/BaseDelegate.ts +++ b/packages/normalizr/src/memo/BaseDelegate.ts @@ -1,7 +1,6 @@ import type { Dep } from './WeakDependencyMap.js'; import { INVALID } from '../denormalize/symbol.js'; import type { - EntityPath, QueryPath, IndexPath, EntitiesPath, @@ -25,7 +24,7 @@ export abstract class BaseDelegate { } abstract getEntities(...path: EntitiesPath): object | undefined; - abstract getEntity(...path: EntityPath): object | undefined; + abstract getEntity(key: string, pk: string): object | undefined; abstract getIndex(...path: IndexPath): object | undefined; abstract getIndexEnd(entity: any, value: string): string | undefined; @@ -42,7 +41,7 @@ export abstract class BaseDelegate { dependencies.push({ path, entity }); return base.getIndexEnd(entity, path[2]); }, - getEntity(...path: EntityPath) { + getEntity(...path: [key: string, pk: string]) { const entity = base.getEntity(...path); dependencies.push({ path, entity }); return entity; diff --git a/packages/normalizr/src/memo/Delegate.immutable.ts b/packages/normalizr/src/memo/Delegate.immutable.ts index 0faa7dd1003e..1f97df63b9b7 100644 --- a/packages/normalizr/src/memo/Delegate.immutable.ts +++ b/packages/normalizr/src/memo/Delegate.immutable.ts @@ -22,7 +22,7 @@ export class DelegateImmutable extends BaseDelegate { return this.entities.get(key)?.toJS(); } - getEntity(...path: EntityPath): any { + getEntity(...path: [key: string, pk: string]): any { return this.entities.getIn(path); } diff --git a/packages/normalizr/src/memo/globalCache.ts b/packages/normalizr/src/memo/globalCache.ts index 8d48d0e9a542..5aca2a12044e 100644 --- a/packages/normalizr/src/memo/globalCache.ts +++ b/packages/normalizr/src/memo/globalCache.ts @@ -58,7 +58,7 @@ export default class GlobalCache implements Cache { else { const trackingIndex = this.dependencies.length; cycleCacheKey.set(pk, trackingIndex); - this.dependencies.push({ path: [key, pk], entity }); + this.dependencies.push({ path: { key, pk }, entity }); /** NON-GLOBAL_CACHE CODE */ computeValue(localCacheKey); @@ -87,7 +87,7 @@ export default class GlobalCache implements Cache { this.cycleIndex = cycleCacheKey.get(pk)!; } else { // with no cycle, globalCacheEntry will have already been set - this.dependencies.push({ path: [key, pk], entity }); + this.dependencies.push({ path: { key, pk }, entity }); } } return localCacheKey.get(pk); @@ -125,7 +125,7 @@ export default class GlobalCache implements Cache { // we want to do this before we add our 'input' entry paths = this.paths(); // for the first entry, `path` is ignored so empty members is fine - this.dependencies.unshift({ path: ['', ''], entity: input }); + this.dependencies.unshift({ path: { key: '', pk: '' }, entity: input }); this.resultCache.set(this.dependencies, data); } else { paths.shift(); From 3a3984d40d0a500abccfa8376915da3f41d1c0f7 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Tue, 15 Apr 2025 16:51:41 +0100 Subject: [PATCH 8/9] enhance: Hoist getEntityCaches computation to MemoCache init --- .../normalizr/src/denormalize/localCache.ts | 2 +- packages/normalizr/src/memo/MemoCache.ts | 9 ++- packages/normalizr/src/memo/entitiesCache.ts | 33 ++++++++++ packages/normalizr/src/memo/globalCache.ts | 60 ++++--------------- .../normalizr/src/normalize/getCheckLoop.ts | 8 +-- .../editor-types/@data-client/core.d.ts | 20 ++++--- .../editor-types/@data-client/endpoint.d.ts | 10 +++- .../editor-types/@data-client/graphql.d.ts | 10 +++- .../editor-types/@data-client/normalizr.d.ts | 20 ++++--- .../editor-types/@data-client/rest.d.ts | 10 +++- .../Playground/editor-types/globals.d.ts | 10 +++- 11 files changed, 105 insertions(+), 87 deletions(-) create mode 100644 packages/normalizr/src/memo/entitiesCache.ts diff --git a/packages/normalizr/src/denormalize/localCache.ts b/packages/normalizr/src/denormalize/localCache.ts index 890ad7967e22..52954a93f548 100644 --- a/packages/normalizr/src/denormalize/localCache.ts +++ b/packages/normalizr/src/denormalize/localCache.ts @@ -15,7 +15,7 @@ export default class LocalCache implements Cache { if (!this.localCache.has(key)) { this.localCache.set(key, new Map()); } - const localCacheKey = this.localCache.get(key) as Map; + const localCacheKey = this.localCache.get(key)!; if (!localCacheKey.get(pk)) { computeValue(localCacheKey); diff --git a/packages/normalizr/src/memo/MemoCache.ts b/packages/normalizr/src/memo/MemoCache.ts index 68d33fdbe238..975d8642d596 100644 --- a/packages/normalizr/src/memo/MemoCache.ts +++ b/packages/normalizr/src/memo/MemoCache.ts @@ -3,6 +3,7 @@ import WeakDependencyMap from './WeakDependencyMap.js'; import buildQueryKey from '../buildQueryKey.js'; import { getDependency, type BaseDelegate } from './BaseDelegate.js'; import { PlainDelegate } from './Delegate.js'; +import { GetEntityCache, getEntityCaches } from './entitiesCache.js'; import { getEntities } from '../denormalize/getEntities.js'; import getUnvisit from '../denormalize/unvisit.js'; import type { @@ -12,7 +13,7 @@ import type { Schema, } from '../interface.js'; import type { DenormalizeNullable, NormalizeNullable } from '../types.js'; -import { EndpointsCache, EntityCache } from './types.js'; +import { EndpointsCache } from './types.js'; import type { INVALID } from '../denormalize/symbol.js'; type DelegateClass = new (v: { entities: any; indexes: any }) => BaseDelegate; @@ -22,15 +23,17 @@ type DelegateClass = new (v: { entities: any; indexes: any }) => BaseDelegate; /** Singleton to store the memoization cache for denormalization methods */ export default class MemoCache { /** Cache for every entity based on its dependencies and its own input */ - protected entities: EntityCache = new Map(); + declare protected _getCache: GetEntityCache; /** Caches the final denormalized form based on input, entities */ protected endpoints: EndpointsCache = new WeakDependencyMap(); /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Map> = new Map(); + declare protected Delegate: DelegateClass; constructor(D: DelegateClass = PlainDelegate) { this.Delegate = D; + this._getCache = getEntityCaches(new Map()); } /** Compute denormalized form maintaining referential equality for same inputs */ @@ -58,7 +61,7 @@ export default class MemoCache { return getUnvisit( getEntity, - new GlobalCache(getEntity, this.entities, this.endpoints), + new GlobalCache(getEntity, this._getCache, this.endpoints), args, )(schema, input); } diff --git a/packages/normalizr/src/memo/entitiesCache.ts b/packages/normalizr/src/memo/entitiesCache.ts new file mode 100644 index 000000000000..18cc1c38173e --- /dev/null +++ b/packages/normalizr/src/memo/entitiesCache.ts @@ -0,0 +1,33 @@ +import { EntityInterface, EntityPath } from '../interface.js'; +import { EntityCache } from './types.js'; +import WeakDependencyMap from './WeakDependencyMap.js'; + +export type GetEntityCache = ( + pk: string, + schema: EntityInterface, +) => WeakDependencyMap; + +export const getEntityCaches = (entityCache: EntityCache): GetEntityCache => { + return (pk: string, schema: EntityInterface) => { + const key = schema.key; + // collections should use the entities they collect over + // TODO: this should be based on a public interface + const entityInstance: EntityInterface = (schema.cacheWith as any) ?? schema; + + if (!entityCache.has(key)) { + entityCache.set(key, new Map()); + } + const entityCacheKey = entityCache.get(key)!; + + if (!entityCacheKey.has(pk)) entityCacheKey.set(pk, new WeakMap()); + const entityCachePk = entityCacheKey.get(pk)!; + + let wem = entityCachePk.get(entityInstance); + if (!wem) { + wem = new WeakDependencyMap(); + entityCachePk.set(entityInstance, wem); + } + + return wem; + }; +}; diff --git a/packages/normalizr/src/memo/globalCache.ts b/packages/normalizr/src/memo/globalCache.ts index 5aca2a12044e..3b57c542a00f 100644 --- a/packages/normalizr/src/memo/globalCache.ts +++ b/packages/normalizr/src/memo/globalCache.ts @@ -1,9 +1,10 @@ -import { EndpointsCache, EntityCache } from './types.js'; +import { EndpointsCache } from './types.js'; import WeakDependencyMap, { type Dep } from './WeakDependencyMap.js'; import type Cache from '../denormalize/cache.js'; import type { GetEntity } from '../denormalize/getEntities.js'; import type { INVALID } from '../denormalize/symbol.js'; import type { EntityInterface, EntityPath } from '../interface.js'; +import type { GetEntityCache } from './entitiesCache.js'; export default class GlobalCache implements Cache { private dependencies: Dep[] = []; @@ -11,22 +12,19 @@ export default class GlobalCache implements Cache { private cycleIndex = -1; private localCache: Map> = new Map(); - declare private getCache: ( - pk: string, - schema: EntityInterface, - ) => WeakDependencyMap; + declare private _getCache: GetEntityCache; declare private _getEntity: GetEntity; - declare private resultCache: EndpointsCache; + declare private _resultCache: EndpointsCache; constructor( getEntity: GetEntity, - entityCache: EntityCache, + getCache: GetEntityCache, resultCache: EndpointsCache, ) { this._getEntity = getEntity; - this.getCache = getEntityCaches(entityCache); - this.resultCache = resultCache; + this._getCache = getCache; + this._resultCache = resultCache; } getEntity( @@ -43,7 +41,7 @@ export default class GlobalCache implements Cache { EntityPath, object, EntityCacheValue - > = this.getCache(pk, schema); + > = this._getCache(pk, schema); const [cacheValue, cachePath] = globalCache.get(entity, this._getEntity); // TODO: what if this just returned the deps - then we don't need to store them @@ -118,7 +116,7 @@ export default class GlobalCache implements Cache { return { data: computeValue(), paths: this.paths() }; } - let [data, paths] = this.resultCache.get(input, this._getEntity); + let [data, paths] = this._resultCache.get(input, this._getEntity); if (paths === undefined) { data = computeValue(); @@ -126,7 +124,7 @@ export default class GlobalCache implements Cache { paths = this.paths(); // for the first entry, `path` is ignored so empty members is fine this.dependencies.unshift({ path: { key: '', pk: '' }, entity: input }); - this.resultCache.set(this.dependencies, data); + this._resultCache.set(this.dependencies, data); } else { paths.shift(); } @@ -142,41 +140,3 @@ interface EntityCacheValue { dependencies: Dep[]; value: object | typeof INVALID | undefined; } - -const getEntityCaches = (entityCache: EntityCache) => { - return (pk: string, schema: EntityInterface) => { - const key = schema.key; - // collections should use the entities they collect over - // TODO: this should be based on a public interface - const entityInstance: EntityInterface = (schema.cacheWith as any) ?? schema; - - if (!entityCache.has(key)) { - entityCache.set(key, new Map()); - } - const entityCacheKey = entityCache.get(key)!; - if (!entityCacheKey.get(pk)) - entityCacheKey.set( - pk, - new WeakMap< - EntityInterface, - WeakDependencyMap - >(), - ); - - const entityCachePk = entityCacheKey.get(pk) as WeakMap< - EntityInterface, - WeakDependencyMap - >; - let wem = entityCachePk.get(entityInstance) as WeakDependencyMap< - EntityPath, - object, - any - >; - if (!wem) { - wem = new WeakDependencyMap(); - entityCachePk.set(entityInstance, wem); - } - - return wem; - }; -}; diff --git a/packages/normalizr/src/normalize/getCheckLoop.ts b/packages/normalizr/src/normalize/getCheckLoop.ts index 89e5989063d2..c86dbff31285 100644 --- a/packages/normalizr/src/normalize/getCheckLoop.ts +++ b/packages/normalizr/src/normalize/getCheckLoop.ts @@ -5,15 +5,13 @@ export function getCheckLoop() { if (!visitedEntities.has(entityKey)) { visitedEntities.set(entityKey, new Map()); } - // we have to tell typescript this can't be undefined (due to line above) - const entitiesOneType: Map = visitedEntities.get( - entityKey, - ) as Map; + const entitiesOneType = visitedEntities.get(entityKey)!; if (!entitiesOneType.has(pk)) { entitiesOneType.set(pk, []); } - const visitedEntityList = entitiesOneType.get(pk) as object[]; + const visitedEntityList = entitiesOneType.get(pk)!; + if (visitedEntityList.some((entity: any) => entity === input)) { return true; } 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 2ef7ff095e9c..232c88fbb346 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 @@ -49,19 +49,23 @@ interface NormalizedIndex { }; }; } -type EntityPath = [key: string, pk: string]; +/** Used in denormalize. Lookup to find an entity in the store table */ +interface EntityPath { + key: string; + pk: string; +} type IndexPath = [key: string, index: string, value: string]; type EntitiesPath = [key: string]; -type QueryPath = IndexPath | EntityPath | EntitiesPath; +type QueryPath = IndexPath | [key: string, pk: string] | EntitiesPath; /** Get all normalized entities of one type from store */ interface GetEntities { - (...path: EntitiesPath): { + (key: string): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (...path: EntityPath): any; + (key: string, pk: string): any; } /** Get PK using an Entity Index */ interface GetIndex { @@ -226,16 +230,16 @@ declare abstract class BaseDelegate { indexes: any; }); abstract getEntities(...path: EntitiesPath): object | undefined; - abstract getEntity(...path: EntityPath): object | undefined; + abstract getEntity(key: string, pk: string): object | undefined; abstract getIndex(...path: IndexPath): object | undefined; abstract getIndexEnd(entity: any, value: string): string | undefined; tracked(schema: any): [delegate: IQueryDelegate, dependencies: Dep[]]; } -interface EntityCache extends Map>>> { -} type EndpointsCache = WeakDependencyMap; +type GetEntityCache = (pk: string, schema: EntityInterface) => WeakDependencyMap; + type DelegateClass = new (v: { entities: any; indexes: any; @@ -243,7 +247,7 @@ type DelegateClass = new (v: { /** Singleton to store the memoization cache for denormalization methods */ declare class MemoCache { /** Cache for every entity based on its dependencies and its own input */ - protected entities: EntityCache; + protected _getCache: GetEntityCache; /** Caches the final denormalized form based on input, entities */ protected endpoints: EndpointsCache; /** Caches the queryKey based on schema, args, and any used entities or indexes */ 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 0bbf62914b19..23c95a1ad867 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 @@ -253,7 +253,11 @@ interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; creating?: boolean; } -type EntityPath = [key: string, pk: string]; +/** Used in denormalize. Lookup to find an entity in the store table */ +interface EntityPath { + key: string; + pk: string; +} type IndexPath = [key: string, index: string, value: string]; type EntitiesPath = [key: string]; /** Returns true if a circular reference is found */ @@ -262,13 +266,13 @@ interface CheckLoop { } /** Get all normalized entities of one type from store */ interface GetEntities { - (...path: EntitiesPath): { + (key: string): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (...path: EntityPath): any; + (key: string, pk: string): any; } /** Get PK using an Entity Index */ interface GetIndex { 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 a1009e5cb144..9dd0754bbe9f 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 @@ -253,7 +253,11 @@ interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; creating?: boolean; } -type EntityPath = [key: string, pk: string]; +/** Used in denormalize. Lookup to find an entity in the store table */ +interface EntityPath { + key: string; + pk: string; +} type IndexPath = [key: string, index: string, value: string]; type EntitiesPath = [key: string]; /** Returns true if a circular reference is found */ @@ -262,13 +266,13 @@ interface CheckLoop { } /** Get all normalized entities of one type from store */ interface GetEntities { - (...path: EntitiesPath): { + (key: string): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (...path: EntityPath): any; + (key: string, pk: string): any; } /** Get PK using an Entity Index */ interface GetIndex { 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 35b404dc382c..a36a76bf4fe0 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 @@ -58,23 +58,27 @@ interface EntityTable { interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; } -type EntityPath = [key: string, pk: string]; +/** Used in denormalize. Lookup to find an entity in the store table */ +interface EntityPath { + key: string; + pk: string; +} type IndexPath = [key: string, index: string, value: string]; type EntitiesPath = [key: string]; -type QueryPath = IndexPath | EntityPath | EntitiesPath; +type QueryPath = IndexPath | [key: string, pk: string] | EntitiesPath; /** Returns true if a circular reference is found */ interface CheckLoop { (entityKey: string, pk: string, input: object): boolean; } /** Get all normalized entities of one type from store */ interface GetEntities { - (...path: EntitiesPath): { + (key: string): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (...path: EntityPath): any; + (key: string, pk: string): any; } /** Get PK using an Entity Index */ interface GetIndex { @@ -266,16 +270,16 @@ declare abstract class BaseDelegate { indexes: any; }); abstract getEntities(...path: EntitiesPath): object | undefined; - abstract getEntity(...path: EntityPath): object | undefined; + abstract getEntity(key: string, pk: string): object | undefined; abstract getIndex(...path: IndexPath): object | undefined; abstract getIndexEnd(entity: any, value: string): string | undefined; tracked(schema: any): [delegate: IQueryDelegate, dependencies: Dep[]]; } -interface EntityCache extends Map>>> { -} type EndpointsCache = WeakDependencyMap; +type GetEntityCache = (pk: string, schema: EntityInterface) => WeakDependencyMap; + type DelegateClass = new (v: { entities: any; indexes: any; @@ -283,7 +287,7 @@ type DelegateClass = new (v: { /** Singleton to store the memoization cache for denormalization methods */ declare class MemoCache { /** Cache for every entity based on its dependencies and its own input */ - protected entities: EntityCache; + protected _getCache: GetEntityCache; /** Caches the final denormalized form based on input, entities */ protected endpoints: EndpointsCache; /** Caches the queryKey based on schema, args, and any used entities or indexes */ 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 53e49034b5d1..659d8424b964 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 @@ -255,7 +255,11 @@ interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; creating?: boolean; } -type EntityPath = [key: string, pk: string]; +/** Used in denormalize. Lookup to find an entity in the store table */ +interface EntityPath { + key: string; + pk: string; +} type IndexPath = [key: string, index: string, value: string]; type EntitiesPath = [key: string]; /** Returns true if a circular reference is found */ @@ -264,13 +268,13 @@ interface CheckLoop { } /** Get all normalized entities of one type from store */ interface GetEntities { - (...path: EntitiesPath): { + (key: string): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (...path: EntityPath): any; + (key: string, pk: string): any; } /** Get PK using an Entity Index */ interface GetIndex { diff --git a/website/src/components/Playground/editor-types/globals.d.ts b/website/src/components/Playground/editor-types/globals.d.ts index 4b374f9ae48a..1428107a1cff 100644 --- a/website/src/components/Playground/editor-types/globals.d.ts +++ b/website/src/components/Playground/editor-types/globals.d.ts @@ -259,7 +259,11 @@ interface Visit { (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; creating?: boolean; } -type EntityPath = [key: string, pk: string]; +/** Used in denormalize. Lookup to find an entity in the store table */ +interface EntityPath { + key: string; + pk: string; +} type IndexPath = [key: string, index: string, value: string]; type EntitiesPath = [key: string]; /** Returns true if a circular reference is found */ @@ -268,13 +272,13 @@ interface CheckLoop { } /** Get all normalized entities of one type from store */ interface GetEntities { - (...path: EntitiesPath): { + (key: string): { readonly [pk: string]: any; } | undefined; } /** Get normalized Entity from store */ interface GetEntity { - (...path: EntityPath): any; + (key: string, pk: string): any; } /** Get PK using an Entity Index */ interface GetIndex { From c01460fc23962d7c3e483f0a604eb4d1f6b722fc Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Mon, 21 Apr 2025 10:36:31 +0100 Subject: [PATCH 9/9] Integrate denormalize getEntities into MemoCache's delegate interface --- .changeset/big-impalas-tap.md | 28 + .changeset/legal-files-fly.md | 24 + .changeset/proud-taxes-bake.md | 8 + .changeset/ten-lions-retire.md | 9 + .../src/schemas/__tests__/All.test.ts | 22 +- .../src/schemas/__tests__/Array.test.js | 19 +- .../src/schemas/__tests__/Entity.test.ts | 79 +- .../src/schemas/__tests__/EntityMixin.test.ts | 85 +- .../src/schemas/__tests__/Invalidate.test.ts | 34 +- .../src/schemas/__tests__/Object.test.js | 42 +- .../src/schemas/__tests__/Query.test.ts | 8 +- .../src/schemas/__tests__/Union.test.js | 10 +- .../src/schemas/__tests__/Values.test.js | 15 +- .../src/schemas/__tests__/denormalize.ts | 6 + packages/normalizr/package.json | 6 +- packages/normalizr/src/__tests__/MemoCache.ts | 8 +- .../src/__tests__/WeakDependencyMap.test.ts | 4 +- .../normalizr/src/__tests__/immutable.test.ts | 2 +- .../src/{memo => delegate}/BaseDelegate.ts | 10 +- .../Delegate.imm.ts} | 6 +- .../src/{memo => delegate}/Delegate.ts | 3 +- .../src/denormalize/denormalize.imm.ts | 24 + .../normalizr/src/denormalize/denormalize.ts | 4 +- .../normalizr/src/denormalize/getEntities.ts | 21 - packages/normalizr/src/denormalize/unvisit.ts | 6 +- packages/normalizr/src/imm.ts | 2 + packages/normalizr/src/immutable.ts | 1 - packages/normalizr/src/index.ts | 5 +- packages/normalizr/src/memo/MemoCache.ts | 28 +- packages/normalizr/src/memo/Policy.imm.ts | 14 + packages/normalizr/src/memo/Policy.ts | 12 + packages/normalizr/src/memo/globalCache.ts | 8 +- packages/normalizr/src/memo/types.ts | 10 +- .../src/normalize/NormalizeDelegate.ts | 5 +- .../editor-types/@data-client/core.d.ts | 17 +- .../editor-types/@data-client/endpoint.d.ts | 2 +- .../editor-types/@data-client/graphql.d.ts | 2 +- .../editor-types/@data-client/normalizr.d.ts | 30 +- .../editor-types/@data-client/rest.d.ts | 2 +- .../Playground/editor-types/bignumber.d.ts | 1822 +---------------- .../Playground/editor-types/globals.d.ts | 2 +- .../Playground/editor-types/qs.d.ts | 1 + .../Playground/editor-types/react.d.ts | 48 +- 43 files changed, 446 insertions(+), 2048 deletions(-) create mode 100644 .changeset/big-impalas-tap.md create mode 100644 .changeset/legal-files-fly.md create mode 100644 .changeset/proud-taxes-bake.md create mode 100644 .changeset/ten-lions-retire.md rename packages/normalizr/src/{memo => delegate}/BaseDelegate.ts (86%) rename packages/normalizr/src/{memo/Delegate.immutable.ts => delegate/Delegate.imm.ts} (85%) rename packages/normalizr/src/{memo => delegate}/Delegate.ts (88%) create mode 100644 packages/normalizr/src/denormalize/denormalize.imm.ts delete mode 100644 packages/normalizr/src/denormalize/getEntities.ts create mode 100644 packages/normalizr/src/imm.ts delete mode 100644 packages/normalizr/src/immutable.ts create mode 100644 packages/normalizr/src/memo/Policy.imm.ts create mode 100644 packages/normalizr/src/memo/Policy.ts diff --git a/.changeset/big-impalas-tap.md b/.changeset/big-impalas-tap.md new file mode 100644 index 000000000000..41e74d826def --- /dev/null +++ b/.changeset/big-impalas-tap.md @@ -0,0 +1,28 @@ +--- +'@data-client/normalizr': patch +--- + +Add /imm exports path for handling ImmutableJS state + +#### MemoCache + +```ts +import { MemoCache } from '@data-client/normalizr'; +import { MemoPolicy } from '@data-client/normalizr/imm'; + +const memo = new MemoCache(MemoPolicy); + +// entities is an ImmutableJS Map +const value = MemoCache.denormalize(Todo, '1', entities); +``` + +#### denormalize + +non-memoized denormalize + +```ts +import { denormalize } from '@data-client/normalizr/imm'; + +// entities is an ImmutableJS Map +const value = denormalize(Todo, '1', entities); +``` \ No newline at end of file diff --git a/.changeset/legal-files-fly.md b/.changeset/legal-files-fly.md new file mode 100644 index 000000000000..b59f2aa1981e --- /dev/null +++ b/.changeset/legal-files-fly.md @@ -0,0 +1,24 @@ +--- +'@data-client/normalizr': minor +--- + +BREAKING: denormalize no longer detects ImmutableJS state + +Use `/imm` exports to handle ImmutableJS state + +#### Before + +```ts +import { MemoCache, denormalize } from '@data-client/normalizr'; + +const memo = new MemoCache(); +``` + +#### After + +```ts +import { MemoCache } from '@data-client/normalizr'; +import { MemoPolicy, denormalize } from '@data-client/normalizr/imm'; + +const memo = new MemoCache(MemoPolicy); +``` diff --git a/.changeset/proud-taxes-bake.md b/.changeset/proud-taxes-bake.md new file mode 100644 index 000000000000..e715bd51dd0e --- /dev/null +++ b/.changeset/proud-taxes-bake.md @@ -0,0 +1,8 @@ +--- +'@data-client/normalizr': minor +'@data-client/endpoint': minor +--- + +delegate.getEntity(this.key) -> delegate.getEntities(this.key) + +This applies to both schema.queryKey and schema.normalize method delegates. \ No newline at end of file diff --git a/.changeset/ten-lions-retire.md b/.changeset/ten-lions-retire.md new file mode 100644 index 000000000000..c1d0f26c37c1 --- /dev/null +++ b/.changeset/ten-lions-retire.md @@ -0,0 +1,9 @@ +--- +'@data-client/normalizr': patch +'@data-client/core': patch +'@data-client/react': patch +--- + +Improve performance of get/denormalize for small responses + +- 10-20% performance improvement due to removing immutablejs check for every call \ No newline at end of file diff --git a/packages/endpoint/src/schemas/__tests__/All.test.ts b/packages/endpoint/src/schemas/__tests__/All.test.ts index d6d718cd3d4f..4c410f313a00 100644 --- a/packages/endpoint/src/schemas/__tests__/All.test.ts +++ b/packages/endpoint/src/schemas/__tests__/All.test.ts @@ -3,11 +3,14 @@ import { initialState, State, Controller } from '@data-client/core'; import { normalize, MemoCache, - denormalize, + denormalize as plainDenormalize, INVALID, - PlainDelegate, + MemoPolicy as PojoDelegate, } from '@data-client/normalizr'; -import { DelegateImmutable } from '@data-client/normalizr/immutable'; +import { + MemoPolicy as ImmDelegate, + denormalize as immDenormalize, +} from '@data-client/normalizr/imm'; import { IDEntity } from '__tests__/new'; import { schema } from '../..'; @@ -103,16 +106,23 @@ describe.each([[]])(`${schema.All.name} normalization (%s)`, () => { }); describe.each([ - ['direct', (data: T) => data, (data: T) => data, PlainDelegate], + [ + 'direct', + (data: T) => data, + (data: T) => data, + PojoDelegate, + plainDenormalize, + ], [ 'immutable', fromJSState, (v: any) => (typeof v?.toJS === 'function' ? v.toJS() : v), - DelegateImmutable, + ImmDelegate, + immDenormalize, ], ])( `${schema.Array.name} denormalization (%s)`, - (_, createInput, createOutput, MyDelegate) => { + (_, createInput, createOutput, MyDelegate, denormalize) => { test('denormalizes a single entity', () => { class Cat extends IDEntity {} const state: State = createInput({ diff --git a/packages/endpoint/src/schemas/__tests__/Array.test.js b/packages/endpoint/src/schemas/__tests__/Array.test.js index af3683f79205..67c7ab3e5288 100644 --- a/packages/endpoint/src/schemas/__tests__/Array.test.js +++ b/packages/endpoint/src/schemas/__tests__/Array.test.js @@ -1,5 +1,9 @@ // eslint-env jest -import { normalize, denormalize } from '@data-client/normalizr'; +import { + normalize, + denormalize as plainDenormalize, +} from '@data-client/normalizr'; +import { denormalize as immDenormalize } from '@data-client/normalizr/imm'; import { IDEntity } from '__tests__/new'; import { fromJS } from 'immutable'; @@ -138,9 +142,9 @@ describe.each([ }); describe.each([ - ['direct', data => data, data => data], - ['immutable', fromJS, fromJSEntities], -])(`input (%s)`, (_, createInput, createEntities) => { + ['direct', data => data, data => data, plainDenormalize], + ['immutable', fromJS, fromJSEntities, immDenormalize], +])(`input (%s)`, (_, createInput, createEntities, denormalize) => { test('denormalizes plain arrays with nothing inside', () => { class User extends IDEntity {} const entities = { @@ -155,7 +159,6 @@ describe.each([ expect( denormalize(sch, createInput({ user: '1' }), createEntities(entities)), ).toMatchSnapshot(); - expect( denormalize(sch, { user: '1', tacos: [] }, createEntities(entities)), ).toMatchSnapshot(); @@ -416,9 +419,9 @@ describe.each([ ]; const output = normalize(catList, input); expect(output).toMatchSnapshot(); - expect(denormalize(catList, output.result, output.entities)).toEqual( - input, - ); + expect( + denormalize(catList, output.result, createEntities(output.entities)), + ).toEqual(input); }); }); }); diff --git a/packages/endpoint/src/schemas/__tests__/Entity.test.ts b/packages/endpoint/src/schemas/__tests__/Entity.test.ts index a322da5d7bf0..5b3b80fda8e7 100644 --- a/packages/endpoint/src/schemas/__tests__/Entity.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Entity.test.ts @@ -1,6 +1,7 @@ // eslint-env jest -import { normalize, denormalize } from '@data-client/normalizr'; -import { INVALID } from '@data-client/normalizr'; +import { normalize, INVALID } from '@data-client/normalizr'; +import { denormalize as plainDenormalize } from '@data-client/normalizr'; +import { denormalize as immDenormalize } from '@data-client/normalizr/imm'; import { Temporal } from '@js-temporal/polyfill'; import { IDEntity } from '__tests__/new'; @@ -632,8 +633,10 @@ describe(`${Entity.name} denormalization`, () => { '1': { id: '1', name: 'foo' }, }, }; - expect(denormalize(Tacos, '1', entities)).toMatchSnapshot(); - expect(denormalize(Tacos, '1', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Tacos, '1', entities)).toMatchSnapshot(); + expect( + immDenormalize(Tacos, '1', fromJSEntities(entities)), + ).toMatchSnapshot(); }); class Food extends Entity {} @@ -654,13 +657,13 @@ describe(`${Entity.name} denormalization`, () => { }, }; - const de1 = denormalize(Menu, '1', entities); + const de1 = plainDenormalize(Menu, '1', entities); expect(de1).toMatchSnapshot(); - expect(denormalize(Menu, '1', fromJSEntities(entities))).toEqual(de1); + expect(immDenormalize(Menu, '1', fromJSEntities(entities))).toEqual(de1); - const de2 = denormalize(Menu, '2', entities); + const de2 = plainDenormalize(Menu, '2', entities); expect(de2).toMatchSnapshot(); - expect(denormalize(Menu, '2', fromJSEntities(entities))).toEqual(de2); + expect(immDenormalize(Menu, '2', fromJSEntities(entities))).toEqual(de2); }); test('denormalizes deep entities while maintaining referential equality', () => { @@ -687,7 +690,7 @@ describe(`${Entity.name} denormalization`, () => { test('denormalizes to undefined when validate() returns string', () => { class MyTacos extends Tacos { - static validate(entity) { + static validate(entity: any) { if (!Object.hasOwn(entity, 'name')) return 'no name'; } } @@ -696,8 +699,10 @@ describe(`${Entity.name} denormalization`, () => { '1': { id: '1' }, }, }; - expect(denormalize(MyTacos, '1', entities)).toEqual(expect.any(Symbol)); - expect(denormalize(MyTacos, '1', fromJSEntities(entities))).toEqual( + expect(plainDenormalize(MyTacos, '1', entities)).toEqual( + expect.any(Symbol), + ); + expect(immDenormalize(MyTacos, '1', fromJSEntities(entities))).toEqual( expect.any(Symbol), ); }); @@ -712,11 +717,15 @@ describe(`${Entity.name} denormalization`, () => { }, }; - expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); - expect(denormalize(Menu, '1', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Menu, '1', entities)).toMatchSnapshot(); + expect( + immDenormalize(Menu, '1', fromJSEntities(entities)), + ).toMatchSnapshot(); - expect(denormalize(Menu, '2', entities)).toMatchSnapshot(); - expect(denormalize(Menu, '2', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Menu, '2', entities)).toMatchSnapshot(); + expect( + immDenormalize(Menu, '2', fromJSEntities(entities)), + ).toMatchSnapshot(); }); it('should handle optional schema entries Entity', () => { @@ -735,7 +744,7 @@ describe(`${Entity.name} denormalization`, () => { const schema = MyEntity; expect( - denormalize(schema, 'bob', { + plainDenormalize(schema, 'bob', { MyEntity: { bob: { name: 'bob', secondthing: 'hi' } }, }), ).toMatchInlineSnapshot(` @@ -763,7 +772,7 @@ describe(`${Entity.name} denormalization`, () => { const schema = MyEntity; expect( - denormalize(schema, 'bob', { + plainDenormalize(schema, 'bob', { MyEntity: { bob: { name: 'bob', secondthing: 'hi', blarb: null } }, }), ).toMatchInlineSnapshot(` @@ -787,11 +796,15 @@ describe(`${Entity.name} denormalization`, () => { }, }; - expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); - expect(denormalize(Menu, '1', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Menu, '1', entities)).toMatchSnapshot(); + expect( + immDenormalize(Menu, '1', fromJSEntities(entities)), + ).toMatchSnapshot(); - expect(denormalize(Menu, '2', entities)).toMatchSnapshot(); - expect(denormalize(Menu, '2', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Menu, '2', entities)).toMatchSnapshot(); + expect( + immDenormalize(Menu, '2', fromJSEntities(entities)), + ).toMatchSnapshot(); }); test('can denormalize already partially denormalized data', () => { @@ -805,8 +818,10 @@ describe(`${Entity.name} denormalization`, () => { }, }; - expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); - expect(denormalize(Menu, '1', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Menu, '1', entities)).toMatchSnapshot(); + expect( + immDenormalize(Menu, '1', fromJSEntities(entities)), + ).toMatchSnapshot(); }); class User extends IDEntity { @@ -854,14 +869,14 @@ describe(`${Entity.name} denormalization`, () => { }, }; - expect(denormalize(Report, '123', entities)).toMatchSnapshot(); + expect(plainDenormalize(Report, '123', entities)).toMatchSnapshot(); expect( - denormalize(Report, '123', fromJSEntities(entities)), + immDenormalize(Report, '123', fromJSEntities(entities)), ).toMatchSnapshot(); - expect(denormalize(User, '456', entities)).toMatchSnapshot(); + expect(plainDenormalize(User, '456', entities)).toMatchSnapshot(); expect( - denormalize(User, '456', fromJSEntities(entities)), + immDenormalize(User, '456', fromJSEntities(entities)), ).toMatchSnapshot(); }); @@ -1000,7 +1015,7 @@ describe(`${Entity.name} denormalization`, () => { describe('optional entities', () => { it('should be marked as found even when optional is not there', () => { - const denormalized = denormalize(WithOptional, 'abc', { + const denormalized = plainDenormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: { id: 'abc', @@ -1025,7 +1040,7 @@ describe(`${Entity.name} denormalization`, () => { }); it('should be marked as found when nested entity is missing', () => { - const denormalized = denormalize(WithOptional, 'abc', { + const denormalized = plainDenormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: WithOptional.fromJS({ id: 'abc', @@ -1051,7 +1066,7 @@ describe(`${Entity.name} denormalization`, () => { }); it('should be marked as deleted when required entity is deleted symbol', () => { - const denormalized = denormalize(WithOptional, 'abc', { + const denormalized = plainDenormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: { id: 'abc', @@ -1068,7 +1083,7 @@ describe(`${Entity.name} denormalization`, () => { }); it('should be non-required deleted members should not result in deleted indicator', () => { - const denormalized = denormalize(WithOptional, 'abc', { + const denormalized = plainDenormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: WithOptional.fromJS({ id: 'abc', @@ -1096,7 +1111,7 @@ describe(`${Entity.name} denormalization`, () => { }); it('should be deleted when both are true in different parts of schema', () => { - const denormalized = denormalize( + const denormalized = plainDenormalize( new schema.Object({ data: WithOptional, other: ArticleEntity }), { data: 'abc' }, { diff --git a/packages/endpoint/src/schemas/__tests__/EntityMixin.test.ts b/packages/endpoint/src/schemas/__tests__/EntityMixin.test.ts index 423cf01bd879..90ad4e1ddc15 100644 --- a/packages/endpoint/src/schemas/__tests__/EntityMixin.test.ts +++ b/packages/endpoint/src/schemas/__tests__/EntityMixin.test.ts @@ -1,6 +1,7 @@ // eslint-env jest -import { normalize, denormalize } from '@data-client/normalizr'; -import { INVALID } from '@data-client/normalizr'; +import { normalize, INVALID } from '@data-client/normalizr'; +import { denormalize as plainDenormalize } from '@data-client/normalizr'; +import { denormalize as immDenormalize } from '@data-client/normalizr/imm'; import { Temporal } from '@js-temporal/polyfill'; import { SimpleMemoCache, fromJSEntities } from './denormalize'; @@ -668,7 +669,7 @@ describe(`${schema.Entity.name} normalization`, () => { id: '1', name: 'foo', }); - const final = denormalize(ProcessTaco, result, entities); + const final = plainDenormalize(ProcessTaco, result, entities); expect(final).not.toEqual(expect.any(Symbol)); if (typeof final === 'symbol') return; expect(final?.slug).toEqual('thing-1'); @@ -705,7 +706,7 @@ describe(`${schema.Entity.name} normalization`, () => { content: 'parent', child: { id: '4', content: 'child' }, }); - const final = denormalize(ParentEntity, result, entities); + const final = plainDenormalize(ParentEntity, result, entities); expect(final).not.toEqual(expect.any(Symbol)); if (typeof final === 'symbol') return; expect(final?.child?.parentId).toEqual('1'); @@ -754,7 +755,7 @@ describe(`${schema.Entity.name} normalization`, () => { const { entities, result } = normalize(EntriesEntity, { message: { id: '123', data: { attachment: { id: '456' } } }, }); - const final = denormalize(EntriesEntity, result, entities); + const final = plainDenormalize(EntriesEntity, result, entities); expect(final).not.toEqual(expect.any(Symbol)); if (typeof final === 'symbol') return; expect(final?.type).toEqual('message'); @@ -772,8 +773,10 @@ describe(`${schema.Entity.name} denormalization`, () => { '1': { id: '1', name: 'foo' }, }, }; - expect(denormalize(Tacos, '1', entities)).toMatchSnapshot(); - expect(denormalize(Tacos, '1', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Tacos, '1', entities)).toMatchSnapshot(); + expect( + immDenormalize(Tacos, '1', fromJSEntities(entities)), + ).toMatchSnapshot(); }); class Food extends EntityMixin( @@ -798,13 +801,13 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - const de1 = denormalize(Menu, '1', entities); + const de1 = plainDenormalize(Menu, '1', entities); expect(de1).toMatchSnapshot(); - expect(denormalize(Menu, '1', fromJSEntities(entities))).toEqual(de1); + expect(immDenormalize(Menu, '1', fromJSEntities(entities))).toEqual(de1); - const de2 = denormalize(Menu, '2', entities); + const de2 = plainDenormalize(Menu, '2', entities); expect(de2).toMatchSnapshot(); - expect(denormalize(Menu, '2', fromJSEntities(entities))).toEqual(de2); + expect(immDenormalize(Menu, '2', fromJSEntities(entities))).toEqual(de2); }); test('denormalizes deep entities while maintaining referential equality', () => { @@ -831,7 +834,7 @@ describe(`${schema.Entity.name} denormalization`, () => { test('denormalizes to undefined when validate() returns string', () => { class MyTacos extends Tacos { - static validate(entity) { + static validate(entity: any) { if (!Object.hasOwn(entity, 'name')) return 'no name'; } } @@ -840,8 +843,10 @@ describe(`${schema.Entity.name} denormalization`, () => { '1': { id: '1' }, }, }; - expect(denormalize(MyTacos, '1', entities)).toEqual(expect.any(Symbol)); - expect(denormalize(MyTacos, '1', fromJSEntities(entities))).toEqual( + expect(plainDenormalize(MyTacos, '1', entities)).toEqual( + expect.any(Symbol), + ); + expect(immDenormalize(MyTacos, '1', fromJSEntities(entities))).toEqual( expect.any(Symbol), ); }); @@ -856,11 +861,15 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); - expect(denormalize(Menu, '1', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Menu, '1', entities)).toMatchSnapshot(); + expect( + immDenormalize(Menu, '1', fromJSEntities(entities)), + ).toMatchSnapshot(); - expect(denormalize(Menu, '2', entities)).toMatchSnapshot(); - expect(denormalize(Menu, '2', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Menu, '2', entities)).toMatchSnapshot(); + expect( + immDenormalize(Menu, '2', fromJSEntities(entities)), + ).toMatchSnapshot(); }); it('should handle optional schema entries Entity', () => { @@ -875,7 +884,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }) {} expect( - denormalize(MyEntity, 'bob', { + plainDenormalize(MyEntity, 'bob', { MyEntity: { bob: { name: 'bob', secondthing: 'hi' } }, }), ).toMatchInlineSnapshot(` @@ -899,7 +908,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }) {} expect( - denormalize(MyEntity, 'bob', { + plainDenormalize(MyEntity, 'bob', { MyEntity: { bob: { name: 'bob', secondthing: 'hi', blarb: null } }, }), ).toMatchInlineSnapshot(` @@ -923,11 +932,15 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); - expect(denormalize(Menu, '1', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Menu, '1', entities)).toMatchSnapshot(); + expect( + immDenormalize(Menu, '1', fromJSEntities(entities)), + ).toMatchSnapshot(); - expect(denormalize(Menu, '2', entities)).toMatchSnapshot(); - expect(denormalize(Menu, '2', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Menu, '2', entities)).toMatchSnapshot(); + expect( + immDenormalize(Menu, '2', fromJSEntities(entities)), + ).toMatchSnapshot(); }); test('can denormalize already partially denormalized data', () => { @@ -941,8 +954,10 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); - expect(denormalize(Menu, '1', fromJSEntities(entities))).toMatchSnapshot(); + expect(plainDenormalize(Menu, '1', entities)).toMatchSnapshot(); + expect( + immDenormalize(Menu, '1', fromJSEntities(entities)), + ).toMatchSnapshot(); }); describe('nesting', () => { @@ -995,14 +1010,14 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - expect(denormalize(Report, '123', entities)).toMatchSnapshot(); + expect(plainDenormalize(Report, '123', entities)).toMatchSnapshot(); expect( - denormalize(Report, '123', fromJSEntities(entities)), + immDenormalize(Report, '123', fromJSEntities(entities)), ).toMatchSnapshot(); - expect(denormalize(User, '456', entities)).toMatchSnapshot(); + expect(plainDenormalize(User, '456', entities)).toMatchSnapshot(); expect( - denormalize(User, '456', fromJSEntities(entities)), + immDenormalize(User, '456', fromJSEntities(entities)), ).toMatchSnapshot(); }); @@ -1143,7 +1158,7 @@ describe(`${schema.Entity.name} denormalization`, () => { describe('optional entities', () => { it('should be marked as found even when optional is not there', () => { - const denormalized = denormalize(WithOptional, 'abc', { + const denormalized = plainDenormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: { id: 'abc', @@ -1168,7 +1183,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }); it('should be marked as found when nested entity is missing', () => { - const denormalized = denormalize(WithOptional, 'abc', { + const denormalized = plainDenormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: WithOptional.fromJS({ id: 'abc', @@ -1196,7 +1211,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }); it('should be marked as deleted when required entity is deleted symbol', () => { - const denormalized = denormalize(WithOptional, 'abc', { + const denormalized = plainDenormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: { id: 'abc', @@ -1213,7 +1228,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }); it('should be non-required deleted members should not result in deleted indicator', () => { - const denormalized = denormalize(WithOptional, 'abc', { + const denormalized = plainDenormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: WithOptional.fromJS({ id: 'abc', @@ -1242,7 +1257,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }); it('should be both deleted and not found when both are true in different parts of schema', () => { - const denormalized = denormalize( + const denormalized = plainDenormalize( new schema.Object({ data: WithOptional, other: ArticleEntity }), { data: 'abc' }, { diff --git a/packages/endpoint/src/schemas/__tests__/Invalidate.test.ts b/packages/endpoint/src/schemas/__tests__/Invalidate.test.ts index a13ec9441276..3962762973aa 100644 --- a/packages/endpoint/src/schemas/__tests__/Invalidate.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Invalidate.test.ts @@ -1,16 +1,21 @@ // eslint-env jest -import { INVALID, Schema, normalize } from '@data-client/normalizr'; +import { + INVALID, + Schema, + normalize, + MemoPolicy as POJOPolicy, +} from '@data-client/normalizr'; +import { MemoPolicy as ImmPolicy } from '@data-client/normalizr/imm'; import { IDEntity } from '__tests__/new'; import { fromJS } from 'immutable'; -import { SimpleMemoCache, fromJSEntities } from './denormalize'; +import SimpleMemoCache, { fromJSEntities } from './denormalize'; import { schema } from '../..'; import Entity from '../Entity'; let dateSpy: jest.SpyInstance; beforeAll(() => { dateSpy = jest - .spyOn(global.Date, 'now') .mockImplementation(() => new Date('2019-05-14T11:01:58.135Z').valueOf()); }); @@ -92,7 +97,7 @@ describe(`${schema.Invalidate.name} denormalization`, () => { }; test('denormalizes an object in the same manner as the Entity', () => { - const user = new SimpleMemoCache().denormalize( + const user = new SimpleMemoCache(POJOPolicy).denormalize( new schema.Invalidate(User), '1', entities, @@ -102,24 +107,35 @@ describe(`${schema.Invalidate.name} denormalization`, () => { expect(user).toBeDefined(); expect(user).toBeInstanceOf(User); expect(user?.username).toBe('Janey'); + // Immutable version + const userImm = new SimpleMemoCache(ImmPolicy).denormalize( + new schema.Invalidate(User), + '1', + fromJSEntities(entities), + ); + expect(userImm).not.toEqual(expect.any(Symbol)); + if (typeof userImm === 'symbol') return; + expect(userImm).toBeDefined(); + expect(userImm).toBeInstanceOf(User); + expect(userImm?.username).toBe('Janey'); }); describe.each([ - ['direct', (data: T) => data, (data: T) => data], - ['immutable', fromJS, fromJSEntities], - ])(`input (%s)`, (_, createInput, createEntities) => { + ['direct', (data: T) => data, (data: T) => data, POJOPolicy], + ['immutable', fromJS, fromJSEntities, ImmPolicy], + ])(`input (%s)`, (_, createInput, createEntities, Delegate) => { describe.each([ [ 'class', (sch: T) => new schema.Array(sch), >(sch: T) => new schema.Object(sch), - new SimpleMemoCache().denormalize, + new SimpleMemoCache(Delegate).denormalize, ], [ 'object, direct', (sch: T) => [sch], >(sch: T) => sch, - new SimpleMemoCache().denormalize, + new SimpleMemoCache(Delegate).denormalize, ], ])( `schema construction (%s)`, diff --git a/packages/endpoint/src/schemas/__tests__/Object.test.js b/packages/endpoint/src/schemas/__tests__/Object.test.js index 89ba79bb07c6..3fcd1fbffe3c 100644 --- a/packages/endpoint/src/schemas/__tests__/Object.test.js +++ b/packages/endpoint/src/schemas/__tests__/Object.test.js @@ -1,5 +1,9 @@ // eslint-env jest -import { normalize, denormalize } from '@data-client/normalizr'; +import { + normalize, + denormalize as plainDenormalize, +} from '@data-client/normalizr'; +import { denormalize as immDenormalize } from '@data-client/normalizr/imm'; import { Temporal } from '@js-temporal/polyfill'; import { IDEntity } from '__tests__/new'; import { fromJS, Map } from 'immutable'; @@ -91,12 +95,12 @@ describe(`${schema.Object.name} denormalization`, () => { 1: { id: '1', name: 'Nacho' }, }, }; - expect(denormalize(object, { user: '1' }, entities)).toMatchSnapshot(); + expect(plainDenormalize(object, { user: '1' }, entities)).toMatchSnapshot(); expect( - denormalize(object, { user: '1' }, fromJSEntities(entities)), + immDenormalize(object, { user: '1' }, fromJSEntities(entities)), ).toMatchSnapshot(); expect( - denormalize(object, fromJS({ user: '1' }), fromJSEntities(entities)), + immDenormalize(object, fromJS({ user: '1' }), fromJSEntities(entities)), ).toMatchSnapshot(); }); @@ -111,12 +115,12 @@ describe(`${schema.Object.name} denormalization`, () => { 1: { id: '1', name: 'Nacho' }, }, }; - expect(denormalize(object, { user: '1' }, entities)).toMatchSnapshot(); + expect(plainDenormalize(object, { user: '1' }, entities)).toMatchSnapshot(); expect( - denormalize(object, { user: '1' }, fromJSEntities(entities)), + immDenormalize(object, { user: '1' }, fromJSEntities(entities)), ).toMatchSnapshot(); expect( - denormalize(object, fromJS({ user: '1' }), fromJSEntities(entities)), + immDenormalize(object, fromJS({ user: '1' }), fromJSEntities(entities)), ).toMatchSnapshot(); }); @@ -132,11 +136,11 @@ describe(`${schema.Object.name} denormalization`, () => { 1: { id: '1', name: 'Nacho' }, }, }; - let value = denormalize(object, { item: null }, entities); + let value = plainDenormalize(object, { item: null }, entities); expect(value).toMatchSnapshot(); - value = denormalize(object, { item: null }, fromJSEntities(entities)); + value = immDenormalize(object, { item: null }, fromJSEntities(entities)); expect(value).toMatchSnapshot(); - value = denormalize( + value = immDenormalize( object, fromJS({ item: null }), fromJSEntities(entities), @@ -152,21 +156,21 @@ describe(`${schema.Object.name} denormalization`, () => { }, }; expect( - denormalize( + plainDenormalize( new schema.Object({ user: User, tacos: {} }), { user: '1' }, entities, ), ).toMatchSnapshot(); expect( - denormalize( + immDenormalize( new schema.Object({ user: User, tacos: {} }), { user: '1' }, fromJSEntities(entities), ), ).toMatchSnapshot(); expect( - denormalize( + immDenormalize( new schema.Object({ user: User, tacos: {} }), fromJS({ user: '1' }), fromJSEntities(entities), @@ -174,21 +178,21 @@ describe(`${schema.Object.name} denormalization`, () => { ).toMatchSnapshot(); expect( - denormalize( + plainDenormalize( new schema.Object({ user: User, tacos: {} }), { user: '1', tacos: {} }, entities, ), ).toMatchSnapshot(); expect( - denormalize( + immDenormalize( new schema.Object({ user: User, tacos: {} }), { user: '1', tacos: {} }, fromJSEntities(entities), ), ).toMatchSnapshot(); expect( - denormalize( + immDenormalize( new schema.Object({ user: User, tacos: {} }), fromJS({ user: '1', tacos: {} }), fromJSEntities(entities), @@ -206,12 +210,12 @@ describe(`${schema.Object.name} denormalization`, () => { 0: { id: '0', name: 'Chancho' }, }, }; - expect(denormalize(object, { user: '0' }, entities)).toMatchSnapshot(); + expect(plainDenormalize(object, { user: '0' }, entities)).toMatchSnapshot(); expect( - denormalize(object, { user: '0' }, fromJSEntities(entities)), + immDenormalize(object, { user: '0' }, fromJSEntities(entities)), ).toMatchSnapshot(); expect( - denormalize(object, fromJS({ user: '0' }), fromJSEntities(entities)), + immDenormalize(object, fromJS({ user: '0' }), fromJSEntities(entities)), ).toMatchSnapshot(); }); }); diff --git a/packages/endpoint/src/schemas/__tests__/Query.test.ts b/packages/endpoint/src/schemas/__tests__/Query.test.ts index 928f2d23a57b..5cc515f6f3aa 100644 --- a/packages/endpoint/src/schemas/__tests__/Query.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Query.test.ts @@ -1,6 +1,6 @@ // eslint-env jest -import { MemoCache, PlainDelegate } from '@data-client/normalizr'; -import { DelegateImmutable } from '@data-client/normalizr/immutable'; +import { MemoCache, MemoPolicy as PojoPolicy } from '@data-client/normalizr'; +import { MemoPolicy as ImmPolicy } from '@data-client/normalizr/imm'; import { useQuery, useSuspense, __INTERNAL__ } from '@data-client/react'; import { RestEndpoint } from '@data-client/rest'; import { IDEntity } from '__tests__/new'; @@ -27,12 +27,12 @@ class User extends IDEntity { } describe.each([ - ['direct', (data: T) => data, (data: T) => data, PlainDelegate], + ['direct', (data: T) => data, (data: T) => data, PojoPolicy], [ 'immutable', fromJSState, (v: any) => (typeof v?.toJS === 'function' ? v.toJS() : v), - DelegateImmutable, + ImmPolicy, ], ])(`input (%s)`, (_, createInput, createOutput, MyDelegate) => { const SCHEMA_CASES = [ diff --git a/packages/endpoint/src/schemas/__tests__/Union.test.js b/packages/endpoint/src/schemas/__tests__/Union.test.js index 7d987f81f39e..036fe155ce73 100644 --- a/packages/endpoint/src/schemas/__tests__/Union.test.js +++ b/packages/endpoint/src/schemas/__tests__/Union.test.js @@ -1,5 +1,7 @@ // eslint-env jest import { normalize } from '@data-client/normalizr'; +import { denormalize as plainDenormalize } from '@data-client/normalizr'; +import { denormalize as immDenormalize } from '@data-client/normalizr/imm'; import { IDEntity } from '__tests__/new'; import { waterfallSchema } from '__tests__/UnionSchema'; import { fromJS } from 'immutable'; @@ -269,10 +271,10 @@ const entities = { }, }; describe.each([ - ['direct', data => data, data => data], - ['immutable', fromJS, fromJSEntities], -])(`input (%s)`, (_, createInput, createEntities) => { - describe.each([['current', new SimpleMemoCache().denormalize]])( + ['direct', plainDenormalize, data => data, data => data], + ['immutable', immDenormalize, fromJS, fromJSEntities], +])(`input (%s)`, (_, denormalize, createInput, createEntities) => { + describe.each([['current', denormalize]])( `${schema.Union.name} denormalization (%s)`, (_, denormalize) => { test('denormalizes an object using string schemaAttribute', () => { diff --git a/packages/endpoint/src/schemas/__tests__/Values.test.js b/packages/endpoint/src/schemas/__tests__/Values.test.js index f9ae7775fd04..a77f24f05fab 100644 --- a/packages/endpoint/src/schemas/__tests__/Values.test.js +++ b/packages/endpoint/src/schemas/__tests__/Values.test.js @@ -1,5 +1,10 @@ // eslint-env jest -import { normalize, INVALID } from '@data-client/normalizr'; +import { + normalize, + INVALID, + MemoPolicy as PojoPolicy, +} from '@data-client/normalizr'; +import { MemoPolicy as ImmPolicy } from '@data-client/normalizr/imm'; import { IDEntity } from '__tests__/new'; import { SimpleMemoCache, fromJSEntities } from './denormalize'; @@ -194,10 +199,10 @@ describe(`${schema.Values.name} normalization`, () => { }); describe.each([ - ['direct', data => data], - ['immutable', fromJSEntities], -])(`input (%s)`, (_, createInput) => { - describe.each([['current', new SimpleMemoCache().denormalize]])( + ['direct', data => data, PojoPolicy], + ['immutable', fromJSEntities, ImmPolicy], +])(`input (%s)`, (_, createInput, Delegate) => { + describe.each([['current', new SimpleMemoCache(Delegate).denormalize]])( `${schema.Values.name} denormalization (%s)`, (_, denormalize) => { test('denormalizes without schemaAttribute', () => { diff --git a/packages/endpoint/src/schemas/__tests__/denormalize.ts b/packages/endpoint/src/schemas/__tests__/denormalize.ts index d1e4cfe0373e..dc0cd26b3f78 100644 --- a/packages/endpoint/src/schemas/__tests__/denormalize.ts +++ b/packages/endpoint/src/schemas/__tests__/denormalize.ts @@ -3,12 +3,18 @@ import { Schema, Denormalize, DenormalizeNullable, + IMemoPolicy, + MemoPolicy, } from '@data-client/normalizr'; import { fromJS, Map } from 'immutable'; export class SimpleMemoCache { private memo = new MemoCache(); + constructor(D: IMemoPolicy = MemoPolicy) { + this.memo = new MemoCache(D); + } + denormalize = ( schema: S | undefined, input: any, diff --git a/packages/normalizr/package.json b/packages/normalizr/package.json index 705b62146d54..a9be596a9476 100644 --- a/packages/normalizr/package.json +++ b/packages/normalizr/package.json @@ -72,9 +72,9 @@ "react-native": "./lib/index.js", "default": "./lib/index.js" }, - "./immutable": { - "types": "./lib/immutable.d.ts", - "default": "./lib/immutable.js" + "./imm": { + "types": "./lib/imm.d.ts", + "default": "./lib/imm.js" }, "./package.json": "./package.json" }, diff --git a/packages/normalizr/src/__tests__/MemoCache.ts b/packages/normalizr/src/__tests__/MemoCache.ts index f1dfe3214787..728f394a2d0a 100644 --- a/packages/normalizr/src/__tests__/MemoCache.ts +++ b/packages/normalizr/src/__tests__/MemoCache.ts @@ -10,9 +10,9 @@ import { import { fromJSState } from './immutable.test'; import { IQueryDelegate } from '../interface'; -import { PlainDelegate } from '../memo/Delegate'; -import { DelegateImmutable } from '../memo/Delegate.immutable'; import MemoCache from '../memo/MemoCache'; +import { MemoPolicy as POJOPolicy } from '../memo/Policy'; +import { MemoPolicy as ImmPolicy } from '../memo/Policy.imm'; class IDEntity extends Entity { id = ''; @@ -1011,8 +1011,8 @@ describe('MemoCache', () => { }); describe.each([ - ['direct', (data: T) => data, PlainDelegate], - ['immutable', fromJSState, DelegateImmutable], + ['direct', (data: T) => data, POJOPolicy], + ['immutable', fromJSState, ImmPolicy], ])(`query (%s)`, (_, createInput, Delegate) => { class Cat extends IDEntity { id = '0'; diff --git a/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts b/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts index 2a6a603a3d39..3273cd578363 100644 --- a/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts +++ b/packages/normalizr/src/__tests__/WeakDependencyMap.test.ts @@ -1,7 +1,7 @@ import { Temporal } from '@js-temporal/polyfill'; -import { getEntities } from '../denormalize/getEntities'; import { EntityPath } from '../interface'; +import { MemoPolicy } from '../memo/Policy'; import WeakDependencyMap from '../memo/WeakDependencyMap'; describe('WeakDependencyMap', () => { @@ -19,7 +19,7 @@ describe('WeakDependencyMap', () => { '3': c, }, }; - const getEntity = getEntities(state); + const getEntity = MemoPolicy.getEntities(state); const depA = { path: { key: 'A', pk: '1' }, entity: a }; const depB = { path: { key: 'B', pk: '2' }, entity: b }; const depC = { path: { key: 'C', pk: '3' }, entity: c }; diff --git a/packages/normalizr/src/__tests__/immutable.test.ts b/packages/normalizr/src/__tests__/immutable.test.ts index 7951452689d8..ff4dc9cd3dae 100644 --- a/packages/normalizr/src/__tests__/immutable.test.ts +++ b/packages/normalizr/src/__tests__/immutable.test.ts @@ -3,7 +3,7 @@ import { Entity, schema } from '@data-client/endpoint'; import { fromJS, Record, Map } from 'immutable'; import { normalize } from '..'; -import { denormalize } from '../denormalize/denormalize'; +import { denormalize } from '../denormalize/denormalize.imm'; export function fromJSEntities(entities: { [k: string]: { [k: string]: any }; diff --git a/packages/normalizr/src/memo/BaseDelegate.ts b/packages/normalizr/src/delegate/BaseDelegate.ts similarity index 86% rename from packages/normalizr/src/memo/BaseDelegate.ts rename to packages/normalizr/src/delegate/BaseDelegate.ts index 276a730a6287..5558abe41f6f 100644 --- a/packages/normalizr/src/memo/BaseDelegate.ts +++ b/packages/normalizr/src/delegate/BaseDelegate.ts @@ -1,4 +1,3 @@ -import type { Dep } from './WeakDependencyMap.js'; import { INVALID } from '../denormalize/symbol.js'; import type { QueryPath, @@ -6,14 +5,9 @@ import type { EntitiesPath, IQueryDelegate, } from '../interface.js'; +import type { Dep } from '../memo/WeakDependencyMap.js'; -export const getDependency = - (delegate: BaseDelegate) => - (path: QueryPath): object | undefined => - delegate[['', 'getEntities', 'getEntity', 'getIndex'][path.length]]( - ...path, - ); - +/** Basic state interfaces for normalize side */ export abstract class BaseDelegate { declare entities: any; declare indexes: any; diff --git a/packages/normalizr/src/memo/Delegate.immutable.ts b/packages/normalizr/src/delegate/Delegate.imm.ts similarity index 85% rename from packages/normalizr/src/memo/Delegate.immutable.ts rename to packages/normalizr/src/delegate/Delegate.imm.ts index 1f97df63b9b7..b1d34054e103 100644 --- a/packages/normalizr/src/memo/Delegate.immutable.ts +++ b/packages/normalizr/src/delegate/Delegate.imm.ts @@ -1,13 +1,13 @@ -import type { EntitiesPath, EntityPath, IndexPath } from '../interface.js'; import { BaseDelegate } from './BaseDelegate.js'; -type ImmutableJSEntityTable = { +export type ImmutableJSEntityTable = { get(key: string): { toJS(): any } | undefined; getIn(k: [key: string, pk: string]): { toJS(): any } | undefined; setIn(k: [key: string, pk: string], value: any); }; -export class DelegateImmutable extends BaseDelegate { +/** Basic ImmutableJS state interfaces for normalize side */ +export class ImmDelegate extends BaseDelegate { declare entities: ImmutableJSEntityTable; declare indexes: ImmutableJSEntityTable; diff --git a/packages/normalizr/src/memo/Delegate.ts b/packages/normalizr/src/delegate/Delegate.ts similarity index 88% rename from packages/normalizr/src/memo/Delegate.ts rename to packages/normalizr/src/delegate/Delegate.ts index 54f2270609c4..b5fa41884b03 100644 --- a/packages/normalizr/src/memo/Delegate.ts +++ b/packages/normalizr/src/delegate/Delegate.ts @@ -1,7 +1,8 @@ import type { EntityTable, NormalizedIndex } from '../interface.js'; import { BaseDelegate } from './BaseDelegate.js'; -export class PlainDelegate extends BaseDelegate { +/** Basic POJO state interfaces for normalize side */ +export class POJODelegate extends BaseDelegate { declare entities: EntityTable; declare indexes: { [entityKey: string]: { diff --git a/packages/normalizr/src/denormalize/denormalize.imm.ts b/packages/normalizr/src/denormalize/denormalize.imm.ts new file mode 100644 index 000000000000..72b6b6663da1 --- /dev/null +++ b/packages/normalizr/src/denormalize/denormalize.imm.ts @@ -0,0 +1,24 @@ +import LocalCache from './localCache.js'; +import getUnvisit from './unvisit.js'; +import type { Schema } from '../interface.js'; +import type { DenormalizeNullable } from '../types.js'; +import type { INVALID } from './symbol.js'; +import { MemoPolicy } from '../memo/Policy.imm.js'; + +export function denormalize( + schema: S | undefined, + input: any, + entities: any, + args: readonly any[] = [], +): DenormalizeNullable | typeof INVALID { + // undefined means don't do anything + if (schema === undefined || input === undefined) { + return input as any; + } + + return getUnvisit( + MemoPolicy.getEntities(entities), + new LocalCache(), + args, + )(schema, input).data; +} diff --git a/packages/normalizr/src/denormalize/denormalize.ts b/packages/normalizr/src/denormalize/denormalize.ts index 9a322c8795cc..c30654832e64 100644 --- a/packages/normalizr/src/denormalize/denormalize.ts +++ b/packages/normalizr/src/denormalize/denormalize.ts @@ -1,9 +1,9 @@ -import { getEntities } from './getEntities.js'; import LocalCache from './localCache.js'; import getUnvisit from './unvisit.js'; import type { Schema } from '../interface.js'; import type { DenormalizeNullable } from '../types.js'; import type { INVALID } from './symbol.js'; +import { MemoPolicy } from '../memo/Policy.js'; export function denormalize( schema: S | undefined, @@ -17,7 +17,7 @@ export function denormalize( } return getUnvisit( - getEntities(entities), + MemoPolicy.getEntities(entities), new LocalCache(), args, )(schema, input).data; diff --git a/packages/normalizr/src/denormalize/getEntities.ts b/packages/normalizr/src/denormalize/getEntities.ts deleted file mode 100644 index c19827b31345..000000000000 --- a/packages/normalizr/src/denormalize/getEntities.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { EntityPath } from '../interface.js'; -import type { GetDependency } from '../memo/WeakDependencyMap.js'; -import { isImmutable } from '../schemas/ImmutableUtils.js'; - -export function getEntities( - state: State, -): GetDependency { - const entityIsImmutable = isImmutable(state); - - if (entityIsImmutable) { - return ({ key, pk }: EntityPath) => state.getIn([key, pk]); - } else { - return ({ key, pk }: EntityPath) => state[key]?.[pk]; - } -} - -export type State = - | Record> - | { getIn(path: [string, string]): K }; - -export type GetEntity = GetDependency; diff --git a/packages/normalizr/src/denormalize/unvisit.ts b/packages/normalizr/src/denormalize/unvisit.ts index 234fb1626871..daeec49664dd 100644 --- a/packages/normalizr/src/denormalize/unvisit.ts +++ b/packages/normalizr/src/denormalize/unvisit.ts @@ -1,14 +1,14 @@ import type Cache from './cache.js'; -import { type GetEntity } from './getEntities.js'; import { INVALID } from './symbol.js'; import { UNDEF } from './UNDEF.js'; import type { EntityInterface, EntityPath } from '../interface.js'; import { isEntity } from '../isEntity.js'; +import type { DenormGetEntity } from '../memo/types.js'; import { denormalize as arrayDenormalize } from '../schemas/Array.js'; import { denormalize as objectDenormalize } from '../schemas/Object.js'; const getUnvisitEntity = ( - getEntity: GetEntity, + getEntity: DenormGetEntity, cache: Cache, args: readonly any[], unvisit: (schema: any, input: any) => any, @@ -98,7 +98,7 @@ function noCacheGetEntity( } const getUnvisit = ( - getEntity: GetEntity, + getEntity: DenormGetEntity, cache: Cache, args: readonly any[], ) => { diff --git a/packages/normalizr/src/imm.ts b/packages/normalizr/src/imm.ts new file mode 100644 index 000000000000..764011533b99 --- /dev/null +++ b/packages/normalizr/src/imm.ts @@ -0,0 +1,2 @@ +export { MemoPolicy } from './memo/Policy.imm.js'; +export { denormalize } from './denormalize/denormalize.imm.js'; diff --git a/packages/normalizr/src/immutable.ts b/packages/normalizr/src/immutable.ts deleted file mode 100644 index 77f0c2809652..000000000000 --- a/packages/normalizr/src/immutable.ts +++ /dev/null @@ -1 +0,0 @@ -export { DelegateImmutable } from './memo/Delegate.immutable.js'; diff --git a/packages/normalizr/src/index.ts b/packages/normalizr/src/index.ts index ebd02750337b..3eb4d4da9619 100644 --- a/packages/normalizr/src/index.ts +++ b/packages/normalizr/src/index.ts @@ -4,8 +4,8 @@ import WeakDependencyMap from './memo/WeakDependencyMap.js'; import { normalize } from './normalize/normalize.js'; export { default as MemoCache } from './memo/MemoCache.js'; -export { BaseDelegate } from './memo/BaseDelegate.js'; -export { PlainDelegate } from './memo/Delegate.js'; +export { BaseDelegate } from './delegate/BaseDelegate.js'; +export { MemoPolicy } from './memo/Policy.js'; export type { AbstractInstanceType, NormalizeReturnType, @@ -19,6 +19,7 @@ export type { export type { NI } from './NoInfer.js'; export * from './endpoint/types.js'; export * from './interface.js'; +export type * from './memo/types.js'; export * from './Expiry.js'; export { INVALID } from './denormalize/symbol.js'; export { validateQueryKey } from './buildQueryKey.js'; diff --git a/packages/normalizr/src/memo/MemoCache.ts b/packages/normalizr/src/memo/MemoCache.ts index 975d8642d596..439443327c93 100644 --- a/packages/normalizr/src/memo/MemoCache.ts +++ b/packages/normalizr/src/memo/MemoCache.ts @@ -1,10 +1,10 @@ import GlobalCache from './globalCache.js'; import WeakDependencyMap from './WeakDependencyMap.js'; import buildQueryKey from '../buildQueryKey.js'; -import { getDependency, type BaseDelegate } from './BaseDelegate.js'; -import { PlainDelegate } from './Delegate.js'; import { GetEntityCache, getEntityCaches } from './entitiesCache.js'; -import { getEntities } from '../denormalize/getEntities.js'; +import { MemoPolicy } from './Policy.js'; +import type { BaseDelegate } from '../delegate/BaseDelegate.js'; +import type { INVALID } from '../denormalize/symbol.js'; import getUnvisit from '../denormalize/unvisit.js'; import type { EntityPath, @@ -13,10 +13,7 @@ import type { Schema, } from '../interface.js'; import type { DenormalizeNullable, NormalizeNullable } from '../types.js'; -import { EndpointsCache } from './types.js'; -import type { INVALID } from '../denormalize/symbol.js'; - -type DelegateClass = new (v: { entities: any; indexes: any }) => BaseDelegate; +import type { IMemoPolicy, EndpointsCache } from './types.js'; // TODO: make MemoCache generic on the arguments sent to Delegate constructor @@ -29,10 +26,10 @@ export default class MemoCache { /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Map> = new Map(); - declare protected Delegate: DelegateClass; + declare protected policy: IMemoPolicy; - constructor(D: DelegateClass = PlainDelegate) { - this.Delegate = D; + constructor(policy: IMemoPolicy = MemoPolicy) { + this.policy = policy; this._getCache = getEntityCaches(new Map()); } @@ -57,7 +54,7 @@ export default class MemoCache { if (input === undefined) { return { data: undefined as any, paths: [] }; } - const getEntity = getEntities(entities); + const getEntity = this.policy.getEntities(entities); return getUnvisit( getEntity, @@ -113,7 +110,7 @@ export default class MemoCache { any >; - const baseDelegate = new this.Delegate(state); + const baseDelegate = new this.policy.QueryDelegate(state); // eslint-disable-next-line prefer-const let [value, paths] = queryCache.get( schema as any, @@ -143,3 +140,10 @@ type StateInterface = { getIn(k: string[]): any; }; }; + +const getDependency = + (delegate: BaseDelegate) => + (path: QueryPath): object | undefined => + delegate[['', 'getEntities', 'getEntity', 'getIndex'][path.length]]( + ...path, + ); diff --git a/packages/normalizr/src/memo/Policy.imm.ts b/packages/normalizr/src/memo/Policy.imm.ts new file mode 100644 index 000000000000..6737ebb198f7 --- /dev/null +++ b/packages/normalizr/src/memo/Policy.imm.ts @@ -0,0 +1,14 @@ +import { ImmDelegate } from '../delegate/Delegate.imm.js'; +import type { EntityPath } from '../interface.js'; +import type { DenormGetEntity } from './types.js'; + +/** Handles ImmutableJS state for MemoCache methods */ +export const MemoPolicy = { + QueryDelegate: ImmDelegate, + getEntities(entities: { + getIn(path: [string, string]): unknown; + }): DenormGetEntity { + return ({ key, pk }: EntityPath): symbol | object | undefined => + entities.getIn([key, pk]) as any; + }, +}; diff --git a/packages/normalizr/src/memo/Policy.ts b/packages/normalizr/src/memo/Policy.ts new file mode 100644 index 000000000000..25943b49dd75 --- /dev/null +++ b/packages/normalizr/src/memo/Policy.ts @@ -0,0 +1,12 @@ +import type { DenormGetEntity } from './types.js'; +import { POJODelegate } from '../delegate/Delegate.js'; +import type { EntityPath, EntityTable } from '../interface.js'; + +/** Handles POJO state for MemoCache methods */ +export const MemoPolicy = { + QueryDelegate: POJODelegate, + getEntities(entities: EntityTable): DenormGetEntity { + return ({ key, pk }: EntityPath): symbol | object | undefined => + entities[key]?.[pk] as any; + }, +}; diff --git a/packages/normalizr/src/memo/globalCache.ts b/packages/normalizr/src/memo/globalCache.ts index 3b57c542a00f..85ffe0df16a6 100644 --- a/packages/normalizr/src/memo/globalCache.ts +++ b/packages/normalizr/src/memo/globalCache.ts @@ -1,10 +1,10 @@ +import type { GetEntityCache } from './entitiesCache.js'; import { EndpointsCache } from './types.js'; import WeakDependencyMap, { type Dep } from './WeakDependencyMap.js'; import type Cache from '../denormalize/cache.js'; -import type { GetEntity } from '../denormalize/getEntities.js'; import type { INVALID } from '../denormalize/symbol.js'; import type { EntityInterface, EntityPath } from '../interface.js'; -import type { GetEntityCache } from './entitiesCache.js'; +import type { DenormGetEntity } from './types.js'; export default class GlobalCache implements Cache { private dependencies: Dep[] = []; @@ -14,11 +14,11 @@ export default class GlobalCache implements Cache { declare private _getCache: GetEntityCache; - declare private _getEntity: GetEntity; + declare private _getEntity: DenormGetEntity; declare private _resultCache: EndpointsCache; constructor( - getEntity: GetEntity, + getEntity: DenormGetEntity, getCache: GetEntityCache, resultCache: EndpointsCache, ) { diff --git a/packages/normalizr/src/memo/types.ts b/packages/normalizr/src/memo/types.ts index 64a6fd34963e..ce383e9c65e4 100644 --- a/packages/normalizr/src/memo/types.ts +++ b/packages/normalizr/src/memo/types.ts @@ -1,4 +1,5 @@ -import WeakDependencyMap from './WeakDependencyMap.js'; +import WeakDependencyMap, { GetDependency } from './WeakDependencyMap.js'; +import type { BaseDelegate } from '../delegate/BaseDelegate.js'; import type { EntityInterface, EntityPath } from '../interface.js'; export interface EntityCache @@ -11,3 +12,10 @@ export interface EntityCache > {} export type EndpointsCache = WeakDependencyMap; + +export type DenormGetEntity = GetDependency; + +export interface IMemoPolicy { + QueryDelegate: new (v: { entities: any; indexes: any }) => BaseDelegate; + getEntities(entities: any): DenormGetEntity; +} diff --git a/packages/normalizr/src/normalize/NormalizeDelegate.ts b/packages/normalizr/src/normalize/NormalizeDelegate.ts index de76a1254b62..a4a1ad685300 100644 --- a/packages/normalizr/src/normalize/NormalizeDelegate.ts +++ b/packages/normalizr/src/normalize/NormalizeDelegate.ts @@ -5,11 +5,12 @@ import { Mergeable, } from '../interface.js'; import { getCheckLoop } from './getCheckLoop.js'; +import { POJODelegate } from '../delegate/Delegate.js'; import { INVALID } from '../denormalize/symbol.js'; -import { PlainDelegate } from '../memo/Delegate.js'; +/** Full normalize() logic for POJO state */ export class NormalizeDelegate - extends PlainDelegate + extends POJODelegate implements INormalizeDelegate { declare readonly entitiesMeta: { 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 232c88fbb346..9676541caa81 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 @@ -222,6 +222,7 @@ interface Dep { entity: K | undefined; } +/** Basic state interfaces for normalize side */ declare abstract class BaseDelegate { entities: any; indexes: any; @@ -237,13 +238,17 @@ declare abstract class BaseDelegate { } type EndpointsCache = WeakDependencyMap; +type DenormGetEntity = GetDependency; +interface IMemoPolicy { + QueryDelegate: new (v: { + entities: any; + indexes: any; + }) => BaseDelegate; + getEntities(entities: any): DenormGetEntity; +} type GetEntityCache = (pk: string, schema: EntityInterface) => WeakDependencyMap; -type DelegateClass = new (v: { - entities: any; - indexes: any; -}) => BaseDelegate; /** Singleton to store the memoization cache for denormalization methods */ declare class MemoCache { /** Cache for every entity based on its dependencies and its own input */ @@ -252,8 +257,8 @@ declare class MemoCache { protected endpoints: EndpointsCache; /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Map>; - protected Delegate: DelegateClass; - constructor(D?: DelegateClass); + protected policy: IMemoPolicy; + constructor(policy?: IMemoPolicy); /** Compute denormalized form maintaining referential equality for same inputs */ denormalize(schema: S | undefined, input: unknown, entities: any, args?: readonly any[]): { data: DenormalizeNullable | typeof INVALID; 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 23c95a1ad867..d56a617b8860 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 @@ -1194,4 +1194,4 @@ declare function validateRequired(processedEntity: any, requiredDefaults: Record /** https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#the-noinfer-utility-type */ type NI = NoInfer; -export { type AbstractInstanceType, Array$1 as Array, type CheckLoop, Collection, type DefaultArgs, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type FetchFunction, type GetEntities, type GetEntity, type GetIndex, type INormalizeDelegate, type IQueryDelegate, type IndexPath, Invalidate, type KeyofEndpointInstance, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type Visit, schema_d as schema, validateRequired }; +export { type AbstractInstanceType, Array$1 as Array, type CheckLoop, Collection, type DefaultArgs, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type FetchFunction, type GetEntities, type GetEntity, type GetIndex, type IEntityClass, type IEntityInstance, type INormalizeDelegate, type IQueryDelegate, type IndexPath, Invalidate, type KeyofEndpointInstance, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type Visit, schema_d as schema, validateRequired }; 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 9dd0754bbe9f..7da085efe538 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 @@ -1235,4 +1235,4 @@ interface GQLError { path: (string | number)[]; } -export { type AbstractInstanceType, Array$1 as Array, type CheckLoop, Collection, type DefaultArgs, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type FetchFunction, GQLEndpoint, GQLEntity, type GQLError, GQLNetworkError, type GQLOptions, type GetEntities, type GetEntity, type GetIndex, type INormalizeDelegate, type IQueryDelegate, type IndexPath, Invalidate, type KeyofEndpointInstance, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type Visit, schema_d as schema, validateRequired }; +export { type AbstractInstanceType, Array$1 as Array, type CheckLoop, Collection, type DefaultArgs, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type FetchFunction, GQLEndpoint, GQLEntity, type GQLError, GQLNetworkError, type GQLOptions, type GetEntities, type GetEntity, type GetIndex, type IEntityClass, type IEntityInstance, type INormalizeDelegate, type IQueryDelegate, type IndexPath, Invalidate, type KeyofEndpointInstance, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type Visit, schema_d as schema, validateRequired }; 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 a36a76bf4fe0..026f4767c375 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 @@ -262,6 +262,7 @@ interface Dep { declare const normalize: | undefined> = Record>, R = NormalizeNullable>(schema: S | undefined, input: any, args?: readonly any[], { entities, indexes, entitiesMeta }?: StoreData, meta?: NormalizeMeta) => NormalizedSchema; +/** Basic state interfaces for normalize side */ declare abstract class BaseDelegate { entities: any; indexes: any; @@ -276,14 +277,20 @@ declare abstract class BaseDelegate { tracked(schema: any): [delegate: IQueryDelegate, dependencies: Dep[]]; } +interface EntityCache extends Map>>> { +} type EndpointsCache = WeakDependencyMap; +type DenormGetEntity = GetDependency; +interface IMemoPolicy { + QueryDelegate: new (v: { + entities: any; + indexes: any; + }) => BaseDelegate; + getEntities(entities: any): DenormGetEntity; +} type GetEntityCache = (pk: string, schema: EntityInterface) => WeakDependencyMap; -type DelegateClass = new (v: { - entities: any; - indexes: any; -}) => BaseDelegate; /** Singleton to store the memoization cache for denormalization methods */ declare class MemoCache { /** Cache for every entity based on its dependencies and its own input */ @@ -292,8 +299,8 @@ declare class MemoCache { protected endpoints: EndpointsCache; /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Map>; - protected Delegate: DelegateClass; - constructor(D?: DelegateClass); + protected policy: IMemoPolicy; + constructor(policy?: IMemoPolicy); /** Compute denormalized form maintaining referential equality for same inputs */ denormalize(schema: S | undefined, input: unknown, entities: any, args?: readonly any[]): { data: DenormalizeNullable | typeof INVALID; @@ -315,7 +322,8 @@ type StateInterface = { }; }; -declare class PlainDelegate extends BaseDelegate { +/** Basic POJO state interfaces for normalize side */ +declare class POJODelegate extends BaseDelegate { entities: EntityTable; indexes: { [entityKey: string]: { @@ -334,6 +342,12 @@ declare class PlainDelegate extends BaseDelegate { getIndexEnd(entity: object | undefined, value: string): any; } +/** Handles POJO state for MemoCache methods */ +declare const MemoPolicy: { + QueryDelegate: typeof POJODelegate; + getEntities(entities: EntityTable): DenormGetEntity; +}; + /** https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#the-noinfer-utility-type */ type NI = NoInfer; @@ -448,4 +462,4 @@ type FetchFunction = (...args: A) => Pr declare function validateQueryKey(queryKey: unknown): boolean; -export { type AbstractInstanceType, type ArrayElement, BaseDelegate, type CheckLoop, type Denormalize, type DenormalizeNullable, type EndpointExtraOptions, type EndpointInterface, type EntitiesPath, type EntityInterface, type EntityPath, type EntityTable, type ErrorTypes, ExpiryStatus, type ExpiryStatusInterface, type FetchFunction, type GetEntities, type GetEntity, type GetIndex, INVALID, type INormalizeDelegate, type IQueryDelegate, type IndexInterface, type IndexParams, type IndexPath, type InferReturn, MemoCache, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeReturnType, type NormalizedIndex, type NormalizedSchema, type OptimisticUpdateParams, PlainDelegate, type QueryPath, type Queryable, type ReadEndpoint, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type UpdateFunction, type Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; +export { type AbstractInstanceType, type ArrayElement, BaseDelegate, type CheckLoop, type DenormGetEntity, type Denormalize, type DenormalizeNullable, type EndpointExtraOptions, type EndpointInterface, type EndpointsCache, type EntitiesPath, type EntityCache, type EntityInterface, type EntityPath, type EntityTable, type ErrorTypes, ExpiryStatus, type ExpiryStatusInterface, type FetchFunction, type GetEntities, type GetEntity, type GetIndex, type IMemoPolicy, INVALID, type INormalizeDelegate, type IQueryDelegate, type IndexInterface, type IndexParams, type IndexPath, type InferReturn, MemoCache, MemoPolicy, type Mergeable, type MutateEndpoint, type NI, type NetworkError, type Normalize, type NormalizeNullable, type NormalizeReturnType, type NormalizedIndex, type NormalizedSchema, type OptimisticUpdateParams, type QueryPath, type Queryable, type ReadEndpoint, type ResolveType, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type SnapshotInterface, type UnknownError, type UpdateFunction, type Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; 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 659d8424b964..7492327912b1 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 @@ -1793,4 +1793,4 @@ declare class NetworkError extends Error { constructor(response: Response); } -export { type AbstractInstanceType, type AddEndpoint, Array$1 as Array, type CheckLoop, Collection, type CustomResource, type DefaultArgs, type Defaults, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type ExtendedResource, type FetchFunction, type FetchGet, type FetchMutate, type FromFallBack, type GetEndpoint, type GetEntities, type GetEntity, type GetIndex, type HookResource, type HookableEndpointInterface, type INormalizeDelegate, type IQueryDelegate, type RestEndpoint$1 as IRestEndpoint, type IndexPath, Invalidate, type KeyofEndpointInstance, type KeyofRestEndpoint, type KeysToArgs, type Mergeable, type MethodToSide, type MutateEndpoint, type NI, NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type OptionsToFunction, type PaginationEndpoint, type PaginationFieldEndpoint, type ParamFetchNoBody, type ParamFetchWithBody, type ParamToArgs, type PartialRestGenerics, type PathArgs, type PathArgsAndSearch, type PathKeys, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Resource, type ResourceEndpointExtensions, type ResourceExtension, type ResourceGenerics, type ResourceInterface, type ResourceOptions, RestEndpoint, type RestEndpointConstructor, type RestEndpointConstructorOptions, type RestEndpointExtendOptions, type RestEndpointOptions, type RestExtendedEndpoint, type RestFetch, type RestGenerics, type RestInstance, type RestInstanceBase, type RestType, type RestTypeNoBody, type RestTypeWithBody, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type ShortenPath, type SnapshotInterface, type UnknownError, type Visit, resource as createResource, getUrlBase, getUrlTokens, hookifyResource, resource, schema_d as schema, validateRequired }; +export { type AbstractInstanceType, type AddEndpoint, Array$1 as Array, type CheckLoop, Collection, type CustomResource, type DefaultArgs, type Defaults, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type ExtendedResource, type FetchFunction, type FetchGet, type FetchMutate, type FromFallBack, type GetEndpoint, type GetEntities, type GetEntity, type GetIndex, type HookResource, type HookableEndpointInterface, type IEntityClass, type IEntityInstance, type INormalizeDelegate, type IQueryDelegate, type RestEndpoint$1 as IRestEndpoint, type IndexPath, Invalidate, type KeyofEndpointInstance, type KeyofRestEndpoint, type KeysToArgs, type Mergeable, type MethodToSide, type MutateEndpoint, type NI, NetworkError, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type OptionsToFunction, type PaginationEndpoint, type PaginationFieldEndpoint, type ParamFetchNoBody, type ParamFetchWithBody, type ParamToArgs, type PartialRestGenerics, type PathArgs, type PathArgsAndSearch, type PathKeys, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Resource, type ResourceEndpointExtensions, type ResourceExtension, type ResourceGenerics, type ResourceInterface, type ResourceOptions, RestEndpoint, type RestEndpointConstructor, type RestEndpointConstructorOptions, type RestEndpointExtendOptions, type RestEndpointOptions, type RestExtendedEndpoint, type RestFetch, type RestGenerics, type RestInstance, type RestInstanceBase, type RestType, type RestTypeNoBody, type RestTypeWithBody, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type ShortenPath, type SnapshotInterface, type UnknownError, type Visit, resource as createResource, getUrlBase, getUrlTokens, hookifyResource, resource, schema_d as schema, validateRequired }; diff --git a/website/src/components/Playground/editor-types/bignumber.d.ts b/website/src/components/Playground/editor-types/bignumber.d.ts index 3e950b714e9a..3ee5405a6210 100644 --- a/website/src/components/Playground/editor-types/bignumber.d.ts +++ b/website/src/components/Playground/editor-types/bignumber.d.ts @@ -1,1821 +1,5 @@ -// Type definitions for bignumber.js >=8.1.0 -// Project: https://github.com/MikeMcl/bignumber.js -// Definitions by: Michael Mclaughlin -// Definitions: https://github.com/MikeMcl/bignumber.js +/// -// Documentation: http://mikemcl.github.io/bignumber.js/ -// -// class BigNumber -// type BigNumber.Constructor -// type BigNumber.ModuloMode -// type BigNumber.RoundingMode -// type BigNumber.Value -// interface BigNumber.Config -// interface BigNumber.Format -// interface BigNumber.Instance -// -// Example: -// -// import {BigNumber} from "bignumber.js" -// //import BigNumber from "bignumber.js" -// -// let rm: BigNumber.RoundingMode = BigNumber.ROUND_UP; -// let f: BigNumber.Format = { decimalSeparator: ',' }; -// let c: BigNumber.Config = { DECIMAL_PLACES: 4, ROUNDING_MODE: rm, FORMAT: f }; -// BigNumber.config(c); -// -// let v: BigNumber.Value = '12345.6789'; -// let b: BigNumber = new BigNumber(v); -// -// The use of compiler option `--strictNullChecks` is recommended. +export = BigNumber; -declare namespace BigNumber { - - /** See `BigNumber.config` (alias `BigNumber.set`) and `BigNumber.clone`. */ - interface Config { - - /** - * An integer, 0 to 1e+9. Default value: 20. - * - * The maximum number of decimal places of the result of operations involving division, i.e. - * division, square root and base conversion operations, and exponentiation when the exponent is - * negative. - * - * ```ts - * BigNumber.config({ DECIMAL_PLACES: 5 }) - * BigNumber.set({ DECIMAL_PLACES: 5 }) - * ``` - */ - DECIMAL_PLACES?: number; - - /** - * An integer, 0 to 8. Default value: `BigNumber.ROUND_HALF_UP` (4). - * - * The rounding mode used in operations that involve division (see `DECIMAL_PLACES`) and the - * default rounding mode of the `decimalPlaces`, `precision`, `toExponential`, `toFixed`, - * `toFormat` and `toPrecision` methods. - * - * The modes are available as enumerated properties of the BigNumber constructor. - * - * ```ts - * BigNumber.config({ ROUNDING_MODE: 0 }) - * BigNumber.set({ ROUNDING_MODE: BigNumber.ROUND_UP }) - * ``` - */ - ROUNDING_MODE?: BigNumber.RoundingMode; - - /** - * An integer, 0 to 1e+9, or an array, [-1e+9 to 0, 0 to 1e+9]. - * Default value: `[-7, 20]`. - * - * The exponent value(s) at which `toString` returns exponential notation. - * - * If a single number is assigned, the value is the exponent magnitude. - * - * If an array of two numbers is assigned then the first number is the negative exponent value at - * and beneath which exponential notation is used, and the second number is the positive exponent - * value at and above which exponential notation is used. - * - * For example, to emulate JavaScript numbers in terms of the exponent values at which they begin - * to use exponential notation, use `[-7, 20]`. - * - * ```ts - * BigNumber.config({ EXPONENTIAL_AT: 2 }) - * new BigNumber(12.3) // '12.3' e is only 1 - * new BigNumber(123) // '1.23e+2' - * new BigNumber(0.123) // '0.123' e is only -1 - * new BigNumber(0.0123) // '1.23e-2' - * - * BigNumber.config({ EXPONENTIAL_AT: [-7, 20] }) - * new BigNumber(123456789) // '123456789' e is only 8 - * new BigNumber(0.000000123) // '1.23e-7' - * - * // Almost never return exponential notation: - * BigNumber.config({ EXPONENTIAL_AT: 1e+9 }) - * - * // Always return exponential notation: - * BigNumber.config({ EXPONENTIAL_AT: 0 }) - * ``` - * - * Regardless of the value of `EXPONENTIAL_AT`, the `toFixed` method will always return a value in - * normal notation and the `toExponential` method will always return a value in exponential form. - * Calling `toString` with a base argument, e.g. `toString(10)`, will also always return normal - * notation. - */ - EXPONENTIAL_AT?: number | [number, number]; - - /** - * An integer, magnitude 1 to 1e+9, or an array, [-1e+9 to -1, 1 to 1e+9]. - * Default value: `[-1e+9, 1e+9]`. - * - * The exponent value(s) beyond which overflow to Infinity and underflow to zero occurs. - * - * If a single number is assigned, it is the maximum exponent magnitude: values wth a positive - * exponent of greater magnitude become Infinity and those with a negative exponent of greater - * magnitude become zero. - * - * If an array of two numbers is assigned then the first number is the negative exponent limit and - * the second number is the positive exponent limit. - * - * For example, to emulate JavaScript numbers in terms of the exponent values at which they - * become zero and Infinity, use [-324, 308]. - * - * ```ts - * BigNumber.config({ RANGE: 500 }) - * BigNumber.config().RANGE // [ -500, 500 ] - * new BigNumber('9.999e499') // '9.999e+499' - * new BigNumber('1e500') // 'Infinity' - * new BigNumber('1e-499') // '1e-499' - * new BigNumber('1e-500') // '0' - * - * BigNumber.config({ RANGE: [-3, 4] }) - * new BigNumber(99999) // '99999' e is only 4 - * new BigNumber(100000) // 'Infinity' e is 5 - * new BigNumber(0.001) // '0.01' e is only -3 - * new BigNumber(0.0001) // '0' e is -4 - * ``` - * The largest possible magnitude of a finite BigNumber is 9.999...e+1000000000. - * The smallest possible magnitude of a non-zero BigNumber is 1e-1000000000. - */ - RANGE?: number | [number, number]; - - /** - * A boolean: `true` or `false`. Default value: `false`. - * - * The value that determines whether cryptographically-secure pseudo-random number generation is - * used. If `CRYPTO` is set to true then the random method will generate random digits using - * `crypto.getRandomValues` in browsers that support it, or `crypto.randomBytes` if using a - * version of Node.js that supports it. - * - * If neither function is supported by the host environment then attempting to set `CRYPTO` to - * `true` will fail and an exception will be thrown. - * - * If `CRYPTO` is `false` then the source of randomness used will be `Math.random` (which is - * assumed to generate at least 30 bits of randomness). - * - * See `BigNumber.random`. - * - * ```ts - * // Node.js - * global.crypto = require('crypto') - * - * BigNumber.config({ CRYPTO: true }) - * BigNumber.config().CRYPTO // true - * BigNumber.random() // 0.54340758610486147524 - * ``` - */ - CRYPTO?: boolean; - - /** - * An integer, 0, 1, 3, 6 or 9. Default value: `BigNumber.ROUND_DOWN` (1). - * - * The modulo mode used when calculating the modulus: `a mod n`. - * The quotient, `q = a / n`, is calculated according to the `ROUNDING_MODE` that corresponds to - * the chosen `MODULO_MODE`. - * The remainder, `r`, is calculated as: `r = a - n * q`. - * - * The modes that are most commonly used for the modulus/remainder operation are shown in the - * following table. Although the other rounding modes can be used, they may not give useful - * results. - * - * Property | Value | Description - * :------------------|:------|:------------------------------------------------------------------ - * `ROUND_UP` | 0 | The remainder is positive if the dividend is negative. - * `ROUND_DOWN` | 1 | The remainder has the same sign as the dividend. - * | | Uses 'truncating division' and matches JavaScript's `%` operator . - * `ROUND_FLOOR` | 3 | The remainder has the same sign as the divisor. - * | | This matches Python's `%` operator. - * `ROUND_HALF_EVEN` | 6 | The IEEE 754 remainder function. - * `EUCLID` | 9 | The remainder is always positive. - * | | Euclidian division: `q = sign(n) * floor(a / abs(n))` - * - * The rounding/modulo modes are available as enumerated properties of the BigNumber constructor. - * - * See `modulo`. - * - * ```ts - * BigNumber.config({ MODULO_MODE: BigNumber.EUCLID }) - * BigNumber.set({ MODULO_MODE: 9 }) // equivalent - * ``` - */ - MODULO_MODE?: BigNumber.ModuloMode; - - /** - * An integer, 0 to 1e+9. Default value: 0. - * - * The maximum precision, i.e. number of significant digits, of the result of the power operation - * - unless a modulus is specified. - * - * If set to 0, the number of significant digits will not be limited. - * - * See `exponentiatedBy`. - * - * ```ts - * BigNumber.config({ POW_PRECISION: 100 }) - * ``` - */ - POW_PRECISION?: number; - - /** - * An object including any number of the properties shown below. - * - * The object configures the format of the string returned by the `toFormat` method. - * The example below shows the properties of the object that are recognised, and - * their default values. - * - * Unlike the other configuration properties, the values of the properties of the `FORMAT` object - * will not be checked for validity - the existing object will simply be replaced by the object - * that is passed in. - * - * See `toFormat`. - * - * ```ts - * BigNumber.config({ - * FORMAT: { - * // string to prepend - * prefix: '', - * // the decimal separator - * decimalSeparator: '.', - * // the grouping separator of the integer part - * groupSeparator: ',', - * // the primary grouping size of the integer part - * groupSize: 3, - * // the secondary grouping size of the integer part - * secondaryGroupSize: 0, - * // the grouping separator of the fraction part - * fractionGroupSeparator: ' ', - * // the grouping size of the fraction part - * fractionGroupSize: 0, - * // string to append - * suffix: '' - * } - * }) - * ``` - */ - FORMAT?: BigNumber.Format; - - /** - * The alphabet used for base conversion. The length of the alphabet corresponds to the maximum - * value of the base argument that can be passed to the BigNumber constructor or `toString`. - * - * Default value: `'0123456789abcdefghijklmnopqrstuvwxyz'`. - * - * There is no maximum length for the alphabet, but it must be at least 2 characters long, - * and it must not contain whitespace or a repeated character, or the sign indicators '+' and - * '-', or the decimal separator '.'. - * - * ```ts - * // duodecimal (base 12) - * BigNumber.config({ ALPHABET: '0123456789TE' }) - * x = new BigNumber('T', 12) - * x.toString() // '10' - * x.toString(12) // 'T' - * ``` - */ - ALPHABET?: string; - } - - /** See `FORMAT` and `toFormat`. */ - interface Format { - - /** The string to prepend. */ - prefix?: string; - - /** The decimal separator. */ - decimalSeparator?: string; - - /** The grouping separator of the integer part. */ - groupSeparator?: string; - - /** The primary grouping size of the integer part. */ - groupSize?: number; - - /** The secondary grouping size of the integer part. */ - secondaryGroupSize?: number; - - /** The grouping separator of the fraction part. */ - fractionGroupSeparator?: string; - - /** The grouping size of the fraction part. */ - fractionGroupSize?: number; - - /** The string to append. */ - suffix?: string; - } - - interface Instance { - - /** The coefficient of the value of this BigNumber, an array of base 1e14 integer numbers, or null. */ - readonly c: number[] | null; - - /** The exponent of the value of this BigNumber, an integer number, -1000000000 to 1000000000, or null. */ - readonly e: number | null; - - /** The sign of the value of this BigNumber, -1, 1, or null. */ - readonly s: number | null; - - [key: string]: any; - } - - type Constructor = typeof BigNumber; - type ModuloMode = 0 | 1 | 3 | 6 | 9; - type RoundingMode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; - type Value = string | number | bigint | Instance; -} - -declare class BigNumber implements BigNumber.Instance { - - /** Used internally to identify a BigNumber instance. */ - private readonly _isBigNumber: true; - - /** The coefficient of the value of this BigNumber, an array of base 1e14 integer numbers, or null. */ - readonly c: number[] | null; - - /** The exponent of the value of this BigNumber, an integer number, -1000000000 to 1000000000, or null. */ - readonly e: number | null; - - /** The sign of the value of this BigNumber, -1, 1, or null. */ - readonly s: number | null; - - /** - * Returns a new instance of a BigNumber object with value `n`, where `n` is a numeric value in - * the specified `base`, or base 10 if `base` is omitted. - * - * ```ts - * x = new BigNumber(123.4567) // '123.4567' - * // 'new' is optional - * y = BigNumber(x) // '123.4567' - * ``` - * - * If `n` is a base 10 value it can be in normal (fixed-point) or exponential notation. - * Values in other bases must be in normal notation. Values in any base can have fraction digits, - * i.e. digits after the decimal point. - * - * ```ts - * new BigNumber(43210) // '43210' - * new BigNumber('4.321e+4') // '43210' - * new BigNumber('-735.0918e-430') // '-7.350918e-428' - * new BigNumber('123412421.234324', 5) // '607236.557696' - * ``` - * - * Signed `0`, signed `Infinity` and `NaN` are supported. - * - * ```ts - * new BigNumber('-Infinity') // '-Infinity' - * new BigNumber(NaN) // 'NaN' - * new BigNumber(-0) // '0' - * new BigNumber('.5') // '0.5' - * new BigNumber('+2') // '2' - * ``` - * - * String values in hexadecimal literal form, e.g. `'0xff'`, are valid, as are string values with - * the octal and binary prefixs `'0o'` and `'0b'`. String values in octal literal form without the - * prefix will be interpreted as decimals, e.g. `'011'` is interpreted as 11, not 9. - * - * ```ts - * new BigNumber(-10110100.1, 2) // '-180.5' - * new BigNumber('-0b10110100.1') // '-180.5' - * new BigNumber('ff.8', 16) // '255.5' - * new BigNumber('0xff.8') // '255.5' - * ``` - * - * If a base is specified, `n` is rounded according to the current `DECIMAL_PLACES` and - * `ROUNDING_MODE` settings. This includes base 10, so don't include a `base` parameter for decimal - * values unless this behaviour is desired. - * - * ```ts - * BigNumber.config({ DECIMAL_PLACES: 5 }) - * new BigNumber(1.23456789) // '1.23456789' - * new BigNumber(1.23456789, 10) // '1.23457' - * ``` - * - * An error is thrown if `base` is invalid. - * - * There is no limit to the number of digits of a value of type string (other than that of - * JavaScript's maximum array size). See `RANGE` to set the maximum and minimum possible exponent - * value of a BigNumber. - * - * ```ts - * new BigNumber('5032485723458348569331745.33434346346912144534543') - * new BigNumber('4.321e10000000') - * ``` - * - * BigNumber `NaN` is returned if `n` is invalid (unless `BigNumber.DEBUG` is `true`, see below). - * - * ```ts - * new BigNumber('.1*') // 'NaN' - * new BigNumber('blurgh') // 'NaN' - * new BigNumber(9, 2) // 'NaN' - * ``` - * - * To aid in debugging, if `BigNumber.DEBUG` is `true` then an error will be thrown on an - * invalid `n`. An error will also be thrown if `n` is of type number with more than 15 - * significant digits, as calling `toString` or `valueOf` on these numbers may not result in the - * intended value. - * - * ```ts - * console.log(823456789123456.3) // 823456789123456.2 - * new BigNumber(823456789123456.3) // '823456789123456.2' - * BigNumber.DEBUG = true - * // 'Error: Number has more than 15 significant digits' - * new BigNumber(823456789123456.3) - * // 'Error: Not a base 2 number' - * new BigNumber(9, 2) - * ``` - * - * A BigNumber can also be created from an object literal. - * Use `isBigNumber` to check that it is well-formed. - * - * ```ts - * new BigNumber({ s: 1, e: 2, c: [ 777, 12300000000000 ], _isBigNumber: true }) // '777.123' - * ``` - * - * @param n A numeric value. - * @param base The base of `n`, integer, 2 to 36 (or `ALPHABET.length`, see `ALPHABET`). - */ - constructor(n: BigNumber.Value, base?: number); - - /** - * Returns a BigNumber whose value is the absolute value, i.e. the magnitude, of the value of this - * BigNumber. - * - * The return value is always exact and unrounded. - * - * ```ts - * x = new BigNumber(-0.8) - * x.absoluteValue() // '0.8' - * ``` - */ - absoluteValue(): BigNumber; - - /** - * Returns a BigNumber whose value is the absolute value, i.e. the magnitude, of the value of this - * BigNumber. - * - * The return value is always exact and unrounded. - * - * ```ts - * x = new BigNumber(-0.8) - * x.abs() // '0.8' - * ``` - */ - abs(): BigNumber; - - /** - * Returns | | - * :-------:|:--------------------------------------------------------------| - * 1 | If the value of this BigNumber is greater than the value of `n` - * -1 | If the value of this BigNumber is less than the value of `n` - * 0 | If this BigNumber and `n` have the same value - * `null` | If the value of either this BigNumber or `n` is `NaN` - * - * ```ts - * - * x = new BigNumber(Infinity) - * y = new BigNumber(5) - * x.comparedTo(y) // 1 - * x.comparedTo(x.minus(1)) // 0 - * y.comparedTo(NaN) // null - * y.comparedTo('110', 2) // -1 - * ``` - * @param n A numeric value. - * @param [base] The base of n. - */ - comparedTo(n: BigNumber.Value, base?: number): 1 | -1 | 0 | null; - - /** - * Returns a BigNumber whose value is the value of this BigNumber rounded by rounding mode - * `roundingMode` to a maximum of `decimalPlaces` decimal places. - * - * If `decimalPlaces` is omitted, the return value is the number of decimal places of the value of - * this BigNumber, or `null` if the value of this BigNumber is ±`Infinity` or `NaN`. - * - * If `roundingMode` is omitted, `ROUNDING_MODE` is used. - * - * Throws if `decimalPlaces` or `roundingMode` is invalid. - * - * ```ts - * x = new BigNumber(1234.56) - * x.decimalPlaces() // 2 - * x.decimalPlaces(1) // '1234.6' - * x.decimalPlaces(2) // '1234.56' - * x.decimalPlaces(10) // '1234.56' - * x.decimalPlaces(0, 1) // '1234' - * x.decimalPlaces(0, 6) // '1235' - * x.decimalPlaces(1, 1) // '1234.5' - * x.decimalPlaces(1, BigNumber.ROUND_HALF_EVEN) // '1234.6' - * x // '1234.56' - * y = new BigNumber('9.9e-101') - * y.decimalPlaces() // 102 - * ``` - * - * @param [decimalPlaces] Decimal places, integer, 0 to 1e+9. - * @param [roundingMode] Rounding mode, integer, 0 to 8. - */ - decimalPlaces(): number | null; - decimalPlaces(decimalPlaces: number, roundingMode?: BigNumber.RoundingMode): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber rounded by rounding mode - * `roundingMode` to a maximum of `decimalPlaces` decimal places. - * - * If `decimalPlaces` is omitted, the return value is the number of decimal places of the value of - * this BigNumber, or `null` if the value of this BigNumber is ±`Infinity` or `NaN`. - * - * If `roundingMode` is omitted, `ROUNDING_MODE` is used. - * - * Throws if `decimalPlaces` or `roundingMode` is invalid. - * - * ```ts - * x = new BigNumber(1234.56) - * x.dp() // 2 - * x.dp(1) // '1234.6' - * x.dp(2) // '1234.56' - * x.dp(10) // '1234.56' - * x.dp(0, 1) // '1234' - * x.dp(0, 6) // '1235' - * x.dp(1, 1) // '1234.5' - * x.dp(1, BigNumber.ROUND_HALF_EVEN) // '1234.6' - * x // '1234.56' - * y = new BigNumber('9.9e-101') - * y.dp() // 102 - * ``` - * - * @param [decimalPlaces] Decimal places, integer, 0 to 1e+9. - * @param [roundingMode] Rounding mode, integer, 0 to 8. - */ - dp(): number | null; - dp(decimalPlaces: number, roundingMode?: BigNumber.RoundingMode): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber divided by `n`, rounded - * according to the current `DECIMAL_PLACES` and `ROUNDING_MODE` settings. - * - * ```ts - * x = new BigNumber(355) - * y = new BigNumber(113) - * x.dividedBy(y) // '3.14159292035398230088' - * x.dividedBy(5) // '71' - * x.dividedBy(47, 16) // '5' - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - dividedBy(n: BigNumber.Value, base?: number): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber divided by `n`, rounded - * according to the current `DECIMAL_PLACES` and `ROUNDING_MODE` settings. - * - * ```ts - * x = new BigNumber(355) - * y = new BigNumber(113) - * x.div(y) // '3.14159292035398230088' - * x.div(5) // '71' - * x.div(47, 16) // '5' - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - div(n: BigNumber.Value, base?: number): BigNumber; - - /** - * Returns a BigNumber whose value is the integer part of dividing the value of this BigNumber by - * `n`. - * - * ```ts - * x = new BigNumber(5) - * y = new BigNumber(3) - * x.dividedToIntegerBy(y) // '1' - * x.dividedToIntegerBy(0.7) // '7' - * x.dividedToIntegerBy('0.f', 16) // '5' - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - dividedToIntegerBy(n: BigNumber.Value, base?: number): BigNumber; - - /** - * Returns a BigNumber whose value is the integer part of dividing the value of this BigNumber by - * `n`. - * - * ```ts - * x = new BigNumber(5) - * y = new BigNumber(3) - * x.idiv(y) // '1' - * x.idiv(0.7) // '7' - * x.idiv('0.f', 16) // '5' - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - idiv(n: BigNumber.Value, base?: number): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber exponentiated by `n`, i.e. - * raised to the power `n`, and optionally modulo a modulus `m`. - * - * If `n` is negative the result is rounded according to the current `DECIMAL_PLACES` and - * `ROUNDING_MODE` settings. - * - * As the number of digits of the result of the power operation can grow so large so quickly, - * e.g. 123.456**10000 has over 50000 digits, the number of significant digits calculated is - * limited to the value of the `POW_PRECISION` setting (unless a modulus `m` is specified). - * - * By default `POW_PRECISION` is set to 0. This means that an unlimited number of significant - * digits will be calculated, and that the method's performance will decrease dramatically for - * larger exponents. - * - * If `m` is specified and the value of `m`, `n` and this BigNumber are integers and `n` is - * positive, then a fast modular exponentiation algorithm is used, otherwise the operation will - * be performed as `x.exponentiatedBy(n).modulo(m)` with a `POW_PRECISION` of 0. - * - * Throws if `n` is not an integer. - * - * ```ts - * Math.pow(0.7, 2) // 0.48999999999999994 - * x = new BigNumber(0.7) - * x.exponentiatedBy(2) // '0.49' - * BigNumber(3).exponentiatedBy(-2) // '0.11111111111111111111' - * ``` - * - * @param n The exponent, an integer. - * @param [m] The modulus. - */ - exponentiatedBy(n: BigNumber.Value, m?: BigNumber.Value): BigNumber; - exponentiatedBy(n: number, m?: BigNumber.Value): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber exponentiated by `n`, i.e. - * raised to the power `n`, and optionally modulo a modulus `m`. - * - * If `n` is negative the result is rounded according to the current `DECIMAL_PLACES` and - * `ROUNDING_MODE` settings. - * - * As the number of digits of the result of the power operation can grow so large so quickly, - * e.g. 123.456**10000 has over 50000 digits, the number of significant digits calculated is - * limited to the value of the `POW_PRECISION` setting (unless a modulus `m` is specified). - * - * By default `POW_PRECISION` is set to 0. This means that an unlimited number of significant - * digits will be calculated, and that the method's performance will decrease dramatically for - * larger exponents. - * - * If `m` is specified and the value of `m`, `n` and this BigNumber are integers and `n` is - * positive, then a fast modular exponentiation algorithm is used, otherwise the operation will - * be performed as `x.pow(n).modulo(m)` with a `POW_PRECISION` of 0. - * - * Throws if `n` is not an integer. - * - * ```ts - * Math.pow(0.7, 2) // 0.48999999999999994 - * x = new BigNumber(0.7) - * x.pow(2) // '0.49' - * BigNumber(3).pow(-2) // '0.11111111111111111111' - * ``` - * - * @param n The exponent, an integer. - * @param [m] The modulus. - */ - pow(n: BigNumber.Value, m?: BigNumber.Value): BigNumber; - pow(n: number, m?: BigNumber.Value): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber rounded to an integer using - * rounding mode `rm`. - * - * If `rm` is omitted, `ROUNDING_MODE` is used. - * - * Throws if `rm` is invalid. - * - * ```ts - * x = new BigNumber(123.456) - * x.integerValue() // '123' - * x.integerValue(BigNumber.ROUND_CEIL) // '124' - * y = new BigNumber(-12.7) - * y.integerValue() // '-13' - * x.integerValue(BigNumber.ROUND_DOWN) // '-12' - * ``` - * - * @param {BigNumber.RoundingMode} [rm] The roundng mode, an integer, 0 to 8. - */ - integerValue(rm?: BigNumber.RoundingMode): BigNumber; - - /** - * Returns `true` if the value of this BigNumber is equal to the value of `n`, otherwise returns - * `false`. - * - * As with JavaScript, `NaN` does not equal `NaN`. - * - * ```ts - * 0 === 1e-324 // true - * x = new BigNumber(0) - * x.isEqualTo('1e-324') // false - * BigNumber(-0).isEqualTo(x) // true ( -0 === 0 ) - * BigNumber(255).isEqualTo('ff', 16) // true - * - * y = new BigNumber(NaN) - * y.isEqualTo(NaN) // false - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - isEqualTo(n: BigNumber.Value, base?: number): boolean; - - /** - * Returns `true` if the value of this BigNumber is equal to the value of `n`, otherwise returns - * `false`. - * - * As with JavaScript, `NaN` does not equal `NaN`. - * - * ```ts - * 0 === 1e-324 // true - * x = new BigNumber(0) - * x.eq('1e-324') // false - * BigNumber(-0).eq(x) // true ( -0 === 0 ) - * BigNumber(255).eq('ff', 16) // true - * - * y = new BigNumber(NaN) - * y.eq(NaN) // false - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - eq(n: BigNumber.Value, base?: number): boolean; - - /** - * Returns `true` if the value of this BigNumber is a finite number, otherwise returns `false`. - * - * The only possible non-finite values of a BigNumber are `NaN`, `Infinity` and `-Infinity`. - * - * ```ts - * x = new BigNumber(1) - * x.isFinite() // true - * y = new BigNumber(Infinity) - * y.isFinite() // false - * ``` - */ - isFinite(): boolean; - - /** - * Returns `true` if the value of this BigNumber is greater than the value of `n`, otherwise - * returns `false`. - * - * ```ts - * 0.1 > (0.3 - 0.2) // true - * x = new BigNumber(0.1) - * x.isGreaterThan(BigNumber(0.3).minus(0.2)) // false - * BigNumber(0).isGreaterThan(x) // false - * BigNumber(11, 3).isGreaterThan(11.1, 2) // true - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - isGreaterThan(n: BigNumber.Value, base?: number): boolean; - - /** - * Returns `true` if the value of this BigNumber is greater than the value of `n`, otherwise - * returns `false`. - * - * ```ts - * 0.1 > (0.3 - 0.2) // true - * x = new BigNumber(0.1) - * x.gt(BigNumber(0.3).minus(0.2)) // false - * BigNumber(0).gt(x) // false - * BigNumber(11, 3).gt(11.1, 2) // true - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - gt(n: BigNumber.Value, base?: number): boolean; - - /** - * Returns `true` if the value of this BigNumber is greater than or equal to the value of `n`, - * otherwise returns `false`. - * - * ```ts - * (0.3 - 0.2) >= 0.1 // false - * x = new BigNumber(0.3).minus(0.2) - * x.isGreaterThanOrEqualTo(0.1) // true - * BigNumber(1).isGreaterThanOrEqualTo(x) // true - * BigNumber(10, 18).isGreaterThanOrEqualTo('i', 36) // true - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - isGreaterThanOrEqualTo(n: BigNumber.Value, base?: number): boolean; - - /** - * Returns `true` if the value of this BigNumber is greater than or equal to the value of `n`, - * otherwise returns `false`. - * - * ```ts - * (0.3 - 0.2) >= 0.1 // false - * x = new BigNumber(0.3).minus(0.2) - * x.gte(0.1) // true - * BigNumber(1).gte(x) // true - * BigNumber(10, 18).gte('i', 36) // true - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - gte(n: BigNumber.Value, base?: number): boolean; - - /** - * Returns `true` if the value of this BigNumber is an integer, otherwise returns `false`. - * - * ```ts - * x = new BigNumber(1) - * x.isInteger() // true - * y = new BigNumber(123.456) - * y.isInteger() // false - * ``` - */ - isInteger(): boolean; - - /** - * Returns `true` if the value of this BigNumber is less than the value of `n`, otherwise returns - * `false`. - * - * ```ts - * (0.3 - 0.2) < 0.1 // true - * x = new BigNumber(0.3).minus(0.2) - * x.isLessThan(0.1) // false - * BigNumber(0).isLessThan(x) // true - * BigNumber(11.1, 2).isLessThan(11, 3) // true - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - isLessThan(n: BigNumber.Value, base?: number): boolean; - - /** - * Returns `true` if the value of this BigNumber is less than the value of `n`, otherwise returns - * `false`. - * - * ```ts - * (0.3 - 0.2) < 0.1 // true - * x = new BigNumber(0.3).minus(0.2) - * x.lt(0.1) // false - * BigNumber(0).lt(x) // true - * BigNumber(11.1, 2).lt(11, 3) // true - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - lt(n: BigNumber.Value, base?: number): boolean; - - /** - * Returns `true` if the value of this BigNumber is less than or equal to the value of `n`, - * otherwise returns `false`. - * - * ```ts - * 0.1 <= (0.3 - 0.2) // false - * x = new BigNumber(0.1) - * x.isLessThanOrEqualTo(BigNumber(0.3).minus(0.2)) // true - * BigNumber(-1).isLessThanOrEqualTo(x) // true - * BigNumber(10, 18).isLessThanOrEqualTo('i', 36) // true - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - isLessThanOrEqualTo(n: BigNumber.Value, base?: number): boolean; - - /** - * Returns `true` if the value of this BigNumber is less than or equal to the value of `n`, - * otherwise returns `false`. - * - * ```ts - * 0.1 <= (0.3 - 0.2) // false - * x = new BigNumber(0.1) - * x.lte(BigNumber(0.3).minus(0.2)) // true - * BigNumber(-1).lte(x) // true - * BigNumber(10, 18).lte('i', 36) // true - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - lte(n: BigNumber.Value, base?: number): boolean; - - /** - * Returns `true` if the value of this BigNumber is `NaN`, otherwise returns `false`. - * - * ```ts - * x = new BigNumber(NaN) - * x.isNaN() // true - * y = new BigNumber('Infinity') - * y.isNaN() // false - * ``` - */ - isNaN(): boolean; - - /** - * Returns `true` if the value of this BigNumber is negative, otherwise returns `false`. - * - * ```ts - * x = new BigNumber(-0) - * x.isNegative() // true - * y = new BigNumber(2) - * y.isNegative() // false - * ``` - */ - isNegative(): boolean; - - /** - * Returns `true` if the value of this BigNumber is positive, otherwise returns `false`. - * - * ```ts - * x = new BigNumber(-0) - * x.isPositive() // false - * y = new BigNumber(2) - * y.isPositive() // true - * ``` - */ - isPositive(): boolean; - - /** - * Returns `true` if the value of this BigNumber is zero or minus zero, otherwise returns `false`. - * - * ```ts - * x = new BigNumber(-0) - * x.isZero() // true - * ``` - */ - isZero(): boolean; - - /** - * Returns a BigNumber whose value is the value of this BigNumber minus `n`. - * - * The return value is always exact and unrounded. - * - * ```ts - * 0.3 - 0.1 // 0.19999999999999998 - * x = new BigNumber(0.3) - * x.minus(0.1) // '0.2' - * x.minus(0.6, 20) // '0' - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - minus(n: BigNumber.Value, base?: number): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber modulo `n`, i.e. the integer - * remainder of dividing this BigNumber by `n`. - * - * The value returned, and in particular its sign, is dependent on the value of the `MODULO_MODE` - * setting of this BigNumber constructor. If it is 1 (default value), the result will have the - * same sign as this BigNumber, and it will match that of Javascript's `%` operator (within the - * limits of double precision) and BigDecimal's `remainder` method. - * - * The return value is always exact and unrounded. - * - * See `MODULO_MODE` for a description of the other modulo modes. - * - * ```ts - * 1 % 0.9 // 0.09999999999999998 - * x = new BigNumber(1) - * x.modulo(0.9) // '0.1' - * y = new BigNumber(33) - * y.modulo('a', 33) // '3' - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - modulo(n: BigNumber.Value, base?: number): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber modulo `n`, i.e. the integer - * remainder of dividing this BigNumber by `n`. - * - * The value returned, and in particular its sign, is dependent on the value of the `MODULO_MODE` - * setting of this BigNumber constructor. If it is 1 (default value), the result will have the - * same sign as this BigNumber, and it will match that of Javascript's `%` operator (within the - * limits of double precision) and BigDecimal's `remainder` method. - * - * The return value is always exact and unrounded. - * - * See `MODULO_MODE` for a description of the other modulo modes. - * - * ```ts - * 1 % 0.9 // 0.09999999999999998 - * x = new BigNumber(1) - * x.mod(0.9) // '0.1' - * y = new BigNumber(33) - * y.mod('a', 33) // '3' - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - mod(n: BigNumber.Value, base?: number): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber multiplied by `n`. - * - * The return value is always exact and unrounded. - * - * ```ts - * 0.6 * 3 // 1.7999999999999998 - * x = new BigNumber(0.6) - * y = x.multipliedBy(3) // '1.8' - * BigNumber('7e+500').multipliedBy(y) // '1.26e+501' - * x.multipliedBy('-a', 16) // '-6' - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - multipliedBy(n: BigNumber.Value, base?: number): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber multiplied by `n`. - * - * The return value is always exact and unrounded. - * - * ```ts - * 0.6 * 3 // 1.7999999999999998 - * x = new BigNumber(0.6) - * y = x.times(3) // '1.8' - * BigNumber('7e+500').times(y) // '1.26e+501' - * x.times('-a', 16) // '-6' - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - times(n: BigNumber.Value, base?: number): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber negated, i.e. multiplied by -1. - * - * ```ts - * x = new BigNumber(1.8) - * x.negated() // '-1.8' - * y = new BigNumber(-1.3) - * y.negated() // '1.3' - * ``` - */ - negated(): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber plus `n`. - * - * The return value is always exact and unrounded. - * - * ```ts - * 0.1 + 0.2 // 0.30000000000000004 - * x = new BigNumber(0.1) - * y = x.plus(0.2) // '0.3' - * BigNumber(0.7).plus(x).plus(y) // '1.1' - * x.plus('0.1', 8) // '0.225' - * ``` - * - * @param n A numeric value. - * @param [base] The base of n. - */ - plus(n: BigNumber.Value, base?: number): BigNumber; - - /** - * Returns the number of significant digits of the value of this BigNumber, or `null` if the value - * of this BigNumber is ±`Infinity` or `NaN`. - * - * If `includeZeros` is true then any trailing zeros of the integer part of the value of this - * BigNumber are counted as significant digits, otherwise they are not. - * - * Throws if `includeZeros` is invalid. - * - * ```ts - * x = new BigNumber(9876.54321) - * x.precision() // 9 - * y = new BigNumber(987000) - * y.precision(false) // 3 - * y.precision(true) // 6 - * ``` - * - * @param [includeZeros] Whether to include integer trailing zeros in the significant digit count. - */ - precision(includeZeros?: boolean): number; - - /** - * Returns a BigNumber whose value is the value of this BigNumber rounded to a precision of - * `significantDigits` significant digits using rounding mode `roundingMode`. - * - * If `roundingMode` is omitted, `ROUNDING_MODE` will be used. - * - * Throws if `significantDigits` or `roundingMode` is invalid. - * - * ```ts - * x = new BigNumber(9876.54321) - * x.precision(6) // '9876.54' - * x.precision(6, BigNumber.ROUND_UP) // '9876.55' - * x.precision(2) // '9900' - * x.precision(2, 1) // '9800' - * x // '9876.54321' - * ``` - * - * @param significantDigits Significant digits, integer, 1 to 1e+9. - * @param [roundingMode] Rounding mode, integer, 0 to 8. - */ - precision(significantDigits: number, roundingMode?: BigNumber.RoundingMode): BigNumber; - - /** - * Returns the number of significant digits of the value of this BigNumber, - * or `null` if the value of this BigNumber is ±`Infinity` or `NaN`. - * - * If `includeZeros` is true then any trailing zeros of the integer part of - * the value of this BigNumber are counted as significant digits, otherwise - * they are not. - * - * Throws if `includeZeros` is invalid. - * - * ```ts - * x = new BigNumber(9876.54321) - * x.sd() // 9 - * y = new BigNumber(987000) - * y.sd(false) // 3 - * y.sd(true) // 6 - * ``` - * - * @param [includeZeros] Whether to include integer trailing zeros in the significant digit count. - */ - sd(includeZeros?: boolean): number; - - /** - * Returns a BigNumber whose value is the value of this BigNumber rounded to a precision of - * `significantDigits` significant digits using rounding mode `roundingMode`. - * - * If `roundingMode` is omitted, `ROUNDING_MODE` will be used. - * - * Throws if `significantDigits` or `roundingMode` is invalid. - * - * ```ts - * x = new BigNumber(9876.54321) - * x.sd(6) // '9876.54' - * x.sd(6, BigNumber.ROUND_UP) // '9876.55' - * x.sd(2) // '9900' - * x.sd(2, 1) // '9800' - * x // '9876.54321' - * ``` - * - * @param significantDigits Significant digits, integer, 1 to 1e+9. - * @param [roundingMode] Rounding mode, integer, 0 to 8. - */ - sd(significantDigits: number, roundingMode?: BigNumber.RoundingMode): BigNumber; - - /** - * Returns a BigNumber whose value is the value of this BigNumber shifted by `n` places. - * - * The shift is of the decimal point, i.e. of powers of ten, and is to the left if `n` is negative - * or to the right if `n` is positive. - * - * The return value is always exact and unrounded. - * - * Throws if `n` is invalid. - * - * ```ts - * x = new BigNumber(1.23) - * x.shiftedBy(3) // '1230' - * x.shiftedBy(-3) // '0.00123' - * ``` - * - * @param n The shift value, integer, -9007199254740991 to 9007199254740991. - */ - shiftedBy(n: number): BigNumber; - - /** - * Returns a BigNumber whose value is the square root of the value of this BigNumber, rounded - * according to the current `DECIMAL_PLACES` and `ROUNDING_MODE` settings. - * - * The return value will be correctly rounded, i.e. rounded as if the result was first calculated - * to an infinite number of correct digits before rounding. - * - * ```ts - * x = new BigNumber(16) - * x.squareRoot() // '4' - * y = new BigNumber(3) - * y.squareRoot() // '1.73205080756887729353' - * ``` - */ - squareRoot(): BigNumber; - - /** - * Returns a BigNumber whose value is the square root of the value of this BigNumber, rounded - * according to the current `DECIMAL_PLACES` and `ROUNDING_MODE` settings. - * - * The return value will be correctly rounded, i.e. rounded as if the result was first calculated - * to an infinite number of correct digits before rounding. - * - * ```ts - * x = new BigNumber(16) - * x.sqrt() // '4' - * y = new BigNumber(3) - * y.sqrt() // '1.73205080756887729353' - * ``` - */ - sqrt(): BigNumber; - - /** - * Returns a string representing the value of this BigNumber in exponential notation rounded using - * rounding mode `roundingMode` to `decimalPlaces` decimal places, i.e with one digit before the - * decimal point and `decimalPlaces` digits after it. - * - * If the value of this BigNumber in exponential notation has fewer than `decimalPlaces` fraction - * digits, the return value will be appended with zeros accordingly. - * - * If `decimalPlaces` is omitted, the number of digits after the decimal point defaults to the - * minimum number of digits necessary to represent the value exactly. - * - * If `roundingMode` is omitted, `ROUNDING_MODE` is used. - * - * Throws if `decimalPlaces` or `roundingMode` is invalid. - * - * ```ts - * x = 45.6 - * y = new BigNumber(x) - * x.toExponential() // '4.56e+1' - * y.toExponential() // '4.56e+1' - * x.toExponential(0) // '5e+1' - * y.toExponential(0) // '5e+1' - * x.toExponential(1) // '4.6e+1' - * y.toExponential(1) // '4.6e+1' - * y.toExponential(1, 1) // '4.5e+1' (ROUND_DOWN) - * x.toExponential(3) // '4.560e+1' - * y.toExponential(3) // '4.560e+1' - * ``` - * - * @param [decimalPlaces] Decimal places, integer, 0 to 1e+9. - * @param [roundingMode] Rounding mode, integer, 0 to 8. - */ - toExponential(decimalPlaces: number, roundingMode?: BigNumber.RoundingMode): string; - toExponential(): string; - - /** - * Returns a string representing the value of this BigNumber in normal (fixed-point) notation - * rounded to `decimalPlaces` decimal places using rounding mode `roundingMode`. - * - * If the value of this BigNumber in normal notation has fewer than `decimalPlaces` fraction - * digits, the return value will be appended with zeros accordingly. - * - * Unlike `Number.prototype.toFixed`, which returns exponential notation if a number is greater or - * equal to 10**21, this method will always return normal notation. - * - * If `decimalPlaces` is omitted, the return value will be unrounded and in normal notation. - * This is also unlike `Number.prototype.toFixed`, which returns the value to zero decimal places. - * It is useful when normal notation is required and the current `EXPONENTIAL_AT` setting causes - * `toString` to return exponential notation. - * - * If `roundingMode` is omitted, `ROUNDING_MODE` is used. - * - * Throws if `decimalPlaces` or `roundingMode` is invalid. - * - * ```ts - * x = 3.456 - * y = new BigNumber(x) - * x.toFixed() // '3' - * y.toFixed() // '3.456' - * y.toFixed(0) // '3' - * x.toFixed(2) // '3.46' - * y.toFixed(2) // '3.46' - * y.toFixed(2, 1) // '3.45' (ROUND_DOWN) - * x.toFixed(5) // '3.45600' - * y.toFixed(5) // '3.45600' - * ``` - * - * @param [decimalPlaces] Decimal places, integer, 0 to 1e+9. - * @param [roundingMode] Rounding mode, integer, 0 to 8. - */ - toFixed(decimalPlaces: number, roundingMode?: BigNumber.RoundingMode): string; - toFixed(): string; - - /** - * Returns a string representing the value of this BigNumber in normal (fixed-point) notation - * rounded to `decimalPlaces` decimal places using rounding mode `roundingMode`, and formatted - * according to the properties of the `format` or `FORMAT` object. - * - * The formatting object may contain some or all of the properties shown in the examples below. - * - * If `decimalPlaces` is omitted, then the return value is not rounded to a fixed number of - * decimal places. - * - * If `roundingMode` is omitted, `ROUNDING_MODE` is used. - * - * If `format` is omitted, `FORMAT` is used. - * - * Throws if `decimalPlaces`, `roundingMode`, or `format` is invalid. - * - * ```ts - * fmt = { - * decimalSeparator: '.', - * groupSeparator: ',', - * groupSize: 3, - * secondaryGroupSize: 0, - * fractionGroupSeparator: ' ', - * fractionGroupSize: 0 - * } - * - * x = new BigNumber('123456789.123456789') - * - * // Set the global formatting options - * BigNumber.config({ FORMAT: fmt }) - * - * x.toFormat() // '123,456,789.123456789' - * x.toFormat(3) // '123,456,789.123' - * - * // If a reference to the object assigned to FORMAT has been retained, - * // the format properties can be changed directly - * fmt.groupSeparator = ' ' - * fmt.fractionGroupSize = 5 - * x.toFormat() // '123 456 789.12345 6789' - * - * // Alternatively, pass the formatting options as an argument - * fmt = { - * decimalSeparator: ',', - * groupSeparator: '.', - * groupSize: 3, - * secondaryGroupSize: 2 - * } - * - * x.toFormat() // '123 456 789.12345 6789' - * x.toFormat(fmt) // '12.34.56.789,123456789' - * x.toFormat(2, fmt) // '12.34.56.789,12' - * x.toFormat(3, BigNumber.ROUND_UP, fmt) // '12.34.56.789,124' - * ``` - * - * @param [decimalPlaces] Decimal places, integer, 0 to 1e+9. - * @param [roundingMode] Rounding mode, integer, 0 to 8. - * @param [format] Formatting options object. See `BigNumber.Format`. - */ - toFormat(decimalPlaces: number, roundingMode: BigNumber.RoundingMode, format?: BigNumber.Format): string; - toFormat(decimalPlaces: number, roundingMode?: BigNumber.RoundingMode): string; - toFormat(decimalPlaces?: number): string; - toFormat(decimalPlaces: number, format: BigNumber.Format): string; - toFormat(format: BigNumber.Format): string; - - /** - * Returns an array of two BigNumbers representing the value of this BigNumber as a simple - * fraction with an integer numerator and an integer denominator. - * The denominator will be a positive non-zero value less than or equal to `max_denominator`. - * If a maximum denominator, `max_denominator`, is not specified, the denominator will be the - * lowest value necessary to represent the number exactly. - * - * Throws if `max_denominator` is invalid. - * - * ```ts - * x = new BigNumber(1.75) - * x.toFraction() // '7, 4' - * - * pi = new BigNumber('3.14159265358') - * pi.toFraction() // '157079632679,50000000000' - * pi.toFraction(100000) // '312689, 99532' - * pi.toFraction(10000) // '355, 113' - * pi.toFraction(100) // '311, 99' - * pi.toFraction(10) // '22, 7' - * pi.toFraction(1) // '3, 1' - * ``` - * - * @param [max_denominator] The maximum denominator, integer > 0, or Infinity. - */ - toFraction(max_denominator?: BigNumber.Value): [BigNumber, BigNumber]; - - /** As `valueOf`. */ - toJSON(): string; - - /** - * Returns the value of this BigNumber as a JavaScript primitive number. - * - * Using the unary plus operator gives the same result. - * - * ```ts - * x = new BigNumber(456.789) - * x.toNumber() // 456.789 - * +x // 456.789 - * - * y = new BigNumber('45987349857634085409857349856430985') - * y.toNumber() // 4.598734985763409e+34 - * - * z = new BigNumber(-0) - * 1 / z.toNumber() // -Infinity - * 1 / +z // -Infinity - * ``` - */ - toNumber(): number; - - /** - * Returns a string representing the value of this BigNumber rounded to `significantDigits` - * significant digits using rounding mode `roundingMode`. - * - * If `significantDigits` is less than the number of digits necessary to represent the integer - * part of the value in normal (fixed-point) notation, then exponential notation is used. - * - * If `significantDigits` is omitted, then the return value is the same as `n.toString()`. - * - * If `roundingMode` is omitted, `ROUNDING_MODE` is used. - * - * Throws if `significantDigits` or `roundingMode` is invalid. - * - * ```ts - * x = 45.6 - * y = new BigNumber(x) - * x.toPrecision() // '45.6' - * y.toPrecision() // '45.6' - * x.toPrecision(1) // '5e+1' - * y.toPrecision(1) // '5e+1' - * y.toPrecision(2, 0) // '4.6e+1' (ROUND_UP) - * y.toPrecision(2, 1) // '4.5e+1' (ROUND_DOWN) - * x.toPrecision(5) // '45.600' - * y.toPrecision(5) // '45.600' - * ``` - * - * @param [significantDigits] Significant digits, integer, 1 to 1e+9. - * @param [roundingMode] Rounding mode, integer 0 to 8. - */ - toPrecision(significantDigits: number, roundingMode?: BigNumber.RoundingMode): string; - toPrecision(): string; - - /** - * Returns a string representing the value of this BigNumber in base `base`, or base 10 if `base` - * is omitted. - * - * For bases above 10, and using the default base conversion alphabet (see `ALPHABET`), values - * from 10 to 35 are represented by a-z (the same as `Number.prototype.toString`). - * - * If a base is specified the value is rounded according to the current `DECIMAL_PLACES` and - * `ROUNDING_MODE` settings, otherwise it is not. - * - * If a base is not specified, and this BigNumber has a positive exponent that is equal to or - * greater than the positive component of the current `EXPONENTIAL_AT` setting, or a negative - * exponent equal to or less than the negative component of the setting, then exponential notation - * is returned. - * - * Throws if `base` is invalid. - * - * ```ts - * x = new BigNumber(750000) - * x.toString() // '750000' - * BigNumber.config({ EXPONENTIAL_AT: 5 }) - * x.toString() // '7.5e+5' - * - * y = new BigNumber(362.875) - * y.toString(2) // '101101010.111' - * y.toString(9) // '442.77777777777777777778' - * y.toString(32) // 'ba.s' - * - * BigNumber.config({ DECIMAL_PLACES: 4 }); - * z = new BigNumber('1.23456789') - * z.toString() // '1.23456789' - * z.toString(10) // '1.2346' - * ``` - * - * @param [base] The base, integer, 2 to 36 (or `ALPHABET.length`, see `ALPHABET`). - */ - toString(base?: number): string; - - /** - * As `toString`, but does not accept a base argument and includes the minus sign for negative - * zero. - * - * ``ts - * x = new BigNumber('-0') - * x.toString() // '0' - * x.valueOf() // '-0' - * y = new BigNumber('1.777e+457') - * y.valueOf() // '1.777e+457' - * ``` - */ - valueOf(): string; - - /** Helps ES6 import. */ - private static readonly default?: BigNumber.Constructor; - - /** Helps ES6 import. */ - private static readonly BigNumber?: BigNumber.Constructor; - - /** Rounds away from zero. */ - static readonly ROUND_UP: 0; - - /** Rounds towards zero. */ - static readonly ROUND_DOWN: 1; - - /** Rounds towards Infinity. */ - static readonly ROUND_CEIL: 2; - - /** Rounds towards -Infinity. */ - static readonly ROUND_FLOOR: 3; - - /** Rounds towards nearest neighbour. If equidistant, rounds away from zero . */ - static readonly ROUND_HALF_UP: 4; - - /** Rounds towards nearest neighbour. If equidistant, rounds towards zero. */ - static readonly ROUND_HALF_DOWN: 5; - - /** Rounds towards nearest neighbour. If equidistant, rounds towards even neighbour. */ - static readonly ROUND_HALF_EVEN: 6; - - /** Rounds towards nearest neighbour. If equidistant, rounds towards Infinity. */ - static readonly ROUND_HALF_CEIL: 7; - - /** Rounds towards nearest neighbour. If equidistant, rounds towards -Infinity. */ - static readonly ROUND_HALF_FLOOR: 8; - - /** See `MODULO_MODE`. */ - static readonly EUCLID: 9; - - /** - * To aid in debugging, if a `BigNumber.DEBUG` property is `true` then an error will be thrown - * if the BigNumber constructor receives an invalid `BigNumber.Value`, or if `BigNumber.isBigNumber` - * receives a BigNumber instance that is malformed. - * - * ```ts - * // No error, and BigNumber NaN is returned. - * new BigNumber('blurgh') // 'NaN' - * new BigNumber(9, 2) // 'NaN' - * BigNumber.DEBUG = true - * new BigNumber('blurgh') // '[BigNumber Error] Not a number' - * new BigNumber(9, 2) // '[BigNumber Error] Not a base 2 number' - * ``` - * - * An error will also be thrown if a `BigNumber.Value` is of type number with more than 15 - * significant digits, as calling `toString` or `valueOf` on such numbers may not result - * in the intended value. - * - * ```ts - * console.log(823456789123456.3) // 823456789123456.2 - * // No error, and the returned BigNumber does not have the same value as the number literal. - * new BigNumber(823456789123456.3) // '823456789123456.2' - * BigNumber.DEBUG = true - * new BigNumber(823456789123456.3) - * // '[BigNumber Error] Number primitive has more than 15 significant digits' - * ``` - * - * Check that a BigNumber instance is well-formed: - * - * ```ts - * x = new BigNumber(10) - * - * BigNumber.DEBUG = false - * // Change x.c to an illegitimate value. - * x.c = NaN - * // No error, as BigNumber.DEBUG is false. - * BigNumber.isBigNumber(x) // true - * - * BigNumber.DEBUG = true - * BigNumber.isBigNumber(x) // '[BigNumber Error] Invalid BigNumber' - * ``` - */ - static DEBUG?: boolean; - - /** - * Returns a new independent BigNumber constructor with configuration as described by `object`, or - * with the default configuration if object is omitted. - * - * Throws if `object` is not an object. - * - * ```ts - * BigNumber.config({ DECIMAL_PLACES: 5 }) - * BN = BigNumber.clone({ DECIMAL_PLACES: 9 }) - * - * x = new BigNumber(1) - * y = new BN(1) - * - * x.div(3) // 0.33333 - * y.div(3) // 0.333333333 - * - * // BN = BigNumber.clone({ DECIMAL_PLACES: 9 }) is equivalent to: - * BN = BigNumber.clone() - * BN.config({ DECIMAL_PLACES: 9 }) - * ``` - * - * @param [object] The configuration object. - */ - static clone(object?: BigNumber.Config): BigNumber.Constructor; - - /** - * Configures the settings that apply to this BigNumber constructor. - * - * The configuration object, `object`, contains any number of the properties shown in the example - * below. - * - * Returns an object with the above properties and their current values. - * - * Throws if `object` is not an object, or if an invalid value is assigned to one or more of the - * properties. - * - * ```ts - * BigNumber.config({ - * DECIMAL_PLACES: 40, - * ROUNDING_MODE: BigNumber.ROUND_HALF_CEIL, - * EXPONENTIAL_AT: [-10, 20], - * RANGE: [-500, 500], - * CRYPTO: true, - * MODULO_MODE: BigNumber.ROUND_FLOOR, - * POW_PRECISION: 80, - * FORMAT: { - * groupSize: 3, - * groupSeparator: ' ', - * decimalSeparator: ',' - * }, - * ALPHABET: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_' - * }); - * - * BigNumber.config().DECIMAL_PLACES // 40 - * ``` - * - * @param object The configuration object. - */ - static config(object?: BigNumber.Config): BigNumber.Config; - - /** - * Returns `true` if `value` is a BigNumber instance, otherwise returns `false`. - * - * If `BigNumber.DEBUG` is `true`, throws if a BigNumber instance is not well-formed. - * - * ```ts - * x = 42 - * y = new BigNumber(x) - * - * BigNumber.isBigNumber(x) // false - * y instanceof BigNumber // true - * BigNumber.isBigNumber(y) // true - * - * BN = BigNumber.clone(); - * z = new BN(x) - * z instanceof BigNumber // false - * BigNumber.isBigNumber(z) // true - * ``` - * - * @param value The value to test. - */ - static isBigNumber(value: any): value is BigNumber; - - /** - * Returns a BigNumber whose value is the maximum of the arguments. - * - * The return value is always exact and unrounded. - * - * ```ts - * x = new BigNumber('3257869345.0378653') - * BigNumber.maximum(4e9, x, '123456789.9') // '4000000000' - * - * arr = [12, '13', new BigNumber(14)] - * BigNumber.maximum.apply(null, arr) // '14' - * ``` - * - * @param n A numeric value. - */ - static maximum(...n: BigNumber.Value[]): BigNumber; - - /** - * Returns a BigNumber whose value is the maximum of the arguments. - * - * The return value is always exact and unrounded. - * - * ```ts - * x = new BigNumber('3257869345.0378653') - * BigNumber.max(4e9, x, '123456789.9') // '4000000000' - * - * arr = [12, '13', new BigNumber(14)] - * BigNumber.max.apply(null, arr) // '14' - * ``` - * - * @param n A numeric value. - */ - static max(...n: BigNumber.Value[]): BigNumber; - - /** - * Returns a BigNumber whose value is the minimum of the arguments. - * - * The return value is always exact and unrounded. - * - * ```ts - * x = new BigNumber('3257869345.0378653') - * BigNumber.minimum(4e9, x, '123456789.9') // '123456789.9' - * - * arr = [2, new BigNumber(-14), '-15.9999', -12] - * BigNumber.minimum.apply(null, arr) // '-15.9999' - * ``` - * - * @param n A numeric value. - */ - static minimum(...n: BigNumber.Value[]): BigNumber; - - /** - * Returns a BigNumber whose value is the minimum of the arguments. - * - * The return value is always exact and unrounded. - * - * ```ts - * x = new BigNumber('3257869345.0378653') - * BigNumber.min(4e9, x, '123456789.9') // '123456789.9' - * - * arr = [2, new BigNumber(-14), '-15.9999', -12] - * BigNumber.min.apply(null, arr) // '-15.9999' - * ``` - * - * @param n A numeric value. - */ - static min(...n: BigNumber.Value[]): BigNumber; - - /** - * Returns a new BigNumber with a pseudo-random value equal to or greater than 0 and less than 1. - * - * The return value will have `decimalPlaces` decimal places, or less if trailing zeros are - * produced. If `decimalPlaces` is omitted, the current `DECIMAL_PLACES` setting will be used. - * - * Depending on the value of this BigNumber constructor's `CRYPTO` setting and the support for the - * `crypto` object in the host environment, the random digits of the return value are generated by - * either `Math.random` (fastest), `crypto.getRandomValues` (Web Cryptography API in recent - * browsers) or `crypto.randomBytes` (Node.js). - * - * To be able to set `CRYPTO` to true when using Node.js, the `crypto` object must be available - * globally: - * - * ```ts - * global.crypto = require('crypto') - * ``` - * - * If `CRYPTO` is true, i.e. one of the `crypto` methods is to be used, the value of a returned - * BigNumber should be cryptographically secure and statistically indistinguishable from a random - * value. - * - * Throws if `decimalPlaces` is invalid. - * - * ```ts - * BigNumber.config({ DECIMAL_PLACES: 10 }) - * BigNumber.random() // '0.4117936847' - * BigNumber.random(20) // '0.78193327636914089009' - * ``` - * - * @param [decimalPlaces] Decimal places, integer, 0 to 1e+9. - */ - static random(decimalPlaces?: number): BigNumber; - - /** - * Returns a BigNumber whose value is the sum of the arguments. - * - * The return value is always exact and unrounded. - * - * ```ts - * x = new BigNumber('3257869345.0378653') - * BigNumber.sum(4e9, x, '123456789.9') // '7381326134.9378653' - * - * arr = [2, new BigNumber(14), '15.9999', 12] - * BigNumber.sum.apply(null, arr) // '43.9999' - * ``` - * - * @param n A numeric value. - */ - static sum(...n: BigNumber.Value[]): BigNumber; - - /** - * Configures the settings that apply to this BigNumber constructor. - * - * The configuration object, `object`, contains any number of the properties shown in the example - * below. - * - * Returns an object with the above properties and their current values. - * - * Throws if `object` is not an object, or if an invalid value is assigned to one or more of the - * properties. - * - * ```ts - * BigNumber.set({ - * DECIMAL_PLACES: 40, - * ROUNDING_MODE: BigNumber.ROUND_HALF_CEIL, - * EXPONENTIAL_AT: [-10, 20], - * RANGE: [-500, 500], - * CRYPTO: true, - * MODULO_MODE: BigNumber.ROUND_FLOOR, - * POW_PRECISION: 80, - * FORMAT: { - * groupSize: 3, - * groupSeparator: ' ', - * decimalSeparator: ',' - * }, - * ALPHABET: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_' - * }); - * - * BigNumber.set().DECIMAL_PLACES // 40 - * ``` - * - * @param object The configuration object. - */ - static set(object?: BigNumber.Config): BigNumber.Config; -} - -declare function BigNumber(n: BigNumber.Value, base?: number): BigNumber; +export as namespace BigNumber; diff --git a/website/src/components/Playground/editor-types/globals.d.ts b/website/src/components/Playground/editor-types/globals.d.ts index 1428107a1cff..d340e5e47b1f 100644 --- a/website/src/components/Playground/editor-types/globals.d.ts +++ b/website/src/components/Playground/editor-types/globals.d.ts @@ -1973,4 +1973,4 @@ declare function useController(): Controller; declare function useLive>(endpoint: E, ...args: readonly [...Parameters]): E['schema'] extends undefined | null ? ResolveType$1 : Denormalize$1; declare function useLive>(endpoint: E, ...args: readonly [...Parameters] | readonly [null]): E['schema'] extends undefined | null ? ResolveType$1 | undefined : DenormalizeNullable$1; -export { type AbstractInstanceType, type AddEndpoint, Array$1 as Array, _default as AsyncBoundary, type CheckLoop, Collection, type CustomResource, DataProvider, type DefaultArgs, type Defaults, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes$1 as ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type ExtendedResource, type FetchFunction, type FetchGet, type FetchMutate, type FromFallBack, type GetEndpoint, type GetEntities, type GetEntity, type GetIndex, type HookResource, type HookableEndpointInterface, type INormalizeDelegate, type IQueryDelegate, type RestEndpoint$1 as IRestEndpoint, type IndexPath, Invalidate, type KeyofEndpointInstance, type KeyofRestEndpoint, type KeysToArgs, type Mergeable, type MethodToSide, type MutateEndpoint, type NI, NetworkError, ErrorBoundary as NetworkErrorBoundary, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type OptionsToFunction, type PaginationEndpoint, type PaginationFieldEndpoint, type ParamFetchNoBody, type ParamFetchWithBody, type ParamToArgs, type PartialRestGenerics, type PathArgs, type PathArgsAndSearch, type PathKeys, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Resource, type ResourceEndpointExtensions, type ResourceExtension, type ResourceGenerics, type ResourceInterface, type ResourceOptions, RestEndpoint, type RestEndpointConstructor, type RestEndpointConstructorOptions, type RestEndpointExtendOptions, type RestEndpointOptions, type RestExtendedEndpoint, type RestFetch, type RestGenerics, type RestInstance, type RestInstanceBase, type RestType, type RestTypeNoBody, type RestTypeWithBody, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type ShortenPath, type SnapshotInterface, type UnknownError, type Visit, resource as createResource, getUrlBase, getUrlTokens, hookifyResource, resource, schema_d as schema, useCache, useController, useDLE, useError, useFetch, useLive, useQuery, useSubscription, useSuspense, validateRequired }; +export { type AbstractInstanceType, type AddEndpoint, Array$1 as Array, _default as AsyncBoundary, type CheckLoop, Collection, type CustomResource, DataProvider, type DefaultArgs, type Defaults, type Denormalize, type DenormalizeNullable, type DenormalizeNullableObject, type DenormalizeObject, Endpoint, type EndpointExtendOptions, type EndpointExtraOptions, type EndpointInstance, type EndpointInstanceInterface, type EndpointInterface, type EndpointOptions, type EndpointParam, type EndpointToFunction, type EntitiesPath, Entity, type EntityFields, type EntityInterface, type EntityMap, EntityMixin, type EntityPath, type EntityTable, type ErrorTypes$1 as ErrorTypes, type ExpiryStatusInterface, ExtendableEndpoint, type ExtendedResource, type FetchFunction, type FetchGet, type FetchMutate, type FromFallBack, type GetEndpoint, type GetEntities, type GetEntity, type GetIndex, type HookResource, type HookableEndpointInterface, type IEntityClass, type IEntityInstance, type INormalizeDelegate, type IQueryDelegate, type RestEndpoint$1 as IRestEndpoint, type IndexPath, Invalidate, type KeyofEndpointInstance, type KeyofRestEndpoint, type KeysToArgs, type Mergeable, type MethodToSide, type MutateEndpoint, type NI, NetworkError, ErrorBoundary as NetworkErrorBoundary, type Normalize, type NormalizeNullable, type NormalizeObject, type NormalizedEntity, type NormalizedIndex, type NormalizedNullableObject, type ObjectArgs, type OptionsToFunction, type PaginationEndpoint, type PaginationFieldEndpoint, type ParamFetchNoBody, type ParamFetchWithBody, type ParamToArgs, type PartialRestGenerics, type PathArgs, type PathArgsAndSearch, type PathKeys, type PolymorphicInterface, type Queryable, type ReadEndpoint, type RecordClass, type ResolveType, type Resource, type ResourceEndpointExtensions, type ResourceExtension, type ResourceGenerics, type ResourceInterface, type ResourceOptions, RestEndpoint, type RestEndpointConstructor, type RestEndpointConstructorOptions, type RestEndpointExtendOptions, type RestEndpointOptions, type RestExtendedEndpoint, type RestFetch, type RestGenerics, type RestInstance, type RestInstanceBase, type RestType, type RestTypeNoBody, type RestTypeWithBody, type Schema, type SchemaArgs, type SchemaClass, type SchemaSimple, type Serializable, type ShortenPath, type SnapshotInterface, type UnknownError, type Visit, resource as createResource, getUrlBase, getUrlTokens, hookifyResource, resource, schema_d as schema, useCache, useController, useDLE, useError, useFetch, useLive, useQuery, useSubscription, useSuspense, validateRequired }; diff --git a/website/src/components/Playground/editor-types/qs.d.ts b/website/src/components/Playground/editor-types/qs.d.ts index 972eddac222e..b993ed432437 100644 --- a/website/src/components/Playground/editor-types/qs.d.ts +++ b/website/src/components/Playground/editor-types/qs.d.ts @@ -58,6 +58,7 @@ declare namespace QueryString { allowEmptyArrays?: boolean | undefined; duplicates?: "combine" | "first" | "last" | undefined; strictDepth?: boolean | undefined; + throwOnLimitExceeded?: boolean | undefined; } type IParseDynamicOptions = AllowDots extends true diff --git a/website/src/components/Playground/editor-types/react.d.ts b/website/src/components/Playground/editor-types/react.d.ts index 8062bca1f0d8..ec668be49bb0 100755 --- a/website/src/components/Playground/editor-types/react.d.ts +++ b/website/src/components/Playground/editor-types/react.d.ts @@ -11,6 +11,7 @@ type NativeClipboardEvent = ClipboardEvent; type NativeCompositionEvent = CompositionEvent; type NativeDragEvent = DragEvent; type NativeFocusEvent = FocusEvent; +type NativeInputEvent = InputEvent; type NativeKeyboardEvent = KeyboardEvent; type NativeMouseEvent = MouseEvent; type NativeTouchEvent = TouchEvent; @@ -1684,20 +1685,6 @@ declare namespace React { reducer: (prevState: S, ...args: A) => S, initialState: S, ): [S, ActionDispatch]; - /** - * An alternative to `useState`. - * - * `useReducer` is usually preferable to `useState` when you have complex state logic that involves - * multiple sub-values. It also lets you optimize performance for components that trigger deep - * updates because you can pass `dispatch` down instead of callbacks. - * - * @version 16.8.0 - * @see {@link https://react.dev/reference/react/useReducer} - */ - function useReducer( - reducer: (prevState: S, ...args: A) => S, - initialState: S, - ): [S, ActionDispatch]; /** * An alternative to `useState`. * @@ -2017,6 +2004,10 @@ declare namespace React { target: EventTarget & T; } + interface InputEvent extends SyntheticEvent { + data: string; + } + export type ModifierKey = | "Alt" | "AltGraph" @@ -2137,6 +2128,7 @@ declare namespace React { type FocusEventHandler = EventHandler>; type FormEventHandler = EventHandler>; type ChangeEventHandler = EventHandler>; + type InputEventHandler = EventHandler>; type KeyboardEventHandler = EventHandler>; type MouseEventHandler = EventHandler>; type TouchEventHandler = EventHandler>; @@ -2195,7 +2187,7 @@ declare namespace React { // Form Events onChange?: FormEventHandler | undefined; onChangeCapture?: FormEventHandler | undefined; - onBeforeInput?: FormEventHandler | undefined; + onBeforeInput?: InputEventHandler | undefined; onBeforeInputCapture?: FormEventHandler | undefined; onInput?: FormEventHandler | undefined; onInputCapture?: FormEventHandler | undefined; @@ -2253,8 +2245,6 @@ declare namespace React { onProgressCapture?: ReactEventHandler | undefined; onRateChange?: ReactEventHandler | undefined; onRateChangeCapture?: ReactEventHandler | undefined; - onResize?: ReactEventHandler | undefined; - onResizeCapture?: ReactEventHandler | undefined; onSeeked?: ReactEventHandler | undefined; onSeekedCapture?: ReactEventHandler | undefined; onSeeking?: ReactEventHandler | undefined; @@ -3060,6 +3050,8 @@ declare namespace React { width?: number | string | undefined; } + interface DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_IMG_SRC_TYPES {} + interface ImgHTMLAttributes extends HTMLAttributes { alt?: string | undefined; crossOrigin?: CrossOrigin; @@ -3069,7 +3061,12 @@ declare namespace React { loading?: "eager" | "lazy" | undefined; referrerPolicy?: HTMLAttributeReferrerPolicy | undefined; sizes?: string | undefined; - src?: string | undefined; + src?: + | string + | DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_IMG_SRC_TYPES[ + keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_IMG_SRC_TYPES + ] + | undefined; srcSet?: string | undefined; useMap?: string | undefined; width?: number | string | undefined; @@ -3228,6 +3225,7 @@ declare namespace React { interface LinkHTMLAttributes extends HTMLAttributes { as?: string | undefined; + blocking?: "render" | (string & {}) | undefined; crossOrigin?: CrossOrigin; fetchPriority?: "high" | "low" | "auto"; href?: string | undefined; @@ -3253,6 +3251,8 @@ declare namespace React { type?: string | undefined; } + interface DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_MEDIA_SRC_TYPES {} + interface MediaHTMLAttributes extends HTMLAttributes { autoPlay?: boolean | undefined; controls?: boolean | undefined; @@ -3263,7 +3263,12 @@ declare namespace React { muted?: boolean | undefined; playsInline?: boolean | undefined; preload?: string | undefined; - src?: string | undefined; + src?: + | string + | DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_MEDIA_SRC_TYPES[ + keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_MEDIA_SRC_TYPES + ] + | undefined; } interface MetaHTMLAttributes extends HTMLAttributes { @@ -3340,6 +3345,7 @@ declare namespace React { interface ScriptHTMLAttributes extends HTMLAttributes { async?: boolean | undefined; + blocking?: "render" | (string & {}) | undefined; /** @deprecated */ charSet?: string | undefined; crossOrigin?: CrossOrigin; @@ -3374,6 +3380,7 @@ declare namespace React { } interface StyleHTMLAttributes extends HTMLAttributes { + blocking?: "render" | (string & {}) | undefined; media?: string | undefined; scoped?: boolean | undefined; type?: string | undefined; @@ -3454,6 +3461,9 @@ declare namespace React { width?: number | string | undefined; disablePictureInPicture?: boolean | undefined; disableRemotePlayback?: boolean | undefined; + + onResize?: ReactEventHandler | undefined; + onResizeCapture?: ReactEventHandler | undefined; } // this list is "complete" in that it contains every SVG attribute