Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .changeset/plenty-regions-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@data-client/normalizr': patch
'@data-client/endpoint': patch
'@data-client/react': patch
'@data-client/core': patch
'@data-client/rest': patch
'@data-client/graphql': patch
---

Normalize delegate.invalidate() first argument only has `key` param.

`indexes` optional param no longer provided as it was never used.


```ts
normalize(
input: any,
parent: any,
key: string | undefined,
args: any[],
visit: (...args: any) => any,
delegate: INormalizeDelegate,
): string {
delegate.invalidate({ key: this._entity.key }, pk);
return pk;
}
```
25 changes: 25 additions & 0 deletions .changeset/weak-grapes-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'@data-client/endpoint': patch
'@data-client/rest': patch
'@data-client/graphql': patch
---

Unions can query() without type discriminator

#### Before
```tsx
// @ts-expect-error
const event = useQuery(EventUnion, { id });
// event is undefined
const newsEvent = useQuery(EventUnion, { id, type: 'news' });
// newsEvent is found
```

#### After

```tsx
const event = useQuery(EventUnion, { id });
// event is found
const newsEvent = useQuery(EventUnion, { id, type: 'news' });
// newsEvent is found
```
17 changes: 17 additions & 0 deletions packages/core/src/controller/__tests__/__snapshots__/get.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ Group {
}
`;

exports[`Controller.get() Union based on args with function schemaAttribute 1`] = `
User {
"id": "1",
"type": "users",
"username": "bob",
}
`;

exports[`Controller.get() Union based on args with function schemaAttribute 2`] = `
Group {
"groupname": "fast",
"id": "2",
"memberCount": 5,
"type": "groups",
}
`;

exports[`Controller.get() indexes query Entity based on index 1`] = `
User {
"id": "1",
Expand Down
68 changes: 66 additions & 2 deletions packages/core/src/controller/__tests__/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,11 @@ describe('Controller.get()', () => {
id: string = '';
}
class User extends IDEntity {
type = 'user';
type = 'users';
username: string = '';
}
class Group extends IDEntity {
type = 'group';
type = 'groups';
groupname: string = '';
memberCount = 0;
}
Expand Down Expand Up @@ -349,6 +349,70 @@ describe('Controller.get()', () => {
// @ts-expect-error
() => controller.get(queryPerson, { id: '1', doesnotexist: 5 }, state);
});

it('Union based on args with function schemaAttribute', () => {
class IDEntity extends Entity {
id: string = '';
}
class User extends IDEntity {
type = 'user';
username: string = '';
}
class Group extends IDEntity {
type = 'group';
groupname: string = '';
memberCount = 0;
}
const queryPerson = new schema.Union(
{
users: User,
groups: Group,
},
(value: { type: 'users' | 'groups' }) => value.type,
);
const controller = new Controller();
const state = {
...initialState,
entities: {
User: {
'1': { id: '1', type: 'users', username: 'bob' },
},
Group: {
'2': { id: '2', type: 'groups', groupname: 'fast', memberCount: 5 },
},
},
};
const user = controller.get(queryPerson, { id: '1', type: 'users' }, state);
expect(user).toBeDefined();
expect(user).toBeInstanceOf(User);
expect(user).toMatchSnapshot();
const group = controller.get(
queryPerson,
{ id: '2', type: 'groups' },
state,
);
expect(group).toBeDefined();
expect(group).toBeInstanceOf(Group);
expect(group).toMatchSnapshot();

// should maintain referential equality
expect(user).toBe(
controller.get(queryPerson, { id: '1', type: 'users' }, state),
);

// these are the 'fallback case' where it cannot determine type discriminator, so just enumerates
() => controller.get(queryPerson, { id: '1' }, state);
// @ts-expect-error
() => controller.get(queryPerson, { id: '1', type: 'notrealtype' }, state);
// @ts-expect-error
() => controller.get(queryPerson, { id: { bob: 5 }, type: 'users' }, state);
// @ts-expect-error
expect(controller.get(queryPerson, 5, state)).toBeUndefined();
// @ts-expect-error
() => controller.get(queryPerson, { doesnotexist: 5 }, state);
// @ts-expect-error
() => controller.get(queryPerson, { id: '1', doesnotexist: 5 }, state);
});
});

describe('Snapshot.getQueryMeta()', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/endpoint/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export interface INormalizeDelegate {
meta?: { fetchedAt: number; date: number; expiresAt: number },
): void;
/** Invalidates an entity, potentially triggering suspense */
invalidate(schema: { key: string; indexes?: any }, pk: string): void;
invalidate(schema: { key: string }, pk: string): void;
/** Returns true when we're in a cycle, so we should not continue recursing */
checkLoop(key: string, pk: string, input: object): boolean;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/endpoint/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export interface UnionConstructor {
schemaAttribute: SchemaAttribute,
): UnionInstance<
Choices,
UnionSchemaToArgs<Choices, SchemaAttribute> &
Partial<UnionSchemaToArgs<Choices, SchemaAttribute>> &
Partial<AbstractInstanceType<Choices[keyof Choices]>>
>;

Expand Down
5 changes: 3 additions & 2 deletions packages/endpoint/src/schemas/Invalidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class Invalidate<
this._entity = entity;
}

get key() {
get key(): string {
return this._entity.key;
}

Expand Down Expand Up @@ -73,7 +73,7 @@ export default class Invalidate<

// any queued updates are meaningless with delete, so we should just set it
// and creates will have a different pk
delegate.invalidate(this as any, pk);
delegate.invalidate({ key: this._entity.key }, pk);
return pk;
}

Expand All @@ -86,6 +86,7 @@ export default class Invalidate<
args: readonly any[],
unvisit: (schema: any, input: any) => any,
): AbstractInstanceType<E> {
// TODO: is this really always going to be the full object - validate that calling fetch will give this even when input is a string
return unvisit(this._entity, id) as any;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/endpoint/src/schemas/Polymorphic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Value: ${JSON.stringify(value, undefined, 2)}`,
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && value) {
console.warn(
`TypeError: Unable to infer schema for ${this.constructor.name}
`TypeError: Unable to determine schema for ${this.constructor.name}
Value: ${JSON.stringify(value, undefined, 2)}.`,
);
}
Expand Down
20 changes: 15 additions & 5 deletions packages/endpoint/src/schemas/Union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,23 @@ export default class UnionSchema extends PolymorphicSchema {

queryKey(args: any, unvisit: (schema: any, args: any) => any) {
if (!args[0]) return;
// Often we have sufficient information in the first arg like { id, type }
const schema = this.getSchemaAttribute(args[0], undefined, '');
const discriminatedSchema = this.schema[schema];

// Was unable to infer the entity's schema from params
if (discriminatedSchema === undefined) return;
const id = unvisit(discriminatedSchema, args);
if (id === undefined) return;
return { id, schema };
// Fast case - args include type discriminator
if (discriminatedSchema) {
const id = unvisit(discriminatedSchema, args);
if (id === undefined) return;
return { id, schema };
}

// Fallback to trying every possible schema if it cannot be determined
for (const key in this.schema) {
const id = unvisit(this.schema[key], args);
if (id !== undefined) {
return { id, schema: key };
}
}
}
}
Loading