diff --git a/.changeset/hungry-spies-design.md b/.changeset/hungry-spies-design.md new file mode 100644 index 000000000000..20522139d2ef --- /dev/null +++ b/.changeset/hungry-spies-design.md @@ -0,0 +1,45 @@ +--- +'@data-client/normalizr': minor +'@data-client/endpoint': minor +'@data-client/graphql': minor +'@data-client/rest': minor +'@data-client/react': patch +'@data-client/core': patch +--- + +Change Schema.normalize `visit()` interface; removing non-contextual arguments. + +```ts +/** Visits next data + schema while recurisvely normalizing */ +export interface Visit { + (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; + creating?: boolean; +} +``` + +This results in a 10% normalize performance boost. + +```ts title="Before" +processedEntity[key] = visit( + processedEntity[key], + processedEntity, + key, + this.schema[key], + addEntity, + visitedEntities, + storeEntities, + args, +); +``` + +```ts title="After" +processedEntity[key] = visit( + this.schema[key], + processedEntity[key], + processedEntity, + key, + args, +); +``` + +The information needed from these arguments are provided by [closing](https://en.wikipedia.org/wiki/Closure_(computer_programming)) `visit()` around them. \ No newline at end of file diff --git a/.changeset/purple-cougars-unite.md b/.changeset/purple-cougars-unite.md new file mode 100644 index 000000000000..654186944535 --- /dev/null +++ b/.changeset/purple-cougars-unite.md @@ -0,0 +1,35 @@ +--- +'@data-client/normalizr': minor +'@data-client/endpoint': minor +'@data-client/graphql': minor +'@data-client/rest': minor +'@data-client/react': patch +'@data-client/core': patch +--- + +Change Schema.normalize interface from direct data access, to using functions like `getEntity` + +```ts +interface SchemaSimple { + normalize( + input: any, + parent: any, + key: any, + args: any[], + visit: (schema: any, value: any, parent: any, key: any, args: readonly any[]) => any, + addEntity: (...args: any) => any, + getEntity: (...args: any) => any, + checkLoop: (...args: any) => any, + ): any; +} +``` + +We also add `checkLoop()`, which moves some logic in [Entity](https://dataclient.io/rest/api/Entity) +to the core normalize algorithm. + +```ts +/** Returns true if a circular reference is found */ +export interface CheckLoop { + (entityKey: string, pk: string, input: object): boolean; +} +``` \ No newline at end of file diff --git a/.changeset/purple-cougars-unite2.md b/.changeset/purple-cougars-unite2.md new file mode 100644 index 000000000000..2e256c64be48 --- /dev/null +++ b/.changeset/purple-cougars-unite2.md @@ -0,0 +1,23 @@ +--- +'@data-client/normalizr': minor +'@data-client/endpoint': minor +'@data-client/graphql': minor +'@data-client/rest': minor +'@data-client/react': patch +'@data-client/core': patch +--- + +Change Schema.denormalize `unvisit` to have [schema](https://dataclient.io/rest/api/schema) argument first. + +```ts +interface SchemaSimple { + denormalize( + input: {}, + args: readonly any[], + unvisit: (schema: any, input: any) => any, + ): T; +} +``` + + + diff --git a/.changeset/rich-frogs-move.md b/.changeset/rich-frogs-move.md new file mode 100644 index 000000000000..9314c09c8a2c --- /dev/null +++ b/.changeset/rich-frogs-move.md @@ -0,0 +1,26 @@ +--- +'@data-client/normalizr': minor +--- + +Change normalize() interface + +```ts +function normalize( + schema, + input, + { date, expiresAt, fetchedAt, args }, + { entities, indexes, entityMeta }, +); +``` + +#### Usage + +```ts +const { result, entities, indexes, entityMeta } = normalize( + action.endpoint.schema, + payload, + action.meta, + state, +); +``` + diff --git a/.changeset/sharp-birds-tie.md b/.changeset/sharp-birds-tie.md new file mode 100644 index 000000000000..8de1eccec100 --- /dev/null +++ b/.changeset/sharp-birds-tie.md @@ -0,0 +1,15 @@ +--- +'@data-client/normalizr': minor +--- + +Change denormalize() interface + +```ts +function denormalize(schema, input, entities, args); +``` + +#### Usage + +```ts +const value = denormalize(endpoint.schema, input, state.entities, args); +``` diff --git a/.changeset/silly-eagles-knock.md b/.changeset/silly-eagles-knock.md new file mode 100644 index 000000000000..542b444c0f0a --- /dev/null +++ b/.changeset/silly-eagles-knock.md @@ -0,0 +1,7 @@ +--- +'@data-client/test': patch +'@data-client/img': patch +'@data-client/ssr': patch +--- + +Expand peerdep support range to include ^0.14.0 diff --git a/.changeset/smooth-houses-tickle.md b/.changeset/smooth-houses-tickle.md new file mode 100644 index 000000000000..78cfae6b3527 --- /dev/null +++ b/.changeset/smooth-houses-tickle.md @@ -0,0 +1,13 @@ +--- +'@data-client/normalizr': minor +--- + +Change MemoCache methods interface + +```ts +class MemoCache { + denormalize(schema, input, entities, args): { data, paths }; + query(schema, args, entities, indexes): data; + buildQueryKey(schema, args, entities, indexes): normalized; +} +``` \ No newline at end of file diff --git a/.gitignore b/.gitignore index 337eb71b10fa..b73464eb6524 100644 --- a/.gitignore +++ b/.gitignore @@ -73,4 +73,4 @@ typings/ # build info **/tsconfig*.tsbuildinfo -/codemods/ \ No newline at end of file +/codemods diff --git a/docs/rest/api/schema.md b/docs/rest/api/schema.md index d444e4b09a08..7737e6160526 100644 --- a/docs/rest/api/schema.md +++ b/docs/rest/api/schema.md @@ -129,7 +129,7 @@ class Article extends Entity { import { normalize } from '@data-client/normalizr'; const args = [{ id: '123' }]; -const normalizedData = normalize(originalData, Article, args); +const normalizedData = normalize(Article, originalData, args); ``` Now, `normalizedData` will create a single serializable source of truth for all entities: @@ -175,8 +175,8 @@ Now, `normalizedData` will create a single serializable source of truth for all import { denormalize } from '@data-client/normalizr'; const denormalizedData = denormalize( - normalizedData.result, Article, + normalizedData.result, normalizedData.entities, args, ); @@ -218,14 +218,14 @@ import { MemoCache } from '@data-client/normalizr'; const memo = new MemoCache(); const { data, paths } = memo.denormalize( - normalizedData.result, Article, + normalizedData.result, normalizedData.entities, args, ); const { data: data2 } = memo.denormalize( - normalizedData.result, Article, + normalizedData.result, normalizedData.entities, args, ); @@ -242,11 +242,7 @@ is an Array of paths of all entities included in the result. `memo.query()` allows denormalizing [Queryable](#queryable) based on args alone, rather than a normalized input. ```ts -// key is just any serialization of args -const key = JSON.stringify(args); - const data = memo.query( - key, Article, args, normalizedData.entities, diff --git a/examples/benchmark/README.md b/examples/benchmark/README.md index 8650047156f7..c06b65d5dfaf 100644 --- a/examples/benchmark/README.md +++ b/examples/benchmark/README.md @@ -23,7 +23,7 @@ Performance compared to normalizr package (higher is better): | | no cache | with cache | | ------------------- | -------- | ---------- | -| normalize (long) | 113% | 113% | +| normalize (long) | 119% | 119% | | denormalize (long) | 158% | 1,262% | | denormalize (short) | 676% | 2,367% | diff --git a/examples/benchmark/normalizr.js b/examples/benchmark/normalizr.js index e4fa744810f0..d632f4229da3 100644 --- a/examples/benchmark/normalizr.js +++ b/examples/benchmark/normalizr.js @@ -18,37 +18,36 @@ import { } from './schemas.js'; import userData from './user.json' with { type: 'json' }; -const { result, entities } = normalize(data, ProjectSchema); -const queryState = normalize(data, ProjectQuery); +const { result, entities } = normalize(ProjectSchema, data); +const queryState = normalize(ProjectQuery, data); const queryMemo = new MemoCache(); queryState.result = queryMemo.buildQueryKey( - '', ProjectQuery, [], queryState.entities, queryState.indexes, ); const queryInfer = queryMemo.buildQueryKey( - '', ProjectQuerySorted, [], queryState.entities, queryState.indexes, ); -let githubState = normalize(userData, User); +let githubState = normalize(User, userData); const actionMeta = { fetchedAt: Date.now(), date: Date.now(), expiresAt: Date.now() + 10000000, + args: [], }; export default function addNormlizrSuite(suite) { const memo = new MemoCache(); // prime the cache - memo.denormalize(result, ProjectSchema, entities, []); - memo.denormalize(queryState.result, ProjectQuery, queryState.entities, []); + memo.denormalize(ProjectSchema, result, entities, []); + memo.denormalize(ProjectQuery, queryState.result, queryState.entities, []); %OptimizeFunctionOnNextCall(memo.denormalize); %OptimizeFunctionOnNextCall(denormalize); %OptimizeFunctionOnNextCall(normalize); @@ -56,20 +55,11 @@ export default function addNormlizrSuite(suite) { let curState = initialState; return suite .add('normalizeLong', () => { - normalize( - data, - ProjectSchema, - [], - curState.entities, - curState.indexes, - curState.entityMeta, - actionMeta, - ); + normalize(ProjectSchema, data, actionMeta, curState); curState = { ...initialState, entities: {}, endpoints: {} }; }) .add('infer All', () => { return new MemoCache().buildQueryKey( - '', ProjectQuery, [], queryState.entities, @@ -77,52 +67,52 @@ export default function addNormlizrSuite(suite) { ); }) .add('denormalizeLong', () => { - return new MemoCache().denormalize(result, ProjectSchema, entities); + return new MemoCache().denormalize(ProjectSchema, result, entities); }) .add('denormalizeLong donotcache', () => { - return denormalize(result, ProjectSchema, entities); + return denormalize(ProjectSchema, result, entities); }) .add('denormalizeShort donotcache 500x', () => { for (let i = 0; i < 500; ++i) { - denormalize('gnoff', User, githubState.entities); + denormalize(User, 'gnoff', githubState.entities); } }) .add('denormalizeShort 500x', () => { for (let i = 0; i < 500; ++i) { - new MemoCache().denormalize('gnoff', User, githubState.entities); + new MemoCache().denormalize(User, 'gnoff', githubState.entities); } }) .add('denormalizeShort 500x withCache', () => { for (let i = 0; i < 500; ++i) { - memo.denormalize('gnoff', User, githubState.entities, []); + memo.denormalize(User, 'gnoff', githubState.entities, []); } }) .add('denormalizeLong with mixin Entity', () => { - return new MemoCache().denormalize(result, ProjectSchemaMixin, entities); + return new MemoCache().denormalize(ProjectSchemaMixin, result, entities); }) .add('denormalizeLong withCache', () => { - return memo.denormalize(result, ProjectSchema, entities, []); + return memo.denormalize(ProjectSchema, result, entities, []); }) .add('denormalizeLong All withCache', () => { return memo.denormalize( - queryState.result, ProjectQuery, + queryState.result, queryState.entities, [], ); }) .add('denormalizeLong Query-sorted withCache', () => { return memo.denormalize( - queryInfer, ProjectQuerySorted, + queryInfer, queryState.entities, [], ); }) .add('denormalizeLongAndShort withEntityCacheOnly', () => { memo.endpoints = new WeakDependencyMap(); - memo.denormalize(result, ProjectSchema, entities); - memo.denormalize('gnoff', User, githubState.entities); + memo.denormalize(ProjectSchema, result, entities); + memo.denormalize(User, 'gnoff', githubState.entities); }) .on('complete', function () { if (process.env.SHOW_OPTIMIZATION) { diff --git a/examples/normalizr-github/index.js b/examples/normalizr-github/index.js index ae4d20f7ea92..e34d289d5ef6 100644 --- a/examples/normalizr-github/index.js +++ b/examples/normalizr-github/index.js @@ -22,8 +22,8 @@ const request = https.request( res.on('end', () => { const normalizedData = normalize( - JSON.parse(data), schema.IssueOrPullRequest, + JSON.parse(data), ); const out = JSON.stringify(normalizedData, null, 2); fs.writeFileSync(path.resolve(__dirname, './output.json'), out); diff --git a/examples/normalizr-redux/src/redux/modules/commits.js b/examples/normalizr-redux/src/redux/modules/commits.js index 01618eed37fa..4c0248417cd9 100644 --- a/examples/normalizr-redux/src/redux/modules/commits.js +++ b/examples/normalizr-redux/src/redux/modules/commits.js @@ -31,7 +31,7 @@ export const getCommits = repo, }) .then(response => { - const data = normalize(response.data, [schema.Commit]); + const data = normalize([schema.Commit], response.data); dispatch(addEntities(data.entities)); return response; }) @@ -40,4 +40,4 @@ export const getCommits = }); }; -export const selectHydrated = (state, id) => denormalize(id, Commit, state); +export const selectHydrated = (state, id) => denormalize(Commit, id, state); diff --git a/examples/normalizr-redux/src/redux/modules/issues.js b/examples/normalizr-redux/src/redux/modules/issues.js index 9d85598a334e..b182f2f14771 100644 --- a/examples/normalizr-redux/src/redux/modules/issues.js +++ b/examples/normalizr-redux/src/redux/modules/issues.js @@ -31,7 +31,7 @@ export const getIssues = repo, }) .then(response => { - const data = normalize(response.data, [schema.Issue]); + const data = normalize([schema.Issue], response.data); dispatch(addEntities(data.entities)); return response; }) @@ -40,4 +40,4 @@ export const getIssues = }); }; -export const selectHydrated = (state, id) => denormalize(id, Issue, state); +export const selectHydrated = (state, id) => denormalize(Issue, id, state); diff --git a/examples/normalizr-redux/src/redux/modules/labels.js b/examples/normalizr-redux/src/redux/modules/labels.js index f5423f22b761..b6617b26b8c0 100644 --- a/examples/normalizr-redux/src/redux/modules/labels.js +++ b/examples/normalizr-redux/src/redux/modules/labels.js @@ -32,7 +32,7 @@ export const getLabels = }) .then(response => { console.log(response.data); - const data = normalize(response.data, [schema.Label]); + const data = normalize([schema.Label], response.data); dispatch(addEntities(data.entities)); return response; }) @@ -41,4 +41,4 @@ export const getLabels = }); }; -export const selectHydrated = (state, id) => denormalize(id, Label, state); +export const selectHydrated = (state, id) => denormalize(Label, id, state); diff --git a/examples/normalizr-redux/src/redux/modules/milestones.js b/examples/normalizr-redux/src/redux/modules/milestones.js index 95c560c01d0e..2e3b8acb933c 100644 --- a/examples/normalizr-redux/src/redux/modules/milestones.js +++ b/examples/normalizr-redux/src/redux/modules/milestones.js @@ -31,7 +31,7 @@ export const getMilestones = repo, }) .then(response => { - const data = normalize(response.data, [schema.Milestone]); + const data = normalize([schema.Milestone], response.data); dispatch(addEntities(data.entities)); return response; }) @@ -40,4 +40,4 @@ export const getMilestones = }); }; -export const selectHydrated = (state, id) => denormalize(id, Milestone, state); +export const selectHydrated = (state, id) => denormalize(Milestone, id, state); diff --git a/examples/normalizr-redux/src/redux/modules/pull-requests.js b/examples/normalizr-redux/src/redux/modules/pull-requests.js index 1c34d010dd25..9e98db226a16 100644 --- a/examples/normalizr-redux/src/redux/modules/pull-requests.js +++ b/examples/normalizr-redux/src/redux/modules/pull-requests.js @@ -31,7 +31,7 @@ export const getPullRequests = repo, }) .then(response => { - const data = normalize(response.data, [schema.PullRequest]); + const data = normalize([schema.PullRequest], response.data); dispatch(addEntities(data.entities)); return response; }) @@ -41,4 +41,4 @@ export const getPullRequests = }; export const selectHydrated = (state, id) => - denormalize(id, PullRequest, state); + denormalize(PullRequest, id, state); diff --git a/examples/normalizr-redux/src/redux/modules/users.js b/examples/normalizr-redux/src/redux/modules/users.js index 98338104fa35..5be7c8246494 100644 --- a/examples/normalizr-redux/src/redux/modules/users.js +++ b/examples/normalizr-redux/src/redux/modules/users.js @@ -27,4 +27,4 @@ export default function reducer(state = {}, action) { } } -export const selectHydrated = (state, id) => denormalize(id, User, state); +export const selectHydrated = (state, id) => denormalize(User, id, state); diff --git a/examples/normalizr-relationships/index.js b/examples/normalizr-relationships/index.js index 6fd3e84f0b26..ee9a87bd1426 100644 --- a/examples/normalizr-relationships/index.js +++ b/examples/normalizr-relationships/index.js @@ -8,6 +8,6 @@ import postsSchema from './schema'; MockDate.set(new Date('2/20/2000')); -const normalizedData = normalize(input, postsSchema); +const normalizedData = normalize(postsSchema, input); const output = JSON.stringify(normalizedData, null, 2); fs.writeFileSync(path.resolve(__dirname, './output.json'), output); diff --git a/package.json b/package.json index 1ecdaeb33c19..f6942cbdd40b 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,8 @@ "**/dist*/*", "packages/*/native/*", "**/node_modules*/*", - "node_modules/*" + "node_modules/*", + "**/src-*-types/*" ] } } diff --git a/packages/core/src/controller/Controller.ts b/packages/core/src/controller/Controller.ts index 3de12f7aa3be..431c4b54a9d1 100644 --- a/packages/core/src/controller/Controller.ts +++ b/packages/core/src/controller/Controller.ts @@ -117,7 +117,7 @@ export default class Controller< if (endpoint.schema) { return action.meta.promise.then(input => - denormalize(input, endpoint.schema, {}, args), + denormalize(endpoint.schema, input, {}, args), ) as any; } return action.meta.promise as any; @@ -430,11 +430,11 @@ export default class Controller< shouldQuery ? // nothing in endpoints cache, so try querying if we have a schema to do so this.memo.buildQueryKey( - key, schema, args, state.entities as any, state.indexes, + key, ) : cacheEndpoints; @@ -464,8 +464,8 @@ export default class Controller< // second argument is false if any entities are missing // eslint-disable-next-line prefer-const const { data, paths } = this.memo.denormalize( - input, schema, + input, state.entities, args, ) as { data: any; paths: EntityPath[] }; @@ -500,16 +500,7 @@ export default class Controller< .slice(0, rest.length - 1) .map(ensurePojo) as SchemaArgs; - // NOTE: different orders can result in cache busting here; but since it's just a perf penalty we will allow for now - const key = JSON.stringify(args); - - return this.memo.query( - key, - schema, - args, - state.entities as any, - state.indexes, - ); + return this.memo.query(schema, args, state.entities as any, state.indexes); } private getSchemaResponse( diff --git a/packages/core/src/controller/__tests__/Controller.ts b/packages/core/src/controller/__tests__/Controller.ts index e4c0be3c89a8..eb264d774329 100644 --- a/packages/core/src/controller/__tests__/Controller.ts +++ b/packages/core/src/controller/__tests__/Controller.ts @@ -36,8 +36,8 @@ describe('Controller', () => { tags: ['a', 'best', 'react'], }; const { entities, result } = normalize( - payload, CoolerArticleResource.get.schema, + payload, ); const fetchKey = CoolerArticleResource.get.key({ id: payload.id }); const state = { @@ -73,8 +73,8 @@ describe('Controller', () => { tags: ['a', 'best', 'react'], }; const { entities, result } = normalize( - payload, CoolerArticleResource.get.schema, + payload, ); const fetchKey = CoolerArticleResource.get.key({ id: payload.id }); const state = { diff --git a/packages/core/src/state/reducer/setReducer.ts b/packages/core/src/state/reducer/setReducer.ts index b16224ae9b45..36d5d39ba9f7 100644 --- a/packages/core/src/state/reducer/setReducer.ts +++ b/packages/core/src/state/reducer/setReducer.ts @@ -22,13 +22,10 @@ export function setReducer( } try { const { entities, indexes, entityMeta } = normalize( - value, action.schema, - action.meta.args as any, - state.entities, - state.indexes, - state.entityMeta, + value, action.meta, + state, ); return { entities, diff --git a/packages/core/src/state/reducer/setResponseReducer.ts b/packages/core/src/state/reducer/setResponseReducer.ts index d1e2dc3c7bce..9f1650acb1f5 100644 --- a/packages/core/src/state/reducer/setResponseReducer.ts +++ b/packages/core/src/state/reducer/setResponseReducer.ts @@ -42,13 +42,10 @@ export function setResponseReducer( payload = action.payload; } const { result, entities, indexes, entityMeta } = normalize( - payload, action.endpoint.schema, - action.meta.args as any, - state.entities, - state.indexes, - state.entityMeta, + payload, action.meta, + state, ); const endpoints: Record = { ...state.endpoints, diff --git a/packages/endpoint/src-4.0-types/schemas/Entity.d.ts b/packages/endpoint/src-4.0-types/schemas/Entity.d.ts index dd982ecea9d8..add13efcba00 100644 --- a/packages/endpoint/src-4.0-types/schemas/Entity.d.ts +++ b/packages/endpoint/src-4.0-types/schemas/Entity.d.ts @@ -89,7 +89,7 @@ export default abstract class Entity extends Entity_base { this: T, input: any, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ) => AbstractInstanceType; } export {}; diff --git a/packages/endpoint/src-4.0-types/schemas/EntitySchema.d.ts b/packages/endpoint/src-4.0-types/schemas/EntityTypes.d.ts similarity index 95% rename from packages/endpoint/src-4.0-types/schemas/EntitySchema.d.ts rename to packages/endpoint/src-4.0-types/schemas/EntityTypes.d.ts index 75dd5ba762fe..bcde965e85ab 100644 --- a/packages/endpoint/src-4.0-types/schemas/EntitySchema.d.ts +++ b/packages/endpoint/src-4.0-types/schemas/EntityTypes.d.ts @@ -1,57 +1,6 @@ // we just removed instances of 'abstract new' -import type { Schema, GetIndex, GetEntity } from '../interface.js'; +import type { Schema, GetEntity, GetIndex } from '../interface.js'; import { AbstractInstanceType } from '../normal.js'; -export type Constructor = new (...args: any[]) => {}; -export type IDClass = new (...args: any[]) => { - id: string | number | undefined; -}; -export type PKClass = new (...args: any[]) => { - pk( - parent?: any, - key?: string, - args?: readonly any[], - ): string | number | undefined; -}; -type ValidSchemas = { - [k in keyof TInstance]?: Schema; -}; -export type EntityOptions = { - readonly schema?: ValidSchemas; - readonly pk?: - | (( - value: TInstance, - parent?: any, - key?: string, - ) => string | number | undefined) - | keyof TInstance; - readonly key?: string; -} & { - readonly [K in Extract< - keyof IEntityClass, - | 'process' - | 'merge' - | 'expiresAt' - | 'createIfValid' - | 'mergeWithStore' - | 'validate' - | 'shouldReorder' - | 'shouldUpdate' - >]?: IEntityClass TInstance>[K]; -}; -export interface RequiredPKOptions - extends EntityOptions { - readonly pk: - | (( - value: TInstance, - parent?: any, - key?: string, - ) => string | number | undefined) - | keyof TInstance; -} -export default function EntitySchema( - Base: TBase, - options?: EntityOptions>, -): any; export interface IEntityClass { toJSON(): { name: string; @@ -113,7 +62,7 @@ export interface IEntityClass { existing: any, incoming: any, ): boolean; - /** Determines the order of incoming entity vs entity already in store\ + /** Determines the order of incoming entity vs entity already in store * * @see https://dataclient.io/docs/api/schema.Entity#shouldReorder * @returns true if incoming entity should be first argument of merge() @@ -207,9 +156,11 @@ export interface IEntityClass { input: any, parent: any, key: string | undefined, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, + getEntity: (...args: any) => any, + checkLoop: (...args: any) => any, ): any; /** Do any transformations when first receiving input * @@ -234,7 +185,7 @@ export interface IEntityClass { this: T, input: any, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): AbstractInstanceType; /** All instance defaults set */ readonly defaults: any; @@ -253,4 +204,51 @@ export interface IEntityInstance { args?: readonly any[], ): string | number | undefined; } +export type Constructor = new (...args: any[]) => {}; +export type IDClass = new (...args: any[]) => { + id: string | number | undefined; +}; +export type PKClass = new (...args: any[]) => { + pk( + parent?: any, + key?: string, + args?: readonly any[], + ): string | number | undefined; +}; +type ValidSchemas = { + [k in keyof TInstance]?: Schema; +}; +export type EntityOptions = { + readonly schema?: ValidSchemas; + readonly pk?: + | (( + value: TInstance, + parent?: any, + key?: string, + ) => string | number | undefined) + | keyof TInstance; + readonly key?: string; +} & { + readonly [K in Extract< + keyof IEntityClass, + | 'process' + | 'merge' + | 'expiresAt' + | 'createIfValid' + | 'mergeWithStore' + | 'validate' + | 'shouldReorder' + | 'shouldUpdate' + >]?: IEntityClass TInstance>[K]; +}; +export interface RequiredPKOptions + extends EntityOptions { + readonly pk: + | (( + value: TInstance, + parent?: any, + key?: string, + ) => string | number | undefined) + | keyof TInstance; +} export {}; diff --git a/packages/endpoint/src/__tests__/validateRequired.test.ts b/packages/endpoint/src/__tests__/validateRequired.test.ts index 10385906c8de..4d02e3ac545d 100644 --- a/packages/endpoint/src/__tests__/validateRequired.test.ts +++ b/packages/endpoint/src/__tests__/validateRequired.test.ts @@ -58,7 +58,7 @@ describe(`validateRequired`, () => { const schema = MyEntity; expect( - new SimpleMemoCache().denormalize('bob', schema, { + new SimpleMemoCache().denormalize(schema, 'bob', { MyEntity: { bob: { name: 'bob', secondthing: 'hi' } }, }), ).toMatchInlineSnapshot(` @@ -73,7 +73,7 @@ describe(`validateRequired`, () => { const schema = MyEntity; expect( - new SimpleMemoCache().denormalize('bob', schema, { + new SimpleMemoCache().denormalize(schema, 'bob', { MyEntity: { bob: { name: 'bob', @@ -93,7 +93,7 @@ describe(`validateRequired`, () => { it('should be invalid (suspend) with required fields missing', () => { const schema = MyEntity; - const data = new SimpleMemoCache().denormalize('bob', schema, { + const data = new SimpleMemoCache().denormalize(schema, 'bob', { MyEntity: { bob: { name: 'bob', diff --git a/packages/endpoint/src/interface.ts b/packages/endpoint/src/interface.ts index c98610d3509c..654f538311c4 100644 --- a/packages/endpoint/src/interface.ts +++ b/packages/endpoint/src/interface.ts @@ -29,16 +29,16 @@ export interface SchemaSimple { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args: any[], + getEntity: (...args: any) => any, + checkLoop: (...args: any) => any, ): any; denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): T; queryKey( args: Args, @@ -110,10 +110,21 @@ export interface EntityTable { | undefined; } +/** Visits next data + schema while recurisvely normalizing */ +export interface Visit { + (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; + creating?: boolean; +} + +/** 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 */ export interface GetEntity { - (entityKey: string): { readonly [pk: string]: any } | undefined; - (entityKey: string, pk: string | number): any; + (entityKey: string | symbol): { readonly [pk: string]: any } | undefined; + (entityKey: string | symbol, pk: string | number): any; } /** Get PK using an Entity Index */ export interface GetIndex { diff --git a/packages/endpoint/src/schema.d.ts b/packages/endpoint/src/schema.d.ts index ce1b9bcebd52..0c58895f9c6e 100644 --- a/packages/endpoint/src/schema.d.ts +++ b/packages/endpoint/src/schema.d.ts @@ -5,6 +5,7 @@ import type { SchemaClass, GetIndex, GetEntity, + CheckLoop, } from './interface.js'; import type { AbstractInstanceType, @@ -19,15 +20,15 @@ import type { EntityMap, } from './normal.js'; import { EntityFields } from './schemas/EntityFields.js'; -import { - EntityOptions, +import type { IEntityClass, IEntityInstance, + EntityOptions, RequiredPKOptions, IDClass, Constructor, PKClass, -} from './schemas/EntitySchema.js'; +} from './schemas/EntityTypes.js'; import { default as Invalidate } from './schemas/Invalidate.js'; import { default as Query } from './schemas/Query.js'; import type { @@ -69,11 +70,11 @@ export class Array implements SchemaClass { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): @@ -87,7 +88,7 @@ export class Array implements SchemaClass { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): (S extends EntityMap ? T : Denormalize)[]; queryKey( @@ -126,11 +127,11 @@ export class All< input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): @@ -144,7 +145,7 @@ export class All< denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): (S extends EntityMap ? T : Denormalize)[]; queryKey( @@ -174,11 +175,11 @@ export class Object = Record> input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): NormalizeObject; _normalizeNullable(): NormalizedNullableObject; @@ -188,7 +189,7 @@ export class Object = Record> denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): DenormalizeObject; queryKey( @@ -264,11 +265,11 @@ export interface UnionInstance< input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): UnionResult; _normalizeNullable(): UnionResult | undefined; @@ -280,7 +281,7 @@ export interface UnionInstance< denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): AbstractInstanceType; queryKey( @@ -338,11 +339,11 @@ export class Values implements SchemaClass { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): Record< string, Choices extends EntityMap ? UnionResult : Normalize @@ -365,7 +366,7 @@ export class Values implements SchemaClass { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): Record< string, Choices extends EntityMap ? T : Denormalize diff --git a/packages/endpoint/src/schemaTypes.ts b/packages/endpoint/src/schemaTypes.ts index 372c92e71374..ff0d7344f087 100644 --- a/packages/endpoint/src/schemaTypes.ts +++ b/packages/endpoint/src/schemaTypes.ts @@ -3,6 +3,8 @@ import type { SchemaSimple, Schema, PolymorphicInterface, + GetEntity, + CheckLoop, } from './interface.js'; import type { EntityMap } from './normal.js'; import { CollectionOptions } from './schemas/Collection.js'; @@ -61,11 +63,11 @@ export interface CollectionInterface< input: any, parent: Parent, key: string, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args: any, + getEntity: GetEntity, + checkLoop: CheckLoop, ): string; /** Creates new instance copying over defined values of arguments @@ -143,7 +145,7 @@ export interface CollectionInterface< denormalize( input: any, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): ReturnType; _denormalizeNullable(): ReturnType; diff --git a/packages/endpoint/src/schemas/All.ts b/packages/endpoint/src/schemas/All.ts index 540a6b29a775..e7c3108408af 100644 --- a/packages/endpoint/src/schemas/All.ts +++ b/packages/endpoint/src/schemas/All.ts @@ -1,5 +1,5 @@ import ArraySchema from './Array.js'; -import { EntityTable, GetEntity } from '../interface.js'; +import { EntityTable, GetEntity, Visit } from '../interface.js'; import { EntityInterface, EntityMap, SchemaFunction } from '../schema.js'; import { INVALID } from '../special.js'; @@ -24,22 +24,22 @@ export default class AllSchema< input: any, parent: any, key: any, - visit: any, + args: any[], + visit: Visit, addEntity: any, - visitedEntities: any, - storeEntities: any, - args?: any[], + getEntity: any, + checkLoop: any, ): any { // we return undefined super.normalize( input, parent, key, + args, visit, addEntity, - visitedEntities, - storeEntities, - args, + getEntity, + checkLoop, ); } diff --git a/packages/endpoint/src/schemas/Array.ts b/packages/endpoint/src/schemas/Array.ts index c1b5fbfa3291..08feae66d324 100644 --- a/packages/endpoint/src/schemas/Array.ts +++ b/packages/endpoint/src/schemas/Array.ts @@ -1,5 +1,6 @@ import PolymorphicSchema from './Polymorphic.js'; import { filterEmpty, getValues } from './utils.js'; +import { Visit } from '../interface.js'; /** * Represents arrays @@ -10,32 +11,23 @@ export default class ArraySchema extends PolymorphicSchema { input: any, parent: any, key: any, - visit: any, + args: any[], + visit: Visit, addEntity: any, - visitedEntities: any, - storeEntities: any, - args?: any[], + getEntity: any, + checkLoop: any, ): any { const values = getValues(input); return values.map((value, index) => - this.normalizeValue( - value, - parent, - key, - visit, - addEntity, - visitedEntities, - storeEntities, - args, - ), + this.normalizeValue(value, parent, key, args, visit), ); } denormalize( input: any, args: any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ) { return input.map ? input diff --git a/packages/endpoint/src/schemas/Collection.ts b/packages/endpoint/src/schemas/Collection.ts index eb0fb4be6d0e..31d0f241859c 100644 --- a/packages/endpoint/src/schemas/Collection.ts +++ b/packages/endpoint/src/schemas/Collection.ts @@ -1,8 +1,7 @@ import { consistentSerialize } from './consistentSerialize.js'; -import { CREATE } from './special.js'; -import { GetEntity, PolymorphicInterface } from '../interface.js'; +import { CheckLoop, GetEntity, PolymorphicInterface } from '../interface.js'; import { Values, Array as ArraySchema } from '../schema.js'; -import type { DefaultArgs } from '../schemaTypes.js'; +import type { DefaultArgs, EntityInterface } from '../schemaTypes.js'; const pushMerge = (existing: any, incoming: any) => { return [...existing, ...incoming]; @@ -149,21 +148,21 @@ export default class CollectionSchema< input: any, parent: Parent, key: string, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args: any, + getEntity: any, + checkLoop: any, ): string { const normalizedValue = this.schema.normalize( input, parent, key, + args, visit, addEntity, - visitedEntities, - storeEntities, - args, + getEntity, + checkLoop, ); const id = this.pk(normalizedValue, parent, key, args); @@ -234,7 +233,7 @@ export default class CollectionSchema< denormalize( input: any, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): ReturnType { return this.schema.denormalize(input, args, unvisit) as any; } @@ -307,31 +306,36 @@ function normalizeCreate( input: any, parent: any, key: string, - visit: (...args: any) => any, - addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: Record, args: readonly any[], + visit: ((...args: any) => any) & { creating?: boolean }, + addEntity: (schema: any, processedEntity: any, id: string) => void, + getEntity: GetEntity, + checkLoop: CheckLoop, ): any { - // means 'this is a creation endpoint' - so real PKs are not required - visitedEntities[CREATE] = {}; + if (process.env.NODE_ENV !== 'production') { + // means 'this is a creation endpoint' - so real PKs are not required + // this is used by Entity.normalize() to determine whether to allow empty pks + // visit instances are created on each normalize call so this will safely be reset + visit.creating = true; + } const normalizedValue = this.schema.normalize( !(this.schema instanceof ArraySchema) || Array.isArray(input) ? input : [input], parent, key, + args, visit, addEntity, - visitedEntities, - storeEntities, - args, + getEntity, + checkLoop, ); // parent is args when not nested const filterCollections = (this.createCollectionFilter as any)(...args); // add to any collections that match this - if (storeEntities[this.key]) - Object.keys(storeEntities[this.key]).forEach(collectionPk => { + const entities = getEntity(this.key); + if (entities) + Object.keys(entities).forEach(collectionPk => { if (!filterCollections(JSON.parse(collectionPk))) return; addEntity(this, normalizedValue, collectionPk); }); @@ -347,7 +351,7 @@ function denormalize( this: CollectionSchema, input: any, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): any { return Array.isArray(input) ? (this.schema.denormalize(input, args, unvisit) as any) diff --git a/packages/endpoint/src/schemas/Entity.ts b/packages/endpoint/src/schemas/Entity.ts index 4a12cace57f6..76a31b4b48a9 100644 --- a/packages/endpoint/src/schemas/Entity.ts +++ b/packages/endpoint/src/schemas/Entity.ts @@ -110,7 +110,7 @@ First three members: ${JSON.stringify(input.slice(0, 3), null, 2)}`; this: T, input: any, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ) => AbstractInstanceType; } diff --git a/packages/endpoint/src/schemas/EntitySchema.ts b/packages/endpoint/src/schemas/EntitySchema.ts index 0e015a5a2bee..75ad7cde986b 100644 --- a/packages/endpoint/src/schemas/EntitySchema.ts +++ b/packages/endpoint/src/schemas/EntitySchema.ts @@ -1,58 +1,14 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { CREATE } from './special.js'; -import type { Schema, GetIndex, GetEntity } from '../interface.js'; +import { Constructor, EntityOptions } from './EntityTypes.js'; +import type { + Schema, + GetIndex, + GetEntity, + CheckLoop, + Visit, +} from '../interface.js'; import { AbstractInstanceType } from '../normal.js'; -export type Constructor = abstract new (...args: any[]) => {}; -export type IDClass = abstract new (...args: any[]) => { - id: string | number | undefined; -}; -export type PKClass = abstract new (...args: any[]) => { - pk( - parent?: any, - key?: string, - args?: readonly any[], - ): string | number | undefined; -}; - -// TODO: Figure out what Schema must be for each key -type ValidSchemas = { [k in keyof TInstance]?: Schema }; - -export type EntityOptions = { - readonly schema?: ValidSchemas; - readonly pk?: - | (( - value: TInstance, - parent?: any, - key?: string, - ) => string | number | undefined) - | keyof TInstance; - readonly key?: string; -} & { - readonly [K in Extract< - keyof IEntityClass, - | 'process' - | 'merge' - | 'expiresAt' - | 'createIfValid' - | 'mergeWithStore' - | 'validate' - | 'shouldReorder' - | 'shouldUpdate' - >]?: IEntityClass TInstance>[K]; -}; - -export interface RequiredPKOptions - extends EntityOptions { - readonly pk: - | (( - value: TInstance, - parent?: any, - key?: string, - ) => string | number | undefined) - | keyof TInstance; -} - /** * Represents data that should be deduped by specifying a primary key. * @see https://dataclient.io/rest/api/schema.Entity @@ -266,11 +222,11 @@ export default function EntitySchema( input: any, parent: any, key: string | undefined, - visit: (...args: any) => any, + args: readonly any[], + visit: Visit, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: readonly any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): any { const processedEntity = this.process(input, parent, key, args); let id = this.pk(processedEntity, parent, key, args); @@ -279,7 +235,7 @@ export default function EntitySchema( // this is useful for optimistic creates that don't need real ids - just something to hold their place id = `MISS-${Math.random()}`; // 'creates' conceptually should allow missing PK to make optimistic creates easy - if (process.env.NODE_ENV !== 'production' && !visitedEntities[CREATE]) { + if (process.env.NODE_ENV !== 'production' && !visit.creating) { const error = new Error( `Missing usable primary key when normalizing response. @@ -302,19 +258,7 @@ export default function EntitySchema( } /* Circular reference short-circuiter */ - const entityType = this.key; - if (!(entityType in visitedEntities)) { - visitedEntities[entityType] = {}; - } - if (!(id in visitedEntities[entityType])) { - visitedEntities[entityType][id] = []; - } - if ( - visitedEntities[entityType][id].some((entity: any) => entity === input) - ) { - return id; - } - visitedEntities[entityType][id].push(input); + if (checkLoop(this.key, id, input)) return id; const errorMessage = this.validate(processedEntity); throwValidationError(errorMessage); @@ -322,13 +266,10 @@ export default function EntitySchema( Object.keys(this.schema).forEach(key => { if (Object.hasOwn(processedEntity, key)) { processedEntity[key] = visit( + this.schema[key], processedEntity[key], processedEntity, key, - this.schema[key], - addEntity, - visitedEntities, - storeEntities, args, ); } @@ -358,7 +299,7 @@ export default function EntitySchema( this: T, input: any, args: any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): AbstractInstanceType { if (typeof input === 'symbol') { return input as any; @@ -367,7 +308,7 @@ export default function EntitySchema( // note: iteration order must be stable for (const key of Object.keys(this.schema)) { const schema = this.schema[key]; - const value = unvisit(input[key], schema); + const value = unvisit(schema, input[key]); if (typeof value === 'symbol') { // if default is not 'falsy', then this is required, so propagate INVALID symbol @@ -496,207 +437,6 @@ function throwValidationError(errorMessage: string | undefined) { } } -export interface IEntityClass { - toJSON(): { - name: string; - schema: { - [k: string]: Schema; - }; - key: string; - }; - /** Defines nested entities - * - * @see https://dataclient.io/rest/api/Entity#schema - */ - schema: { - [k: string]: Schema; - }; - /** Returns the globally unique identifier for the static Entity - * - * @see https://dataclient.io/rest/api/Entity#key - */ - key: string; - /** Defines indexes to enable lookup by - * - * @see https://dataclient.io/rest/api/Entity#indexes - */ - indexes?: readonly string[] | undefined; - /** - * A unique identifier for each Entity - * - * @see https://dataclient.io/rest/api/Entity#pk - * @param [value] POJO of the entity or subset used - * @param [parent] When normalizing, the object which included the entity - * @param [key] When normalizing, the key where this entity was found - * @param [args] ...args sent to Endpoint - */ - pk< - T extends (abstract new ( - ...args: any[] - ) => IEntityInstance & InstanceType) & - IEntityClass & - TBase, - >( - this: T, - value: Partial>, - parent?: any, - key?: string, - args?: any[], - ): string | number | undefined; - /** Return true to merge incoming data; false keeps existing entity - * - * @see https://dataclient.io/docs/api/schema.Entity#shouldUpdate - */ - shouldUpdate( - existingMeta: { - date: number; - fetchedAt: number; - }, - incomingMeta: { - date: number; - fetchedAt: number; - }, - existing: any, - incoming: any, - ): boolean; - /** Determines the order of incoming entity vs entity already in store - * - * @see https://dataclient.io/docs/api/schema.Entity#shouldReorder - * @returns true if incoming entity should be first argument of merge() - */ - shouldReorder( - existingMeta: { date: number; fetchedAt: number }, - incomingMeta: { date: number; fetchedAt: number }, - existing: any, - incoming: any, - ): boolean; - /** Creates new instance copying over defined values of arguments - * - * @see https://dataclient.io/docs/api/schema.Entity#merge - */ - merge(existing: any, incoming: any): any; - /** Run when an existing entity is found in the store - * - * @see https://dataclient.io/docs/api/schema.Entity#mergeWithStore - */ - mergeWithStore( - existingMeta: { - date: number; - fetchedAt: number; - }, - incomingMeta: { - date: number; - fetchedAt: number; - }, - existing: any, - incoming: any, - ): any; - /** Run when an existing entity is found in the store - * - * @see https://dataclient.io/docs/api/schema.Entity#mergeMetaWithStore - */ - mergeMetaWithStore( - existingMeta: { - expiresAt: number; - date: number; - fetchedAt: number; - }, - incomingMeta: { expiresAt: number; date: number; fetchedAt: number }, - existing: any, - incoming: any, - ): { - expiresAt: number; - date: number; - fetchedAt: number; - }; - /** Factory method to convert from Plain JS Objects. - * - * @param [props] Plain Object of properties to assign. - */ - fromJS< - T extends (abstract new ( - ...args: any[] - ) => IEntityInstance & InstanceType) & - IEntityClass & - TBase, - >( - this: T, - props?: Partial>, - ): AbstractInstanceType; - /** Called when denormalizing an entity to create an instance when 'valid' - * - * @param [props] Plain Object of properties to assign. - * @see https://dataclient.io/rest/api/Entity#createIfValid - */ - createIfValid< - T extends (abstract new ( - ...args: any[] - ) => IEntityInstance & InstanceType) & - IEntityClass & - TBase, - >( - this: T, - props: Partial>, - ): AbstractInstanceType | undefined; - /** Do any transformations when first receiving input - * - * @see https://dataclient.io/rest/api/Entity#process - */ - process(input: any, parent: any, key: string | undefined, args: any[]): any; - normalize( - input: any, - parent: any, - key: string | undefined, - visit: (...args: any) => any, - addEntity: (...args: any) => any, - visitedEntities: Record, - ): any; - /** Do any transformations when first receiving input - * - * @see https://dataclient.io/rest/api/Entity#validate - */ - validate(processedEntity: any): string | undefined; - /** Builds a key access the entity without endpoint results - * - * @see https://dataclient.io/rest/api/Entity#queryKey - */ - queryKey( - args: readonly any[], - queryKey: any, - getEntity: GetEntity, - getIndex: GetIndex, - ): any; - denormalize< - T extends (abstract new ( - ...args: any[] - ) => IEntityInstance & InstanceType) & - IEntityClass & - TBase, - >( - this: T, - input: any, - args: readonly any[], - unvisit: (input: any, schema: any) => any, - ): AbstractInstanceType; - /** All instance defaults set */ - readonly defaults: any; - //set(entity: any, key: string, value: any): void; -} -export interface IEntityInstance { - /** - * A unique identifier for each Entity - * - * @param [parent] When normalizing, the object which included the entity - * @param [key] When normalizing, the key where this entity was found - * @param [args] ...args sent to Endpoint - */ - pk( - parent?: any, - key?: string, - args?: readonly any[], - ): string | number | undefined; -} - function queryKeyCandidate( schema: any, args: readonly any[], diff --git a/packages/endpoint/src/schemas/EntityTypes.ts b/packages/endpoint/src/schemas/EntityTypes.ts new file mode 100644 index 000000000000..493a1352a3d2 --- /dev/null +++ b/packages/endpoint/src/schemas/EntityTypes.ts @@ -0,0 +1,255 @@ +import type { Schema, GetEntity, GetIndex } from '../interface.js'; +import { AbstractInstanceType } from '../normal.js'; + +export interface IEntityClass { + toJSON(): { + name: string; + schema: { + [k: string]: Schema; + }; + key: string; + }; + /** Defines nested entities + * + * @see https://dataclient.io/rest/api/Entity#schema + */ + schema: { + [k: string]: Schema; + }; + /** Returns the globally unique identifier for the static Entity + * + * @see https://dataclient.io/rest/api/Entity#key + */ + key: string; + /** Defines indexes to enable lookup by + * + * @see https://dataclient.io/rest/api/Entity#indexes + */ + indexes?: readonly string[] | undefined; + /** + * A unique identifier for each Entity + * + * @see https://dataclient.io/rest/api/Entity#pk + * @param [value] POJO of the entity or subset used + * @param [parent] When normalizing, the object which included the entity + * @param [key] When normalizing, the key where this entity was found + * @param [args] ...args sent to Endpoint + */ + pk< + T extends (abstract new ( + ...args: any[] + ) => IEntityInstance & InstanceType) & + IEntityClass & + TBase, + >( + this: T, + value: Partial>, + parent?: any, + key?: string, + args?: any[], + ): string | number | undefined; + /** Return true to merge incoming data; false keeps existing entity + * + * @see https://dataclient.io/docs/api/schema.Entity#shouldUpdate + */ + shouldUpdate( + existingMeta: { + date: number; + fetchedAt: number; + }, + incomingMeta: { + date: number; + fetchedAt: number; + }, + existing: any, + incoming: any, + ): boolean; + /** Determines the order of incoming entity vs entity already in store + * + * @see https://dataclient.io/docs/api/schema.Entity#shouldReorder + * @returns true if incoming entity should be first argument of merge() + */ + shouldReorder( + existingMeta: { date: number; fetchedAt: number }, + incomingMeta: { date: number; fetchedAt: number }, + existing: any, + incoming: any, + ): boolean; + /** Creates new instance copying over defined values of arguments + * + * @see https://dataclient.io/docs/api/schema.Entity#merge + */ + merge(existing: any, incoming: any): any; + /** Run when an existing entity is found in the store + * + * @see https://dataclient.io/docs/api/schema.Entity#mergeWithStore + */ + mergeWithStore( + existingMeta: { + date: number; + fetchedAt: number; + }, + incomingMeta: { + date: number; + fetchedAt: number; + }, + existing: any, + incoming: any, + ): any; + /** Run when an existing entity is found in the store + * + * @see https://dataclient.io/docs/api/schema.Entity#mergeMetaWithStore + */ + mergeMetaWithStore( + existingMeta: { + expiresAt: number; + date: number; + fetchedAt: number; + }, + incomingMeta: { expiresAt: number; date: number; fetchedAt: number }, + existing: any, + incoming: any, + ): { + expiresAt: number; + date: number; + fetchedAt: number; + }; + /** Factory method to convert from Plain JS Objects. + * + * @param [props] Plain Object of properties to assign. + */ + fromJS< + T extends (abstract new ( + ...args: any[] + ) => IEntityInstance & InstanceType) & + IEntityClass & + TBase, + >( + this: T, + props?: Partial>, + ): AbstractInstanceType; + /** Called when denormalizing an entity to create an instance when 'valid' + * + * @param [props] Plain Object of properties to assign. + * @see https://dataclient.io/rest/api/Entity#createIfValid + */ + createIfValid< + T extends (abstract new ( + ...args: any[] + ) => IEntityInstance & InstanceType) & + IEntityClass & + TBase, + >( + this: T, + props: Partial>, + ): AbstractInstanceType | undefined; + /** Do any transformations when first receiving input + * + * @see https://dataclient.io/rest/api/Entity#process + */ + process(input: any, parent: any, key: string | undefined, args: any[]): any; + normalize( + input: any, + parent: any, + key: string | undefined, + args: any[], + visit: (...args: any) => any, + addEntity: (...args: any) => any, + getEntity: (...args: any) => any, + checkLoop: (...args: any) => any, + ): any; + /** Do any transformations when first receiving input + * + * @see https://dataclient.io/rest/api/Entity#validate + */ + validate(processedEntity: any): string | undefined; + /** Builds a key access the entity without endpoint results + * + * @see https://dataclient.io/rest/api/Entity#queryKey + */ + queryKey( + args: readonly any[], + queryKey: any, + getEntity: GetEntity, + getIndex: GetIndex, + ): any; + denormalize< + T extends (abstract new ( + ...args: any[] + ) => IEntityInstance & InstanceType) & + IEntityClass & + TBase, + >( + this: T, + input: any, + args: readonly any[], + unvisit: (schema: any, input: any) => any, + ): AbstractInstanceType; + /** All instance defaults set */ + readonly defaults: any; +} +export interface IEntityInstance { + /** + * A unique identifier for each Entity + * + * @param [parent] When normalizing, the object which included the entity + * @param [key] When normalizing, the key where this entity was found + * @param [args] ...args sent to Endpoint + */ + pk( + parent?: any, + key?: string, + args?: readonly any[], + ): string | number | undefined; +} + +export type Constructor = abstract new (...args: any[]) => {}; +export type IDClass = abstract new (...args: any[]) => { + id: string | number | undefined; +}; +export type PKClass = abstract new (...args: any[]) => { + pk( + parent?: any, + key?: string, + args?: readonly any[], + ): string | number | undefined; +}; +// TODO: Figure out what Schema must be for each key +type ValidSchemas = { + [k in keyof TInstance]?: Schema; +}; + +export type EntityOptions = { + readonly schema?: ValidSchemas; + readonly pk?: + | (( + value: TInstance, + parent?: any, + key?: string, + ) => string | number | undefined) + | keyof TInstance; + readonly key?: string; +} & { + readonly [K in Extract< + keyof IEntityClass, + | 'process' + | 'merge' + | 'expiresAt' + | 'createIfValid' + | 'mergeWithStore' + | 'validate' + | 'shouldReorder' + | 'shouldUpdate' + >]?: IEntityClass TInstance>[K]; +}; + +export interface RequiredPKOptions + extends EntityOptions { + readonly pk: + | (( + value: TInstance, + parent?: any, + key?: string, + ) => string | number | undefined) + | keyof TInstance; +} diff --git a/packages/endpoint/src/schemas/ImmutableUtils.ts b/packages/endpoint/src/schemas/ImmutableUtils.ts index 038aa4b4b152..c3f93e721e7e 100644 --- a/packages/endpoint/src/schemas/ImmutableUtils.ts +++ b/packages/endpoint/src/schemas/ImmutableUtils.ts @@ -35,7 +35,7 @@ export function isImmutable(object: {}): object is { export function denormalizeImmutable( schema: any, input: any, - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): any { let deleted; const value = Object.keys(schema).reduce((object, key) => { @@ -43,7 +43,7 @@ export function denormalizeImmutable( // we're accessing them using string keys. const stringKey = `${key}`; - const item = unvisit(object.get(stringKey), schema[stringKey]); + const item = unvisit(schema[stringKey], object.get(stringKey)); if (typeof item === 'symbol') { deleted = item; } diff --git a/packages/endpoint/src/schemas/Invalidate.ts b/packages/endpoint/src/schemas/Invalidate.ts index e447d122fee4..6ea4270f59e3 100644 --- a/packages/endpoint/src/schemas/Invalidate.ts +++ b/packages/endpoint/src/schemas/Invalidate.ts @@ -37,16 +37,15 @@ export default class Invalidate< } /** Normalize lifecycles **/ - normalize( input: any, parent: any, key: string | undefined, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: Record, - args?: any[], + getEntity: any, + checkLoop: any, ): string | number | undefined { // TODO: what's store needs to be a differing type from fromJS const processedEntity = this._entity.process(input, parent, key, args); @@ -117,9 +116,9 @@ export default class Invalidate< denormalize( id: string, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): AbstractInstanceType { - return unvisit(id, this._entity) as any; + return unvisit(this._entity, id) as any; } /* istanbul ignore next */ diff --git a/packages/endpoint/src/schemas/Object.ts b/packages/endpoint/src/schemas/Object.ts index ec7ae77b5d83..62b682c6656f 100644 --- a/packages/endpoint/src/schemas/Object.ts +++ b/packages/endpoint/src/schemas/Object.ts @@ -1,30 +1,21 @@ import { isImmutable, denormalizeImmutable } from './ImmutableUtils.js'; -import { GetIndex, GetEntity } from '../interface.js'; +import { GetIndex, GetEntity, Visit } from '../interface.js'; export const normalize = ( schema: any, input: any, parent: any, key: any, - visit: any, - addEntity: any, - visitedEntities: any, - storeEntities: any, args: any[], + visit: Visit, + addEntity: any, + getEntity: any, + checkLoop: any, ) => { const object = { ...input }; Object.keys(schema).forEach(key => { const localSchema = schema[key]; - const value = visit( - input[key], - input, - key, - localSchema, - addEntity, - visitedEntities, - storeEntities, - args, - ); + const value = visit(localSchema, input[key], input, key, args); if (value === undefined) { delete object[key]; } else { @@ -38,7 +29,7 @@ export function denormalize( schema: any, input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): any { if (isImmutable(input)) { return denormalizeImmutable(schema, input, unvisit); @@ -47,7 +38,7 @@ export function denormalize( const object: Record = { ...input }; for (const key of Object.keys(schema)) { - const item = unvisit(object[key], schema[key]); + const item = unvisit(schema[key], object[key]); if (object[key] !== undefined) { object[key] = item; } @@ -99,11 +90,11 @@ export default class ObjectSchema { input: any, parent: any, key: any, + args: any[], visit: any, addEntity: any, - visitedEntities: any, - storeEntities: any, - args: any[], + getEntity: any, + checkLoop: any, ] ) { return normalize(this.schema, ...args); @@ -112,7 +103,7 @@ export default class ObjectSchema { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): any { return denormalize(this.schema, input, args, unvisit); } diff --git a/packages/endpoint/src/schemas/Polymorphic.ts b/packages/endpoint/src/schemas/Polymorphic.ts index 6dd2f9ccd7c3..0642f8add909 100644 --- a/packages/endpoint/src/schemas/Polymorphic.ts +++ b/packages/endpoint/src/schemas/Polymorphic.ts @@ -1,4 +1,5 @@ import { isImmutable } from './ImmutableUtils.js'; +import { Visit } from '../interface.js'; export default class PolymorphicSchema { private declare _schemaAttribute: any; @@ -44,16 +45,7 @@ export default class PolymorphicSchema { return this.schema[attr]; } - normalizeValue( - value: any, - parent: any, - key: any, - visit: any, - addEntity: any, - visitedEntities: any, - storeEntities: any, - args?: any[], - ) { + normalizeValue(value: any, parent: any, key: any, args: any[], visit: Visit) { if (!value) return value; const schema = this.inferSchema(value, parent, key); if (!schema) { @@ -75,16 +67,7 @@ Value: ${JSON.stringify(value, undefined, 2)}`, } return value; } - const normalizedValue = visit( - value, - parent, - key, - schema, - addEntity, - visitedEntities, - storeEntities, - args, - ); + const normalizedValue = visit(schema, value, parent, key, args); return ( this.isSingleSchema || normalizedValue === undefined || @@ -108,7 +91,7 @@ Value: ${JSON.stringify(value, undefined, 2)}`, // construct the correct Entity instance if (typeof value === 'object' && value !== null) { const schema = this.inferSchema(value, undefined, undefined); - if (schema) return unvisit(value, schema); + if (schema) return unvisit(schema, value); } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production' && value) { @@ -124,6 +107,6 @@ Value: ${JSON.stringify(value, undefined, 2)}.`, : isImmutable(value) ? value.get('id') : value.id; const schema = this.isSingleSchema ? this.schema : this.schema[schemaKey]; - return unvisit(id || value, schema); + return unvisit(schema, id || value); } } diff --git a/packages/endpoint/src/schemas/Query.ts b/packages/endpoint/src/schemas/Query.ts index 3401bbe2b7fc..3e5891d64559 100644 --- a/packages/endpoint/src/schemas/Query.ts +++ b/packages/endpoint/src/schemas/Query.ts @@ -34,7 +34,7 @@ export default class Query< } denormalize(input: {}, args: any, unvisit: any): ReturnType

{ - const value = unvisit(input, this.schema); + const value = unvisit(this.schema, input); return typeof value === 'symbol' ? value : this.process(value, ...args); } @@ -55,7 +55,7 @@ export default class Query< declare _denormalizeNullable: ( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ) => ReturnType

| undefined; declare _normalizeNullable: () => NormalizeNullable; diff --git a/packages/endpoint/src/schemas/Union.ts b/packages/endpoint/src/schemas/Union.ts index 6d8ce37d97e9..5faa16edf54d 100644 --- a/packages/endpoint/src/schemas/Union.ts +++ b/packages/endpoint/src/schemas/Union.ts @@ -1,5 +1,5 @@ import PolymorphicSchema from './Polymorphic.js'; -import { GetIndex, GetEntity } from '../interface.js'; +import { GetIndex, GetEntity, Visit } from '../interface.js'; /** * Represents polymorphic values. @@ -19,28 +19,19 @@ export default class UnionSchema extends PolymorphicSchema { input: any, parent: any, key: any, - visit: any, - addEntity: any, - visitedEntities: any, - storeEntities: any, args: any[], + visit: Visit, + addEntity: any, + getEntity: any, + checkLoop: any, ) { - return this.normalizeValue( - input, - parent, - key, - visit, - addEntity, - visitedEntities, - storeEntities, - args, - ); + return this.normalizeValue(input, parent, key, args, visit); } denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ) { return this.denormalizeValue(input, unvisit); } diff --git a/packages/endpoint/src/schemas/Values.ts b/packages/endpoint/src/schemas/Values.ts index 1b64384eb4c3..20168b3ed010 100644 --- a/packages/endpoint/src/schemas/Values.ts +++ b/packages/endpoint/src/schemas/Values.ts @@ -1,4 +1,5 @@ import PolymorphicSchema from './Polymorphic.js'; +import { Visit } from '../interface.js'; /** * Represents variably sized objects @@ -9,27 +10,18 @@ export default class ValuesSchema extends PolymorphicSchema { input: any, parent: any, key: any, - visit: any, - addEntity: any, - visitedEntities: any, - storeEntities: any, args: any[], + visit: Visit, + addEntity: any, + getEntity: any, + checkLoop: any, ) { return Object.keys(input).reduce((output, key, index) => { const value = input[key]; return value !== undefined && value !== null ? { ...output, - [key]: this.normalizeValue( - value, - input, - key, - visit, - addEntity, - visitedEntities, - storeEntities, - args, - ), + [key]: this.normalizeValue(value, input, key, args, visit), } : output; }, {}); @@ -38,7 +30,7 @@ export default class ValuesSchema extends PolymorphicSchema { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): any { return Object.keys(input).reduce((output, key) => { const entityOrId = (input as any)[key]; diff --git a/packages/endpoint/src/schemas/__tests__/All.test.ts b/packages/endpoint/src/schemas/__tests__/All.test.ts index 3c140b3b4d01..9c61eeb31f90 100644 --- a/packages/endpoint/src/schemas/__tests__/All.test.ts +++ b/packages/endpoint/src/schemas/__tests__/All.test.ts @@ -22,7 +22,7 @@ describe.each([[]])(`${schema.All.name} normalization (%s)`, () => { class User extends IDEntity {} const sch = new schema.All(User); function normalizeBad() { - normalize('abc', sch); + normalize(sch, 'abc'); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -31,17 +31,17 @@ describe.each([[]])(`${schema.All.name} normalization (%s)`, () => { class User extends IDEntity {} const sch = new schema.All(User); function normalizeBad() { - normalize('[{"id":5}]', sch); + normalize(sch, '[{"id":5}]'); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); test('normalizes Objects using their values', () => { class User extends IDEntity {} - const { result, entities } = normalize( - { foo: { id: '1' }, bar: { id: '2' } }, - new schema.All(User), - ); + const { result, entities } = normalize(new schema.All(User), { + foo: { id: '1' }, + bar: { id: '2' }, + }); expect(result).toBeUndefined(); expect(entities).toMatchSnapshot(); }); @@ -51,7 +51,7 @@ describe.each([[]])(`${schema.All.name} normalization (%s)`, () => { test('normalizes a single entity', () => { const listSchema = new schema.All(Cats); expect( - normalize([{ id: '1' }, { id: '2' }], listSchema).entities, + normalize(listSchema, [{ id: '1' }, { id: '2' }]).entities, ).toMatchSnapshot(); }); @@ -66,15 +66,12 @@ describe.each([[]])(`${schema.All.name} normalization (%s)`, () => { inferSchemaFn, ); - const { result, entities } = normalize( - [ - { type: 'Cat', id: '123' }, - { type: 'people', id: '123' }, - { id: '789', name: 'fido' }, - { type: 'Cat', id: '456' }, - ], - listSchema, - ); + const { result, entities } = normalize(listSchema, [ + { type: 'Cat', id: '123' }, + { type: 'people', id: '123' }, + { id: '789', name: 'fido' }, + { type: 'Cat', id: '456' }, + ]); expect(result).toBeUndefined(); expect(entities).toMatchSnapshot(); expect(inferSchemaFn.mock.calls).toMatchSnapshot(); @@ -84,7 +81,7 @@ describe.each([[]])(`${schema.All.name} normalization (%s)`, () => { class User extends IDEntity {} const users = new schema.All(User); expect( - normalize({ foo: { id: '1' }, bar: { id: '2' } }, users).entities, + normalize(users, { foo: { id: '1' }, bar: { id: '2' } }).entities, ).toMatchSnapshot(); }); @@ -92,7 +89,7 @@ describe.each([[]])(`${schema.All.name} normalization (%s)`, () => { class User extends IDEntity {} const users = new schema.All(User); expect( - normalize([undefined, { id: '123' }, null], users).entities, + normalize(users, [undefined, { id: '123' }, null]).entities, ).toMatchSnapshot(); }); }); @@ -118,7 +115,7 @@ describe.each([ }; const sch = new schema.All(Cat); expect( - new MemoCache().query('', sch, [], createInput(entities), {}), + new MemoCache().query(sch, [], createInput(entities), {}), ).toMatchSnapshot(); }); @@ -132,7 +129,7 @@ describe.each([ }, }; expect( - new MemoCache().query('', catSchema, [], createInput(entities), {}), + new MemoCache().query(catSchema, [], createInput(entities), {}), ).toMatchSnapshot(); }); @@ -146,7 +143,6 @@ describe.each([ }, }; const value = new MemoCache().query( - '', catSchema, [], createInput(entities), @@ -170,7 +166,6 @@ describe.each([ }, }; const value = new MemoCache().query( - '', catSchema, [], createInput(entities) as any, @@ -194,11 +189,11 @@ describe.each([ }, }; const memo = new MemoCache(); - const value = memo.query('', catSchema, [], entities, {}); + const value = memo.query(catSchema, [], entities, {}); expect(createOutput(value).results?.length).toBe(2); expect(createOutput(value).results).toMatchSnapshot(); - const value2 = memo.query('', catSchema, [], entities, {}); + const value2 = memo.query(catSchema, [], entities, {}); expect(createOutput(value).results[0]).toBe( createOutput(value2).results[0], ); @@ -211,7 +206,7 @@ describe.each([ 3: { id: '3', name: 'Jelico' }, }, }; - const value3 = memo.query('', catSchema, [], entities, {}); + const value3 = memo.query(catSchema, [], entities, {}); expect(createOutput(value3).results?.length).toBe(3); expect(createOutput(value3).results).toMatchSnapshot(); expect(createOutput(value).results[0]).toBe( @@ -234,7 +229,6 @@ describe.each([ }; const value = new MemoCache().query( - '', catSchema, [], createInput(entities), @@ -270,7 +264,6 @@ describe.each([ }, }; const value = new MemoCache().query( - '', listSchema, [], createInput(entities), @@ -293,7 +286,7 @@ describe.each([ }, }, }; - expect(denormalize('123', Taco, createInput(entities))).toMatchSnapshot(); + expect(denormalize(Taco, '123', createInput(entities))).toMatchSnapshot(); }); test('denormalizes multiple entities', () => { @@ -334,7 +327,6 @@ describe.each([ }, }; const value = new MemoCache().query( - '', listSchema, [], createInput(entities) as any, diff --git a/packages/endpoint/src/schemas/__tests__/Array.test.js b/packages/endpoint/src/schemas/__tests__/Array.test.js index c2881e639857..d0d69e9e21d8 100644 --- a/packages/endpoint/src/schemas/__tests__/Array.test.js +++ b/packages/endpoint/src/schemas/__tests__/Array.test.js @@ -18,13 +18,13 @@ afterAll(() => { test(`normalizes plain arrays as shorthand for ${schema.Array.name}`, () => { class User extends IDEntity {} - expect(normalize([{ id: '1' }, { id: '2' }], [User])).toMatchSnapshot(); + expect(normalize([User], [{ id: '1' }, { id: '2' }])).toMatchSnapshot(); }); test('throws an error if created with more than one schema', () => { class User extends IDEntity {} class Cat extends IDEntity {} - expect(() => normalize([{ id: '1' }], [Cat, User])).toThrow(); + expect(() => normalize([Cat, User], [{ id: '1' }])).toThrow(); }); describe.each([ ['schema', sch => new schema.Array(sch)], @@ -35,7 +35,7 @@ describe.each([ class User extends IDEntity {} const sch = createSchema(User); function normalizeBad() { - normalize('abc', sch); + normalize(sch, 'abc'); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -44,7 +44,7 @@ describe.each([ class User extends IDEntity {} const sch = createSchema(User); function normalizeBad() { - normalize('[{"id":5}]', sch); + normalize(sch, '[{"id":5}]'); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -71,21 +71,18 @@ describe.each([ } expect( - normalize( - { - id: '1', - content: 'parent', - children: [{ id: 4, content: 'child' }], - }, - Parent, - ), + normalize(Parent, { + id: '1', + content: 'parent', + children: [{ id: 4, content: 'child' }], + }), ).toMatchSnapshot(); }); test('normalizes Objects using their values', () => { class User extends IDEntity {} expect( - normalize({ foo: { id: '1' }, bar: { id: '2' } }, createSchema(User)), + normalize(createSchema(User), { foo: { id: '1' }, bar: { id: '2' } }), ).toMatchSnapshot(); }); }); @@ -95,7 +92,7 @@ describe.each([ test('normalizes a single entity', () => { const listSchema = createSchema(Cats); expect( - normalize([{ id: '1' }, { id: '2' }], listSchema), + normalize(listSchema, [{ id: '1' }, { id: '2' }]), ).toMatchSnapshot(); }); @@ -111,15 +108,12 @@ describe.each([ ); expect( - normalize( - [ - { type: 'Cat', id: '123' }, - { type: 'people', id: '123' }, - { id: '789', name: 'fido' }, - { type: 'Cat', id: '456' }, - ], - listSchema, - ), + normalize(listSchema, [ + { type: 'Cat', id: '123' }, + { type: 'people', id: '123' }, + { id: '789', name: 'fido' }, + { type: 'Cat', id: '456' }, + ]), ).toMatchSnapshot(); expect(inferSchemaFn.mock.calls).toMatchSnapshot(); }); @@ -128,7 +122,7 @@ describe.each([ class User extends IDEntity {} const users = createSchema(User); expect( - normalize({ foo: { id: '1' }, bar: { id: '2' } }, users), + normalize(users, { foo: { id: '1' }, bar: { id: '2' } }), ).toMatchSnapshot(); }); @@ -136,7 +130,7 @@ describe.each([ class User extends IDEntity {} const users = createSchema(User); expect( - normalize([undefined, { id: '123' }, null], users), + normalize(users, [undefined, { id: '123' }, null]), ).toMatchSnapshot(); }); }); @@ -155,19 +149,19 @@ describe.each([ }; const sch = new schema.Object({ user: User, tacos: [] }); expect( - denormalize({ user: '1' }, sch, createInput(entities)), + denormalize(sch, { user: '1' }, createInput(entities)), ).toMatchSnapshot(); expect( - denormalize(createInput({ user: '1' }), sch, createInput(entities)), + denormalize(sch, createInput({ user: '1' }), createInput(entities)), ).toMatchSnapshot(); expect( - denormalize({ user: '1', tacos: [] }, sch, createInput(entities)), + denormalize(sch, { user: '1', tacos: [] }, createInput(entities)), ).toMatchSnapshot(); expect( denormalize( - createInput({ user: '1', tacos: [] }), sch, + createInput({ user: '1', tacos: [] }), createInput(entities), ), ).toMatchSnapshot(); @@ -186,7 +180,7 @@ describe.each([ }, }; expect( - denormalize(['1', '2'], createSchema(Cat), createInput(entities)), + denormalize(createSchema(Cat), ['1', '2'], createInput(entities)), ).toMatchSnapshot(); }); @@ -200,8 +194,8 @@ describe.each([ }; expect( denormalize( - { a: '1', b: '2' }, createSchema(Cat), + { a: '1', b: '2' }, createInput(entities), ), ).toMatchSnapshot(); @@ -219,19 +213,19 @@ describe.each([ tacos: createSchema({ next: '' }), }); expect( - denormalize({ user: '1' }, sch, createInput(entities)), + denormalize(sch, { user: '1' }, createInput(entities)), ).toMatchSnapshot(); expect( - denormalize(createInput({ user: '1' }), sch, createInput(entities)), + denormalize(sch, createInput({ user: '1' }), createInput(entities)), ).toMatchSnapshot(); expect( - denormalize({ user: '1', tacos: [] }, sch, createInput(entities)), + denormalize(sch, { user: '1', tacos: [] }, createInput(entities)), ).toMatchSnapshot(); expect( denormalize( - createInput({ user: '1', tacos: [] }), sch, + createInput({ user: '1', tacos: [] }), createInput(entities), ), ).toMatchSnapshot(); @@ -247,7 +241,7 @@ describe.each([ }, }; expect( - denormalize({ results: ['1', '2'] }, catSchema, createInput(entities)), + denormalize(catSchema, { results: ['1', '2'] }, createInput(entities)), ).toMatchSnapshot(); }); @@ -264,14 +258,14 @@ describe.each([ }, }; let value = denormalize( - { results: ['1', '2'] }, catSchema, + { results: ['1', '2'] }, createInput(entities), ); expect(value).toMatchSnapshot(); value = denormalize( - createInput({ results: ['1', '2'] }), catSchema, + createInput({ results: ['1', '2'] }), createInput(entities), ); expect(value).toMatchSnapshot(); @@ -290,14 +284,14 @@ describe.each([ }, }; let value = denormalize( - createInput({ results: ['1', undefined, '2', null] }), catSchema, + createInput({ results: ['1', undefined, '2', null] }), createInput(entities), ); expect(value).toMatchSnapshot(); value = denormalize( - { results: ['1', '2'] }, catSchema, + { results: ['1', '2'] }, createInput(entities), ); expect(value).toMatchSnapshot(); @@ -313,8 +307,8 @@ describe.each([ }, }; let value = denormalize( - createInput({ results: undefined }), catSchema, + createInput({ results: undefined }), createInput(entities), ); expect(value).toMatchSnapshot(); @@ -329,8 +323,8 @@ describe.each([ }, }; let value = denormalize( - createInput([{ data: '1' }, { data: '2' }, { data: '3' }]), createSchema(new schema.Object({ data: Cat })), + createInput([{ data: '1' }, { data: '2' }, { data: '3' }]), createInput(entities), ); expect(value).toMatchSnapshot(); @@ -350,7 +344,7 @@ describe.each([ }, }; - expect(denormalize('123', Taco, createInput(entities))).toMatchSnapshot(); + expect(denormalize(Taco, '123', createInput(entities))).toMatchSnapshot(); }); test('denormalizes multiple entities', () => { @@ -396,8 +390,8 @@ describe.each([ ]; const value = denormalize( - createInput(input), listSchema, + createInput(input), createInput(entities), ); expect(value).toMatchSnapshot(); @@ -413,9 +407,9 @@ describe.each([ { cat: { id: '1' }, id: '5' }, { cat: { id: '2' }, id: '6' }, ]; - const output = normalize(input, catList); + const output = normalize(catList, input); expect(output).toMatchSnapshot(); - expect(denormalize(output.result, catList, output.entities)).toEqual( + expect(denormalize(catList, output.result, output.entities)).toEqual( input, ); }); diff --git a/packages/endpoint/src/schemas/__tests__/Collection.test.ts b/packages/endpoint/src/schemas/__tests__/Collection.test.ts index fc8721a8a97e..37dff8316f6f 100644 --- a/packages/endpoint/src/schemas/__tests__/Collection.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Collection.test.ts @@ -60,14 +60,14 @@ describe(`${schema.Collection.name} normalization`, () => { test('should throw a custom error if data loads with string unexpected value', () => { function normalizeBad() { - normalize('abc', userTodos); + normalize(userTodos, 'abc'); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); test('should throw a custom error if data loads with string unexpected value', () => { function normalizeBad() { - normalize(null, userTodos.push); + normalize(userTodos.push, null); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -78,25 +78,22 @@ describe(`${schema.Collection.name} normalization`, () => { [], undefined as any, '', + [], () => undefined, () => undefined, - {}, - {}, - [], + () => undefined, + () => false, ); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); test('normalizes nested collections', () => { - const state = normalize( - { - id: '1', - username: 'bob', - todos: [{ id: '5', title: 'finish collections' }], - }, - User, - ); + const state = normalize(User, { + id: '1', + username: 'bob', + todos: [{ id: '5', title: 'finish collections' }], + }); expect(state).toMatchSnapshot(); //const a: string | undefined = state.result; // @ts-expect-error @@ -105,9 +102,9 @@ describe(`${schema.Collection.name} normalization`, () => { test('normalizes top level collections', () => { const state = normalize( - [{ id: '5', title: 'finish collections' }], userTodos, - [{ userId: '1' }], + [{ id: '5', title: 'finish collections' }], + { args: [{ userId: '1' }] }, ); expect(state).toMatchSnapshot(); //const a: string[] | undefined = state.result; @@ -117,9 +114,9 @@ describe(`${schema.Collection.name} normalization`, () => { test('normalizes top level collections (no args)', () => { const state = normalize( - [{ id: '5', title: 'finish collections' }], new schema.Collection(new schema.Array(Todo)), - [{ userId: '1' }], + [{ id: '5', title: 'finish collections' }], + { args: [{ userId: '1' }] }, ); expect(state).toMatchSnapshot(); //const a: string[] | undefined = state.result; @@ -128,14 +125,11 @@ describe(`${schema.Collection.name} normalization`, () => { }); test('normalizes already processed entities', () => { - const state = normalize( - { - id: '1', - username: 'bob', - todos: ['5', '6'], - }, - User, - ); + const state = normalize(User, { + id: '1', + username: 'bob', + todos: ['5', '6'], + }); expect(state).toMatchSnapshot(); }); @@ -186,12 +180,10 @@ describe(`${schema.Collection.name} normalization`, () => { result: '1', }; const state = normalize( - [{ id: '10', title: 'create new items' }], User.schema.todos.push, - [{ userId: '1' }], - init.entities, - init.indexes, - init.entityMeta, + [{ id: '10', title: 'create new items' }], + { args: [{ userId: '1' }] }, + init, ); expect(state).toMatchSnapshot(); }); @@ -201,56 +193,48 @@ describe(`${schema.Collection.name} normalization`, () => { let state = { ...initialState, ...normalize( - [{ id: '10', title: 'create new items' }], initializingSchema, - [{ userId: '1' }], - initialState.entities, - initialState.indexes, - initialState.entityMeta, + [{ id: '10', title: 'create new items' }], + { args: [{ userId: '1' }] }, + initialState, ), }; state = { ...state, ...normalize( - [{ id: '10', title: 'create new items' }], initializingSchema, - [{ userId: '1', ignoredMe: '5' }], - state.entities, - state.indexes, - state.entityMeta, + [{ id: '10', title: 'create new items' }], + { args: [{ userId: '1', ignoredMe: '5' }] }, + state, ), }; state = { ...state, ...normalize( - [{ id: '20', title: 'second user' }], initializingSchema, - [{ userId: '2' }], - state.entities, - state.indexes, - state.entityMeta, + [{ id: '20', title: 'second user' }], + { args: [{ userId: '2' }] }, + state, ), }; state = { ...state, ...normalize( + initializingSchema, [ { id: '10', title: 'create new items' }, { id: '20', title: 'the ignored one' }, ], - initializingSchema, - [{}], - state.entities, - state.indexes, - state.entityMeta, + { args: [{}] }, + state, ), }; function validate(sch: schema.Collection<(typeof Todo)[]>) { expect( ( denormalize( - JSON.stringify({ userId: '1' }), sch, + JSON.stringify({ userId: '1' }), state.entities, ) as any )?.length, @@ -258,18 +242,16 @@ describe(`${schema.Collection.name} normalization`, () => { const testState = { ...state, ...normalize( - [{ id: '30', title: 'pushed to the end' }], sch.push, - [{ userId: '1' }], - state.entities, - state.indexes, - state.entityMeta, + [{ id: '30', title: 'pushed to the end' }], + { args: [{ userId: '1' }] }, + state, ), }; function getResponse(...args: any) { return denormalize( - sch.pk(undefined, undefined, '', args), sch, + sch.pk(undefined, undefined, '', args), testState.entities, ) as any; } @@ -408,13 +390,13 @@ describe(`${schema.Collection.name} denormalization`, () => { test('denormalizes nested collections', () => { expect( - denormalize(normalizeNested.result, User, normalizeNested.entities), + denormalize(User, normalizeNested.result, normalizeNested.entities), ).toMatchSnapshot(); }); test('denormalizes top level collections', () => { expect( - denormalize('{"userId":"1"}', userTodos, normalizeNested.entities), + denormalize(userTodos, '{"userId":"1"}', normalizeNested.entities), ).toMatchSnapshot(); }); @@ -422,15 +404,15 @@ describe(`${schema.Collection.name} denormalization`, () => { const memo = new SimpleMemoCache(); test('denormalizes nested and top level share referential equality', () => { const todos = memo.denormalize( - '{"userId":"1"}', userTodos, + '{"userId":"1"}', normalizeNested.entities, [{ userId: '1' }], ); const user = memo.denormalize( - normalizeNested.result, User, + normalizeNested.result, normalizeNested.entities, ); expect(user).toBeDefined(); @@ -441,23 +423,21 @@ describe(`${schema.Collection.name} denormalization`, () => { test('push updates cache', () => { const pushedState = normalize( - [{ id: '10', title: 'create new items' }], User.schema.todos.push, - [{ userId: '1' }], - normalizeNested.entities, - normalizeNested.indexes, - normalizeNested.entityMeta, + [{ id: '10', title: 'create new items' }], + { args: [{ userId: '1' }] }, + normalizeNested, ); const todos = memo.denormalize( - '{"userId":"1"}', userTodos, + '{"userId":"1"}', pushedState.entities, [{ userId: '1' }], ); const user = memo.denormalize( - normalizeNested.result, User, + normalizeNested.result, pushedState.entities, [{ id: '1' }], ); @@ -470,22 +450,20 @@ describe(`${schema.Collection.name} denormalization`, () => { test('unshift places at start', () => { const unshiftState = normalize( - [{ id: '2', title: 'from the start' }], User.schema.todos.unshift, - [{ userId: '1' }], - normalizeNested.entities, - normalizeNested.indexes, - normalizeNested.entityMeta, + [{ id: '2', title: 'from the start' }], + { args: [{ userId: '1' }] }, + normalizeNested, ); const todos = memo.denormalize( - '{"userId":"1"}', userTodos, + '{"userId":"1"}', unshiftState.entities, [{ userId: '1' }], ); const user = memo.denormalize( - normalizeNested.result, User, + normalizeNested.result, unshiftState.entities, [{ id: '1' }], ); @@ -499,8 +477,8 @@ describe(`${schema.Collection.name} denormalization`, () => { test('denormalizes unshift', () => { const todos = memo.denormalize( - [{ id: '2', title: 'from the start' }], userTodos.unshift, + [{ id: '2', title: 'from the start' }], {}, [{ id: '1' }], ); @@ -522,8 +500,8 @@ describe(`${schema.Collection.name} denormalization`, () => { test('denormalizes unshift (single item)', () => { const todos = memo.denormalize( - { id: '2', title: 'from the start' }, userTodos.unshift, + { id: '2', title: 'from the start' }, {}, [{ id: '1' }], ); @@ -545,7 +523,6 @@ describe(`${schema.Collection.name} denormalization`, () => { it('should buildQueryKey with matching args', () => { const memo = new MemoCache(); const queryKey = memo.buildQueryKey( - '', userTodos, [{ userId: '1' }], normalizeNested.entities, @@ -553,7 +530,7 @@ describe(`${schema.Collection.name} denormalization`, () => { ); expect(queryKey).toBeDefined(); // now ensure our queryKey is usable - const results = denormalize(queryKey, userTodos, normalizeNested.entities); + const results = denormalize(userTodos, queryKey, normalizeNested.entities); expect(results).toBeDefined(); expect(results).toMatchInlineSnapshot(` [ @@ -570,7 +547,6 @@ describe(`${schema.Collection.name} denormalization`, () => { it('should buildQueryKey undefined when not in cache', () => { const memo = new MemoCache(); const queryKey = memo.buildQueryKey( - '', userTodos, [{ userId: '100' }], normalizeNested.entities, @@ -582,7 +558,6 @@ describe(`${schema.Collection.name} denormalization`, () => { it('should buildQueryKey undefined with nested Collection', () => { const memo = new MemoCache(); const queryKey = memo.buildQueryKey( - '', User.schema.todos, [{ userId: '1' }], normalizeNested.entities, diff --git a/packages/endpoint/src/schemas/__tests__/Entity.test.ts b/packages/endpoint/src/schemas/__tests__/Entity.test.ts index 850751da653b..6ccc0e40678f 100644 --- a/packages/endpoint/src/schemas/__tests__/Entity.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Entity.test.ts @@ -66,7 +66,7 @@ describe(`${Entity.name} normalization`, () => { test('normalizes an entity', () => { class MyEntity extends IDEntity {} - expect(normalize({ id: '1' }, MyEntity)).toMatchSnapshot(); + expect(normalize(MyEntity, { id: '1' })).toMatchSnapshot(); }); test('normalizes already processed entities', () => { @@ -78,12 +78,12 @@ describe(`${Entity.name} normalization`, () => { nest: MyEntity, }; } - expect(normalize(['1'], new schema.Array(MyEntity))).toMatchSnapshot(); + expect(normalize(new schema.Array(MyEntity), ['1'])).toMatchSnapshot(); expect( - normalize({ data: '1' }, new schema.Object({ data: MyEntity })), + normalize(new schema.Object({ data: MyEntity }), { data: '1' }), ).toMatchSnapshot(); expect( - normalize({ title: 'hi', id: '5', nest: '10' }, Nested), + normalize(Nested, { title: 'hi', id: '5', nest: '10' }), ).toMatchSnapshot(); }); @@ -96,17 +96,15 @@ describe(`${Entity.name} normalization`, () => { } } - const { entities, entityMeta } = normalize( - { id: '1', title: 'hi' }, - MyEntity, - ); + const { entities, entityMeta } = normalize(MyEntity, { + id: '1', + title: 'hi', + }); const secondEntities = normalize( - { id: '1', title: 'second' }, MyEntity, - [], - entities, + { id: '1', title: 'second' }, {}, - entityMeta, + { entities, entityMeta, indexes: {} }, ).entities; expect(entities.MyEntity['1']).toBeDefined(); expect(entities.MyEntity['1']).toBe(secondEntities.MyEntity['1']); @@ -122,7 +120,7 @@ describe(`${Entity.name} normalization`, () => { } const schema = MyEntity; function normalizeBad() { - normalize({ secondthing: 'hi' }, schema); + normalize(schema, { secondthing: 'hi' }); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -137,7 +135,7 @@ describe(`${Entity.name} normalization`, () => { } const schema = MyEntity; function normalizeBad() { - normalize({ secondthing: 'hi' }, schema); + normalize(schema, { secondthing: 'hi' }); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -157,7 +155,7 @@ describe(`${Entity.name} normalization`, () => { const schema = MyEntity; expect( - normalize({ name: 'bob', secondthing: 'hi' }, schema), + normalize(schema, { name: 'bob', secondthing: 'hi' }), ).toMatchSnapshot(); }); @@ -176,7 +174,7 @@ describe(`${Entity.name} normalization`, () => { } const schema = MyEntity; - expect(normalize({ name: 'bob', secondthing: 'hi' }, schema)) + expect(normalize(schema, { name: 'bob', secondthing: 'hi' })) .toMatchInlineSnapshot(` { "entities": { @@ -212,7 +210,7 @@ describe(`${Entity.name} normalization`, () => { } const schema = MyEntity; function normalizeBad() { - normalize({}, schema); + normalize(schema, {}); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -227,14 +225,11 @@ describe(`${Entity.name} normalization`, () => { } const schema = MyEntity; function normalizeBad() { - normalize( - [ - { name: 'hi', secondthing: 'ho' }, - { name: 'hi', secondthing: 'ho' }, - { name: 'hi', secondthing: 'ho' }, - ], - schema, - ); + normalize(schema, [ + { name: 'hi', secondthing: 'ho' }, + { name: 'hi', secondthing: 'ho' }, + { name: 'hi', secondthing: 'ho' }, + ]); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -250,14 +245,11 @@ describe(`${Entity.name} normalization`, () => { } const schema = MyEntity; function normalizeBad() { - normalize( - [ - { name: 'hi', secondthing: 'ho' }, - { name: 'hi', secondthing: 'ho' }, - { name: 'hi', secondthing: 'ho' }, - ], - schema, - ); + normalize(schema, [ + { name: 'hi', secondthing: 'ho' }, + { name: 'hi', secondthing: 'ho' }, + { name: 'hi', secondthing: 'ho' }, + ]); } expect(normalizeBad).not.toThrow(); expect(warnSpy.mock.calls.length).toBe(1); @@ -275,33 +267,30 @@ describe(`${Entity.name} normalization`, () => { const schema = MyEntity; expect( - normalize( - { - name: 'hi', - a: 'a', - b: 'b', - c: 'c', - d: 'e', - e: 0, - f: 0, - g: 0, - h: 0, - i: 0, - j: 0, - k: 0, - l: 0, - m: 0, - n: 0, - o: 0, - p: 0, - q: 0, - r: 0, - s: 0, - t: 0, - u: 0, - }, - schema, - ), + normalize(schema, { + name: 'hi', + a: 'a', + b: 'b', + c: 'c', + d: 'e', + e: 0, + f: 0, + g: 0, + h: 0, + i: 0, + j: 0, + k: 0, + l: 0, + m: 0, + n: 0, + o: 0, + p: 0, + q: 0, + r: 0, + s: 0, + t: 0, + u: 0, + }), ).toMatchSnapshot(); expect(warnSpy.mock.calls.length).toBe(0); }); @@ -325,7 +314,7 @@ describe(`${Entity.name} normalization`, () => { } } function normalizeBad() { - normalize({ name: 'hoho', nonexistantthing: 'hi' }, MyEntity); + normalize(MyEntity, { name: 'hoho', nonexistantthing: 'hi' }); } expect(normalizeBad).toThrow(); }); @@ -350,7 +339,7 @@ describe(`${Entity.name} normalization`, () => { } } function normalizeBad() { - normalize({ name: 'bob' }, MyEntity); + normalize(MyEntity, { name: 'bob' }); } expect(normalizeBad).not.toThrow(); expect(warnSpy.mock.calls.length).toBe(0); @@ -368,7 +357,7 @@ describe(`${Entity.name} normalization`, () => { } const schema = MyEntity; function normalizeBad() { - normalize({ name: 'hoho', nonexistantthing: 'hi' }, schema); + normalize(schema, { name: 'hoho', nonexistantthing: 'hi' }); } expect(normalizeBad).not.toThrow(); expect(warnSpy.mock.calls.length).toBe(0); @@ -385,7 +374,7 @@ describe(`${Entity.name} normalization`, () => { } const schema = { data: MyEntity }; function normalizeBad() { - normalize('hibho', schema); + normalize(schema, 'hibho'); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -432,7 +421,7 @@ describe(`${Entity.name} normalization`, () => { } } expect( - normalize({ idStr: '134351', name: 'Kathy' }, User), + normalize(User, { idStr: '134351', name: 'Kathy' }), ).toMatchSnapshot(); }); @@ -446,10 +435,10 @@ describe(`${Entity.name} normalization`, () => { const inputSchema = new schema.Values({ users: User }, () => 'users'); expect( - normalize( - { '4': { name: 'taco' }, '56': { name: 'burrito' } }, - inputSchema, - ), + normalize(inputSchema, { + '4': { name: 'taco' }, + '56': { name: 'burrito' }, + }), ).toMatchSnapshot(); }); @@ -464,10 +453,10 @@ describe(`${Entity.name} normalization`, () => { const inputSchema = new schema.Object({ user: User }); expect( - normalize( - { name: 'tacos', user: { id: '4', name: 'Jimmy' } }, - inputSchema, - ), + normalize(inputSchema, { + name: 'tacos', + user: { id: '4', name: 'Jimmy' }, + }), ).toMatchSnapshot(); }); }); @@ -476,11 +465,11 @@ describe(`${Entity.name} normalization`, () => { test('defaults to plain merging', () => { expect( normalize( + [Tacos], [ { id: '1', name: 'foo' }, { id: '1', name: 'bar', alias: 'bar' }, ], - [Tacos], ), ).toMatchSnapshot(); }); @@ -501,11 +490,11 @@ describe(`${Entity.name} normalization`, () => { expect( normalize( + [MergeTaco], [ { id: '1', name: 'foo' }, { id: '1', name: 'bar', alias: 'bar' }, ], - [MergeTaco], ), ).toMatchSnapshot(); }); @@ -522,13 +511,13 @@ describe(`${Entity.name} normalization`, () => { }; } } - const { entities, result } = normalize( - { id: '1', name: 'foo' }, - ProcessTaco, - ); + const { entities, result } = normalize(ProcessTaco, { + id: '1', + name: 'foo', + }); const final = new SimpleMemoCache().denormalize( - result, ProcessTaco, + result, entities, ); expect(final).not.toEqual(expect.any(Symbol)); @@ -558,17 +547,14 @@ describe(`${Entity.name} normalization`, () => { static schema = { child: ChildEntity }; } - const { entities, result } = normalize( - { - id: '1', - content: 'parent', - child: { id: '4', content: 'child' }, - }, - ParentEntity, - ); + const { entities, result } = normalize(ParentEntity, { + id: '1', + content: 'parent', + child: { id: '4', content: 'child' }, + }); const final = new SimpleMemoCache().denormalize( - result, ParentEntity, + result, entities, ); expect(final).not.toEqual(expect.any(Symbol)); @@ -594,13 +580,12 @@ describe(`${Entity.name} normalization`, () => { } } - const { entities, result } = normalize( - { message: { id: '123', data: { attachment: { id: '456' } } } }, - EntriesEntity, - ); + const { entities, result } = normalize(EntriesEntity, { + message: { id: '123', data: { attachment: { id: '456' } } }, + }); const final = new SimpleMemoCache().denormalize( - result, EntriesEntity, + result, entities, ); expect(final).not.toEqual(expect.any(Symbol)); @@ -618,8 +603,8 @@ describe(`${Entity.name} denormalization`, () => { '1': { id: '1', name: 'foo' }, }, }; - expect(denormalize('1', Tacos, entities)).toMatchSnapshot(); - expect(denormalize('1', Tacos, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Tacos, '1', entities)).toMatchSnapshot(); + expect(denormalize(Tacos, '1', fromJS(entities))).toMatchSnapshot(); }); class Food extends IDEntity {} @@ -640,13 +625,13 @@ describe(`${Entity.name} denormalization`, () => { }, }; - const de1 = denormalize('1', Menu, entities); + const de1 = denormalize(Menu, '1', entities); expect(de1).toMatchSnapshot(); - expect(denormalize('1', Menu, fromJS(entities))).toEqual(de1); + expect(denormalize(Menu, '1', fromJS(entities))).toEqual(de1); - const de2 = denormalize('2', Menu, entities); + const de2 = denormalize(Menu, '2', entities); expect(de2).toMatchSnapshot(); - expect(denormalize('2', Menu, fromJS(entities))).toEqual(de2); + expect(denormalize(Menu, '2', fromJS(entities))).toEqual(de2); }); test('denormalizes deep entities while maintaining referential equality', () => { @@ -661,8 +646,8 @@ describe(`${Entity.name} denormalization`, () => { }; const memo = new SimpleMemoCache(); - const first = memo.denormalize('1', Menu, entities); - const second = memo.denormalize('1', Menu, entities); + const first = memo.denormalize(Menu, '1', entities); + const second = memo.denormalize(Menu, '1', entities); expect(first).not.toEqual(expect.any(Symbol)); if (typeof first === 'symbol') return; expect(second).not.toEqual(expect.any(Symbol)); @@ -682,8 +667,8 @@ describe(`${Entity.name} denormalization`, () => { '1': { id: '1' }, }, }; - expect(denormalize('1', MyTacos, entities)).toEqual(expect.any(Symbol)); - expect(denormalize('1', MyTacos, fromJS(entities))).toEqual( + expect(denormalize(MyTacos, '1', entities)).toEqual(expect.any(Symbol)); + expect(denormalize(MyTacos, '1', fromJS(entities))).toEqual( expect.any(Symbol), ); }); @@ -698,11 +683,11 @@ describe(`${Entity.name} denormalization`, () => { }, }; - expect(denormalize('1', Menu, entities)).toMatchSnapshot(); - expect(denormalize('1', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '1', fromJS(entities))).toMatchSnapshot(); - expect(denormalize('2', Menu, entities)).toMatchSnapshot(); - expect(denormalize('2', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '2', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '2', fromJS(entities))).toMatchSnapshot(); }); it('should handle optional schema entries Entity', () => { @@ -721,7 +706,7 @@ describe(`${Entity.name} denormalization`, () => { const schema = MyEntity; expect( - denormalize('bob', schema, { + denormalize(schema, 'bob', { MyEntity: { bob: { name: 'bob', secondthing: 'hi' } }, }), ).toMatchInlineSnapshot(` @@ -749,7 +734,7 @@ describe(`${Entity.name} denormalization`, () => { const schema = MyEntity; expect( - denormalize('bob', schema, { + denormalize(schema, 'bob', { MyEntity: { bob: { name: 'bob', secondthing: 'hi', blarb: null } }, }), ).toMatchInlineSnapshot(` @@ -773,11 +758,11 @@ describe(`${Entity.name} denormalization`, () => { }, }; - expect(denormalize('1', Menu, entities)).toMatchSnapshot(); - expect(denormalize('1', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '1', fromJS(entities))).toMatchSnapshot(); - expect(denormalize('2', Menu, entities)).toMatchSnapshot(); - expect(denormalize('2', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '2', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '2', fromJS(entities))).toMatchSnapshot(); }); test('denormalizes deep entities with records', () => { @@ -797,11 +782,11 @@ describe(`${Entity.name} denormalization`, () => { }, }; - expect(denormalize('1', Menu, entities)).toMatchSnapshot(); - expect(denormalize('1', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '1', fromJS(entities))).toMatchSnapshot(); - expect(denormalize('2', Menu, entities)).toMatchSnapshot(); - expect(denormalize('2', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '2', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '2', fromJS(entities))).toMatchSnapshot(); }); test('can denormalize already partially denormalized data', () => { @@ -815,8 +800,8 @@ describe(`${Entity.name} denormalization`, () => { }, }; - expect(denormalize('1', Menu, entities)).toMatchSnapshot(); - expect(denormalize('1', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '1', fromJS(entities))).toMatchSnapshot(); }); class User extends IDEntity { @@ -864,11 +849,11 @@ describe(`${Entity.name} denormalization`, () => { }, }; - expect(denormalize('123', Report, entities)).toMatchSnapshot(); - expect(denormalize('123', Report, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Report, '123', entities)).toMatchSnapshot(); + expect(denormalize(Report, '123', fromJS(entities))).toMatchSnapshot(); - expect(denormalize('456', User, entities)).toMatchSnapshot(); - expect(denormalize('456', User, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(User, '456', entities)).toMatchSnapshot(); + expect(denormalize(User, '456', fromJS(entities))).toMatchSnapshot(); }); test('denormalizes recursive entities with referential equality', () => { @@ -903,7 +888,7 @@ describe(`${Entity.name} denormalization`, () => { }; const memo = new SimpleMemoCache(); - const denormalizedReport = memo.denormalize('123', Report, entities); + const denormalizedReport = memo.denormalize(Report, '123', entities); expect(denormalizedReport).not.toEqual(expect.any(Symbol)); if (typeof denormalizedReport === 'symbol') return; @@ -916,7 +901,7 @@ describe(`${Entity.name} denormalization`, () => { denormalizedReport.draftedBy, ); - const denormalizedReport2 = memo.denormalize('123', Report, entities); + const denormalizedReport2 = memo.denormalize(Report, '123', entities); expect(denormalizedReport2).toStrictEqual(denormalizedReport); expect(denormalizedReport2).toBe(denormalizedReport); @@ -962,7 +947,7 @@ describe(`${Entity.name} denormalization`, () => { comment: Comment, }); - const denormalizedReport = memo.denormalize(input, sch, entities); + const denormalizedReport = memo.denormalize(sch, input, entities); expect(denormalizedReport).not.toEqual(expect.any(Symbol)); if (typeof denormalizedReport === 'symbol') return; @@ -975,7 +960,7 @@ describe(`${Entity.name} denormalization`, () => { denormalizedReport.comment.author, ); - const denormalizedReport2 = memo.denormalize(input, sch, entities); + const denormalizedReport2 = memo.denormalize(sch, input, entities); expect(denormalizedReport2).not.toEqual(expect.any(Symbol)); if (typeof denormalizedReport2 === 'symbol') return; @@ -994,7 +979,7 @@ describe(`${Entity.name} denormalization`, () => { }, }; - const denormalizedReport3 = memo.denormalize(input, sch, nextEntities); + const denormalizedReport3 = memo.denormalize(sch, input, nextEntities); expect(denormalizedReport3).not.toEqual(expect.any(Symbol)); if (typeof denormalizedReport3 === 'symbol') return; @@ -1006,7 +991,7 @@ describe(`${Entity.name} denormalization`, () => { describe('optional entities', () => { it('should be marked as found even when optional is not there', () => { - const denormalized = denormalize('abc', WithOptional, { + const denormalized = denormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: { id: 'abc', @@ -1031,7 +1016,7 @@ describe(`${Entity.name} denormalization`, () => { }); it('should be marked as found when nested entity is missing', () => { - const denormalized = denormalize('abc', WithOptional, { + const denormalized = denormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: WithOptional.fromJS({ id: 'abc', @@ -1057,7 +1042,7 @@ describe(`${Entity.name} denormalization`, () => { }); it('should be marked as deleted when required entity is deleted symbol', () => { - const denormalized = denormalize('abc', WithOptional, { + const denormalized = denormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: { id: 'abc', @@ -1074,7 +1059,7 @@ describe(`${Entity.name} denormalization`, () => { }); it('should be non-required deleted members should not result in deleted indicator', () => { - const denormalized = denormalize('abc', WithOptional, { + const denormalized = denormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: WithOptional.fromJS({ id: 'abc', @@ -1103,8 +1088,8 @@ describe(`${Entity.name} denormalization`, () => { it('should be deleted when both are true in different parts of schema', () => { const denormalized = denormalize( - { data: 'abc' }, new schema.Object({ data: WithOptional, other: ArticleEntity }), + { data: 'abc' }, { [WithOptional.key]: { abc: WithOptional.fromJS({ diff --git a/packages/endpoint/src/schemas/__tests__/EntitySchema.test.ts b/packages/endpoint/src/schemas/__tests__/EntitySchema.test.ts index 1073a27c6c73..b7e5bede00fd 100644 --- a/packages/endpoint/src/schemas/__tests__/EntitySchema.test.ts +++ b/packages/endpoint/src/schemas/__tests__/EntitySchema.test.ts @@ -306,7 +306,7 @@ describe(`${schema.Entity.name} normalization`, () => { test('normalizes an entity', () => { class MyEntity extends schema.Entity(IDData) {} - expect(normalize({ id: '1' }, MyEntity)).toMatchSnapshot(); + expect(normalize(MyEntity, { id: '1' })).toMatchSnapshot(); }); test('normalizes already processed entities', () => { @@ -322,12 +322,12 @@ describe(`${schema.Entity.name} normalization`, () => { }, }) {} - expect(normalize(['1'], new schema.Array(MyEntity))).toMatchSnapshot(); + expect(normalize(new schema.Array(MyEntity), ['1'])).toMatchSnapshot(); expect( - normalize({ data: '1' }, new schema.Object({ data: MyEntity })), + normalize(new schema.Object({ data: MyEntity }), { data: '1' }), ).toMatchSnapshot(); expect( - normalize({ title: 'hi', id: '5', nest: '10' }, Nested), + normalize(Nested, { title: 'hi', id: '5', nest: '10' }), ).toMatchSnapshot(); }); @@ -341,17 +341,15 @@ describe(`${schema.Entity.name} normalization`, () => { return false; } } - const { entities, entityMeta } = normalize( - { id: '1', title: 'hi' }, - MyEntity, - ); + const { entities, entityMeta } = normalize(MyEntity, { + id: '1', + title: 'hi', + }); const secondEntities = normalize( - { id: '1', title: 'second' }, MyEntity, - [], - entities, + { id: '1', title: 'second' }, {}, - entityMeta, + { entities, entityMeta, indexes: {} }, ).entities; expect(entities.MyEntity['1']).toBeDefined(); expect(entities.MyEntity['1']).toBe(secondEntities.MyEntity['1']); @@ -365,7 +363,7 @@ describe(`${schema.Entity.name} normalization`, () => { const MyEntity = schema.Entity(MyData, { pk: 'name' }); function normalizeBad() { - normalize({ secondthing: 'hi' }, MyEntity); + normalize(MyEntity, { secondthing: 'hi' }); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); @@ -387,7 +385,7 @@ describe(`${schema.Entity.name} normalization`, () => { }); expect( - normalize({ name: 'bob', secondthing: 'hi' }, MyEntity), + normalize(MyEntity, { name: 'bob', secondthing: 'hi' }), ).toMatchSnapshot(); }); @@ -404,7 +402,7 @@ describe(`${schema.Entity.name} normalization`, () => { }, }) {} - expect(normalize({ name: 'bob', secondthing: 'hi' }, MyEntity)) + expect(normalize(MyEntity, { name: 'bob', secondthing: 'hi' })) .toMatchInlineSnapshot(` { "entities": { @@ -437,7 +435,7 @@ describe(`${schema.Entity.name} normalization`, () => { } const MyEntity = schema.Entity(MyData, { pk: 'name' }); function normalizeBad() { - normalize({}, MyEntity); + normalize(MyEntity, {}); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -449,14 +447,11 @@ describe(`${schema.Entity.name} normalization`, () => { } const MyEntity = schema.Entity(MyData, { pk: 'name' }); function normalizeBad() { - normalize( - [ - { name: 'hi', secondthing: 'ho' }, - { name: 'hi', secondthing: 'ho' }, - { name: 'hi', secondthing: 'ho' }, - ], - MyEntity, - ); + normalize(MyEntity, [ + { name: 'hi', secondthing: 'ho' }, + { name: 'hi', secondthing: 'ho' }, + { name: 'hi', secondthing: 'ho' }, + ]); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -469,12 +464,9 @@ describe(`${schema.Entity.name} normalization`, () => { class MyEntity extends schema.Entity(MyData, { pk: 'e' }) {} expect(() => - normalize( - { - name: 0, - }, - MyEntity, - ), + normalize(MyEntity, { + name: 0, + }), ).toThrowErrorMatchingSnapshot(); }); @@ -486,33 +478,30 @@ describe(`${schema.Entity.name} normalization`, () => { class MyEntity extends schema.Entity(MyData, { pk: 'name' }) {} expect( - normalize( - { - name: 'hi', - a: 'a', - b: 'b', - c: 'c', - d: 'e', - e: 0, - f: 0, - g: 0, - h: 0, - i: 0, - j: 0, - k: 0, - l: 0, - m: 0, - n: 0, - o: 0, - p: 0, - q: 0, - r: 0, - s: 0, - t: 0, - u: 0, - }, - MyEntity, - ), + normalize(MyEntity, { + name: 'hi', + a: 'a', + b: 'b', + c: 'c', + d: 'e', + e: 0, + f: 0, + g: 0, + h: 0, + i: 0, + j: 0, + k: 0, + l: 0, + m: 0, + n: 0, + o: 0, + p: 0, + q: 0, + r: 0, + s: 0, + t: 0, + u: 0, + }), ).toMatchSnapshot(); expect(warnSpy.mock.calls.length).toBe(0); }); @@ -534,7 +523,7 @@ describe(`${schema.Entity.name} normalization`, () => { } class MyEntity extends schema.Entity(MyData, { pk: 'name' }) {} function normalizeBad() { - normalize({ name: 'bob' }, MyEntity); + normalize(MyEntity, { name: 'bob' }); } expect(normalizeBad).not.toThrow(); expect(warnSpy.mock.calls.length).toBe(0); @@ -551,7 +540,7 @@ describe(`${schema.Entity.name} normalization`, () => { } const MyEntity = schema.Entity(MyData); function normalizeBad() { - normalize('hibho', { data: MyEntity }); + normalize({ data: MyEntity }, 'hibho'); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -578,7 +567,7 @@ describe(`${schema.Entity.name} normalization`, () => { } const UserEntity = schema.Entity(User, { pk: 'idStr' }); expect( - normalize({ idStr: '134351', name: 'Kathy' }, UserEntity), + normalize(UserEntity, { idStr: '134351', name: 'Kathy' }), ).toMatchSnapshot(); }); @@ -598,10 +587,10 @@ describe(`${schema.Entity.name} normalization`, () => { ); expect( - normalize( - { '4': { name: 'taco' }, '56': { name: 'burrito' } }, - inputSchema, - ), + normalize(inputSchema, { + '4': { name: 'taco' }, + '56': { name: 'burrito' }, + }), ).toMatchSnapshot(); }); @@ -619,10 +608,10 @@ describe(`${schema.Entity.name} normalization`, () => { const inputSchema = new schema.Object({ user: UserEntity }); expect( - normalize( - { name: 'tacos', user: { id: '4', name: 'Jimmy' } }, - inputSchema, - ), + normalize(inputSchema, { + name: 'tacos', + user: { id: '4', name: 'Jimmy' }, + }), ).toMatchSnapshot(); }); }); @@ -631,11 +620,11 @@ describe(`${schema.Entity.name} normalization`, () => { test('defaults to plain merging', () => { expect( normalize( + [Tacos], [ { id: '1', name: 'foo' }, { id: '1', name: 'bar', alias: 'bar' }, ], - [Tacos], ), ).toMatchSnapshot(); }); @@ -652,11 +641,11 @@ describe(`${schema.Entity.name} normalization`, () => { expect( normalize( + [MergeTaco], [ { id: '1', name: 'foo' }, { id: '1', name: 'bar', alias: 'bar' }, ], - [MergeTaco], ), ).toMatchSnapshot(); }); @@ -673,11 +662,11 @@ describe(`${schema.Entity.name} normalization`, () => { }; } } - const { entities, result } = normalize( - { id: '1', name: 'foo' }, - ProcessTaco, - ); - const final = denormalize(result, ProcessTaco, entities); + const { entities, result } = normalize(ProcessTaco, { + id: '1', + name: 'foo', + }); + const final = denormalize(ProcessTaco, result, entities); expect(final).not.toEqual(expect.any(Symbol)); if (typeof final === 'symbol') return; expect(final?.slug).toEqual('thing-1'); @@ -709,15 +698,12 @@ describe(`${schema.Entity.name} normalization`, () => { schema: { child: ChildEntity }, }); - const { entities, result } = normalize( - { - id: '1', - content: 'parent', - child: { id: '4', content: 'child' }, - }, - ParentEntity, - ); - const final = denormalize(result, ParentEntity, entities); + const { entities, result } = normalize(ParentEntity, { + id: '1', + content: 'parent', + child: { id: '4', content: 'child' }, + }); + const final = denormalize(ParentEntity, result, entities); expect(final).not.toEqual(expect.any(Symbol)); if (typeof final === 'symbol') return; expect(final?.child?.parentId).toEqual('1'); @@ -763,11 +749,10 @@ describe(`${schema.Entity.name} normalization`, () => { it.each([EntriesEntity, EntriesEntity2])( 'is run before and passed to the schema denormalization %s', EntriesEntity => { - const { entities, result } = normalize( - { message: { id: '123', data: { attachment: { id: '456' } } } }, - EntriesEntity, - ); - const final = denormalize(result, EntriesEntity, entities); + const { entities, result } = normalize(EntriesEntity, { + message: { id: '123', data: { attachment: { id: '456' } } }, + }); + const final = denormalize(EntriesEntity, result, entities); expect(final).not.toEqual(expect.any(Symbol)); if (typeof final === 'symbol') return; expect(final?.type).toEqual('message'); @@ -785,8 +770,8 @@ describe(`${schema.Entity.name} denormalization`, () => { '1': { id: '1', name: 'foo' }, }, }; - expect(denormalize('1', Tacos, entities)).toMatchSnapshot(); - expect(denormalize('1', Tacos, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Tacos, '1', entities)).toMatchSnapshot(); + expect(denormalize(Tacos, '1', fromJS(entities))).toMatchSnapshot(); }); class Food extends schema.Entity( @@ -811,13 +796,13 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - const de1 = denormalize('1', Menu, entities); + const de1 = denormalize(Menu, '1', entities); expect(de1).toMatchSnapshot(); - expect(denormalize('1', Menu, fromJS(entities))).toEqual(de1); + expect(denormalize(Menu, '1', fromJS(entities))).toEqual(de1); - const de2 = denormalize('2', Menu, entities); + const de2 = denormalize(Menu, '2', entities); expect(de2).toMatchSnapshot(); - expect(denormalize('2', Menu, fromJS(entities))).toEqual(de2); + expect(denormalize(Menu, '2', fromJS(entities))).toEqual(de2); }); test('denormalizes deep entities while maintaining referential equality', () => { @@ -832,8 +817,8 @@ describe(`${schema.Entity.name} denormalization`, () => { }; const memo = new SimpleMemoCache(); - const first = memo.denormalize('1', Menu, entities); - const second = memo.denormalize('1', Menu, entities); + const first = memo.denormalize(Menu, '1', entities); + const second = memo.denormalize(Menu, '1', entities); expect(first).not.toEqual(expect.any(Symbol)); if (typeof first === 'symbol') return; expect(second).not.toEqual(expect.any(Symbol)); @@ -853,8 +838,8 @@ describe(`${schema.Entity.name} denormalization`, () => { '1': { id: '1' }, }, }; - expect(denormalize('1', MyTacos, entities)).toEqual(expect.any(Symbol)); - expect(denormalize('1', MyTacos, fromJS(entities))).toEqual( + expect(denormalize(MyTacos, '1', entities)).toEqual(expect.any(Symbol)); + expect(denormalize(MyTacos, '1', fromJS(entities))).toEqual( expect.any(Symbol), ); }); @@ -869,11 +854,11 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - expect(denormalize('1', Menu, entities)).toMatchSnapshot(); - expect(denormalize('1', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '1', fromJS(entities))).toMatchSnapshot(); - expect(denormalize('2', Menu, entities)).toMatchSnapshot(); - expect(denormalize('2', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '2', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '2', fromJS(entities))).toMatchSnapshot(); }); it('should handle optional schema entries Entity', () => { @@ -888,7 +873,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }) {} expect( - denormalize('bob', MyEntity, { + denormalize(MyEntity, 'bob', { MyEntity: { bob: { name: 'bob', secondthing: 'hi' } }, }), ).toMatchInlineSnapshot(` @@ -912,7 +897,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }) {} expect( - denormalize('bob', MyEntity, { + denormalize(MyEntity, 'bob', { MyEntity: { bob: { name: 'bob', secondthing: 'hi', blarb: null } }, }), ).toMatchInlineSnapshot(` @@ -936,11 +921,11 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - expect(denormalize('1', Menu, entities)).toMatchSnapshot(); - expect(denormalize('1', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '1', fromJS(entities))).toMatchSnapshot(); - expect(denormalize('2', Menu, entities)).toMatchSnapshot(); - expect(denormalize('2', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '2', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '2', fromJS(entities))).toMatchSnapshot(); }); test('denormalizes deep entities with records', () => { @@ -960,11 +945,11 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - expect(denormalize('1', Menu, entities)).toMatchSnapshot(); - expect(denormalize('1', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '1', fromJS(entities))).toMatchSnapshot(); - expect(denormalize('2', Menu, entities)).toMatchSnapshot(); - expect(denormalize('2', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '2', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '2', fromJS(entities))).toMatchSnapshot(); }); test('can denormalize already partially denormalized data', () => { @@ -978,8 +963,8 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - expect(denormalize('1', Menu, entities)).toMatchSnapshot(); - expect(denormalize('1', Menu, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Menu, '1', entities)).toMatchSnapshot(); + expect(denormalize(Menu, '1', fromJS(entities))).toMatchSnapshot(); }); describe('nesting', () => { @@ -1032,11 +1017,11 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - expect(denormalize('123', Report, entities)).toMatchSnapshot(); - expect(denormalize('123', Report, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(Report, '123', entities)).toMatchSnapshot(); + expect(denormalize(Report, '123', fromJS(entities))).toMatchSnapshot(); - expect(denormalize('456', User, entities)).toMatchSnapshot(); - expect(denormalize('456', User, fromJS(entities))).toMatchSnapshot(); + expect(denormalize(User, '456', entities)).toMatchSnapshot(); + expect(denormalize(User, '456', fromJS(entities))).toMatchSnapshot(); }); test('denormalizes recursive entities with referential equality', () => { @@ -1071,7 +1056,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }; const memo = new SimpleMemoCache(); - const denormalizedReport = memo.denormalize('123', Report, entities); + const denormalizedReport = memo.denormalize(Report, '123', entities); expect(denormalizedReport).not.toEqual(expect.any(Symbol)); if (typeof denormalizedReport === 'symbol') return; @@ -1084,7 +1069,7 @@ describe(`${schema.Entity.name} denormalization`, () => { denormalizedReport.draftedBy, ); - const denormalizedReport2 = memo.denormalize('123', Report, entities); + const denormalizedReport2 = memo.denormalize(Report, '123', entities); expect(denormalizedReport2).not.toEqual(expect.any(Symbol)); if (typeof denormalizedReport2 === 'symbol') return; @@ -1132,7 +1117,7 @@ describe(`${schema.Entity.name} denormalization`, () => { comment: Comment, }); - const denormalizedReport = memo.denormalize(input, sch, entities); + const denormalizedReport = memo.denormalize(sch, input, entities); expect(denormalizedReport).not.toEqual(expect.any(Symbol)); if (typeof denormalizedReport === 'symbol') return; @@ -1145,7 +1130,7 @@ describe(`${schema.Entity.name} denormalization`, () => { denormalizedReport.comment.author, ); - const denormalizedReport2 = memo.denormalize(input, sch, entities); + const denormalizedReport2 = memo.denormalize(sch, input, entities); expect(denormalizedReport2).not.toEqual(expect.any(Symbol)); if (typeof denormalizedReport2 === 'symbol') return; @@ -1164,7 +1149,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }, }; - const denormalizedReport3 = memo.denormalize(input, sch, nextEntities); + const denormalizedReport3 = memo.denormalize(sch, input, nextEntities); expect(denormalizedReport3).not.toEqual(expect.any(Symbol)); if (typeof denormalizedReport3 === 'symbol') return; @@ -1176,7 +1161,7 @@ describe(`${schema.Entity.name} denormalization`, () => { describe('optional entities', () => { it('should be marked as found even when optional is not there', () => { - const denormalized = denormalize('abc', WithOptional, { + const denormalized = denormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: { id: 'abc', @@ -1201,7 +1186,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }); it('should be marked as found when nested entity is missing', () => { - const denormalized = denormalize('abc', WithOptional, { + const denormalized = denormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: WithOptional.fromJS({ id: 'abc', @@ -1229,7 +1214,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }); it('should be marked as deleted when required entity is deleted symbol', () => { - const denormalized = denormalize('abc', WithOptional, { + const denormalized = denormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: { id: 'abc', @@ -1246,7 +1231,7 @@ describe(`${schema.Entity.name} denormalization`, () => { }); it('should be non-required deleted members should not result in deleted indicator', () => { - const denormalized = denormalize('abc', WithOptional, { + const denormalized = denormalize(WithOptional, 'abc', { [WithOptional.key]: { abc: WithOptional.fromJS({ id: 'abc', @@ -1276,8 +1261,8 @@ 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( - { data: 'abc' }, new schema.Object({ data: WithOptional, other: ArticleEntity }), + { data: 'abc' }, { [WithOptional.key]: { abc: WithOptional.fromJS({ diff --git a/packages/endpoint/src/schemas/__tests__/Invalidate.test.ts b/packages/endpoint/src/schemas/__tests__/Invalidate.test.ts index 8b1bf4cfbc63..ea6c24f1c27a 100644 --- a/packages/endpoint/src/schemas/__tests__/Invalidate.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Invalidate.test.ts @@ -29,15 +29,15 @@ describe(`${schema.Invalidate.name} normalization`, () => { class User extends IDEntity {} expect( - normalize({ id: '1', type: 'users' }, new schema.Invalidate(User)), + normalize(new schema.Invalidate(User), { id: '1', type: 'users' }), ).toMatchSnapshot(); }); test('normalizes already processed entities', () => { class MyEntity extends IDEntity {} - expect(normalize('1', new schema.Invalidate(MyEntity))).toMatchSnapshot(); + expect(normalize(new schema.Invalidate(MyEntity), '1')).toMatchSnapshot(); expect( - normalize(['1', '2'], new schema.Array(new schema.Invalidate(MyEntity))), + normalize(new schema.Array(new schema.Invalidate(MyEntity)), ['1', '2']), ).toMatchSnapshot(); }); @@ -63,7 +63,7 @@ describe(`${schema.Invalidate.name} normalization`, () => { } } function normalizeBad() { - normalize({ secondthing: 'hi' }, new schema.Invalidate(MyEntity)); + normalize(new schema.Invalidate(MyEntity), { secondthing: 'hi' }); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -77,7 +77,7 @@ describe(`${schema.Invalidate.name} normalization`, () => { } } function normalizeBad() { - normalize({ secondthing: 'hi' }, new schema.Invalidate(MyEntity)); + normalize(new schema.Invalidate(MyEntity), { secondthing: 'hi' }); } expect(normalizeBad).toThrowErrorMatchingSnapshot(); }); @@ -96,8 +96,8 @@ describe(`${schema.Invalidate.name} denormalization`, () => { test('denormalizes an object in the same manner as the Entity', () => { const user = new SimpleMemoCache().denormalize( - '1', new schema.Invalidate(User), + '1', entities, ); expect(user).not.toEqual(expect.any(Symbol)); @@ -129,8 +129,8 @@ describe(`${schema.Invalidate.name} denormalization`, () => { (_, createArray, createObject, denormalize) => { test('denormalizes deleted entities as symbol', () => { const user = denormalize( - '1', new schema.Invalidate(User), + '1', createInput({ User: { '1': INVALID }, }), @@ -139,8 +139,8 @@ describe(`${schema.Invalidate.name} denormalization`, () => { expect( denormalize( - createInput({ data: '1' }), createObject({ data: new schema.Invalidate(User) }), + createInput({ data: '1' }), createInput({ User: { '1': INVALID }, }), @@ -151,8 +151,8 @@ describe(`${schema.Invalidate.name} denormalization`, () => { test('denormalize removes deleted entries in array', () => { expect( denormalize( - createInput([{ data: '1' }]), createArray(createObject({ data: new schema.Invalidate(User) })), + createInput([{ data: '1' }]), createInput({ User: { '1': INVALID }, }), @@ -160,8 +160,8 @@ describe(`${schema.Invalidate.name} denormalization`, () => { ).toMatchSnapshot(); expect( denormalize( - createInput([{ data: '1' }]), createArray(createObject({ data: User })), + createInput([{ data: '1' }]), createInput({ User: { '1': INVALID }, }), @@ -172,24 +172,24 @@ describe(`${schema.Invalidate.name} denormalization`, () => { test('denormalize sets undefined entities that are not present', () => { expect( denormalize( - createInput([{ data: '1' }]), createArray(createObject({ data: new schema.Invalidate(User) })), + createInput([{ data: '1' }]), createInput([{}]), ), ).toMatchSnapshot(); expect( denormalize( - createInput([{ data: '1' }]), createArray(createObject({ data: User })), + createInput([{ data: '1' }]), createInput([{}]), ), ).toMatchSnapshot(); expect( denormalize( - createInput({ data: '1' }), createObject({ data: User }), + createInput({ data: '1' }), createInput({}), ), ).toMatchSnapshot(); diff --git a/packages/endpoint/src/schemas/__tests__/Object.test.js b/packages/endpoint/src/schemas/__tests__/Object.test.js index 8f9b499df532..60a3c5405db1 100644 --- a/packages/endpoint/src/schemas/__tests__/Object.test.js +++ b/packages/endpoint/src/schemas/__tests__/Object.test.js @@ -24,12 +24,12 @@ describe(`${schema.Object.name} normalization`, () => { const object = new schema.Object({ user: User, }); - expect(normalize({ user: { id: '1' } }, object)).toMatchSnapshot(); + expect(normalize(object, { user: { id: '1' } })).toMatchSnapshot(); }); test(`normalizes plain objects as shorthand for ${schema.Object.name}`, () => { class User extends IDEntity {} - expect(normalize({ user: { id: '1' } }, { user: User })).toMatchSnapshot(); + expect(normalize({ user: User }, { user: { id: '1' } })).toMatchSnapshot(); }); test('filters out undefined and null values', () => { @@ -42,7 +42,7 @@ describe(`${schema.Object.name} normalization`, () => { const oldenv = process.env.NODE_ENV; process.env.NODE_ENV = 'production'; expect( - normalize({ foo: undefined, bar: { id: '1' } }, users), + normalize(users, { foo: undefined, bar: { id: '1' } }), ).toMatchSnapshot(); process.env.NODE_ENV = oldenv; }); @@ -54,14 +54,11 @@ describe(`${schema.Object.name} normalization`, () => { nextPage: '', createdAt: Temporal.Instant.from, }); - const normalized = normalize( - { - user: { id: '5' }, - nextPage: 'blob', - createdAt: '2020-06-07T02:00:15.000Z', - }, - WithOptional, - ); + const normalized = normalize(WithOptional, { + user: { id: '5' }, + nextPage: 'blob', + createdAt: '2020-06-07T02:00:15.000Z', + }); expect(normalized.result.createdAt).toBe(normalized.result.createdAt); expect(typeof normalized.result.createdAt).toBe('string'); expect(normalized).toMatchSnapshot(); @@ -74,13 +71,10 @@ describe(`${schema.Object.name} normalization`, () => { nextPage: '', createdAt: Temporal.Instant.from, }); - const normalized = normalize( - { - user: { id: '5' }, - nextPage: 'blob', - }, - WithOptional, - ); + const normalized = normalize(WithOptional, { + user: { id: '5' }, + nextPage: 'blob', + }); expect(normalized.result.createdAt).toBeUndefined(); expect(normalized).toMatchSnapshot(); }); @@ -97,12 +91,12 @@ describe(`${schema.Object.name} denormalization`, () => { 1: { id: '1', name: 'Nacho' }, }, }; - expect(denormalize({ user: '1' }, object, entities)).toMatchSnapshot(); + expect(denormalize(object, { user: '1' }, entities)).toMatchSnapshot(); expect( - denormalize({ user: '1' }, object, fromJS(entities)), + denormalize(object, { user: '1' }, fromJS(entities)), ).toMatchSnapshot(); expect( - denormalize(fromJS({ user: '1' }), object, fromJS(entities)), + denormalize(object, fromJS({ user: '1' }), fromJS(entities)), ).toMatchSnapshot(); }); @@ -117,12 +111,12 @@ describe(`${schema.Object.name} denormalization`, () => { 1: { id: '1', name: 'Nacho' }, }, }; - expect(denormalize({ user: '1' }, object, entities)).toMatchSnapshot(); + expect(denormalize(object, { user: '1' }, entities)).toMatchSnapshot(); expect( - denormalize({ user: '1' }, object, fromJS(entities)), + denormalize(object, { user: '1' }, fromJS(entities)), ).toMatchSnapshot(); expect( - denormalize(fromJS({ user: '1' }), object, fromJS(entities)), + denormalize(object, fromJS({ user: '1' }), fromJS(entities)), ).toMatchSnapshot(); }); @@ -138,11 +132,11 @@ describe(`${schema.Object.name} denormalization`, () => { 1: { id: '1', name: 'Nacho' }, }, }; - let value = denormalize({ item: null }, object, entities); + let value = denormalize(object, { item: null }, entities); expect(value).toMatchSnapshot(); - value = denormalize({ item: null }, object, fromJS(entities)); + value = denormalize(object, { item: null }, fromJS(entities)); expect(value).toMatchSnapshot(); - value = denormalize(fromJS({ item: null }), object, fromJS(entities)); + value = denormalize(object, fromJS({ item: null }), fromJS(entities)); expect(value).toMatchSnapshot(); }); @@ -155,44 +149,44 @@ describe(`${schema.Object.name} denormalization`, () => { }; expect( denormalize( - { user: '1' }, new schema.Object({ user: User, tacos: {} }), + { user: '1' }, entities, ), ).toMatchSnapshot(); expect( denormalize( - { user: '1' }, new schema.Object({ user: User, tacos: {} }), + { user: '1' }, fromJS(entities), ), ).toMatchSnapshot(); expect( denormalize( - fromJS({ user: '1' }), new schema.Object({ user: User, tacos: {} }), + fromJS({ user: '1' }), fromJS(entities), ), ).toMatchSnapshot(); expect( denormalize( - { user: '1', tacos: {} }, new schema.Object({ user: User, tacos: {} }), + { user: '1', tacos: {} }, entities, ), ).toMatchSnapshot(); expect( denormalize( - { user: '1', tacos: {} }, new schema.Object({ user: User, tacos: {} }), + { user: '1', tacos: {} }, fromJS(entities), ), ).toMatchSnapshot(); expect( denormalize( - fromJS({ user: '1', tacos: {} }), new schema.Object({ user: User, tacos: {} }), + fromJS({ user: '1', tacos: {} }), fromJS(entities), ), ).toMatchSnapshot(); @@ -208,12 +202,12 @@ describe(`${schema.Object.name} denormalization`, () => { 0: { id: '0', name: 'Chancho' }, }, }; - expect(denormalize({ user: '0' }, object, entities)).toMatchSnapshot(); + expect(denormalize(object, { user: '0' }, entities)).toMatchSnapshot(); expect( - denormalize({ user: '0' }, object, fromJS(entities)), + denormalize(object, { user: '0' }, fromJS(entities)), ).toMatchSnapshot(); expect( - denormalize(fromJS({ user: '0' }), object, fromJS(entities)), + denormalize(object, fromJS({ user: '0' }), fromJS(entities)), ).toMatchSnapshot(); }); }); diff --git a/packages/endpoint/src/schemas/__tests__/Query.test.ts b/packages/endpoint/src/schemas/__tests__/Query.test.ts index 40a33fe005e8..7eae1389a043 100644 --- a/packages/endpoint/src/schemas/__tests__/Query.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Query.test.ts @@ -72,7 +72,7 @@ describe.each([ }, }; const users: DenormalizeNullable | symbol = - new MemoCache().query('', sortedUsers, [], createInput(entities), {}); + new MemoCache().query(sortedUsers, [], createInput(entities), {}); expect(users).not.toEqual(expect.any(Symbol)); if (typeof users === 'symbol') return; expect(users && users[0].name).toBe('Zeta'); @@ -95,7 +95,6 @@ describe.each([ }; expect( new MemoCache().query( - '', sortedUsers, [{ asc: true }], createInput(entities), @@ -111,7 +110,7 @@ describe.each([ 2: { id: '2', name: 'Jake' }, }, }; - const data = new MemoCache().query('', sortedUsers, [], entities, {}); + const data = new MemoCache().query(sortedUsers, [], entities, {}); expect(createOutput(data)).toEqual(undefined); }); @@ -147,7 +146,6 @@ describe.each([ const totalCount: | DenormalizeNullable | symbol = new MemoCache().query( - '', userCountByAdmin, [], createInput(entities), @@ -158,7 +156,6 @@ describe.each([ const nonAdminCount: | DenormalizeNullable | symbol = new MemoCache().query( - '', userCountByAdmin, [{ isAdmin: false }], createInput(entities), @@ -168,7 +165,6 @@ describe.each([ const adminCount: | DenormalizeNullable | symbol = new MemoCache().query( - '', userCountByAdmin, [{ isAdmin: true }], createInput(entities), @@ -208,7 +204,7 @@ describe('top level schema', () => { [new schema.Collection([User]).pk({}, undefined, '', [])]: [1, 2, 3, 4], }, }; - const users = new MemoCache().query('', sortedUsers, [], entities, {}); + const users = new MemoCache().query(sortedUsers, [], entities, {}); expect(users).not.toEqual(expect.any(Symbol)); if (typeof users === 'symbol') return; expect(users && users[0].name).toBe('Zeta'); @@ -224,7 +220,7 @@ describe('top level schema', () => { 4: { id: '4', name: 'Alpha' }, }, }; - const users = new MemoCache().query('', sortedUsers, [], entities, {}); + const users = new MemoCache().query(sortedUsers, [], entities, {}); expect(users).toBeUndefined(); }); @@ -239,7 +235,7 @@ describe('top level schema', () => { return sorted.reverse(); }, ); - const users = new MemoCache().query('', allSortedUsers, [], {}, {}); + const users = new MemoCache().query(allSortedUsers, [], {}, {}); expect(users).toBeUndefined(); }); @@ -254,7 +250,7 @@ describe('top level schema', () => { return sorted.reverse(); }, ); - const users = new MemoCache().query('', allSortedUsers, [], {}, {}); + const users = new MemoCache().query(allSortedUsers, [], {}, {}); expect(users).toBeUndefined(); }); @@ -266,7 +262,7 @@ describe('top level schema', () => { }, }; - const value = new MemoCache().query('', sortedUsers, [], entities, {}); + const value = new MemoCache().query(sortedUsers, [], entities, {}); expect(value).toEqual(undefined); }); diff --git a/packages/endpoint/src/schemas/__tests__/Serializable.test.ts b/packages/endpoint/src/schemas/__tests__/Serializable.test.ts index 36d3f9756b31..a60cf181ad7d 100644 --- a/packages/endpoint/src/schemas/__tests__/Serializable.test.ts +++ b/packages/endpoint/src/schemas/__tests__/Serializable.test.ts @@ -35,18 +35,15 @@ const objectSchema = { describe(`Serializable normalization`, () => { test('normalizes date and custom as passthrough', () => { - const norm = normalize( - { - user: { - id: '1', - name: 'Nacho', - createdAt: '2020-06-07T02:00:15+0000', - }, - anotherItem: { thing: 500 }, - time: '2020-06-07T02:00:15+0000', + const norm = normalize(objectSchema, { + user: { + id: '1', + name: 'Nacho', + createdAt: '2020-06-07T02:00:15+0000', }, - objectSchema, - ); + anotherItem: { thing: 500 }, + time: '2020-06-07T02:00:15+0000', + }); expect(norm.result.time).toBe(norm.result.time); expect(typeof norm.result.time).toBe('string'); expect(norm.entities[User.key]['1'].createdAt).toBe( @@ -69,12 +66,12 @@ describe(`Serializable denormalization`, () => { }, }; const response = new SimpleMemoCache().denormalize( + objectSchema, { user: '1', anotherItem: Other({ thing: 500 }), time: '2020-06-07T02:00:15+0000', }, - objectSchema, entities, ); expect(response).not.toEqual(expect.any(Symbol)); @@ -97,12 +94,12 @@ describe(`Serializable denormalization`, () => { }, }; const response = new SimpleMemoCache().denormalize( + objectSchema, { user: '1', anotherItem: { thing: 500 }, time: '2020-06-07T02:00:15Z', }, - objectSchema, entities, ); expect(response).not.toEqual(expect.any(Symbol)); diff --git a/packages/endpoint/src/schemas/__tests__/Union.test.js b/packages/endpoint/src/schemas/__tests__/Union.test.js index 2ccc76b89ad1..e39c203e8078 100644 --- a/packages/endpoint/src/schemas/__tests__/Union.test.js +++ b/packages/endpoint/src/schemas/__tests__/Union.test.js @@ -41,8 +41,8 @@ describe(`${schema.Union.name} normalization`, () => { 'type', ); - expect(normalize({ id: '1', type: 'users' }, union)).toMatchSnapshot(); - expect(normalize({ id: '2', type: 'groups' }, union)).toMatchSnapshot(); + expect(normalize(union, { id: '1', type: 'users' })).toMatchSnapshot(); + expect(normalize(union, { id: '2', type: 'groups' })).toMatchSnapshot(); }); test('normalizes an array of multiple entities using a function to infer the schemaAttribute', () => { @@ -62,11 +62,11 @@ describe(`${schema.Union.name} normalization`, () => { }, ); - expect(normalize({ id: '1', username: 'Janey' }, union)).toMatchSnapshot(); + expect(normalize(union, { id: '1', username: 'Janey' })).toMatchSnapshot(); expect( - normalize({ id: '2', groupname: 'People' }, union), + normalize(union, { id: '2', groupname: 'People' }), ).toMatchSnapshot(); - expect(normalize({ id: '3', notdefined: 'yep' }, union)).toMatchSnapshot(); + expect(normalize(union, { id: '3', notdefined: 'yep' })).toMatchSnapshot(); expect(warnSpy.mock.calls).toMatchSnapshot(); }); }); @@ -246,12 +246,12 @@ describe('complex case', () => { }, ], }; - const denorm = normalize(response, waterfallSchema); + const denorm = normalize(waterfallSchema, response); expect(denorm).toMatchSnapshot(); expect( new SimpleMemoCache().denormalize( - denorm.result, waterfallSchema, + denorm.result, denorm.entities, ), ).toMatchSnapshot(); @@ -286,16 +286,16 @@ describe.each([ expect( denormalize( - createInput({ id: '1', schema: 'users' }), union, + createInput({ id: '1', schema: 'users' }), createInput(entities), ), ).toMatchSnapshot(); expect( denormalize( - createInput({ id: '2', schema: 'groups' }), union, + createInput({ id: '2', schema: 'groups' }), createInput(entities), ), ).toMatchSnapshot(); @@ -314,16 +314,16 @@ describe.each([ expect( denormalize( - createInput({ id: '1', schema: 'users' }), union, + createInput({ id: '1', schema: 'users' }), createInput(entities), ), ).toMatchSnapshot(); expect( denormalize( - createInput({ id: '2', schema: 'groups' }), union, + createInput({ id: '2', schema: 'groups' }), createInput(entities), ), ).toMatchSnapshot(); @@ -345,7 +345,7 @@ describe.each([ ); expect( - denormalize(createInput({ id: '1' }), union, createInput(entities)), + denormalize(union, createInput({ id: '1' }), createInput(entities)), ).toMatchSnapshot(); expect(warnSpy.mock.calls).toMatchSnapshot(); }); @@ -366,7 +366,7 @@ describe.each([ ); expect( - denormalize('1', union, createInput(entities)), + denormalize(union, '1', createInput(entities)), ).toMatchSnapshot(); expect(warnSpy.mock.calls).toMatchSnapshot(); }); @@ -386,7 +386,7 @@ describe.each([ }, ); - expect(denormalize(null, union, createInput(entities))).toBeNull(); + expect(denormalize(union, null, createInput(entities))).toBeNull(); }); test('returns the original value when undefined is given', () => { @@ -405,7 +405,7 @@ describe.each([ ); expect( - denormalize(undefined, union, createInput(entities)), + denormalize(union, undefined, createInput(entities)), ).toBeUndefined(); }); }, diff --git a/packages/endpoint/src/schemas/__tests__/Values.test.js b/packages/endpoint/src/schemas/__tests__/Values.test.js index 53cb3ec1b933..89ed33eaa224 100644 --- a/packages/endpoint/src/schemas/__tests__/Values.test.js +++ b/packages/endpoint/src/schemas/__tests__/Values.test.js @@ -42,19 +42,16 @@ describe(`${schema.Values.name} normalization`, () => { const valuesSchema = new schema.Values(MyEntity); expect( - normalize( - { - first: { - id: '1', - name: 'first thing', - }, - second: { - id: '2', - name: 'second thing', - }, + normalize(valuesSchema, { + first: { + id: '1', + name: 'first thing', }, - valuesSchema, - ), + second: { + id: '2', + name: 'second thing', + }, + }), ).toMatchSnapshot(); }); @@ -68,13 +65,10 @@ describe(`${schema.Values.name} normalization`, () => { ); expect( - normalize( - { - fido: { id: '1', type: 'dogs' }, - fluffy: { id: '1', type: 'cats' }, - }, - valuesSchema, - ), + normalize(valuesSchema, { + fido: { id: '1', type: 'dogs' }, + fluffy: { id: '1', type: 'cats' }, + }), ).toMatchSnapshot(); }); @@ -88,14 +82,11 @@ describe(`${schema.Values.name} normalization`, () => { ); expect( - normalize( - { - fido: { id: '1', type: 'dog' }, - fluffy: { id: '1', type: 'cat' }, - jim: { id: '2', type: 'lizard' }, - }, - valuesSchema, - ), + normalize(valuesSchema, { + fido: { id: '1', type: 'dog' }, + fluffy: { id: '1', type: 'cat' }, + jim: { id: '2', type: 'lizard' }, + }), ).toMatchSnapshot(); expect(warnSpy.mock.calls).toMatchSnapshot(); }); @@ -110,14 +101,11 @@ describe(`${schema.Values.name} normalization`, () => { ); expect( - normalize( - { - fido: undefined, - milo: null, - fluffy: { id: '1', type: 'cats' }, - }, - valuesSchema, - ), + normalize(valuesSchema, { + fido: undefined, + milo: null, + fluffy: { id: '1', type: 'cats' }, + }), ).toMatchSnapshot(); }); @@ -202,7 +190,7 @@ describe(`${schema.Values.name} normalization`, () => { }, }; expect( - normalize(response, { data: { estimates: new schema.Values(Estimate) } }), + normalize({ data: { estimates: new schema.Values(Estimate) } }, response), ).toMatchSnapshot(); }); }); @@ -235,11 +223,11 @@ describe.each([ expect( denormalize( + valuesSchema, { first: '1', second: '2', }, - valuesSchema, createInput(entities), ), ).toMatchSnapshot(); @@ -261,11 +249,11 @@ describe.each([ expect( denormalize( + valuesSchema, { fido: { id: '1', schema: 'dogs' }, fluffy: { id: '1', schema: 'cats' }, }, - valuesSchema, createInput(entities), ), ).toMatchSnapshot(); @@ -287,12 +275,12 @@ describe.each([ expect( denormalize( + valuesSchema, { fido: { id: '1', schema: 'dogs' }, fluffy: { id: '1', schema: 'cats' }, prancy: { id: '5', schema: 'cats' }, }, - valuesSchema, createInput(entities), ), ).toMatchSnapshot(); @@ -314,12 +302,12 @@ describe.each([ expect( denormalize( + valuesSchema, { fido: { id: '1', schema: 'dogs' }, fluffy: { id: '1', schema: 'cats' }, prancy: { id: '5', schema: 'cats' }, }, - valuesSchema, createInput(entities), ), ).toMatchSnapshot(); @@ -408,9 +396,9 @@ describe.each([ const shape = new schema.Object({ data: new schema.Object({ estimates: new schema.Values(Estimate) }), }); - const { result, entities } = normalize(response, shape); + const { result, entities } = normalize(shape, response); expect( - denormalize(result, shape, createInput(entities)), + denormalize(shape, result, createInput(entities)), ).toMatchSnapshot(); }); }, diff --git a/packages/endpoint/src/schemas/__tests__/__snapshots__/Serializable.test.ts.snap b/packages/endpoint/src/schemas/__tests__/__snapshots__/Serializable.test.ts.snap index 3412bca47824..2031c52cf486 100644 --- a/packages/endpoint/src/schemas/__tests__/__snapshots__/Serializable.test.ts.snap +++ b/packages/endpoint/src/schemas/__tests__/__snapshots__/Serializable.test.ts.snap @@ -61,4 +61,4 @@ exports[`Serializable normalization normalizes date and custom as passthrough 1` } `; -exports[`Serializable normalization normalizes date and custom as passthrough 2`] = `"{"entities":{"User":{"1":{"id":"1","name":"Nacho","createdAt":"2020-06-07T02:00:15+0000"}}},"indexes":{},"result":{"user":"1","anotherItem":{"thing":500},"time":"2020-06-07T02:00:15+0000"},"entityMeta":{"User":{"1":{"expiresAt":null,"date":1557831718135,"fetchedAt":0}}}}"`; +exports[`Serializable normalization normalizes date and custom as passthrough 2`] = `"{"result":{"user":"1","anotherItem":{"thing":500},"time":"2020-06-07T02:00:15+0000"},"entities":{"User":{"1":{"id":"1","name":"Nacho","createdAt":"2020-06-07T02:00:15+0000"}}},"indexes":{},"entityMeta":{"User":{"1":{"expiresAt":null,"date":1557831718135,"fetchedAt":0}}}}"`; diff --git a/packages/endpoint/src/schemas/__tests__/denormalize.ts b/packages/endpoint/src/schemas/__tests__/denormalize.ts index 586134052045..e959bb0fa0cb 100644 --- a/packages/endpoint/src/schemas/__tests__/denormalize.ts +++ b/packages/endpoint/src/schemas/__tests__/denormalize.ts @@ -9,12 +9,12 @@ export class SimpleMemoCache { private memo = new MemoCache(); denormalize = ( - input: any, schema: S | undefined, + input: any, entities: any, args: any[] = [], ): Denormalize | DenormalizeNullable | symbol => - this.memo.denormalize(input, schema, entities, args).data as any; + this.memo.denormalize(schema, input, entities, args).data as any; } export default SimpleMemoCache; diff --git a/packages/endpoint/src/schemas/special.ts b/packages/endpoint/src/schemas/special.ts deleted file mode 100644 index f5bb0cf2bd4d..000000000000 --- a/packages/endpoint/src/schemas/special.ts +++ /dev/null @@ -1 +0,0 @@ -export const CREATE = Symbol('create'); diff --git a/packages/endpoint/typescript-tests/array.ts b/packages/endpoint/typescript-tests/array.ts index af0ffa82655b..9eaa16b12a3b 100644 --- a/packages/endpoint/typescript-tests/array.ts +++ b/packages/endpoint/typescript-tests/array.ts @@ -10,13 +10,13 @@ const data = [ class User extends IDEntity {} const userListSchema = new schema.Array(User); -const normalizedData = normalize(data, userListSchema); +const normalizedData = normalize(userListSchema, data); const userListSchemaAlt = [User]; -const normalizedDataAlt = normalize(data, userListSchemaAlt); +const normalizedDataAlt = normalize(userListSchemaAlt, data); const denormalizedData = denormalize( - normalizedData.result, userListSchema, + normalizedData.result, normalizedData.entities, ); diff --git a/packages/endpoint/typescript-tests/array_schema.ts b/packages/endpoint/typescript-tests/array_schema.ts index daa9b3cd460f..17f9a5295147 100644 --- a/packages/endpoint/typescript-tests/array_schema.ts +++ b/packages/endpoint/typescript-tests/array_schema.ts @@ -22,11 +22,11 @@ const myArray = new schema.Array( (input: User | Admin, parent, key) => `${input.type}s`, ); -const normalizedData = normalize(data, myArray); +const normalizedData = normalize(myArray, data); const denormalizedData = denormalize( - normalizedData.result, myArray, + normalizedData.result, normalizedData.entities, ); diff --git a/packages/endpoint/typescript-tests/denormalize.ts b/packages/endpoint/typescript-tests/denormalize.ts index a1ccaa105661..b2c7becbe444 100644 --- a/packages/endpoint/typescript-tests/denormalize.ts +++ b/packages/endpoint/typescript-tests/denormalize.ts @@ -37,8 +37,8 @@ const scheme = { }; const schemeEntity = Magic; -const data = denormalize({}, scheme, {}); -const r = normalize({}, scheme); +const data = denormalize(scheme, {}, {}); +const r = normalize(scheme, {}); type A = DenormalizeNullable; type B = A['thing']['members']; @@ -58,7 +58,7 @@ if (typeof data === 'symbol') { const schemeValues = new schema.Values({ btc: Magic, eth: Magic2 }); const schemeValuesSimple = new schema.Values(Magic); -const valueValues = denormalize({}, schemeValues, {}); +const valueValues = denormalize(schemeValues, {}, {}); if (typeof valueValues !== 'symbol') { Object.keys(schemeValues).forEach(k => { const v = valueValues[k]; @@ -68,4 +68,4 @@ if (typeof valueValues !== 'symbol') { }); } -const valueValuesSimple = denormalize({}, schemeValuesSimple, {}); +const valueValuesSimple = denormalize(schemeValuesSimple, {}, {}); diff --git a/packages/endpoint/typescript-tests/entity.ts b/packages/endpoint/typescript-tests/entity.ts index b80f37f5f570..c10d4bff0388 100644 --- a/packages/endpoint/typescript-tests/entity.ts +++ b/packages/endpoint/typescript-tests/entity.ts @@ -53,10 +53,10 @@ const data = { const user = User; const tweet = Tweet; -const normalizedData = normalize(data, tweet); +const normalizedData = normalize(tweet, data); const denormalizedData = denormalize( - normalizedData.result, tweet, + normalizedData.result, normalizedData.entities, ); diff --git a/packages/endpoint/typescript-tests/github.ts b/packages/endpoint/typescript-tests/github.ts index 71903a5143a1..1f52dcbd4f95 100644 --- a/packages/endpoint/typescript-tests/github.ts +++ b/packages/endpoint/typescript-tests/github.ts @@ -42,5 +42,5 @@ const issueOrPullRequest = new schema.Array( const data = { /* ...*/ }; -const normalizedData = normalize(data, issueOrPullRequest); +const normalizedData = normalize(issueOrPullRequest, data); console.log(normalizedData); diff --git a/packages/endpoint/typescript-tests/object.ts b/packages/endpoint/typescript-tests/object.ts index bdb24e36f439..ca4c87694e4b 100644 --- a/packages/endpoint/typescript-tests/object.ts +++ b/packages/endpoint/typescript-tests/object.ts @@ -11,7 +11,7 @@ class User extends IDEntity {} const responseSchema = new schema.Object({ users: new schema.Array(User), }); -const normalizedData = normalize(data, responseSchema); +const normalizedData = normalize(responseSchema, data); const responseSchemaAlt = { users: new schema.Array(User) }; -const normalizedDataAlt = normalize(data, responseSchemaAlt); +const normalizedDataAlt = normalize(responseSchemaAlt, data); diff --git a/packages/endpoint/typescript-tests/relationships.ts b/packages/endpoint/typescript-tests/relationships.ts index 6a2b3e23a523..02cc93c614ac 100644 --- a/packages/endpoint/typescript-tests/relationships.ts +++ b/packages/endpoint/typescript-tests/relationships.ts @@ -77,5 +77,5 @@ class Post extends IDEntity { const data = { /* ...*/ }; -const normalizedData = normalize(data, Post); +const normalizedData = normalize(Post, data); console.log(normalizedData); diff --git a/packages/endpoint/typescript-tests/union.ts b/packages/endpoint/typescript-tests/union.ts index ec31cc861670..9fc1a97f4a19 100644 --- a/packages/endpoint/typescript-tests/union.ts +++ b/packages/endpoint/typescript-tests/union.ts @@ -29,4 +29,4 @@ const errorUnionSchema = new schema.Union( 'blob', ); -const normalizedData = normalize(data, { owner: unionSchema }); +const normalizedData = normalize({ owner: unionSchema }, data); diff --git a/packages/endpoint/typescript-tests/values.ts b/packages/endpoint/typescript-tests/values.ts index 67ce9b6663c4..f15755cd2d6c 100644 --- a/packages/endpoint/typescript-tests/values.ts +++ b/packages/endpoint/typescript-tests/values.ts @@ -8,4 +8,4 @@ const data = { firstThing: { id: 1 }, secondThing: { id: 2 } }; class Item extends IDEntity {} const valuesSchema = new schema.Values(Item); -const normalizedData = normalize(data, valuesSchema); +const normalizedData = normalize(valuesSchema, data); diff --git a/packages/img/package.json b/packages/img/package.json index 64b8a68f4961..1ba247202a8f 100644 --- a/packages/img/package.json +++ b/packages/img/package.json @@ -73,7 +73,7 @@ "@data-client/endpoint": "^0.13.4" }, "peerDependencies": { - "@data-client/react": "^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0", + "@data-client/react": "^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0", "@types/react": "^16.14.0 || ^17.0.0 || ^18.0.0-0 || ^19.0.0", "react": "^16.14.0 || ^17.0.0 || ^18.0.0-0 || ^19.0.0" }, diff --git a/packages/normalizr/README.md b/packages/normalizr/README.md index 3519cb397ca4..045ce88cdef9 100644 --- a/packages/normalizr/README.md +++ b/packages/normalizr/README.md @@ -127,7 +127,7 @@ class Article extends Entity { import { normalize } from '@data-client/normalizr'; const args = [{ id: '123' }]; -const normalizedData = normalize(originalData, Article, args); +const normalizedData = normalize(Article, originalData, args); ``` Now, `normalizedData` will create a single serializable source of truth for all entities: @@ -174,8 +174,8 @@ Accessing the store can then be done using flux `selectors` by `denormalizing`: import { denormalize } from '@data-client/normalizr'; const denormalizedData = denormalize( - normalizedData.result, Article, + normalizedData.result, normalizedData.entities, args, ); @@ -214,19 +214,19 @@ import { MemoCache } from '@data-client/normalizr'; // you can construct a new memo anytime you want to reset the cache const memo = new MemoCache(); -const { data, paths } = memo.denormalize(input, schema, state.entities, args); +const { data, paths } = memo.denormalize(schema, input, state.entities, args); -const data = memo.query(key, schema, args, state.entities, state.indexes); +const data = memo.query(schema, args, state.entities, state.indexes); -function query(key, schema, args, state) { +function query(schema, args, state, key) { const queryKey = memo.buildQueryKey( - key, schema, args, state.entities, state.indexes, + key, ); - const { data } = this.denormalize(queryKey, schema, state.entities, args); + const { data } = this.denormalize(schema, queryKey, state.entities, args); return typeof data === 'symbol' ? undefined : (data as any); } ``` diff --git a/packages/normalizr/docs/api.md b/packages/normalizr/docs/api.md index a14e7185edb1..b328a73f5f32 100644 --- a/packages/normalizr/docs/api.md +++ b/packages/normalizr/docs/api.md @@ -9,12 +9,12 @@ - [Union](#uniondefinition-schemaattribute) - [Values](#valuesdefinition-schemaattribute) -## `normalize(data, schema)` +## `normalize(schema, data)` Normalizes input data per the schema definition provided. -- `data`: **required** Input JSON (or plain JS object) data that needs normalization. - `schema`: **required** A schema definition +- `data`: **required** Input JSON (or plain JS object) data that needs normalization. ### Usage @@ -23,9 +23,10 @@ import { schema } from '@data-client/endpoint'; import { normalize } from '@data-client/normalizr'; const myData = { users: [{ id: 1 }, { id: 2 }] }; -const user = new schema.Entity('users'); -const mySchema = { users: [user] }; -const normalizedData = normalize(myData, mySchema); +class User { id = 0 } +const userSchema = new schema.Entity(User); +const mySchema = { users: [userSchema] }; +const normalizedData = normalize(mySchema, myData); ``` ### Output @@ -34,7 +35,7 @@ const normalizedData = normalize(myData, mySchema); { result: { users: [ 1, 2 ] }, entities: { - users: { + User: { '1': { id: 1 }, '2': { id: 2 } } @@ -42,16 +43,14 @@ const normalizedData = normalize(myData, mySchema); } ``` -## `denormalize(input, schema, entities): [denormalized, foundAllEntities]` +## `denormalize(schema, input, entities)` Denormalizes an input based on schema and provided entities from a plain object or Immutable data. The reverse of `normalize`. -_Special Note:_ Be careful with denormalization. Prematurely reverting your data to large, nested objects could cause performance impacts in React (and other) applications. - If your schema and data have recursive references, only the first instance of an entity will be given. Subsequent references will be returned as the `id` provided. -- `input`: **required** The normalized result that should be _de-normalized_. Usually the same value that was given in the `result` key of the output of `normalize`. - `schema`: **required** A schema definition that was used to get the value for `input`. +- `input`: **required** The normalized result that should be _de-normalized_. Usually the same value that was given in the `result` key of the output of `normalize`. - `entities`: **required** An object, keyed by entity schema names that may appear in the denormalized output. Also accepts an object with Immutable data. ### Usage @@ -60,17 +59,18 @@ If your schema and data have recursive references, only the first instance of an import { schema } from '@data-client/endpoint'; import { denormalize } from '@data-client/normalizr'; -const user = new schema.Entity('users'); -const mySchema = { users: [user] }; -const entities = { users: { '1': { id: 1 }, '2': { id: 2 } } }; -const denormalizedData = denormalize({ users: [1, 2] }, mySchema, entities); +class User { id = 0 } +const userSchema = new schema.Entity(User); +const mySchema = { users: [userSchema] }; +const entities = { User: { '1': { id: 1 }, '2': { id: 2 } } }; +const denormalizedData = denormalize(mySchema, { users: [1, 2] }, entities); ``` ### Output ```js { - users: [{ id: 1 }, { id: 2 }]; + users: [User { id: 1 }, User { id: 2 }]; } ``` @@ -100,13 +100,13 @@ To describe a simple array of a singular entity type: import { schema } from '@data-client/endpoint'; const data = [{ id: '123', name: 'Jim' }, { id: '456', name: 'Jane' }]; -const userSchema = new schema.Entity('users'); +const userSchema = new schema.Entity(class User {id='';name='';}); const userListSchema = new schema.Array(userSchema); // or use shorthand syntax: const userListSchema = [userSchema]; -const normalizedData = normalize(data, userListSchema); +const normalizedData = normalize(userListSchema, data); ``` #### Output @@ -114,7 +114,7 @@ const normalizedData = normalize(data, userListSchema); ```js { entities: { - users: { + User: { '123': { id: '123', name: 'Jim' }, '456': { id: '456', name: 'Jane' } } @@ -134,8 +134,8 @@ import { schema } from '@data-client/endpoint'; const data = [{ id: 1, type: 'admin' }, { id: 2, type: 'user' }]; -const userSchema = new schema.Entity('users'); -const adminSchema = new schema.Entity('admins'); +const userSchema = new schema.Entity(class User {id='';type='user';}); +const adminSchema = new schema.Entity(class Admin {id='';type='admin';}); const myArray = new schema.Array( { admins: adminSchema, @@ -144,7 +144,7 @@ const myArray = new schema.Array( (input, parent, key) => `${input.type}s` ); -const normalizedData = normalize(data, myArray); +const normalizedData = normalize(myArray, data); ``` #### Output @@ -152,8 +152,8 @@ const normalizedData = normalize(data, myArray); ```js { entities: { - admins: { '1': { id: 1, type: 'admin' } }, - users: { '2': { id: 2, type: 'user' } } + Admin: { '1': { id: 1, type: 'admin' } }, + User: { '2': { id: 2, type: 'user' } } }, result: [ { id: 1, schema: 'admins' }, diff --git a/packages/normalizr/src/__tests__/MemoCache.ts b/packages/normalizr/src/__tests__/MemoCache.ts index 2916e99ea243..15860a37b506 100644 --- a/packages/normalizr/src/__tests__/MemoCache.ts +++ b/packages/normalizr/src/__tests__/MemoCache.ts @@ -42,7 +42,7 @@ describe('MemoCache', () => { 1: Symbol('ENTITY WAS DELETED'), }, }; - expect(new MemoCache().denormalize('1', Tacos, entities).data).toEqual( + expect(new MemoCache().denormalize(Tacos, '1', entities).data).toEqual( expect.any(Symbol), ); }); @@ -57,23 +57,23 @@ describe('MemoCache', () => { const result = ['1', '2']; const schema = [Tacos]; const { data: first, paths: pathsFirst } = memo.denormalize( - result, schema, + result, entities, ); const { data: second, paths: pathsSecond } = memo.denormalize( - result, schema, + result, entities, ); expect(first).toBe(second); expect(pathsFirst).toEqual(pathsSecond); - const { data: third } = memo.denormalize([...result], schema, entities); + const { data: third } = memo.denormalize(schema, [...result], entities); expect(first).not.toBe(third); expect(first).toEqual(third); - const fourth = memo.denormalize(result, schema, { + const fourth = memo.denormalize(schema, result, { Tacos: { ...entities.Tacos, 2: { id: '2', type: 'bar' } }, }).data; expect(first).not.toBe(fourth); @@ -89,10 +89,10 @@ describe('MemoCache', () => { }; const result = { data: ['1', '2'], nextPage: 'initial' }; const schema = { data: [Tacos], nextPage: '' }; - const { data: first, paths } = memo.denormalize(result, schema, entities); + const { data: first, paths } = memo.denormalize(schema, result, entities); const { data: second, paths: pathsSecond } = memo.denormalize( - { ...result, nextPage: 'second' }, schema, + { ...result, nextPage: 'second' }, entities, ); if (typeof first === 'symbol' || typeof second === 'symbol') @@ -106,8 +106,8 @@ describe('MemoCache', () => { } const { data: fourth, paths: fourthPaths } = memo.denormalize( - result, schema, + result, { Tacos: { ...entities.Tacos, 2: { id: '2', type: 'bar' } }, }, @@ -175,11 +175,11 @@ describe('MemoCache', () => { const result = { data: '123' }; const schema = { data: Article }; - const first = memo.denormalize(result, schema, entities).data; - const second = memo.denormalize(result, schema, entities).data; + const first = memo.denormalize(schema, result, entities).data; + const second = memo.denormalize(schema, result, entities).data; expect(first).toBe(second); - const third = memo.denormalize('123', Article, entities).data; - const fourth = memo.denormalize('123', Article, entities).data; + const third = memo.denormalize(Article, '123', entities).data; + const fourth = memo.denormalize(Article, '123', entities).data; expect(third).toBe(fourth); }); @@ -189,13 +189,13 @@ describe('MemoCache', () => { const result1 = { data: '123' }; const result2 = { results: ['123'] }; const first = memo.denormalize( - result1, { data: Article }, + result1, entities, ).data; const second = memo.denormalize( - result2, { results: [Article] }, + result2, entities, ).data; if ( @@ -205,7 +205,7 @@ describe('MemoCache', () => { ) throw new Error(); expect(first.data).toBe(second.results[0]); - const third = memo.denormalize('123', Article, entities).data; + const third = memo.denormalize(Article, '123', entities).data; expect(third).toBe(first.data); // now change @@ -220,14 +220,14 @@ describe('MemoCache', () => { }, }; const firstChanged = memo.denormalize( - result1, { data: Article }, + result1, nextState, ).data; expect(firstChanged).not.toBe(first); const secondChanged = memo.denormalize( - result2, { results: [Article] }, + result2, nextState, ).data; expect(secondChanged).not.toBe(second); @@ -258,8 +258,8 @@ describe('MemoCache', () => { const resultC = '123'; const firstSchema = { data: ArticleSummary }; const secondSchema = { data: Article }; - const first = memo.denormalize(resultA, firstSchema, entities).data; - const second = memo.denormalize(resultB, secondSchema, entities).data; + const first = memo.denormalize(firstSchema, resultA, entities).data; + const second = memo.denormalize(secondSchema, resultB, entities).data; if ( typeof first === 'symbol' || typeof second === 'symbol' || @@ -277,13 +277,13 @@ describe('MemoCache', () => { `); expect(first.data).not.toBe(second.data); const firstWithoutChange = memo.denormalize( - resultA, firstSchema, + resultA, entities, ).data; expect(first).toBe(firstWithoutChange); - const third = memo.denormalize(resultC, Article, entities).data; + const third = memo.denormalize(Article, resultC, entities).data; expect(third).toBe(second.data); // now change @@ -298,14 +298,14 @@ describe('MemoCache', () => { }, }; const firstChanged = memo.denormalize( - resultA, firstSchema, + resultA, nextState, ).data; expect(firstChanged).not.toBe(first); const secondChanged = memo.denormalize( - resultB, secondSchema, + resultB, nextState, ).data; expect(secondChanged).not.toBe(second); @@ -330,26 +330,22 @@ describe('MemoCache', () => { const result = { data: '123' }; const { data: first } = memo.denormalize( - result, { data: Article }, + result, entities, ); - const { data: second } = memo.denormalize( - result, - { data: Article }, - { - ...entities, - Article: { - 123: { - author: '8472', - body: 'This article is great.', - comments: ['comment-123-4738'], - id: '123', - title: 'A Great Article', - }, + const { data: second } = memo.denormalize({ data: Article }, result, { + ...entities, + Article: { + 123: { + author: '8472', + body: 'This article is great.', + comments: ['comment-123-4738'], + id: '123', + title: 'A Great Article', }, }, - ); + }); expect(first).not.toBe(second); if ( typeof first === 'symbol' || @@ -367,25 +363,21 @@ describe('MemoCache', () => { const result = { data: '123' }; const { data: first } = memo.denormalize( - result, { data: Article }, + result, entities, ); - const { data: second } = memo.denormalize( - result, - { data: Article }, - { - ...entities, - Comment: { - 'comment-123-4738': { - comment: 'Updated comment!', - id: 'comment-123-4738', - user: '10293', - }, + const { data: second } = memo.denormalize({ data: Article }, result, { + ...entities, + Comment: { + 'comment-123-4738': { + comment: 'Updated comment!', + id: 'comment-123-4738', + user: '10293', }, }, - ); + }); expect(first).not.toBe(second); if ( @@ -412,20 +404,20 @@ describe('MemoCache', () => { User: {}, }; const { data: first } = memo.denormalize( - result, { data: Article }, + result, emptyEntities, ); const { data: second } = memo.denormalize( - result, { data: Article }, + result, emptyEntities, ); const { data: third } = memo.denormalize( - result, { data: Article }, + result, // now has users entities, ); @@ -485,20 +477,20 @@ describe('MemoCache', () => { User: {}, }; const { data: first } = memo.denormalize( - result, { data: Article }, + result, emptyEntities, ); const { data: second } = memo.denormalize( - result, { data: Article }, + result, emptyEntities, ); const { data: third } = memo.denormalize( - result, { data: Article }, + result, // now has users entities, ); @@ -534,10 +526,10 @@ describe('MemoCache', () => { firstThing: { five: 0, seven: 0 }, secondThing: { cars: '' }, }; - const { data: first } = memo.denormalize(input, schema, {}); + const { data: first } = memo.denormalize(schema, input, {}); expect(first).toEqual(input); // should maintain referential equality - const { data: second } = memo.denormalize(input, schema, {}); + const { data: second } = memo.denormalize(schema, input, {}); expect(second).toBe(first); }); @@ -548,7 +540,7 @@ describe('MemoCache', () => { firstThing: { five: 5, seven: 42 }, secondThing: { cars: 'never' }, }; - const { data } = memo.denormalize(input, null, {}); + const { data } = memo.denormalize(null, input, {}); expect(data).toBe(input); }); @@ -556,7 +548,7 @@ describe('MemoCache', () => { const memo = new MemoCache(); const input = 5; - const { data } = memo.denormalize(input, null, {}); + const { data } = memo.denormalize(null, input, {}); expect(data).toBe(input); }); @@ -567,7 +559,7 @@ describe('MemoCache', () => { firstThing: { five: 5, seven: 42 }, secondThing: { cars: 'never' }, }; - const { data } = memo.denormalize(input, undefined, {}); + const { data } = memo.denormalize(undefined, input, {}); expect(data).toBe(input); }); @@ -593,14 +585,14 @@ describe('MemoCache', () => { test('handles null at top level', () => { const memo = new MemoCache(); - const denorm = memo.denormalize(null, { data: Article }, {}).data; + const denorm = memo.denormalize({ data: Article }, null, {}).data; expect(denorm).toEqual(null); }); test('handles undefined at top level', () => { const memo = new MemoCache(); - const denorm = memo.denormalize(undefined, { data: Article }, {}).data; + const denorm = memo.denormalize({ data: Article }, undefined, {}).data; expect(denorm).toEqual(undefined); }); @@ -610,7 +602,7 @@ describe('MemoCache', () => { const input = { data: { id: '5', title: 'hehe', author: null, comments: [] }, }; - const denorm = memo.denormalize(input, { data: Article }, {}).data; + const denorm = memo.denormalize({ data: Article }, input, {}).data; expect(denorm).toMatchInlineSnapshot(` { "data": Article { @@ -634,7 +626,6 @@ describe('MemoCache', () => { }); expect( new MemoCache().buildQueryKey( - '', schema, [{ id: 5 }], { @@ -655,7 +646,6 @@ describe('MemoCache', () => { }); expect( new MemoCache().buildQueryKey( - '', schema, [5], { @@ -676,7 +666,6 @@ describe('MemoCache', () => { }); expect( new MemoCache().buildQueryKey( - '', schema, ['5'], { @@ -695,7 +684,6 @@ describe('MemoCache', () => { }; expect( new MemoCache().buildQueryKey( - '', schema, [{ id: 5 }], { @@ -712,7 +700,6 @@ describe('MemoCache', () => { }; expect( new MemoCache().buildQueryKey( - '', schema2, [{ id: 5 }], { @@ -731,7 +718,6 @@ describe('MemoCache', () => { }; expect( new MemoCache().buildQueryKey( - '', schema, [{ id: 5 }], { @@ -748,7 +734,6 @@ describe('MemoCache', () => { const schema = UnionResource.get.schema; expect( new MemoCache().buildQueryKey( - '', schema, [{ id: 5 }], { @@ -765,7 +750,6 @@ describe('MemoCache', () => { const schema = UnionResource.get.schema; expect( new MemoCache().buildQueryKey( - '', schema, [{ id: 5, type: 'first' }], { @@ -790,7 +774,6 @@ describe('MemoCache', () => { }; expect( new MemoCache().buildQueryKey( - '', schema, [{ id: 5 }], { @@ -813,7 +796,6 @@ describe('MemoCache', () => { }; expect( new MemoCache().buildQueryKey( - '', schema, [{ username: 'bob' }], { @@ -835,7 +817,6 @@ describe('MemoCache', () => { }); expect( new MemoCache().buildQueryKey( - '', schema, [{ username: 'bob', mary: 'five' }], { @@ -864,7 +845,6 @@ describe('MemoCache', () => { }; expect( new MemoCache().buildQueryKey( - '', schema, [{ username: 'bob' }], { @@ -886,7 +866,6 @@ describe('MemoCache', () => { }); expect( new MemoCache().buildQueryKey( - '', schema, [{ hover: 'bob' }], { @@ -915,7 +894,6 @@ describe('MemoCache', () => { }; expect( new MemoCache().buildQueryKey( - '', schema, [{ username: 'bob' }], { @@ -929,7 +907,6 @@ describe('MemoCache', () => { }); expect( new MemoCache().buildQueryKey( - '', schema, [{ hover: 'bob' }], { @@ -972,7 +949,6 @@ describe('MemoCache', () => { }); expect( new MemoCache().buildQueryKey( - '', schema, ['5'], { @@ -992,12 +968,11 @@ describe('MemoCache', () => { }), }); const memo = new MemoCache(); - expect(memo.buildQueryKey('', schema, ['5'], {}, {})).toEqual({ + expect(memo.buildQueryKey(schema, ['5'], {}, {})).toEqual({ data: { article: undefined }, }); expect( memo.buildQueryKey( - '', schema, ['5'], { [MyEntity.key]: { '5': { id: '5', title: 'hi' } } }, @@ -1020,7 +995,6 @@ describe('MemoCache', () => { indexes: {}, }; const first = memo.buildQueryKey( - '', schema, ['5'], state.entities, @@ -1028,25 +1002,18 @@ describe('MemoCache', () => { ); it('should maintain referential equality', () => { expect( - memo.buildQueryKey( - '', - schema, - ['5'], - state.entities, - state.indexes, - ), + memo.buildQueryKey(schema, ['5'], state.entities, state.indexes), ).toBe(first); }); it('should not change on index update if not used', () => { expect( - memo.buildQueryKey('', schema, ['5'], state.entities, { + memo.buildQueryKey(schema, ['5'], state.entities, { [MyEntity.key]: {}, }), ).toBe(first); }); it('should be new when entity is updated', () => { const withEntity = memo.buildQueryKey( - '', schema, ['5'], { [MyEntity.key]: { '5': { id: '5', title: 'hi' } } }, @@ -1057,7 +1024,6 @@ describe('MemoCache', () => { }); it('should be the same if other entities are updated', () => { const withEntity = memo.buildQueryKey( - '', schema, ['5'], { @@ -1070,7 +1036,6 @@ describe('MemoCache', () => { }); it('should be the same if other entities of the same type are updated', () => { const withEntity = memo.buildQueryKey( - '', schema, ['5'], { @@ -1116,7 +1081,6 @@ describe('MemoCache', () => { test('works with indexes', () => { const m = new MemoCache().query( - '', Cat, [{ username: 'm' }], createInput(entities), @@ -1126,7 +1090,6 @@ describe('MemoCache', () => { expect(m).toMatchSnapshot(); expect( new MemoCache().query( - '', Cat, [{ username: 'doesnotexist' }], createInput(entities), @@ -1137,7 +1100,6 @@ describe('MemoCache', () => { test('works with pk', () => { const m = new MemoCache().query( - '', Cat, [{ id: '1' }], createInput(entities), @@ -1147,7 +1109,6 @@ describe('MemoCache', () => { expect(m).toMatchSnapshot(); expect( new MemoCache().query( - '', Cat, [{ id: 'doesnotexist' }], createInput(entities), diff --git a/packages/normalizr/src/__tests__/index.test.js b/packages/normalizr/src/__tests__/index.test.js index 6247d83d2323..2a8c960d86f9 100644 --- a/packages/normalizr/src/__tests__/index.test.js +++ b/packages/normalizr/src/__tests__/index.test.js @@ -34,42 +34,42 @@ describe('normalize', () => { `cannot normalize input that == %s`, input => { class Test extends IDEntity {} - expect(() => normalize(input, Test)).toThrow(); + expect(() => normalize(Test, input)).toThrow(); }, ); test.each([42, null, undefined, '42', () => {}])( `cannot normalize input that == %s`, input => { class Test extends IDEntity {} - expect(() => normalize(input, { data: Test })).toThrow(); + expect(() => normalize({ data: Test }, input)).toThrow(); }, ); test('can normalize strings for entity (already processed)', () => { class Test extends IDEntity {} - expect(normalize(['42'], new schema.Array(Test)).result).toEqual(['42']); + expect(normalize(new schema.Array(Test), ['42']).result).toEqual(['42']); }); test('can normalize null schema with string response', () => { - expect(normalize('17,234', null).result).toEqual('17,234'); + expect(normalize(null, '17,234').result).toEqual('17,234'); }); test('passthrough with undefined schema', () => { const input = {}; - expect(normalize(input).result).toBe(input); + expect(normalize(undefined, input).result).toBe(input); }); test('passthrough with id in place of entity', () => { const input = { taco: '5' }; - expect(normalize(input, { taco: Tacos }).result).toStrictEqual(input); + expect(normalize({ taco: Tacos }, input).result).toStrictEqual(input); }); test('cannot normalize with null input', () => { - expect(() => normalize(null, Tacos)).toThrow(/null/); + expect(() => normalize(Tacos, null)).toThrow(/null/); }); test('passthrough primitive schema', () => { - expect(normalize({ happy: { bob: 5 } }, { happy: 5 }).result).toStrictEqual( + expect(normalize({ happy: 5 }, { happy: { bob: 5 } }).result).toStrictEqual( { happy: { bob: 5 }, }, @@ -78,7 +78,7 @@ describe('normalize', () => { test('can normalize string', () => { const mySchema = ''; - expect(normalize('bob', mySchema)).toMatchInlineSnapshot(` + expect(normalize(mySchema, 'bob')).toMatchInlineSnapshot(` { "entities": {}, "entityMeta": {}, @@ -91,11 +91,11 @@ describe('normalize', () => { test('normalizes entities', () => { expect( normalize( + [Tacos], [ { id: '1', type: 'foo' }, { id: '2', type: 'bar' }, ], - [Tacos], ), ).toMatchSnapshot(); }); @@ -103,6 +103,16 @@ describe('normalize', () => { test('normalizes schema with extra members', () => { expect( normalize( + { + data: [Tacos], + extra: '', + page: { + first: null, + second: undefined, + third: 0, + complex: { complex: true, next: false }, + }, + }, { data: [ { id: '1', type: 'foo' }, @@ -116,16 +126,6 @@ describe('normalize', () => { complex: { complex: false, next: true }, }, }, - { - data: [Tacos], - extra: '', - page: { - first: null, - second: undefined, - third: 0, - complex: { complex: true, next: false }, - }, - }, ), ).toMatchSnapshot(); }); @@ -133,12 +133,6 @@ describe('normalize', () => { test('normalizes schema with extra members but not set', () => { expect( normalize( - { - data: [ - { id: '1', type: 'foo' }, - { id: '2', type: 'bar' }, - ], - }, { data: [Tacos], extra: '', @@ -149,6 +143,12 @@ describe('normalize', () => { complex: { complex: true, next: false }, }, }, + { + data: [ + { id: '1', type: 'foo' }, + { id: '2', type: 'bar' }, + ], + }, ), ).toMatchSnapshot(); }); @@ -160,6 +160,7 @@ describe('normalize', () => { expect( normalize( + { data: [MyTaco], alt: MyTaco }, { data: [ { id: '1', type: 'foo' }, @@ -167,7 +168,6 @@ describe('normalize', () => { ], alt: { id: '2', type: 'bar2' }, }, - { data: [MyTaco], alt: MyTaco }, ), ).toMatchSnapshot(); }); @@ -182,6 +182,7 @@ describe('normalize', () => { expect( normalize( + { data: [MyTaco], alt: MyTaco }, { data: [ { id: '1', type: 'foo' }, @@ -189,7 +190,6 @@ describe('normalize', () => { ], alt: { id: '2', type: 'bar2' }, }, - { data: [MyTaco], alt: MyTaco }, ), ).toMatchSnapshot(); @@ -213,7 +213,7 @@ describe('normalize', () => { const input = { id: '123', friends: [] }; input.friends.push(input); - expect(normalize(input, User)).toMatchSnapshot(); + expect(normalize(User, input)).toMatchSnapshot(); }); test('normalizes entities with circular references that fails validation', () => { @@ -227,7 +227,7 @@ describe('normalize', () => { const input = { id: '123', friends: [] }; input.friends.push(input); - expect(() => normalize(input, User)).toThrowErrorMatchingSnapshot(); + expect(() => normalize(User, input)).toThrowErrorMatchingSnapshot(); }); test('normalizes nested entities', () => { @@ -266,7 +266,7 @@ describe('normalize', () => { }, ], }; - expect(normalize(input, Article)).toMatchSnapshot(); + expect(normalize(Article, input)).toMatchSnapshot(); }); test('does not modify the original input', () => { @@ -285,7 +285,7 @@ describe('normalize', () => { name: 'Paul', }), }); - expect(() => normalize(input, Article)).not.toThrow(); + expect(() => normalize(Article, input)).not.toThrow(); }); test('handles number ids when nesting', () => { @@ -304,14 +304,14 @@ describe('normalize', () => { name: 'Paul', }, }); - expect(normalize(input, Article).entities).toMatchSnapshot(); + expect(normalize(Article, input).entities).toMatchSnapshot(); }); test('ignores null values', () => { class MyEntity extends IDEntity {} - expect(normalize([null], [MyEntity])).toMatchSnapshot(); - expect(normalize([undefined], [MyEntity])).toMatchSnapshot(); - expect(normalize([false], [MyEntity])).toMatchSnapshot(); + expect(normalize([MyEntity], [null])).toMatchSnapshot(); + expect(normalize([MyEntity], [undefined])).toMatchSnapshot(); + expect(normalize([MyEntity], [false])).toMatchSnapshot(); }); test('can use fully custom entity classes', () => { @@ -330,18 +330,20 @@ describe('normalize', () => { return this.uuid; } - static normalize(input, parent, key, visit, addEntity, visitedEntities) { + static normalize( + input, + parent, + key, + args, + visit, + addEntity, + getEntity, + checkLoop, + ) { const entity = { ...input }; Object.keys(this.schema).forEach(key => { const schema = this.schema[key]; - entity[key] = visit( - input[key], - input, - key, - schema, - addEntity, - visitedEntities, - ); + entity[key] = visit(schema, input[key], input, key, args); }); addEntity(this, entity, this.pk(entity)); return { @@ -353,14 +355,11 @@ describe('normalize', () => { class Food extends MyEntity {} expect( - normalize( - { - uuid: '1234', - name: 'tacos', - children: [{ id: 4, name: 'lettuce' }], - }, - Food, - ), + normalize(Food, { + uuid: '1234', + name: 'tacos', + children: [{ id: 4, name: 'lettuce' }], + }), ).toMatchSnapshot(); }); @@ -377,7 +376,7 @@ describe('normalize', () => { } expect( - normalize({ user: { id: '456' } }, Recommendations, [{ id: '456' }]), + normalize(Recommendations, { user: { id: '456' } }, [{ id: '456' }]), ).toMatchSnapshot(); expect(calls).toMatchSnapshot(); }); @@ -390,13 +389,14 @@ describe('normalize', () => { } expect( - normalize( - { id: '123', title: 'normalizr is great!', author: '1' }, - Article, - ), + normalize(Article, { + id: '123', + title: 'normalizr is great!', + author: '1', + }), ).toMatchSnapshot(); - expect(normalize({ user: '1' }, { user: User })).toMatchSnapshot(); + expect(normalize({ user: User }, { user: '1' })).toMatchSnapshot(); }); test('can normalize object without proper object prototype inheritance', () => { @@ -415,7 +415,7 @@ describe('normalize', () => { }; } - expect(() => normalize(test, Test)).not.toThrow(); + expect(() => normalize(Test, test)).not.toThrow(); }); }); @@ -429,15 +429,15 @@ describe.each([ ])(`denormalize [%s]`, (_, denormalize) => { test('passthrough with undefined schema', () => { const input = {}; - expect(denormalize(input)).toEqual(input); + expect(denormalize(undefined, input)).toEqual(input); }); test('returns the input if undefined', () => { - expect(denormalize(undefined, {}, {})).toEqual(undefined); + expect(denormalize({}, undefined, {})).toEqual(undefined); }); test('returns the input if string', () => { - expect(denormalize('bob', '', {})).toEqual('bob'); + expect(denormalize('', 'bob', {})).toEqual('bob'); }); test('denormalizes entities', () => { @@ -447,15 +447,15 @@ describe.each([ 2: { id: '2', type: 'bar' }, }, }; - expect(denormalize(['1', '2'], [Tacos], entities)).toMatchSnapshot(); + expect(denormalize([Tacos], ['1', '2'], entities)).toMatchSnapshot(); }); test('denormalizes without entities fills undefined', () => { - expect(denormalize({ data: '1' }, { data: Tacos }, {})).toMatchSnapshot(); + expect(denormalize({ data: Tacos }, { data: '1' }, {})).toMatchSnapshot(); expect( - denormalize(fromJS({ data: '1' }), { data: Tacos }, {}), + denormalize({ data: Tacos }, fromJS({ data: '1' }), {}), ).toMatchSnapshot(); - expect(denormalize('1', Tacos, {})).toEqual(undefined); + expect(denormalize(Tacos, '1', {})).toEqual(undefined); }); test('denormalizes ignoring unfound entities in arrays', () => { @@ -464,9 +464,9 @@ describe.each([ 1: { id: '1', type: 'foo' }, }, }; - expect(denormalize(['1', '2'], [Tacos], entities)).toMatchSnapshot(); + expect(denormalize([Tacos], ['1', '2'], entities)).toMatchSnapshot(); expect( - denormalize({ results: ['1', '2'] }, { results: [Tacos] }, entities), + denormalize({ results: [Tacos] }, { results: ['1', '2'] }, entities), ).toMatchSnapshot(); }); @@ -476,7 +476,7 @@ describe.each([ 1: Symbol('ENTITY WAS INVALID'), }, }; - expect(denormalize('1', Tacos, entities)).toEqual(expect.any(Symbol)); + expect(denormalize(Tacos, '1', entities)).toEqual(expect.any(Symbol)); }); test('denormalizes ignoring deleted entities in arrays', () => { @@ -486,9 +486,9 @@ describe.each([ 2: INVALID, }, }; - expect(denormalize(['1', '2'], [Tacos], entities)).toMatchSnapshot(); + expect(denormalize([Tacos], ['1', '2'], entities)).toMatchSnapshot(); expect( - denormalize({ results: ['1', '2'] }, { results: [Tacos] }, entities), + denormalize({ results: [Tacos] }, { results: ['1', '2'] }, entities), ).toMatchSnapshot(); }); @@ -499,10 +499,10 @@ describe.each([ }, }; /*expect( - denormalize([{ data: 1 }, { data: 2 }], [{ data: Tacos }], {}), + denormalize([{ data: Tacos }],[{ data: 1 }, { data: 2 }], {}), ).toEqual([]);*/ expect( - denormalize([{ data: 1 }, { data: 2 }], [{ data: Tacos }], entities), + denormalize([{ data: Tacos }], [{ data: 1 }, { data: 2 }], entities), ).toMatchSnapshot(); }); @@ -515,16 +515,6 @@ describe.each([ }; expect( denormalize( - { - data: ['1', '2'], - extra: '5', - page: { - first: null, - second: { thing: 'two' }, - third: 1, - complex: { complex: false, next: true }, - }, - }, { data: [Tacos], extra: '', @@ -535,6 +525,17 @@ describe.each([ complex: { complex: true, next: false }, }, }, + { + data: ['1', '2'], + extra: '5', + page: { + first: null, + second: { thing: 'two' }, + third: 1, + complex: { complex: false, next: true }, + }, + }, + entities, ), ).toMatchSnapshot(); @@ -549,9 +550,6 @@ describe.each([ }; expect( denormalize( - { - data: ['1', '2'], - }, { data: [Tacos], extra: '', @@ -562,6 +560,10 @@ describe.each([ complex: { complex: true, next: false }, }, }, + { + data: ['1', '2'], + }, + entities, ), ).toMatchSnapshot(); @@ -612,7 +614,7 @@ describe.each([ }, }, }; - expect(denormalize('123', Article, entities)).toMatchSnapshot(); + expect(denormalize(Article, '123', entities)).toMatchSnapshot(); }); test('gracefully handles when nested entities are primitives', () => { @@ -656,7 +658,7 @@ describe.each([ }, }, }; - expect(() => denormalize('123', Article, entities)).toMatchSnapshot(); + expect(() => denormalize(Article, '123', entities)).toMatchSnapshot(); }); test('set to undefined if schema key is not in entities', () => { @@ -693,7 +695,7 @@ describe.each([ }, }, }; - expect(denormalize('123', Article, entities)).toMatchSnapshot(); + expect(denormalize(Article, '123', entities)).toMatchSnapshot(); }); test('does not modify the original entities', () => { @@ -718,7 +720,7 @@ describe.each([ }), }), }); - expect(() => denormalize('123', Article, entities)).not.toThrow(); + expect(() => denormalize(Article, '123', entities)).not.toThrow(); }); test('denormalizes with function as pk()', () => { @@ -745,18 +747,18 @@ describe.each([ }; expect( - denormalize(normalizedData.result, [Patron], normalizedData.entities), + denormalize([Patron], normalizedData.result, normalizedData.entities), ).toMatchSnapshot(); }); test('denormalizes where id is only in key', () => { expect( denormalize( + new schema.Values(Tacos), { 1: { type: 'foo' }, 2: { type: 'bar' }, }, - new schema.Values(Tacos), {}, ), ).toMatchSnapshot(); diff --git a/packages/normalizr/src/__tests__/normalizerMerge.test.tsx b/packages/normalizr/src/__tests__/normalizerMerge.test.tsx index fcdcbc700b5b..4962d8958bcf 100644 --- a/packages/normalizr/src/__tests__/normalizerMerge.test.tsx +++ b/packages/normalizr/src/__tests__/normalizerMerge.test.tsx @@ -8,25 +8,20 @@ describe('normalizer() merging', () => { describe('with instance.constructor.merge()', () => { it('should merge two Resource instances', () => { const id = 20; - const { entities: first, entityMeta: firstEM } = normalize( - { - id, - title: 'hi', - content: 'this is the content', - }, - Article, - ); + const { entities: first, entityMeta: firstEM } = normalize(Article, { + id, + title: 'hi', + content: 'this is the content', + }); const { result, entities } = normalize( - { id, title: 'hello' }, Article, - [], - first, + { id, title: 'hello' }, {}, - firstEM, + { entities: first, entityMeta: firstEM, indexes: {} }, ); - const merged = denormalize(result, Article, entities); + const merged = denormalize(Article, result, entities); expect(merged).toBeInstanceOf(Article); expect(merged).toEqual( Article.fromJS({ @@ -55,10 +50,10 @@ describe('normalizer() merging', () => { }; const { entities } = normalize( - { id, title: 'hi', content: 'this is the content' }, Article, - [], - entitiesA, + { id, title: 'hi', content: 'this is the content' }, + {}, + { entities: entitiesA, indexes: {}, entityMeta: {} }, ); expect(entities[Article.key][42]).toBe(entitiesA[Article.key][42]); @@ -68,25 +63,20 @@ describe('normalizer() merging', () => { describe('basics', function () { it('should assign `null` values', () => { const id = 20; - const { entities: first, entityMeta: firstEM } = normalize( - { - id, - title: 'hi', - content: 'this is the content', - }, - Article, - ); + const { entities: first, entityMeta: firstEM } = normalize(Article, { + id, + title: 'hi', + content: 'this is the content', + }); const { result, entities } = normalize( - { id, title: null }, Article, - [], - first, + { id, title: null }, {}, - firstEM, + { entities: first, entityMeta: firstEM, indexes: {} }, ); - const merged = denormalize(result, Article, entities); + const merged = denormalize(Article, result, entities); expect(merged).toBeInstanceOf(Article); expect(merged).toEqual( Article.fromJS({ @@ -99,18 +89,20 @@ describe('normalizer() merging', () => { it('should not augment source objects', () => { const id = 20; - const { entities: first } = normalize( - { - id, - title: 'hi', - content: 'this is the content', - }, + const { entities: first } = normalize(Article, { + id, + title: 'hi', + content: 'this is the content', + }); + + normalize( Article, + { id, title: 'hello' }, + {}, + { entities: first, indexes: {}, entityMeta: {} }, ); - normalize({ id, title: 'hello' }, Article, [], first); - - const merged = denormalize(id, Article, first); + const merged = denormalize(Article, id, first); expect(merged).toBeInstanceOf(Article); expect(merged).toEqual( Article.fromJS({ @@ -123,13 +115,19 @@ describe('normalizer() merging', () => { it('should still clone even when overwriting', () => { const id = 20; - const { entities: first } = normalize( - { id }, - new schema.Invalidate(Article), - ); + const { entities: first } = normalize(new schema.Invalidate(Article), { + id, + }); const nested = { id, title: 'hello' }; - const { entities } = normalize(nested, Article, [], first); + const { entities } = normalize( + Article, + nested, + { + args: [], + }, + { entities: first, indexes: {}, entityMeta: {} }, + ); expect(entities).toMatchInlineSnapshot(` { diff --git a/packages/normalizr/src/buildQueryKey.ts b/packages/normalizr/src/buildQueryKey.ts index f810005b7900..6dbb3f9a1d50 100644 --- a/packages/normalizr/src/buildQueryKey.ts +++ b/packages/normalizr/src/buildQueryKey.ts @@ -9,13 +9,13 @@ import type { NormalizeNullable } from './types.js'; */ export default function buildQueryKey( schema: S, - args: any[], + args: readonly any[], getEntity: GetEntity, getIndex: GetIndex, ): NormalizeNullable { // schema classes if (canQuery(schema)) { - return schema.queryKey(args, buildQueryKey, getEntity, getIndex); + return schema.queryKey(args as any[], buildQueryKey, getEntity, getIndex); } // plain case diff --git a/packages/normalizr/src/denormalize/denormalize.ts b/packages/normalizr/src/denormalize/denormalize.ts index d1155198531b..b831f7f31129 100644 --- a/packages/normalizr/src/denormalize/denormalize.ts +++ b/packages/normalizr/src/denormalize/denormalize.ts @@ -5,8 +5,8 @@ import type { Schema } from '../interface.js'; import type { DenormalizeNullable } from '../types.js'; export function denormalize( - input: any, schema: S | undefined, + input: any, entities: any, args: readonly any[] = [], ): DenormalizeNullable | symbol { @@ -19,5 +19,5 @@ export function denormalize( getEntities(entities), new LocalCache(), args, - )(input, schema).data; + )(schema, input).data; } diff --git a/packages/normalizr/src/denormalize/unvisit.ts b/packages/normalizr/src/denormalize/unvisit.ts index 1e1e2d68cca8..23d5197a94ff 100644 --- a/packages/normalizr/src/denormalize/unvisit.ts +++ b/packages/normalizr/src/denormalize/unvisit.ts @@ -10,10 +10,10 @@ import { denormalize as objectDenormalize } from '../schemas/Object.js'; import type { EntityPath } from '../types.js'; function unvisitEntity( - entityOrId: Record | string, schema: EntityInterface, + entityOrId: Record | string, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, getEntity: GetEntity, cache: Cache, ): object | undefined | symbol { @@ -82,7 +82,7 @@ function noCacheGetEntity( function unvisitEntityObject( entity: object, schema: EntityInterface, - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, pk: string, localCacheKey: Record, args: readonly any[], @@ -107,7 +107,7 @@ const getUnvisit = ( cache: Cache, args: readonly any[], ) => { - function unvisit(input: any, schema: any): any { + function unvisit(schema: any, input: any): any { if (!schema) return input; if (input === null || input === undefined) { @@ -128,7 +128,7 @@ const getUnvisit = ( } } else { if (isEntity(schema)) { - return unvisitEntity(input, schema, args, unvisit, getEntity, cache); + return unvisitEntity(schema, input, args, unvisit, getEntity, cache); } return schema.denormalize(input, args, unvisit); @@ -137,11 +137,11 @@ const getUnvisit = ( return input; } - return (input: any, schema: any): { data: any; paths: EntityPath[] } => { + return (schema: any, input: any): { data: any; paths: EntityPath[] } => { // in the case where WeakMap cannot be used // this test ensures null is properly excluded from WeakMap const cachable = Object(input) === input && Object(schema) === schema; - return cache.getResults(input, cachable, () => unvisit(input, schema)); + return cache.getResults(input, cachable, () => unvisit(schema, input)); }; }; export default getUnvisit; diff --git a/packages/normalizr/src/interface.ts b/packages/normalizr/src/interface.ts index 74d96d21854d..2cb3280383e3 100644 --- a/packages/normalizr/src/interface.ts +++ b/packages/normalizr/src/interface.ts @@ -25,16 +25,16 @@ export interface SchemaSimple { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args: any[], + getEntity: (...args: any) => any, + checkLoop: (...args: any) => any, ): any; denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): T; queryKey( args: Args, @@ -97,10 +97,20 @@ export interface EntityTable { | undefined; } +/** Visits next data + schema while recurisvely normalizing */ +export interface Visit { + (schema: any, value: any, parent: any, key: any, args: readonly any[]): any; +} + +/** 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 */ export interface GetEntity { - (entityKey: string): { readonly [pk: string]: any } | undefined; - (entityKey: string, pk: string | number): any; + (entityKey: string | symbol): { readonly [pk: string]: any } | undefined; + (entityKey: string | symbol, pk: string | number): any; } /** Get PK using an Entity Index */ export interface GetIndex { diff --git a/packages/normalizr/src/memo/MemoCache.ts b/packages/normalizr/src/memo/MemoCache.ts index cb078efe1b0e..95b586415d18 100644 --- a/packages/normalizr/src/memo/MemoCache.ts +++ b/packages/normalizr/src/memo/MemoCache.ts @@ -25,8 +25,8 @@ export default class MemoCache { /** Compute denormalized form maintaining referential equality for same inputs */ denormalize( - input: unknown, schema: S | undefined, + input: unknown, entities: any, args: readonly any[] = [], ): { @@ -50,14 +50,13 @@ export default class MemoCache { getEntity, new GlobalCache(getEntity, this.entities, this.endpoints), args, - )(input, schema); + )(schema, input); } /** Compute denormalized form maintaining referential equality for same inputs */ query( - argsKey: string, schema: S, - args: any[], + args: readonly any[], entities: | Record> | { @@ -68,21 +67,22 @@ export default class MemoCache { | { getIn(k: string[]): any; }, + // NOTE: different orders can result in cache busting here; but since it's just a perf penalty we will allow for now + argsKey: string = JSON.stringify(args), ): DenormalizeNullable | undefined { - const input = this.buildQueryKey(argsKey, schema, args, entities, indexes); + const input = this.buildQueryKey(schema, args, entities, indexes, argsKey); if (!input) { return; } - const { data } = this.denormalize(input, schema, entities, args); + const { data } = this.denormalize(schema, input, entities, args); return typeof data === 'symbol' ? undefined : (data as any); } buildQueryKey( - argsKey: string, schema: S, - args: any[], + args: readonly any[], entities: | Record> | { @@ -93,6 +93,8 @@ export default class MemoCache { | { getIn(k: string[]): any; }, + // NOTE: different orders can result in cache busting here; but since it's just a perf penalty we will allow for now + argsKey: string = JSON.stringify(args), ): NormalizeNullable { // This is redundant for buildQueryKey checks, but that was is used for recursion so we still need the checks there // TODO: If we make each recursive call include cache lookups, we combine these checks together @@ -169,7 +171,7 @@ export function createGetEntity( if (entityIsImmutable) { return (...args) => entities.getIn(args)?.toJS?.(); } else { - return (entityKey: string, pk?: string): any => + return (entityKey: string | symbol, pk?: string): any => pk ? entities[entityKey]?.[pk] : entities[entityKey]; } } diff --git a/packages/normalizr/src/normalize.ts b/packages/normalizr/src/normalize.ts index 86d431f52e26..883a3f600600 100644 --- a/packages/normalizr/src/normalize.ts +++ b/packages/normalizr/src/normalize.ts @@ -1,58 +1,68 @@ import { INVALID } from './denormalize/symbol.js'; -import type { EntityInterface, Schema, NormalizedIndex } from './interface.js'; +import type { + EntityInterface, + Schema, + NormalizedIndex, + GetEntity, +} from './interface.js'; +import { createGetEntity } from './memo/MemoCache.js'; import { normalize as arrayNormalize } from './schemas/Array.js'; import { normalize as objectNormalize } from './schemas/Object.js'; import type { NormalizeNullable, NormalizedSchema } from './types.js'; -const visit = ( - value: any, - parent: any, - key: any, - schema: any, +const getVisit = ( addEntity: ( schema: EntityInterface, processedEntity: any, id: string, ) => void, - visitedEntities: any, - storeEntities: any, - args: any[], + getEntity: GetEntity, ) => { - if (!value || !schema) { - return value; - } - - if (schema.normalize && typeof schema.normalize === 'function') { - if (typeof value !== 'object') { - if (schema.pk) return `${value}`; + const checkLoop = getCheckLoop(); + const visit = ( + schema: any, + value: any, + parent: any, + key: any, + args: readonly any[], + ) => { + if (!value || !schema) { return value; } - return schema.normalize( + + if (schema.normalize && typeof schema.normalize === 'function') { + if (typeof value !== 'object') { + if (schema.pk) return `${value}`; + return value; + } + return schema.normalize( + value, + parent, + key, + args, + visit, + addEntity, + getEntity, + checkLoop, + ); + } + + if (typeof value !== 'object' || typeof schema !== 'object') return value; + + const method = Array.isArray(schema) ? arrayNormalize : objectNormalize; + return method( + schema, value, parent, key, + args, visit, addEntity, - visitedEntities, - storeEntities, - args, + getEntity, + checkLoop, ); - } - - if (typeof value !== 'object' || typeof schema !== 'object') return value; - - const method = Array.isArray(schema) ? arrayNormalize : objectNormalize; - return method( - schema, - value, - parent, - key, - visit, - addEntity, - visitedEntities, - storeEntities, - args, - ); + }; + return visit; }; const addEntities = @@ -177,6 +187,50 @@ function expectedSchemaType(schema: Schema) { ); } +function getCheckLoop() { + const visitedEntities = {}; + /* Returns true if a circular reference is found */ + return function checkLoop(entityKey: string, pk: string, input: object) { + if (!(entityKey in visitedEntities)) { + visitedEntities[entityKey] = {}; + } + if (!(pk in visitedEntities[entityKey])) { + visitedEntities[entityKey][pk] = []; + } + if ( + visitedEntities[entityKey][pk].some((entity: any) => entity === input) + ) { + return true; + } + visitedEntities[entityKey][pk].push(input); + return false; + }; +} + +interface StoreData { + entities: Readonly; + indexes: Readonly; + entityMeta: { + readonly [entityKey: string]: { + readonly [pk: string]: { + readonly date: number; + readonly expiresAt: number; + readonly fetchedAt: number; + }; + }; + }; +} +const emptyStore: StoreData = { + entities: {}, + indexes: {}, + entityMeta: {}, +}; +interface NormalizeMeta { + expiresAt?: number; + date?: number; + fetchedAt?: number; + args?: readonly any[]; +} // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const normalize = < S extends Schema = Schema, @@ -186,33 +240,23 @@ export const normalize = < >, R = NormalizeNullable, >( + schema: S | undefined, input: any, - schema?: S, - args: any[] = [], - storeEntities: Readonly = {} as any, - storeIndexes: Readonly = {}, - storeEntityMeta: { - readonly [entityKey: string]: { - readonly [pk: string]: { - readonly date: number; - readonly expiresAt: number; - readonly fetchedAt: number; - }; - }; - } = {}, - meta: { expiresAt: number; date: number; fetchedAt: number } = { - date: Date.now(), - expiresAt: Infinity, - fetchedAt: 0, - }, + { + date = Date.now(), + expiresAt = Infinity, + fetchedAt = 0, + args = [], + }: NormalizeMeta = {}, + { entities, indexes, entityMeta }: StoreData = emptyStore, ): NormalizedSchema => { // no schema means we don't process at all if (schema === undefined || schema === null) return { - entities: storeEntities, - indexes: storeIndexes, result: input, - entityMeta: storeEntityMeta, + entities, + indexes, + entityMeta, }; const schemaType = expectedSchemaType(schema); @@ -265,28 +309,22 @@ See https://dataclient.io/rest/api/RestEndpoint#parseResponse for more informati const newEntities: E = {} as any; const newIndexes: NormalizedIndex = {} as any; - const entities: E = { ...storeEntities } as any; - const indexes: NormalizedIndex = { ...storeIndexes }; - const entityMeta: any = { ...storeEntityMeta }; + const ret: NormalizedSchema = { + result: '' as any, + entities: { ...entities }, + indexes: { ...indexes }, + entityMeta: { ...entityMeta }, + }; const addEntity = addEntities( newEntities, newIndexes, - entities, - indexes, - entityMeta, - { expiresAt: meta.expiresAt, date: meta.date, fetchedAt: meta.fetchedAt }, + ret.entities, + ret.indexes, + ret.entityMeta, + { expiresAt, date, fetchedAt }, ); - const visitedEntities = {}; - const result = visit( - input, - input, - undefined, - schema, - addEntity, - visitedEntities, - storeEntities, - args, - ); - return { entities, indexes, result, entityMeta }; + const visit = getVisit(addEntity, createGetEntity(entities)); + ret.result = visit(schema, input, input, undefined, args); + return ret; }; diff --git a/packages/normalizr/src/schemas/Array.ts b/packages/normalizr/src/schemas/Array.ts index 116a4b5b5113..70b446316b7e 100644 --- a/packages/normalizr/src/schemas/Array.ts +++ b/packages/normalizr/src/schemas/Array.ts @@ -1,3 +1,5 @@ +import type { Visit } from '../interface.js'; + const validateSchema = definition => { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { @@ -22,11 +24,11 @@ export const normalize = ( input: any, parent: any, key: any, - visit: any, + args: readonly any[], + visit: Visit, addEntity: any, - visitedEntities: any, - storeEntities: any, - args: any[], + getEntity: any, + checkLoop: any, ) => { schema = validateSchema(schema); @@ -34,18 +36,7 @@ export const normalize = ( // Special case: Arrays pass *their* parent on to their children, since there // is not any special information that can be gathered from themselves directly - return values.map((value, index) => - visit( - value, - parent, - key, - schema, - addEntity, - visitedEntities, - storeEntities, - args, - ), - ); + return values.map((value, index) => visit(schema, value, parent, key, args)); }; export const denormalize = ( @@ -56,7 +47,7 @@ export const denormalize = ( ): any => { schema = validateSchema(schema); return input.map ? - input.map(entityOrId => unvisit(entityOrId, schema)).filter(filterEmpty) + input.map(entityOrId => unvisit(schema, entityOrId)).filter(filterEmpty) : input; }; diff --git a/packages/normalizr/src/schemas/ImmutableUtils.ts b/packages/normalizr/src/schemas/ImmutableUtils.ts index 6e19ace178c7..653bdf715d83 100644 --- a/packages/normalizr/src/schemas/ImmutableUtils.ts +++ b/packages/normalizr/src/schemas/ImmutableUtils.ts @@ -46,7 +46,7 @@ export function denormalizeImmutable( // we're accessing them using string keys. const stringKey = `${key}`; - const item = unvisit(object.get(stringKey), schema[stringKey]); + const item = unvisit(schema[stringKey], object.get(stringKey)); if (typeof item === 'symbol') { deleted = true; } diff --git a/packages/normalizr/src/schemas/Object.ts b/packages/normalizr/src/schemas/Object.ts index 544192eec7b9..4daca1dbb889 100644 --- a/packages/normalizr/src/schemas/Object.ts +++ b/packages/normalizr/src/schemas/Object.ts @@ -1,30 +1,22 @@ import { isImmutable, denormalizeImmutable } from './ImmutableUtils.js'; import { INVALID } from '../denormalize/symbol.js'; +import type { Visit } from '../interface.js'; export const normalize = ( schema: any, input: any, parent: any, key: any, - visit: any, + args: readonly any[], + visit: Visit, addEntity: any, - visitedEntities: any, - storeEntities: any, - args: any[], + getEntity: any, + checkLoop: any, ) => { const object = { ...input }; Object.keys(schema).forEach(key => { const localSchema = schema[key]; - const value = visit( - input[key], - input, - key, - localSchema, - addEntity, - visitedEntities, - storeEntities, - args, - ); + const value = visit(localSchema, input[key], input, key, args); if (value === undefined) { delete object[key]; } else { @@ -48,7 +40,7 @@ export const denormalize = ( const object = { ...input }; let deleted = false; Object.keys(schema).forEach(key => { - const item = unvisit(object[key], schema[key]); + const item = unvisit(schema[key], object[key]); if (object[key] !== undefined) { object[key] = item; } diff --git a/packages/react/src/hooks/__tests__/useDLE.native.tsx b/packages/react/src/hooks/__tests__/useDLE.native.tsx index fedbe43c3a00..3266489dd4db 100644 --- a/packages/react/src/hooks/__tests__/useDLE.native.tsx +++ b/packages/react/src/hooks/__tests__/useDLE.native.tsx @@ -210,7 +210,7 @@ describe('useDLE', () => { describe('result is stale and options.invalidIfStale is false', () => { const fbmock = jest.fn(); - const { entities, result } = normalize(payload, CoolerArticle); + const { entities, result } = normalize(CoolerArticle, payload); const fetchKey = CoolerArticleResource.get.key({ id: payload.id }); const state = { ...initialState, diff --git a/packages/react/src/hooks/__tests__/useFetch.native.tsx b/packages/react/src/hooks/__tests__/useFetch.native.tsx index 8cac41511d41..86620c6f0878 100644 --- a/packages/react/src/hooks/__tests__/useFetch.native.tsx +++ b/packages/react/src/hooks/__tests__/useFetch.native.tsx @@ -196,7 +196,7 @@ describe('useFetch', () => { useFetch(CoolerArticleResource.get, { id: payload.id }); return stuff; } - const { entities, result } = normalize(payload, CoolerArticle); + const { entities, result } = normalize(CoolerArticle, payload); const fetchKey = CoolerArticleResource.get.key({ id: payload.id }); const state = { ...initialState, diff --git a/packages/react/src/hooks/__tests__/useSuspense.native.tsx b/packages/react/src/hooks/__tests__/useSuspense.native.tsx index 46bc1f04e08a..88ed6793aa35 100644 --- a/packages/react/src/hooks/__tests__/useSuspense.native.tsx +++ b/packages/react/src/hooks/__tests__/useSuspense.native.tsx @@ -199,7 +199,7 @@ describe('useSuspense()', () => { expect(title).toBeDefined(); }); describe('result is stale and options.invalidIfStale is false', () => { - const { entities, result } = normalize(payload, CoolerArticle); + const { entities, result } = normalize(CoolerArticle, payload); const fetchKey = CoolerArticleResource.get.key({ id: payload.id }); const state = { ...initialState, @@ -284,7 +284,7 @@ describe('useSuspense()', () => { }); it('should NOT suspend if result is not stale and options.invalidIfStale is true', () => { - const { entities, result } = normalize(payload, CoolerArticle); + const { entities, result } = normalize(CoolerArticle, payload); const fetchKey = InvalidIfStaleArticleResource.get.key({ id: payload.id }); const state = { ...initialState, @@ -314,7 +314,7 @@ describe('useSuspense()', () => { expect(title).toBeDefined(); }); it('should suspend if result stale in cache and options.invalidIfStale is true', () => { - const { entities, result } = normalize(payload, CoolerArticle); + const { entities, result } = normalize(CoolerArticle, payload); const fetchKey = InvalidIfStaleArticleResource.get.key({ id: payload.id }); const state = { ...initialState, diff --git a/packages/react/src/hooks/__tests__/useSuspense.web.tsx b/packages/react/src/hooks/__tests__/useSuspense.web.tsx index f7c484f3275f..76b19ceb7669 100644 --- a/packages/react/src/hooks/__tests__/useSuspense.web.tsx +++ b/packages/react/src/hooks/__tests__/useSuspense.web.tsx @@ -196,7 +196,7 @@ describe('useSuspense()', () => { expect(title.tagName).toBe('H3'); }); it('should NOT suspend even when result is stale and options.invalidIfStale is false', () => { - const { entities, result } = normalize(payload, CoolerArticle); + const { entities, result } = normalize(CoolerArticle, payload); const fetchKey = CoolerArticleResource.get.key({ id: payload.id }); const state = { ...initialState, @@ -229,7 +229,7 @@ describe('useSuspense()', () => { expect(title.tagName).toBe('H3'); }); it('should NOT suspend if result is not stale and options.invalidIfStale is true', () => { - const { entities, result } = normalize(payload, CoolerArticle); + const { entities, result } = normalize(CoolerArticle, payload); const fetchKey = InvalidIfStaleArticleResource.get.key({ id: payload.id }); const state = { ...initialState, @@ -260,7 +260,7 @@ describe('useSuspense()', () => { expect(title.tagName).toBe('H3'); }); it('should suspend if result stale in cache and options.invalidIfStale is true', () => { - const { entities, result } = normalize(payload, CoolerArticle); + const { entities, result } = normalize(CoolerArticle, payload); const fetchKey = InvalidIfStaleArticleResource.get.key({ id: payload.id }); const state = { ...initialState, diff --git a/packages/ssr/package.json b/packages/ssr/package.json index 09c16a5a210f..1f5531ec4bb5 100644 --- a/packages/ssr/package.json +++ b/packages/ssr/package.json @@ -116,8 +116,8 @@ "@babel/runtime": "^7.17.0" }, "peerDependencies": { - "@data-client/react": "^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0", - "@data-client/redux": "^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0", + "@data-client/react": "^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0", + "@data-client/redux": "^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0", "@types/react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "next": ">=12.0.0", "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", diff --git a/packages/test/package.json b/packages/test/package.json index 8bda8360eafd..0a9f6729234d 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -117,7 +117,7 @@ "@testing-library/react-native": "^12.0.1" }, "peerDependencies": { - "@data-client/react": "^0.12.15 || ^0.13.0", + "@data-client/react": "^0.12.15 || ^0.13.0 || ^0.14.0", "@testing-library/react-hooks": "^8.0.0", "@types/react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-0 || ^19.0.0", "@types/react-dom": "*", 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 c1bf62a170b5..bc9bbe6a048c 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 @@ -10,8 +10,8 @@ type Serializable = (value: any) => T; interface SchemaSimple { - normalize(input: any, parent: any, key: any, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any[]): any; - denormalize(input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any): T; + normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; + denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; queryKey(args: Args, queryKey: (...args: any) => any, getEntity: GetEntity, getIndex: GetIndex): any; } interface EntityInterface extends SchemaSimple { @@ -35,10 +35,10 @@ interface NormalizedIndex { } /** Get Array of entities with map function applied */ interface GetEntity { - (entityKey: string): { + (entityKey: string | symbol): { readonly [pk: string]: any; } | undefined; - (entityKey: string, pk: string | number): any; + (entityKey: string | symbol, pk: string | number): any; } /** Get PK using an Entity Index */ interface GetIndex { @@ -140,21 +140,21 @@ declare class MemoCache { /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Record>; /** Compute denormalized form maintaining referential equality for same inputs */ - denormalize(input: unknown, schema: S | undefined, entities: any, args?: readonly any[]): { + denormalize(schema: S | undefined, input: unknown, entities: any, args?: readonly any[]): { data: DenormalizeNullable | symbol; paths: EntityPath[]; }; /** Compute denormalized form maintaining referential equality for same inputs */ - query(argsKey: string, schema: S, args: any[], entities: Record> | { + query(schema: S, args: readonly any[], entities: Record> | { getIn(k: string[]): any; }, indexes: NormalizedIndex | { getIn(k: string[]): any; - }): DenormalizeNullable | undefined; - buildQueryKey(argsKey: string, schema: S, args: any[], entities: Record> | { + }, argsKey?: string): DenormalizeNullable | undefined; + buildQueryKey(schema: S, args: readonly any[], entities: Record> | { getIn(k: string[]): any; }, indexes: NormalizedIndex | { getIn(k: string[]): any; - }): NormalizeNullable; + }, argsKey?: string): NormalizeNullable; } type IndexPath = [key: string, field: string, value: string]; type EntitySchemaPath = [key: string] | [key: string, pk: string]; 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 3cc1f5b2c7e5..5dabb5a0d2e7 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 @@ -138,8 +138,8 @@ type Serializable = (value: any) => T; interface SchemaSimple { - normalize(input: any, parent: any, key: any, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any[]): any; - denormalize(input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any): T; + normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; + denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; queryKey(args: Args, queryKey: (...args: any) => any, getEntity: GetEntity, getIndex: GetIndex): any; } interface SchemaClass extends SchemaSimple { @@ -163,12 +163,16 @@ interface PolymorphicInterface extends Sche _normalizeNullable(): any; _denormalizeNullable(): 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): { + (entityKey: string | symbol): { readonly [pk: string]: any; } | undefined; - (entityKey: string, pk: string | number): any; + (entityKey: string | symbol, pk: string | number): any; } /** Get PK using an Entity Index */ interface GetIndex { @@ -273,26 +277,6 @@ declare let Endpoint: EndpointConstructor; declare let ExtendableEndpoint: ExtendableEndpointConstructor; -type Constructor = abstract new (...args: any[]) => {}; -type IDClass = abstract new (...args: any[]) => { - id: string | number | undefined; -}; -type PKClass = abstract new (...args: any[]) => { - pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; -}; -type ValidSchemas = { - [k in keyof TInstance]?: Schema; -}; -type EntityOptions = { - readonly schema?: ValidSchemas; - readonly pk?: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; - readonly key?: string; -} & { - readonly [K in Extract]?: IEntityClass TInstance>[K]; -}; -interface RequiredPKOptions extends EntityOptions { - readonly pk: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; -} interface IEntityClass { toJSON(): { name: string; @@ -400,7 +384,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#process */ process(input: any, parent: any, key: string | undefined, args: any[]): any; - normalize(input: any, parent: any, key: string | undefined, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record): any; + normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#validate @@ -411,7 +395,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#queryKey */ queryKey(args: readonly any[], queryKey: any, getEntity: GetEntity, getIndex: GetIndex): any; - denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, args: readonly any[], unvisit: (input: any, schema: any) => any): AbstractInstanceType; + denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): AbstractInstanceType; /** All instance defaults set */ readonly defaults: any; } @@ -425,6 +409,26 @@ interface IEntityInstance { */ pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; } +type Constructor = abstract new (...args: any[]) => {}; +type IDClass = abstract new (...args: any[]) => { + id: string | number | undefined; +}; +type PKClass = abstract new (...args: any[]) => { + pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; +}; +type ValidSchemas = { + [k in keyof TInstance]?: Schema; +}; +type EntityOptions = { + readonly schema?: ValidSchemas; + readonly pk?: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; + readonly key?: string; +} & { + readonly [K in Extract]?: IEntityClass TInstance>[K]; +}; +interface RequiredPKOptions extends EntityOptions { + readonly pk: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; +} /** * Marks entity as Invalid. @@ -447,7 +451,7 @@ declare class Invalidate any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: Record, args?: any[]): string | number | undefined; + normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: any, checkLoop: any): string | number | undefined; merge(existing: any, incoming: any): any; mergeWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; mergeMetaWithStore(existingMeta: { @@ -465,7 +469,7 @@ declare class Invalidate any): AbstractInstanceType; + denormalize(id: string, args: readonly any[], unvisit: (schema: any, input: any) => any): AbstractInstanceType; _denormalizeNullable(): AbstractInstanceType | undefined; _normalizeNullable(): string | undefined; } @@ -487,7 +491,7 @@ declare class Query, ... normalize(...args: any): any; denormalize(input: {}, args: any, unvisit: any): ReturnType

; queryKey(args: ProcessParameters, queryKey: (schema: any, args: any, getEntity: GetEntity, getIndex: GetIndex) => any, getEntity: GetEntity, getIndex: GetIndex): any; - _denormalizeNullable: (input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any) => ReturnType

| undefined; + _denormalizeNullable: (input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any) => ReturnType

| undefined; _normalizeNullable: () => NormalizeNullable; } type ProcessParameters = P extends (entries: any, ...args: infer Par) => any ? Par extends [] ? SchemaArgs : Par & SchemaArgs : SchemaArgs; @@ -542,7 +546,7 @@ interface CollectionInterface any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any): string; + normalize(input: any, parent: Parent, key: string, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: GetEntity, checkLoop: CheckLoop): string; /** Creates new instance copying over defined values of arguments * * @see https://dataclient.io/docs/api/Collection#merge @@ -590,7 +594,7 @@ interface CollectionInterface any | undefined; - denormalize(input: any, args: readonly any[], unvisit: (input: any, schema: any) => any): ReturnType; + denormalize(input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): ReturnType; _denormalizeNullable(): ReturnType; _normalizeNullable(): ReturnType; /** Schema to place at the *end* of this Collection @@ -650,11 +654,11 @@ declare class Array$1 implements SchemaClass { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): @@ -668,7 +672,7 @@ declare class Array$1 implements SchemaClass { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): (S extends EntityMap ? T : Denormalize)[]; queryKey( @@ -707,11 +711,11 @@ declare class All< input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): @@ -725,7 +729,7 @@ declare class All< denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): (S extends EntityMap ? T : Denormalize)[]; queryKey( @@ -755,11 +759,11 @@ declare class Object$1 = Record> input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): NormalizeObject; _normalizeNullable(): NormalizedNullableObject; @@ -769,7 +773,7 @@ declare class Object$1 = Record> denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): DenormalizeObject; queryKey( @@ -845,11 +849,11 @@ interface UnionInstance< input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): UnionResult; _normalizeNullable(): UnionResult | undefined; @@ -861,7 +865,7 @@ interface UnionInstance< denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): AbstractInstanceType; queryKey( @@ -919,11 +923,11 @@ declare class Values implements SchemaClass { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): Record< string, Choices extends EntityMap ? UnionResult : Normalize @@ -946,7 +950,7 @@ declare class Values implements SchemaClass { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): Record< string, Choices extends EntityMap ? T : Denormalize @@ -1121,7 +1125,7 @@ declare abstract class Entity extends Entity_base { * @see https://dataclient.io/rest/api/Entity#process */ static process(input: any, parent: any, key: string | undefined, args: any[]): any; - static denormalize: (this: T, input: any, args: readonly any[], unvisit: (input: any, schema: any) => any) => AbstractInstanceType; + static denormalize: (this: T, input: any, args: readonly any[], unvisit: (schema: any, input: any) => any) => AbstractInstanceType; } declare function validateRequired(processedEntity: any, requiredDefaults: Record): string | undefined; 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 15553a96df2c..4d2b91a63cfc 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 @@ -138,8 +138,8 @@ type Serializable = (value: any) => T; interface SchemaSimple { - normalize(input: any, parent: any, key: any, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any[]): any; - denormalize(input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any): T; + normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; + denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; queryKey(args: Args, queryKey: (...args: any) => any, getEntity: GetEntity, getIndex: GetIndex): any; } interface SchemaClass extends SchemaSimple { @@ -163,12 +163,16 @@ interface PolymorphicInterface extends Sche _normalizeNullable(): any; _denormalizeNullable(): 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): { + (entityKey: string | symbol): { readonly [pk: string]: any; } | undefined; - (entityKey: string, pk: string | number): any; + (entityKey: string | symbol, pk: string | number): any; } /** Get PK using an Entity Index */ interface GetIndex { @@ -273,26 +277,6 @@ declare let Endpoint: EndpointConstructor; declare let ExtendableEndpoint: ExtendableEndpointConstructor; -type Constructor = abstract new (...args: any[]) => {}; -type IDClass = abstract new (...args: any[]) => { - id: string | number | undefined; -}; -type PKClass = abstract new (...args: any[]) => { - pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; -}; -type ValidSchemas = { - [k in keyof TInstance]?: Schema; -}; -type EntityOptions = { - readonly schema?: ValidSchemas; - readonly pk?: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; - readonly key?: string; -} & { - readonly [K in Extract]?: IEntityClass TInstance>[K]; -}; -interface RequiredPKOptions extends EntityOptions { - readonly pk: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; -} interface IEntityClass { toJSON(): { name: string; @@ -400,7 +384,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#process */ process(input: any, parent: any, key: string | undefined, args: any[]): any; - normalize(input: any, parent: any, key: string | undefined, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record): any; + normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#validate @@ -411,7 +395,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#queryKey */ queryKey(args: readonly any[], queryKey: any, getEntity: GetEntity, getIndex: GetIndex): any; - denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, args: readonly any[], unvisit: (input: any, schema: any) => any): AbstractInstanceType; + denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): AbstractInstanceType; /** All instance defaults set */ readonly defaults: any; } @@ -425,6 +409,26 @@ interface IEntityInstance { */ pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; } +type Constructor = abstract new (...args: any[]) => {}; +type IDClass = abstract new (...args: any[]) => { + id: string | number | undefined; +}; +type PKClass = abstract new (...args: any[]) => { + pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; +}; +type ValidSchemas = { + [k in keyof TInstance]?: Schema; +}; +type EntityOptions = { + readonly schema?: ValidSchemas; + readonly pk?: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; + readonly key?: string; +} & { + readonly [K in Extract]?: IEntityClass TInstance>[K]; +}; +interface RequiredPKOptions extends EntityOptions { + readonly pk: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; +} /** * Marks entity as Invalid. @@ -447,7 +451,7 @@ declare class Invalidate any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: Record, args?: any[]): string | number | undefined; + normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: any, checkLoop: any): string | number | undefined; merge(existing: any, incoming: any): any; mergeWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; mergeMetaWithStore(existingMeta: { @@ -465,7 +469,7 @@ declare class Invalidate any): AbstractInstanceType; + denormalize(id: string, args: readonly any[], unvisit: (schema: any, input: any) => any): AbstractInstanceType; _denormalizeNullable(): AbstractInstanceType | undefined; _normalizeNullable(): string | undefined; } @@ -487,7 +491,7 @@ declare class Query, ... normalize(...args: any): any; denormalize(input: {}, args: any, unvisit: any): ReturnType

; queryKey(args: ProcessParameters, queryKey: (schema: any, args: any, getEntity: GetEntity, getIndex: GetIndex) => any, getEntity: GetEntity, getIndex: GetIndex): any; - _denormalizeNullable: (input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any) => ReturnType

| undefined; + _denormalizeNullable: (input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any) => ReturnType

| undefined; _normalizeNullable: () => NormalizeNullable; } type ProcessParameters = P extends (entries: any, ...args: infer Par) => any ? Par extends [] ? SchemaArgs : Par & SchemaArgs : SchemaArgs; @@ -542,7 +546,7 @@ interface CollectionInterface any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any): string; + normalize(input: any, parent: Parent, key: string, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: GetEntity, checkLoop: CheckLoop): string; /** Creates new instance copying over defined values of arguments * * @see https://dataclient.io/docs/api/Collection#merge @@ -590,7 +594,7 @@ interface CollectionInterface any | undefined; - denormalize(input: any, args: readonly any[], unvisit: (input: any, schema: any) => any): ReturnType; + denormalize(input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): ReturnType; _denormalizeNullable(): ReturnType; _normalizeNullable(): ReturnType; /** Schema to place at the *end* of this Collection @@ -650,11 +654,11 @@ declare class Array$1 implements SchemaClass { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): @@ -668,7 +672,7 @@ declare class Array$1 implements SchemaClass { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): (S extends EntityMap ? T : Denormalize)[]; queryKey( @@ -707,11 +711,11 @@ declare class All< input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): @@ -725,7 +729,7 @@ declare class All< denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): (S extends EntityMap ? T : Denormalize)[]; queryKey( @@ -755,11 +759,11 @@ declare class Object$1 = Record> input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): NormalizeObject; _normalizeNullable(): NormalizedNullableObject; @@ -769,7 +773,7 @@ declare class Object$1 = Record> denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): DenormalizeObject; queryKey( @@ -845,11 +849,11 @@ interface UnionInstance< input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): UnionResult; _normalizeNullable(): UnionResult | undefined; @@ -861,7 +865,7 @@ interface UnionInstance< denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): AbstractInstanceType; queryKey( @@ -919,11 +923,11 @@ declare class Values implements SchemaClass { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): Record< string, Choices extends EntityMap ? UnionResult : Normalize @@ -946,7 +950,7 @@ declare class Values implements SchemaClass { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): Record< string, Choices extends EntityMap ? T : Denormalize @@ -1121,7 +1125,7 @@ declare abstract class Entity extends Entity_base { * @see https://dataclient.io/rest/api/Entity#process */ static process(input: any, parent: any, key: string | undefined, args: any[]): any; - static denormalize: (this: T, input: any, args: readonly any[], unvisit: (input: any, schema: any) => any) => AbstractInstanceType; + static denormalize: (this: T, input: any, args: readonly any[], unvisit: (schema: any, input: any) => any) => AbstractInstanceType; } declare function validateRequired(processedEntity: any, requiredDefaults: Record): string | undefined; 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 b0fb1e2917d3..e9afaa1668cc 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 @@ -10,8 +10,8 @@ type Serializable = (value: any) => T; interface SchemaSimple { - normalize(input: any, parent: any, key: any, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any[]): any; - denormalize(input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any): T; + normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; + denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; queryKey(args: Args, queryKey: (...args: any) => any, getEntity: GetEntity, getIndex: GetIndex): any; } interface SchemaClass extends SchemaSimple { @@ -42,12 +42,20 @@ interface EntityTable { [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): { + (entityKey: string | symbol): { readonly [pk: string]: any; } | undefined; - (entityKey: string, pk: string | number): any; + (entityKey: string | symbol, pk: string | number): any; } /** Get PK using an Entity Index */ interface GetIndex { @@ -128,7 +136,7 @@ type SchemaArgs = S extends EntityInterface ? [Ent queryKey(args: infer Args, queryKey: (...args: any) => any, getEntity: any, getIndex: any): any; }) ? Args : never; -declare function denormalize(input: any, schema: S | undefined, entities: any, args?: readonly any[]): DenormalizeNullable | symbol; +declare function denormalize(schema: S | undefined, input: any, entities: any, args?: readonly any[]): DenormalizeNullable | symbol; declare function isEntity(schema: Schema): schema is EntityInterface; @@ -151,19 +159,26 @@ interface Dep { entity: K; } -declare const normalize: | undefined> = Record>, R = NormalizeNullable>(input: any, schema?: S, args?: any[], storeEntities?: Readonly, storeIndexes?: Readonly, storeEntityMeta?: { - readonly [entityKey: string]: { - readonly [pk: string]: { - readonly date: number; - readonly expiresAt: number; - readonly fetchedAt: number; +interface StoreData { + entities: Readonly; + indexes: Readonly; + entityMeta: { + readonly [entityKey: string]: { + readonly [pk: string]: { + readonly date: number; + readonly expiresAt: number; + readonly fetchedAt: number; + }; }; }; -}, meta?: { - expiresAt: number; - date: number; - fetchedAt: number; -}) => NormalizedSchema; +} +interface NormalizeMeta { + expiresAt?: number; + date?: number; + fetchedAt?: number; + args?: readonly any[]; +} +declare const normalize: | undefined> = Record>, R = NormalizeNullable>(schema: S | undefined, input: any, { date, expiresAt, fetchedAt, args, }?: NormalizeMeta, { entities, indexes, entityMeta }?: StoreData) => NormalizedSchema; interface EntityCache { [key: string]: { @@ -181,21 +196,21 @@ declare class MemoCache { /** Caches the queryKey based on schema, args, and any used entities or indexes */ protected queryKeys: Record>; /** Compute denormalized form maintaining referential equality for same inputs */ - denormalize(input: unknown, schema: S | undefined, entities: any, args?: readonly any[]): { + denormalize(schema: S | undefined, input: unknown, entities: any, args?: readonly any[]): { data: DenormalizeNullable | symbol; paths: EntityPath[]; }; /** Compute denormalized form maintaining referential equality for same inputs */ - query(argsKey: string, schema: S, args: any[], entities: Record> | { + query(schema: S, args: readonly any[], entities: Record> | { getIn(k: string[]): any; }, indexes: NormalizedIndex | { getIn(k: string[]): any; - }): DenormalizeNullable | undefined; - buildQueryKey(argsKey: string, schema: S, args: any[], entities: Record> | { + }, argsKey?: string): DenormalizeNullable | undefined; + buildQueryKey(schema: S, args: readonly any[], entities: Record> | { getIn(k: string[]): any; }, indexes: NormalizedIndex | { getIn(k: string[]): any; - }): NormalizeNullable; + }, argsKey?: string): NormalizeNullable; } type IndexPath = [key: string, field: string, value: string]; type EntitySchemaPath = [key: string] | [key: string, pk: string]; @@ -300,4 +315,4 @@ declare const INVALID: unique symbol; declare function validateQueryKey(queryKey: unknown): boolean; -export { AbstractInstanceType, ArrayElement, Denormalize, DenormalizeNullable, EndpointExtraOptions, EndpointInterface, EntityInterface, EntityPath, EntityTable, ErrorTypes, ExpiryStatus, ExpiryStatusInterface, FetchFunction, GetEntity, GetIndex, INVALID, IndexInterface, IndexParams, InferReturn, MemoCache, MutateEndpoint, NI, NetworkError, Normalize, NormalizeNullable, NormalizeReturnType, NormalizedIndex, NormalizedSchema, OptimisticUpdateParams, Queryable, ReadEndpoint, ResolveType, Schema, SchemaArgs, SchemaClass, SchemaSimple, Serializable, SnapshotInterface, UnknownError, UpdateFunction, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; +export { AbstractInstanceType, ArrayElement, CheckLoop, Denormalize, DenormalizeNullable, EndpointExtraOptions, EndpointInterface, EntityInterface, EntityPath, EntityTable, ErrorTypes, ExpiryStatus, ExpiryStatusInterface, FetchFunction, GetEntity, GetIndex, INVALID, IndexInterface, IndexParams, InferReturn, MemoCache, MutateEndpoint, NI, NetworkError, Normalize, NormalizeNullable, NormalizeReturnType, NormalizedIndex, NormalizedSchema, OptimisticUpdateParams, Queryable, ReadEndpoint, ResolveType, Schema, SchemaArgs, SchemaClass, SchemaSimple, Serializable, SnapshotInterface, UnknownError, UpdateFunction, Visit, WeakDependencyMap, denormalize, isEntity, normalize, validateQueryKey }; diff --git a/website/src/components/Playground/editor-types/@data-client/react.d.ts b/website/src/components/Playground/editor-types/@data-client/react.d.ts index 3999122c8784..001924f614da 100644 --- a/website/src/components/Playground/editor-types/@data-client/react.d.ts +++ b/website/src/components/Playground/editor-types/@data-client/react.d.ts @@ -226,8 +226,8 @@ type Serializable = (value: any) => T; interface SchemaSimple { - normalize(input: any, parent: any, key: any, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any[]): any; - denormalize(input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any): T; + normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; + denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; queryKey(args: Args, queryKey: (...args: any) => any, getEntity: GetEntity, getIndex: GetIndex): any; } interface EntityInterface extends SchemaSimple { @@ -244,10 +244,10 @@ interface EntityInterface extends SchemaSimple { } /** Get Array of entities with map function applied */ interface GetEntity { - (entityKey: string): { + (entityKey: string | symbol): { readonly [pk: string]: any; } | undefined; - (entityKey: string, pk: string | number): any; + (entityKey: string | symbol, pk: string | number): any; } /** Get PK using an Entity Index */ interface GetIndex { 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 be90cb3a872b..4425e7dcb1f1 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 @@ -140,8 +140,8 @@ type Serializable = (value: any) => T; interface SchemaSimple { - normalize(input: any, parent: any, key: any, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any[]): any; - denormalize(input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any): T; + normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; + denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; queryKey(args: Args, queryKey: (...args: any) => any, getEntity: GetEntity, getIndex: GetIndex): any; } interface SchemaClass extends SchemaSimple { @@ -165,12 +165,16 @@ interface PolymorphicInterface extends Sche _normalizeNullable(): any; _denormalizeNullable(): 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): { + (entityKey: string | symbol): { readonly [pk: string]: any; } | undefined; - (entityKey: string, pk: string | number): any; + (entityKey: string | symbol, pk: string | number): any; } /** Get PK using an Entity Index */ interface GetIndex { @@ -271,26 +275,6 @@ declare let Endpoint: EndpointConstructor; declare let ExtendableEndpoint: ExtendableEndpointConstructor; -type Constructor = abstract new (...args: any[]) => {}; -type IDClass = abstract new (...args: any[]) => { - id: string | number | undefined; -}; -type PKClass = abstract new (...args: any[]) => { - pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; -}; -type ValidSchemas = { - [k in keyof TInstance]?: Schema; -}; -type EntityOptions = { - readonly schema?: ValidSchemas; - readonly pk?: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; - readonly key?: string; -} & { - readonly [K in Extract]?: IEntityClass TInstance>[K]; -}; -interface RequiredPKOptions extends EntityOptions { - readonly pk: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; -} interface IEntityClass { toJSON(): { name: string; @@ -398,7 +382,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#process */ process(input: any, parent: any, key: string | undefined, args: any[]): any; - normalize(input: any, parent: any, key: string | undefined, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record): any; + normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#validate @@ -409,7 +393,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#queryKey */ queryKey(args: readonly any[], queryKey: any, getEntity: GetEntity, getIndex: GetIndex): any; - denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, args: readonly any[], unvisit: (input: any, schema: any) => any): AbstractInstanceType; + denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): AbstractInstanceType; /** All instance defaults set */ readonly defaults: any; } @@ -423,6 +407,26 @@ interface IEntityInstance { */ pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; } +type Constructor = abstract new (...args: any[]) => {}; +type IDClass = abstract new (...args: any[]) => { + id: string | number | undefined; +}; +type PKClass = abstract new (...args: any[]) => { + pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; +}; +type ValidSchemas = { + [k in keyof TInstance]?: Schema; +}; +type EntityOptions = { + readonly schema?: ValidSchemas; + readonly pk?: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; + readonly key?: string; +} & { + readonly [K in Extract]?: IEntityClass TInstance>[K]; +}; +interface RequiredPKOptions extends EntityOptions { + readonly pk: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; +} /** * Marks entity as Invalid. @@ -445,7 +449,7 @@ declare class Invalidate any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: Record, args?: any[]): string | number | undefined; + normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: any, checkLoop: any): string | number | undefined; merge(existing: any, incoming: any): any; mergeWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; mergeMetaWithStore(existingMeta: { @@ -463,7 +467,7 @@ declare class Invalidate any): AbstractInstanceType; + denormalize(id: string, args: readonly any[], unvisit: (schema: any, input: any) => any): AbstractInstanceType; _denormalizeNullable(): AbstractInstanceType | undefined; _normalizeNullable(): string | undefined; } @@ -485,7 +489,7 @@ declare class Query, ... normalize(...args: any): any; denormalize(input: {}, args: any, unvisit: any): ReturnType

; queryKey(args: ProcessParameters, queryKey: (schema: any, args: any, getEntity: GetEntity, getIndex: GetIndex) => any, getEntity: GetEntity, getIndex: GetIndex): any; - _denormalizeNullable: (input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any) => ReturnType

| undefined; + _denormalizeNullable: (input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any) => ReturnType

| undefined; _normalizeNullable: () => NormalizeNullable; } type ProcessParameters = P extends (entries: any, ...args: infer Par) => any ? Par extends [] ? SchemaArgs : Par & SchemaArgs : SchemaArgs; @@ -540,7 +544,7 @@ interface CollectionInterface any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any): string; + normalize(input: any, parent: Parent, key: string, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: GetEntity, checkLoop: CheckLoop): string; /** Creates new instance copying over defined values of arguments * * @see https://dataclient.io/docs/api/Collection#merge @@ -588,7 +592,7 @@ interface CollectionInterface any | undefined; - denormalize(input: any, args: readonly any[], unvisit: (input: any, schema: any) => any): ReturnType; + denormalize(input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): ReturnType; _denormalizeNullable(): ReturnType; _normalizeNullable(): ReturnType; /** Schema to place at the *end* of this Collection @@ -648,11 +652,11 @@ declare class Array$1 implements SchemaClass { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): @@ -666,7 +670,7 @@ declare class Array$1 implements SchemaClass { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): (S extends EntityMap ? T : Denormalize)[]; queryKey( @@ -705,11 +709,11 @@ declare class All< input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): @@ -723,7 +727,7 @@ declare class All< denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): (S extends EntityMap ? T : Denormalize)[]; queryKey( @@ -753,11 +757,11 @@ declare class Object$1 = Record> input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): NormalizeObject; _normalizeNullable(): NormalizedNullableObject; @@ -767,7 +771,7 @@ declare class Object$1 = Record> denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): DenormalizeObject; queryKey( @@ -843,11 +847,11 @@ interface UnionInstance< input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): UnionResult; _normalizeNullable(): UnionResult | undefined; @@ -859,7 +863,7 @@ interface UnionInstance< denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): AbstractInstanceType; queryKey( @@ -917,11 +921,11 @@ declare class Values implements SchemaClass { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): Record< string, Choices extends EntityMap ? UnionResult : Normalize @@ -944,7 +948,7 @@ declare class Values implements SchemaClass { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): Record< string, Choices extends EntityMap ? T : Denormalize @@ -1119,7 +1123,7 @@ declare abstract class Entity extends Entity_base { * @see https://dataclient.io/rest/api/Entity#process */ static process(input: any, parent: any, key: string | undefined, args: any[]): any; - static denormalize: (this: T, input: any, args: readonly any[], unvisit: (input: any, schema: any) => any) => AbstractInstanceType; + static denormalize: (this: T, input: any, args: readonly any[], unvisit: (schema: any, input: any) => any) => AbstractInstanceType; } declare function validateRequired(processedEntity: any, requiredDefaults: Record): string | undefined; diff --git a/website/src/components/Playground/editor-types/globals.d.ts b/website/src/components/Playground/editor-types/globals.d.ts index d261aa400b54..5f35fa166293 100644 --- a/website/src/components/Playground/editor-types/globals.d.ts +++ b/website/src/components/Playground/editor-types/globals.d.ts @@ -144,8 +144,8 @@ type Serializable = (value: any) => T; interface SchemaSimple { - normalize(input: any, parent: any, key: any, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any[]): any; - denormalize(input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any): T; + normalize(input: any, parent: any, key: any, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; + denormalize(input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any): T; queryKey(args: Args, queryKey: (...args: any) => any, getEntity: GetEntity, getIndex: GetIndex): any; } interface SchemaClass extends SchemaSimple { @@ -169,12 +169,16 @@ interface PolymorphicInterface extends Sche _normalizeNullable(): any; _denormalizeNullable(): 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): { + (entityKey: string | symbol): { readonly [pk: string]: any; } | undefined; - (entityKey: string, pk: string | number): any; + (entityKey: string | symbol, pk: string | number): any; } /** Get PK using an Entity Index */ interface GetIndex { @@ -275,26 +279,6 @@ declare let Endpoint: EndpointConstructor; declare let ExtendableEndpoint: ExtendableEndpointConstructor; -type Constructor = abstract new (...args: any[]) => {}; -type IDClass = abstract new (...args: any[]) => { - id: string | number | undefined; -}; -type PKClass = abstract new (...args: any[]) => { - pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; -}; -type ValidSchemas = { - [k in keyof TInstance]?: Schema; -}; -type EntityOptions = { - readonly schema?: ValidSchemas; - readonly pk?: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; - readonly key?: string; -} & { - readonly [K in Extract]?: IEntityClass TInstance>[K]; -}; -interface RequiredPKOptions extends EntityOptions { - readonly pk: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; -} interface IEntityClass { toJSON(): { name: string; @@ -402,7 +386,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#process */ process(input: any, parent: any, key: string | undefined, args: any[]): any; - normalize(input: any, parent: any, key: string | undefined, visit: (...args: any) => any, addEntity: (...args: any) => any, visitedEntities: Record): any; + normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: (...args: any) => any, checkLoop: (...args: any) => any): any; /** Do any transformations when first receiving input * * @see https://dataclient.io/rest/api/Entity#validate @@ -413,7 +397,7 @@ interface IEntityClass { * @see https://dataclient.io/rest/api/Entity#queryKey */ queryKey(args: readonly any[], queryKey: any, getEntity: GetEntity, getIndex: GetIndex): any; - denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, args: readonly any[], unvisit: (input: any, schema: any) => any): AbstractInstanceType; + denormalize IEntityInstance & InstanceType) & IEntityClass & TBase>(this: T, input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): AbstractInstanceType; /** All instance defaults set */ readonly defaults: any; } @@ -427,6 +411,26 @@ interface IEntityInstance { */ pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; } +type Constructor = abstract new (...args: any[]) => {}; +type IDClass = abstract new (...args: any[]) => { + id: string | number | undefined; +}; +type PKClass = abstract new (...args: any[]) => { + pk(parent?: any, key?: string, args?: readonly any[]): string | number | undefined; +}; +type ValidSchemas = { + [k in keyof TInstance]?: Schema; +}; +type EntityOptions = { + readonly schema?: ValidSchemas; + readonly pk?: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; + readonly key?: string; +} & { + readonly [K in Extract]?: IEntityClass TInstance>[K]; +}; +interface RequiredPKOptions extends EntityOptions { + readonly pk: ((value: TInstance, parent?: any, key?: string) => string | number | undefined) | keyof TInstance; +} /** * Marks entity as Invalid. @@ -449,7 +453,7 @@ declare class Invalidate any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: Record, args?: any[]): string | number | undefined; + normalize(input: any, parent: any, key: string | undefined, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: any, checkLoop: any): string | number | undefined; merge(existing: any, incoming: any): any; mergeWithStore(existingMeta: any, incomingMeta: any, existing: any, incoming: any): any; mergeMetaWithStore(existingMeta: { @@ -467,7 +471,7 @@ declare class Invalidate any): AbstractInstanceType; + denormalize(id: string, args: readonly any[], unvisit: (schema: any, input: any) => any): AbstractInstanceType; _denormalizeNullable(): AbstractInstanceType | undefined; _normalizeNullable(): string | undefined; } @@ -489,7 +493,7 @@ declare class Query, ... normalize(...args: any): any; denormalize(input: {}, args: any, unvisit: any): ReturnType

; queryKey(args: ProcessParameters, queryKey: (schema: any, args: any, getEntity: GetEntity, getIndex: GetIndex) => any, getEntity: GetEntity, getIndex: GetIndex): any; - _denormalizeNullable: (input: {}, args: readonly any[], unvisit: (input: any, schema: any) => any) => ReturnType

| undefined; + _denormalizeNullable: (input: {}, args: readonly any[], unvisit: (schema: any, input: any) => any) => ReturnType

| undefined; _normalizeNullable: () => NormalizeNullable; } type ProcessParameters = P extends (entries: any, ...args: infer Par) => any ? Par extends [] ? SchemaArgs : Par & SchemaArgs : SchemaArgs; @@ -544,7 +548,7 @@ interface CollectionInterface any, addEntity: (...args: any) => any, visitedEntities: Record, storeEntities: any, args: any): string; + normalize(input: any, parent: Parent, key: string, args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, getEntity: GetEntity, checkLoop: CheckLoop): string; /** Creates new instance copying over defined values of arguments * * @see https://dataclient.io/docs/api/Collection#merge @@ -592,7 +596,7 @@ interface CollectionInterface any | undefined; - denormalize(input: any, args: readonly any[], unvisit: (input: any, schema: any) => any): ReturnType; + denormalize(input: any, args: readonly any[], unvisit: (schema: any, input: any) => any): ReturnType; _denormalizeNullable(): ReturnType; _normalizeNullable(): ReturnType; /** Schema to place at the *end* of this Collection @@ -652,11 +656,11 @@ declare class Array$1 implements SchemaClass { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): @@ -670,7 +674,7 @@ declare class Array$1 implements SchemaClass { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): (S extends EntityMap ? T : Denormalize)[]; queryKey( @@ -709,11 +713,11 @@ declare class All< input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): (S extends EntityMap ? UnionResult : Normalize)[]; _normalizeNullable(): @@ -727,7 +731,7 @@ declare class All< denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): (S extends EntityMap ? T : Denormalize)[]; queryKey( @@ -757,11 +761,11 @@ declare class Object$1 = Record> input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): NormalizeObject; _normalizeNullable(): NormalizedNullableObject; @@ -771,7 +775,7 @@ declare class Object$1 = Record> denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): DenormalizeObject; queryKey( @@ -847,11 +851,11 @@ interface UnionInstance< input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): UnionResult; _normalizeNullable(): UnionResult | undefined; @@ -863,7 +867,7 @@ interface UnionInstance< denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): AbstractInstanceType; queryKey( @@ -921,11 +925,11 @@ declare class Values implements SchemaClass { input: any, parent: any, key: any, + args: any[], visit: (...args: any) => any, addEntity: (...args: any) => any, - visitedEntities: Record, - storeEntities: any, - args?: any[], + getEntity: GetEntity, + checkLoop: CheckLoop, ): Record< string, Choices extends EntityMap ? UnionResult : Normalize @@ -948,7 +952,7 @@ declare class Values implements SchemaClass { denormalize( input: {}, args: readonly any[], - unvisit: (input: any, schema: any) => any, + unvisit: (schema: any, input: any) => any, ): Record< string, Choices extends EntityMap ? T : Denormalize @@ -1123,7 +1127,7 @@ declare abstract class Entity extends Entity_base { * @see https://dataclient.io/rest/api/Entity#process */ static process(input: any, parent: any, key: string | undefined, args: any[]): any; - static denormalize: (this: T, input: any, args: readonly any[], unvisit: (input: any, schema: any) => any) => AbstractInstanceType; + static denormalize: (this: T, input: any, args: readonly any[], unvisit: (schema: any, input: any) => any) => AbstractInstanceType; } declare function validateRequired(processedEntity: any, requiredDefaults: Record): string | undefined; diff --git a/yarn.lock b/yarn.lock index 96e178d25abd..8af1761fc145 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3201,7 +3201,7 @@ __metadata: "@types/node": "npm:^20.0.0" "@types/react": "npm:^18.0.30" peerDependencies: - "@data-client/react": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 + "@data-client/react": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0 "@types/react": ^16.14.0 || ^17.0.0 || ^18.0.0-0 || ^19.0.0 react: ^16.14.0 || ^17.0.0 || ^18.0.0-0 || ^19.0.0 peerDependenciesMeta: @@ -3290,8 +3290,8 @@ __metadata: react-dom: "npm:^18.2.0" redux: "npm:^5.0.0" peerDependencies: - "@data-client/react": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 - "@data-client/redux": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 + "@data-client/react": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0 + "@data-client/redux": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0 "@types/react": ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 next: ">=12.0.0" react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3321,7 +3321,7 @@ __metadata: "@types/react-test-renderer": "npm:^18" jest: "npm:^29.5.0" peerDependencies: - "@data-client/react": ^0.12.15 || ^0.13.0 + "@data-client/react": ^0.12.15 || ^0.13.0 || ^0.14.0 "@testing-library/react-hooks": ^8.0.0 "@types/react": ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-0 || ^19.0.0 "@types/react-dom": "*"