Skip to content

Commit

Permalink
update query work
Browse files Browse the repository at this point in the history
  • Loading branch information
ntucker committed Oct 10, 2023
1 parent ab99520 commit 2064027
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 45 deletions.
9 changes: 2 additions & 7 deletions examples/todo-app/src/resources/TodoResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ export const TodoResource = createPlaceholderResource({
});

export const queryRemainingTodos = new Query(
new schema.All(Todo),
(entries, { userId } = {}) => {
if (userId !== undefined)
return entries.filter((todo) => todo.userId === userId && !todo.completed)
.length;
return entries.filter((todo) => !todo.completed).length;
},
TodoResource.getList.schema,
(entries) => entries && entries.filter((todo) => !todo.completed).length,
);
1 change: 1 addition & 0 deletions packages/endpoint/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type {
NormalizeNullable,
Denormalize,
DenormalizeNullable,
SchemaToArgs,
} from './normal.js';
export type {
EndpointExtraOptions,
Expand Down
4 changes: 2 additions & 2 deletions packages/endpoint/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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,
Expand All @@ -23,7 +23,7 @@ export interface SchemaSimple<T = any> {
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities: any,
args?: any[],
args: Args,
): any;
denormalize(
input: {},
Expand Down
28 changes: 28 additions & 0 deletions packages/endpoint/src/normal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,31 @@ export type NormalizedSchema<E, R> = {
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 {
normalize(
input: any,
parent: any,
key: any,
visit: (...args: any) => any,
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities: any,
args: infer Args,
): any;
}
? Args
: never;
16 changes: 7 additions & 9 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 } from './normal.js';
import type { Denormalize, SchemaToArgs } from './normal.js';

/**
* Programmatic cache reading
* @see https://dataclient.io/rest/api/Query
*/
export class Query<
S extends SchemaSimple,
P extends any[] = [],
P extends SchemaToArgs<S> = SchemaToArgs<S>,
R = Denormalize<S>,
> {
declare schema: QuerySchema<S, R>;
Expand All @@ -34,17 +34,15 @@ export class Query<

protected createQuerySchema(schema: SchemaSimple) {
const query = Object.create(schema);
query.denormalize = (
{ args, input }: { args: P; input: any },
_: P,
unvisit: any,
) => {
query.denormalize = (input: any, args: P, unvisit: any) => {
if (input === undefined) return undefined;
const value = (schema as any).denormalize(input, args, unvisit);
const value = unvisit(input, schema); //(schema as any).denormalize(input, args, unvisit);
return typeof value === 'symbol'
? undefined
: this.process(value, ...args);
};
// anywhere in the hierarchy
if ('key' in schema) query.key = `QUERY ${schema.key}`;
query.infer = (
args: any,
indexes: any,
Expand All @@ -56,7 +54,7 @@ export class Query<
) => any,
entities: EntityTable,
) => {
return { args, input: recurse(schema, args, indexes, entities) };
return recurse(schema, args, indexes, entities);
};
return query;
}
Expand Down
5 changes: 3 additions & 2 deletions packages/endpoint/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,12 @@ export declare let CollectionRoot: CollectionConstructor;
*/
export declare class Collection<
S extends any[] | PolymorphicInterface = any,
Parent extends any[] = [
Args extends any[] = [
urlParams: Record<string, any>,
body?: Record<string, any>,
],
> extends CollectionRoot<S, Parent> {}
Parent = any,
> extends CollectionRoot<S, Args, Parent> {}

// id is in Instance, so we default to that as pk
/**
Expand Down
25 changes: 16 additions & 9 deletions packages/endpoint/src/schemaTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ export type CollectionArrayAdder<S extends PolymorphicInterface> = S extends {

export interface CollectionInterface<
S extends PolymorphicInterface = any,
Parent extends any[] = any,
Args extends any[] = any[],
Parent = any,
> {
addWith<P extends any[] = Parent>(
addWith<P extends any[] = Args>(
merge: (existing: any, incoming: any) => any,
createCollectionFilter?: (
...args: P
Expand All @@ -46,7 +47,7 @@ export interface CollectionInterface<
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities: any,
args: any[],
args: Args,
): string;

merge(existing: any, incoming: any): any;
Expand Down Expand Up @@ -126,28 +127,34 @@ export interface CollectionInterface<
* @see https://dataclient.io/rest/api/Collection#assign
*/
assign: S extends { denormalize(...args: any): Record<string, unknown> }
? schema.Collection<S, Parent>
? schema.Collection<S, Args, Parent>
: never;
}
export type CollectionFromSchema<
S extends any[] | PolymorphicInterface = any,
Parent extends any[] = [
Args extends any[] = [
urlParams: Record<string, any>,
body?: Record<string, any>,
],
> = CollectionInterface<S extends any[] ? schema.Array<S[number]> : S, Parent>;
Parent = any,
> = CollectionInterface<
S extends any[] ? schema.Array<S[number]> : S,
Args,
Parent
>;

export interface CollectionConstructor {
new <
S extends SchemaSimple[] | PolymorphicInterface = any,
Parent extends any[] = [
Args extends any[] = [
urlParams: Record<string, any>,
body?: Record<string, any>,
],
Parent = any,
>(
schema: S,
options?: CollectionOptions,
): CollectionFromSchema<S, Parent>;
options?: CollectionOptions<Args, Parent>,
): CollectionFromSchema<S, Args, Parent>;
readonly prototype: CollectionInterface;
}

Expand Down
30 changes: 16 additions & 14 deletions packages/endpoint/src/schemas/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ const createValue = (value: any) => ({ ...value });
*/
export default class CollectionSchema<
S extends PolymorphicInterface = any,
Parent extends any[] = [
Args extends any[] = [
urlParams: Record<string, any>,
body?: Record<string, any>,
],
Parent = any,
> {
protected declare nestKey: (parent: any, key: string) => Record<string, any>;

Expand All @@ -35,18 +36,18 @@ export default class CollectionSchema<
declare readonly key: string;

declare push: S extends ArraySchema<any>
? CollectionSchema<S, Parent>
? CollectionSchema<S, Args, Parent>
: undefined;

declare unshift: S extends ArraySchema<any>
? CollectionSchema<S, Parent>
? CollectionSchema<S, Args, Parent>
: undefined;

declare assign: S extends Values<any>
? CollectionSchema<S, Parent>
? CollectionSchema<S, Args, Parent>
: undefined;

addWith<P extends any[] = Parent>(
addWith<P extends any[] = Args>(
merge: (existing: any, incoming: any) => any,
createCollectionFilter?: (
...args: P
Expand All @@ -59,7 +60,7 @@ export default class CollectionSchema<
// so fetch(create, { userId: 'bob', completed: true }, data)
// would possibly add to {}, {userId: 'bob'}, {completed: true}, {userId: 'bob', completed: true } - but only those already in the store
// it ignores keys that start with sort as those are presumed to not filter results
protected createCollectionFilter(...args: Parent) {
protected createCollectionFilter(...args: Args) {
return (collectionKey: Record<string, string>) =>
Object.entries(collectionKey).every(
([key, value]) =>
Expand All @@ -74,7 +75,7 @@ export default class CollectionSchema<
return key.startsWith('order');
}

constructor(schema: S, options?: CollectionOptions) {
constructor(schema: S, options?: CollectionOptions<Args, Parent>) {
this.schema = Array.isArray(schema)
? (new ArraySchema(schema[0]) as any)
: schema;
Expand Down Expand Up @@ -109,7 +110,7 @@ export default class CollectionSchema<
this.createCollectionFilter = (
options as any as {
createCollectionFilter: (
...args: Parent
...args: Args
) => (collectionKey: Record<string, string>) => boolean;
}
).createCollectionFilter.bind(this) as any;
Expand Down Expand Up @@ -152,13 +153,13 @@ export default class CollectionSchema<

normalize(
input: any,
parent: any,
parent: Parent,
key: string,
visit: (...args: any) => any,
addEntity: (...args: any) => any,
visitedEntities: Record<string, any>,
storeEntities: any,
args: any[],
args: Args,
): string {
const pkList = this.schema.normalize(
input,
Expand Down Expand Up @@ -239,22 +240,23 @@ export default class CollectionSchema<
}

export type CollectionOptions<
Parent extends any[] = [
Args extends any[] = [
urlParams: Record<string, any>,
body?: Record<string, any>,
],
Parent = any,
> = (
| {
nestKey?: (parent: any, key: string) => Record<string, any>;
nestKey?: (parent: Parent, key: string) => Record<string, any>;
}
| {
argsKey?: (...args: any) => Record<string, any>;
argsKey?: (...args: Args) => Record<string, any>;
}
) &
(
| {
createCollectionFilter?: (
...args: Parent
...args: Args
) => (collectionKey: Record<string, string>) => boolean;
}
| {
Expand Down
1 change: 1 addition & 0 deletions packages/endpoint/src/schemas/__tests__/Collection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe(`${schema.Collection.name} normalization`, () => {
() => undefined,
{},
{},
// @ts-expect-error
[],
);
}
Expand Down
9 changes: 9 additions & 0 deletions packages/endpoint/src/schemas/__tests__/Query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ describe.each([
inferResults(sortedUsers.schema, [{ asc: true }], {}, entities),
sortedUsers.schema,
createInput(entities),
{},
new WeakEntityMap(),
[{ asc: true }],
),
).toMatchSnapshot();
});
Expand Down Expand Up @@ -137,6 +140,9 @@ describe.each([
),
userCountByAdmin.schema,
createInput(entities),
{},
new WeakEntityMap(),
[{ isAdmin: false }],
);
expect(nonAdminCount).toBe(3);
const adminCount:
Expand All @@ -150,6 +156,9 @@ describe.each([
),
userCountByAdmin.schema,
createInput(entities),
{},
new WeakEntityMap(),
[{ isAdmin: true }],
);
expect(adminCount).toBe(1);
if (typeof totalCount === 'symbol') return;
Expand Down
22 changes: 20 additions & 2 deletions packages/rest/src/resourceTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,16 @@ export interface Resource<
? GetEndpoint<
{
path: ShortenPath<O['path']>;
schema: schema.Collection<[O['schema']]>;
schema: schema.Collection<
[O['schema']],
[
'searchParams' extends keyof O
? O['searchParams'] extends undefined
? PathArgs<ShortenPath<O['path']>>
: O['searchParams'] & PathArgs<ShortenPath<O['path']>>
: PathArgs<ShortenPath<O['path']>>,
]
>;
body: 'body' extends keyof O
? O['body']
: Partial<Denormalize<O['schema']>>;
Expand All @@ -82,7 +91,16 @@ export interface Resource<
: GetEndpoint<
{
path: ShortenPath<O['path']>;
schema: schema.Collection<[O['schema']]>;
schema: schema.Collection<
[O['schema']],
[
'searchParams' extends keyof O
? O['searchParams'] extends undefined
? PathArgs<ShortenPath<O['path']>>
: O['searchParams'] & PathArgs<ShortenPath<O['path']>>
: PathArgs<ShortenPath<O['path']>>,
]
>;
body: 'body' extends keyof O
? O['body']
: Partial<Denormalize<O['schema']>>;
Expand Down

1 comment on commit 2064027

@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: 2064027 Previous: 14560a2 Ratio
normalizeLong 276 ops/sec (±0.33%) 233 ops/sec (±1.80%) 0.84
infer All 5529 ops/sec (±0.65%) 4824 ops/sec (±1.86%) 0.87
denormalizeLong 172 ops/sec (±3.44%) 161 ops/sec (±1.79%) 0.94
denormalizeLong donotcache 474 ops/sec (±0.25%) 396 ops/sec (±2.96%) 0.84
denormalizeShort donotcache 500x 821 ops/sec (±1.31%) 701 ops/sec (±2.95%) 0.85
denormalizeShort 500x 598 ops/sec (±0.13%) 525 ops/sec (±1.87%) 0.88
denormalizeLong with mixin Entity 170 ops/sec (±0.82%) 150 ops/sec (±1.61%) 0.88
denormalizeLong withCache 3842 ops/sec (±0.34%) 3295 ops/sec (±0.66%) 0.86
denormalizeLongAndShort withEntityCacheOnly 919 ops/sec (±0.38%) 795 ops/sec (±1.66%) 0.87
denormalizeLong All withCache 4669 ops/sec (±0.16%) 4122 ops/sec (±0.71%) 0.88
denormalizeLong Query-sorted withCache 4363 ops/sec (±0.38%) 3783 ops/sec (±1.10%) 0.87
getResponse 3131 ops/sec (±2.50%) 3032 ops/sec (±3.66%) 0.97
getResponse (null) 2311512 ops/sec (±0.27%) 2018332 ops/sec (±1.29%) 0.87
getResponse (clear cache) 171 ops/sec (±0.72%) 150 ops/sec (±1.87%) 0.88
getSmallResponse 1641 ops/sec (±0.15%) 1416 ops/sec (±4.00%) 0.86
getSmallInferredResponse 1482 ops/sec (±0.26%) 1284 ops/sec (±0.99%) 0.87
getResponse Query-sorted 424 ops/sec (±1.56%) 364 ops/sec (±1.85%) 0.86
setLong 275 ops/sec (±0.34%) 236 ops/sec (±1.52%) 0.86
setLongWithMerge 120 ops/sec (±0.31%) 103 ops/sec (±2.15%) 0.86
setLongWithSimpleMerge 132 ops/sec (±0.41%) 109 ops/sec (±2.03%) 0.83

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

Please sign in to comment.