Skip to content

Commit

Permalink
feat: Use Querable schemas in useCache()
Browse files Browse the repository at this point in the history
  • Loading branch information
ntucker committed Jan 19, 2024
1 parent ee85e04 commit ff915e7
Show file tree
Hide file tree
Showing 15 changed files with 108 additions and 67 deletions.
21 changes: 11 additions & 10 deletions docs/core/shared/_schema_table.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
| Schema | Description | Data Type | Mutable | Has A |
| ---------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------------- |
| [Entity](/rest/api/Entity) | single _unique_ object | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) || |
| [Object](/rest/api/Object) | statically known keys | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) | 🛑 | |
| [Array](/rest/api/Array) | lists of any size | [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) | 🛑 | |
| [Values](/rest/api/Values) | maps of any size | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) | 🛑 | |
| [All](/rest/api/All) | list of all entities of a kind | [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) | 🛑 | Entity |
| [Collection](/rest/api/Collection) | enables adding new items | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) or [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) || Array or Values |
| [Union](/rest/api/Union) | one of many different types (`A \| B`) | Polymorphic [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) || Entity |
| [Invalidate](/rest/api/Invalidate) | [remove an entity](../concepts/expiry-policy.md#any-endpoint-with-an-entity) | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) || Entity |
| Schema | Description | Data Type | Mutable | Queryable | Of A |
| ---------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------- | --------- |
| [Entity](/rest/api/Entity) | single _unique_ object | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) ||| |
| [Object](/rest/api/Object) | statically known keys | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) | 🛑 | 🛑 | |
| [Array](/rest/api/Array) | lists of any size | [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) | 🛑 | 🛑 | |
| [Values](/rest/api/Values) | maps of any size | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) | 🛑 | 🛑 | |
| [All](/rest/api/All) | list of all entities of a kind | [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) | 🛑 || |
| [Collection](/rest/api/Collection) | enables adding new items | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) or [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) ||| |
| [Query](/rest/api/Query) | memoized custom transforms | any | 🛑 || Queryable |
| [Union](/rest/api/Union) | one of many different types (`A \| B`) | Polymorphic [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) ||| Entity |
| [Invalidate](/rest/api/Invalidate) | [remove an entity](../concepts/expiry-policy.md#any-endpoint-with-an-entity) | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) || 🛑 | Entity |
4 changes: 4 additions & 0 deletions packages/endpoint/src-4.0-types/schemas/EntityFields.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** Attempts to infer reasonable input type to construct an Entity */
export type EntityFields<U> = {
readonly [K in keyof U]?: U[K];
};
2 changes: 1 addition & 1 deletion packages/endpoint/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export type {
NormalizeNullable,
Denormalize,
DenormalizeNullable,
SchemaToArgs,
SchemaArgs,
} from './normal.js';
export type {
EndpointExtraOptions,
Expand Down
26 changes: 20 additions & 6 deletions packages/endpoint/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ export type Schema =
| SchemaSimple
| Serializable;

export type Queryable = {
infer(
args: readonly any[],
indexes: NormalizedIndex,
recurse: (...args: any) => any,
entities: EntityTable,
// Must be non-void
): {};
};

export type Serializable<
T extends { toJSON(): string } = { toJSON(): string },
> = (value: any) => T;
Expand All @@ -23,30 +33,33 @@ export interface SchemaSimple<T = any, Args extends any[] = any[]> {
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities: any,
args: Args,
args: any[],
): any;
denormalize(
input: {},
args: readonly any[],
unvisit: (input: any, schema: any) => any,
): T;
infer(
args: readonly any[],
args: Args,
indexes: NormalizedIndex,
recurse: (...args: any) => any,
entities: EntityTable,
): any;
}

export interface SchemaClass<T = any, N = T | undefined>
extends SchemaSimple<T> {
export interface SchemaClass<
T = any,
N = T | undefined,
Args extends any[] = any[],
> extends SchemaSimple<T, Args> {
// this is not an actual member, but is needed for the recursive NormalizeNullable<> type algo
_normalizeNullable(): any;
// this is not an actual member, but is needed for the recursive DenormalizeNullable<> type algo
_denormalizeNullable(): N;
}

export interface EntityInterface<T = any> extends SchemaSimple {
export interface EntityInterface<T = any> extends SchemaSimple<T> {
createIfValid(props: any): any;
pk(params: any, parent?: any, key?: string, args?: any[]): string | undefined;
readonly key: string;
Expand All @@ -69,7 +82,8 @@ export interface EntityInterface<T = any> extends SchemaSimple {
}

/** Represents Array or Values */
export interface PolymorphicInterface<T = any> extends SchemaSimple<T> {
export interface PolymorphicInterface<T = any, Args extends any[] = any[]>
extends SchemaSimple<T, Args> {
readonly schema: any;
// this is not an actual member, but is needed for the recursive NormalizeNullable<> type algo
_normalizeNullable(): any;
Expand Down
34 changes: 10 additions & 24 deletions packages/endpoint/src/normal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import type {
Serializable,
EntityInterface,
NormalizedIndex,
Queryable,
} from './interface.js';
import { EntityFields } from './schemas/EntityFields.js';

// TypeScript <4.2 InstanceType<> does not work on abstract classes
export type AbstractInstanceType<T> =
Expand Down Expand Up @@ -117,33 +119,17 @@ export interface EntityMap<T = any> {
readonly [k: string]: EntityInterface<T>;
}

export type SchemaToArgs<
S extends {
normalize(
input: any,
parent: any,
key: any,
visit: (...args: any) => any,
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities: any,
args: any,
): any;
},
> =
S extends (
export type SchemaArgs<S extends Queryable> =
S extends EntityInterface<infer U> ? EntityFields<U>
: S extends (
{
normalize(
input: any,
parent: any,
key: any,
visit: (...args: any) => any,
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities: any,
infer(
args: infer Args,
indexes: any,
recurse: (...args: any) => any,
entities: any,
): any;
}
) ?
Args
: never;
: [];
4 changes: 2 additions & 2 deletions packages/endpoint/src/queryEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import type {
NormalizedIndex,
SchemaSimple,
} from './interface.js';
import type { Denormalize, SchemaToArgs } from './normal.js';
import type { Denormalize, SchemaArgs } from './normal.js';

/**
* Programmatic cache reading
* @see https://dataclient.io/rest/api/Query
*/
export class Query<
S extends SchemaSimple,
P extends SchemaToArgs<S> = SchemaToArgs<S>,
P extends SchemaArgs<S> = SchemaArgs<S>,
R = Denormalize<S>,
> {
declare schema: QuerySchema<S, R>;
Expand Down
11 changes: 6 additions & 5 deletions packages/endpoint/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
NormalizedNullableObject,
EntityMap,
} from './normal.js';
import { EntityFields } from './schemas/EntityFields.js';
import {
EntityOptions,
IEntityClass,
Expand Down Expand Up @@ -66,7 +67,7 @@ export class Array<S extends Schema = Schema> implements SchemaClass {
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities: any,
args?: any[],
args: any[],
): (S extends EntityMap ? UnionResult<S> : Normalize<S>)[];

_normalizeNullable():
Expand All @@ -88,7 +89,7 @@ export class Array<S extends Schema = Schema> implements SchemaClass {
indexes: NormalizedIndex,
recurse: (...args: any) => any,
entities: any,
): any;
): undefined;
}

/**
Expand Down Expand Up @@ -136,7 +137,7 @@ export class All<
): (S extends EntityMap<infer T> ? T : Denormalize<S>)[];

infer(
args: readonly any[],
args: [],
indexes: NormalizedIndex,
recurse: (...args: any) => any,
entities: EntityTable,
Expand Down Expand Up @@ -222,7 +223,7 @@ export class Union<Choices extends EntityMap = any> implements SchemaClass {
): AbstractInstanceType<Choices[keyof Choices]>;

infer(
args: readonly any[],
args: [EntityFields<AbstractInstanceType<Choices[keyof Choices]>>],
indexes: NormalizedIndex,
recurse: (...args: any) => any,
entities: any,
Expand Down Expand Up @@ -293,7 +294,7 @@ export class Values<Choices extends Schema = any> implements SchemaClass {
indexes: NormalizedIndex,
recurse: (...args: any) => any,
entities: any,
): any;
): undefined;
}

export type CollectionArrayAdder<S extends PolymorphicInterface> =
Expand Down
9 changes: 2 additions & 7 deletions packages/endpoint/src/schemaTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface CollectionInterface<
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities: any,
args: Args,
args: any,
): string;

merge(existing: any, incoming: any): any;
Expand Down Expand Up @@ -99,12 +99,7 @@ export interface CollectionInterface<
fetchedAt: number;
};

infer(
args: unknown,
indexes: unknown,
recurse: unknown,
entities: unknown,
): any;
infer(args: Args, indexes: unknown, recurse: unknown, entities: unknown): any;

createIfValid: (value: any) => any | undefined;
denormalize(
Expand Down
4 changes: 2 additions & 2 deletions packages/endpoint/src/schemas/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export default class CollectionSchema<
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities: any,
args: Args,
args: any,
): string {
const normalizedValue = this.schema.normalize(
input,
Expand Down Expand Up @@ -218,7 +218,7 @@ export default class CollectionSchema<

// >>>>>>>>>>>>>>DENORMALIZE<<<<<<<<<<<<<<

infer(args: any, indexes: unknown, recurse: unknown, entities: any): any {
infer(args: Args, indexes: unknown, recurse: unknown, entities: any): any {
if (this.argsKey) {
const id = this.pk(undefined, undefined, '', args);
if (entities[this.key]?.[id]) return id;
Expand Down
5 changes: 5 additions & 0 deletions packages/endpoint/src/schemas/EntityFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** Attempts to infer reasonable input type to construct an Entity */
export type EntityFields<U> = {
readonly [K in keyof U as U[K] extends (...args: any) => any ? never
: K]?: U[K];
};
2 changes: 1 addition & 1 deletion packages/endpoint/src/schemas/Invalidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default class Invalidate<

/** /End Normalize lifecycles **/

infer(args: any, indexes: any, recurse: any): any {
infer(args: any, indexes: any, recurse: any): undefined {
return undefined;
}

Expand Down
5 changes: 5 additions & 0 deletions packages/normalizr/src/EntityFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** Attempts to infer reasonable input type to construct an Entity */
export type EntityFields<U> = {
readonly [K in keyof U as U[K] extends (...args: any) => any ? never
: K]?: U[K];
};
1 change: 1 addition & 0 deletions packages/normalizr/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type {
DenormalizeNullable,
Normalize,
NormalizeNullable,
SchemaArgs,
} from './types.js';
export * from './endpoint/types.js';
export * from './interface.js';
Expand Down
29 changes: 21 additions & 8 deletions packages/normalizr/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,56 @@ export type Schema =
| SchemaSimple
| Serializable;

export type Queryable = {
infer(
args: readonly any[],
indexes: NormalizedIndex,
recurse: (...args: any) => any,
entities: EntityTable,
// Must be non-void
): {};
};

export type Serializable<
T extends { toJSON(): string } = { toJSON(): string },
> = (value: any) => T;

export interface SchemaSimple<T = any> {
export interface SchemaSimple<T = any, Args extends any[] = any[]> {
normalize(
input: any,
parent: any,
key: any,
visit: (...args: any) => any,
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities?: any,
args?: any[],
storeEntities: any,
args: any[],
): any;
denormalize(
input: {},
args: any,
args: readonly any[],
unvisit: (input: any, schema: any) => any,
): T;
infer(
args: readonly any[],
args: Args,
indexes: NormalizedIndex,
recurse: (...args: any) => any,
entities: EntityTable,
): any;
}

export interface SchemaClass<T = any, N = T | undefined>
extends SchemaSimple<T> {
export interface SchemaClass<
T = any,
N = T | undefined,
Args extends any[] = any[],
> extends SchemaSimple<T, Args> {
// this is not an actual member, but is needed for the recursive NormalizeNullable<> type algo
_normalizeNullable(): any;
// this is not an actual member, but is needed for the recursive DenormalizeNullable<> type algo
_denormalizeNullable(): N;
}

export interface EntityInterface<T = any> extends SchemaSimple {
export interface EntityInterface<T = any> extends SchemaSimple<T> {
createIfValid(props: any): any;
pk(
params: any,
Expand Down
18 changes: 17 additions & 1 deletion packages/normalizr/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { EntityFields } from './EntityFields.js';
import type {
Schema,
Serializable,
EntityInterface,
NormalizedIndex,
SchemaClass,
Queryable,
} from './interface.js';
import type WeakEntityMap from './WeakEntityMap.js';

Expand Down Expand Up @@ -132,3 +133,18 @@ export type NormalizedSchema<E, R> = {
};

export type EntityMap<T = any> = Record<string, EntityInterface<T>>;

export type SchemaArgs<S extends Queryable> =
S extends EntityInterface<infer U> ? EntityFields<U>
: S extends (
{
infer(
args: infer Args,
indexes: any,
recurse: (...args: any) => any,
entities: any,
): any;
}
) ?
Args
: [];

1 comment on commit ff915e7

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: ff915e7 Previous: 7240cf7 Ratio
normalizeLong 457 ops/sec (±0.82%) 424 ops/sec (±2.32%) 0.93
infer All 8788 ops/sec (±1.19%) 8956 ops/sec (±2.03%) 1.02
denormalizeLong 276 ops/sec (±1.77%) 287 ops/sec (±2.10%) 1.04
denormalizeLong donotcache 737 ops/sec (±0.57%) 747 ops/sec (±0.48%) 1.01
denormalizeShort donotcache 500x 1264 ops/sec (±0.14%) 1315 ops/sec (±0.43%) 1.04
denormalizeShort 500x 882 ops/sec (±1.24%) 911 ops/sec (±0.50%) 1.03
denormalizeLong with mixin Entity 277 ops/sec (±0.48%) 274 ops/sec (±0.29%) 0.99
denormalizeLong withCache 5851 ops/sec (±0.20%) 6038 ops/sec (±0.26%) 1.03
denormalizeLongAndShort withEntityCacheOnly 1473 ops/sec (±0.37%) 1518 ops/sec (±0.49%) 1.03
denormalizeLong All withCache 7125 ops/sec (±0.16%) 7102 ops/sec (±0.29%) 1.00
denormalizeLong Query-sorted withCache 6969 ops/sec (±0.22%) 7095 ops/sec (±0.31%) 1.02
getResponse 5539 ops/sec (±1.04%) 5833 ops/sec (±1.47%) 1.05
getResponse (null) 2911710 ops/sec (±0.14%) 2895891 ops/sec (±0.44%) 0.99
getResponse (clear cache) 261 ops/sec (±0.55%) 266 ops/sec (±0.59%) 1.02
getSmallResponse 2032 ops/sec (±0.15%) 2144 ops/sec (±1.22%) 1.06
getSmallInferredResponse 1781 ops/sec (±0.45%) 1790 ops/sec (±0.53%) 1.01
getResponse Query-sorted 685 ops/sec (±1.12%) 675 ops/sec (±1.49%) 0.99
getResponse Collection 5159 ops/sec (±1.20%) 5012 ops/sec (±0.95%) 0.97
setLong 452 ops/sec (±0.27%) 424 ops/sec (±2.79%) 0.94
setLongWithMerge 183 ops/sec (±0.24%) 188 ops/sec (±0.39%) 1.03
setLongWithSimpleMerge 197 ops/sec (±0.27%) 202 ops/sec (±0.53%) 1.03

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.