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
18 changes: 18 additions & 0 deletions .changeset/stale-socks-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@data-client/normalizr': minor
---

MemoCache.query returns `{ data, paths }` just like denormalize. `data` could be INVALID

#### Before

```ts
return this.memo.query(schema, args, state);
```

#### After

```ts
const { data } = this.memo.query(schema, args, state);
return typeof data === 'symbol' ? undefined : (data as any);
```
3 changes: 1 addition & 2 deletions docs/rest/api/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,7 @@ is an Array of paths of all entities included in the result.
const data = memo.query(
Article,
args,
normalizedData.entities,
normalizedData.indexes,
normalizedData,
);
```

Expand Down
24 changes: 4 additions & 20 deletions packages/core/src/controller/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,8 @@ export default class Controller<
.slice(0, rest.length - 1)
.map(ensurePojo) as SchemaArgs<S>;

return this.memo.query(schema, args, state);
const { data } = this.memo.query(schema, args, state);
return typeof data === 'symbol' ? undefined : (data as any);
}

/**
Expand All @@ -612,27 +613,10 @@ export default class Controller<
.slice(0, rest.length - 1)
.map(ensurePojo) as SchemaArgs<S>;

// TODO: breaking: Switch back to this.memo.query(schema, args, state.entities as any, state.indexes) to do
// this logic
const input = this.memo.buildQueryKey(
schema,
args,
state,
JSON.stringify(args),
);
const { data, paths } = this.memo.query(schema, args, state);

if (!input) {
return { data: undefined, countRef: () => () => undefined };
}

const { data, paths } = this.memo.denormalize(
schema,
input,
state.entities,
args,
);
return {
data: typeof data === 'symbol' ? undefined : (data as any),
data: typeof data === 'symbol' ? undefined : data,
countRef: this.gcPolicy.createCountRef({ paths }),
};
}
Expand Down
24 changes: 13 additions & 11 deletions packages/endpoint/src/schemas/__tests__/All.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ describe.each([
indexes: {},
});
// use memocache because we don't support 'object' schemas in controller yet
expect(new MemoCache().query(catSchema, [], state)).toMatchSnapshot();
expect(
new MemoCache().query(catSchema, [], state).data,
).toMatchSnapshot();
});

test('denormalizes nested in object with primitive', () => {
Expand All @@ -150,7 +152,7 @@ describe.each([
},
indexes: {},
});
const value = new MemoCache().query(catSchema, [], state);
const value = new MemoCache().query(catSchema, [], state).data;
expect(value).not.toEqual(expect.any(Symbol));
if (typeof value === 'symbol' || value === undefined) return;
expect(createOutput(value.results)).toMatchSnapshot();
Expand All @@ -171,7 +173,7 @@ describe.each([
},
indexes: {},
});
const value = new MemoCache().query(catSchema, [], state);
const value = new MemoCache().query(catSchema, [], state).data;
expect(value).not.toEqual(expect.any(Symbol));
if (typeof value === 'symbol' || value === undefined) return;
expect(createOutput(value.results).length).toBe(2);
Expand All @@ -194,11 +196,11 @@ describe.each([
indexes: {},
};
const memo = new MemoCache();
const value = memo.query(catSchema, [], state);
const value = memo.query(catSchema, [], state).data;

expect(createOutput(value).results?.length).toBe(2);
expect(createOutput(value).results).toMatchSnapshot();
const value2 = memo.query(catSchema, [], state);
const value2 = memo.query(catSchema, [], state).data;
expect(createOutput(value).results[0]).toBe(
createOutput(value2).results[0],
);
Expand All @@ -214,7 +216,7 @@ describe.each([
},
},
};
const value3 = memo.query(catSchema, [], state);
const value3 = memo.query(catSchema, [], state).data;
expect(createOutput(value3).results?.length).toBe(3);
expect(createOutput(value3).results).toMatchSnapshot();
expect(createOutput(value).results[0]).toBe(
Expand All @@ -238,8 +240,8 @@ describe.each([
},
indexes: {},
});
const value = new MemoCache().query(catSchema, [], state);
expect(createOutput(value)).toBeUndefined();
const value = new MemoCache().query(catSchema, [], state).data;
expect(createOutput(value)).toEqual(expect.any(Symbol));
});

test('denormalizes should not be found when no entities are present (polymorphic)', () => {
Expand Down Expand Up @@ -270,8 +272,8 @@ describe.each([
},
indexes: {},
});
const value = new MemoCache().query(listSchema, [], state);
expect(createOutput(value)).toBeUndefined();
const value = new MemoCache().query(listSchema, [], state).data;
expect(createOutput(value)).toEqual(expect.any(Symbol));
});

test('returns the input value if is null', () => {
Expand Down Expand Up @@ -333,7 +335,7 @@ describe.each([
},
indexes: {},
});
const value = new MemoCache().query(listSchema, [], state);
const value = new MemoCache().query(listSchema, [], state).data;
expect(value).not.toEqual(expect.any(Symbol));
if (typeof value === 'symbol') return;
expect(value).toMatchSnapshot();
Expand Down
28 changes: 14 additions & 14 deletions packages/endpoint/src/schemas/__tests__/Query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe.each([
},
});
const users: DenormalizeNullable<typeof sortedUsers> | symbol =
new MemoCache().query(sortedUsers, [], state);
new MemoCache().query(sortedUsers, [], state).data;
expect(users).not.toEqual(expect.any(Symbol));
if (typeof users === 'symbol') return;
expect(users && users[0].name).toBe('Zeta');
Expand All @@ -101,7 +101,7 @@ describe.each([
},
});
expect(
new MemoCache().query(sortedUsers, [{ asc: true }], state),
new MemoCache().query(sortedUsers, [{ asc: true }], state).data,
).toMatchSnapshot();
});

Expand All @@ -115,9 +115,9 @@ describe.each([
},
},
};
const data = new MemoCache().query(sortedUsers, [], state);
const { data } = new MemoCache().query(sortedUsers, [], state);

expect(createOutput(data)).toEqual(undefined);
expect(createOutput(data)).not.toEqual(expect.any(Array));
});

test('denormalize aggregates', () => {
Expand Down Expand Up @@ -152,7 +152,7 @@ describe.each([
});
const totalCount:
| DenormalizeNullable<typeof userCountByAdmin>
| symbol = new MemoCache().query(userCountByAdmin, [], state);
| symbol = new MemoCache().query(userCountByAdmin, [], state).data;

expect(totalCount).toBe(4);
const nonAdminCount:
Expand All @@ -161,15 +161,15 @@ describe.each([
userCountByAdmin,
[{ isAdmin: false }],
state,
);
).data;
expect(nonAdminCount).toBe(3);
const adminCount:
| DenormalizeNullable<typeof userCountByAdmin>
| symbol = new MemoCache().query(
userCountByAdmin,
[{ isAdmin: true }],
state,
);
).data;
expect(adminCount).toBe(1);
if (typeof totalCount === 'symbol') return;

Expand Down Expand Up @@ -209,7 +209,7 @@ describe('top level schema', () => {
},
},
};
const users = new MemoCache().query(sortedUsers, [], state);
const users = new MemoCache().query(sortedUsers, [], state).data;
expect(users).not.toEqual(expect.any(Symbol));
if (typeof users === 'symbol') return;
expect(users && users[0].name).toBe('Zeta');
Expand All @@ -228,7 +228,7 @@ describe('top level schema', () => {
},
},
};
const users = new MemoCache().query(sortedUsers, [], state);
const users = new MemoCache().query(sortedUsers, [], state).data;
expect(users).toBeUndefined();
});

Expand All @@ -243,8 +243,8 @@ describe('top level schema', () => {
return sorted.reverse();
},
);
const users = new MemoCache().query(allSortedUsers, [], initialState);
expect(users).toBeUndefined();
const users = new MemoCache().query(allSortedUsers, [], initialState).data;
expect(users).toEqual(expect.any(Symbol));
});

test('works with nested schemas', () => {
Expand All @@ -258,8 +258,8 @@ describe('top level schema', () => {
return sorted.reverse();
},
);
const users = new MemoCache().query(allSortedUsers, [], initialState);
expect(users).toBeUndefined();
const users = new MemoCache().query(allSortedUsers, [], initialState).data;
expect(users).toEqual(expect.any(Symbol));
});

test('denormalizes should not be found when no entities are present', () => {
Expand All @@ -273,7 +273,7 @@ describe('top level schema', () => {
},
};

const value = new MemoCache().query(sortedUsers, [], state);
const value = new MemoCache().query(sortedUsers, [], state).data;

expect(value).toEqual(undefined);
});
Expand Down
5 changes: 2 additions & 3 deletions packages/normalizr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,13 @@ const memo = new MemoCache();

const { data, paths } = memo.denormalize(schema, input, state.entities, args);

const data = memo.query(schema, args, state.entities, state.indexes);
({ data, paths } = memo.query(schema, args, state));

function query(schema, args, state, key) {
const queryKey = memo.buildQueryKey(
schema,
args,
state.entities,
state.indexes,
state,
key,
);
const { data } = this.denormalize(schema, queryKey, state.entities, args);
Expand Down
6 changes: 3 additions & 3 deletions packages/normalizr/src/__tests__/MemoCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1038,11 +1038,11 @@ describe('MemoCache', () => {
});

test('works with indexes', () => {
const m = new MemoCache().query(Cat, [{ username: 'm' }], state);
const m = new MemoCache().query(Cat, [{ username: 'm' }], state).data;
expect(m).toBeDefined();
expect(m).toMatchSnapshot();
expect(
new MemoCache().query(Cat, [{ username: 'doesnotexist' }], state),
new MemoCache().query(Cat, [{ username: 'doesnotexist' }], state).data,
).toBeUndefined();
});

Expand All @@ -1051,7 +1051,7 @@ describe('MemoCache', () => {
expect(m).toBeDefined();
expect(m).toMatchSnapshot();
expect(
new MemoCache().query(Cat, [{ id: 'doesnotexist' }], state),
new MemoCache().query(Cat, [{ id: 'doesnotexist' }], state).data,
).toBeUndefined();
});
});
Expand Down
32 changes: 24 additions & 8 deletions packages/normalizr/src/__tests__/__snapshots__/MemoCache.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@ Cat {
`;

exports[`MemoCache query (direct) works with pk 1`] = `
Cat {
"id": "1",
"name": "Milo",
"username": "m",
{
"data": Cat {
"id": "1",
"name": "Milo",
"username": "m",
},
"paths": [
{
"key": "Cat",
"pk": "1",
},
],
}
`;

Expand All @@ -25,10 +33,18 @@ Cat {
`;

exports[`MemoCache query (immutable) works with pk 1`] = `
Cat {
"id": "1",
"name": "Milo",
"username": "m",
{
"data": Cat {
"id": "1",
"name": "Milo",
"username": "m",
},
"paths": [
{
"key": "Cat",
"pk": "1",
},
],
}
`;

Expand Down
10 changes: 6 additions & 4 deletions packages/normalizr/src/memo/MemoCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,17 @@ export default class MemoCache {
state: StateInterface,
// 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<S> | undefined {
): {
data: DenormalizeNullable<S> | symbol;
paths: EntityPath[];
} {
const input = this.buildQueryKey(schema, args, state, argsKey);

if (!input) {
return;
return { data: undefined as any, paths: [] };
}

const { data } = this.denormalize(schema, input, state.entities, args);
return typeof data === 'symbol' ? undefined : (data as any);
return this.denormalize(schema, input, state.entities, args);
}

buildQueryKey<S extends Schema>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,17 +224,20 @@ declare class MemoCache {
paths: EntityPath[];
};
/** Compute denormalized form maintaining referential equality for same inputs */
query<S extends Schema>(schema: S, args: readonly any[], entities: Record<string, Record<string, any> | undefined> | {
getIn(k: string[]): any;
}, indexes: NormalizedIndex | {
getIn(k: string[]): any;
}, argsKey?: string): DenormalizeNullable<S> | undefined;
buildQueryKey<S extends Schema>(schema: S, args: readonly any[], entities: Record<string, Record<string, any> | undefined> | {
query<S extends Schema>(schema: S, args: readonly any[], state: StateInterface, argsKey?: string): {
data: DenormalizeNullable<S> | symbol;
paths: EntityPath[];
};
buildQueryKey<S extends Schema>(schema: S, args: readonly any[], state: StateInterface, argsKey?: string): NormalizeNullable<S>;
}
type StateInterface = {
entities: Record<string, Record<string, any> | undefined> | {
getIn(k: string[]): any;
}, indexes: NormalizedIndex | {
};
indexes: NormalizedIndex | {
getIn(k: string[]): any;
}, argsKey?: string): NormalizeNullable<S>;
}
};
};

/** https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#the-noinfer-utility-type */
type NI<T> = NoInfer<T>;
Expand Down
Loading
Loading