Skip to content

Commit 5699005

Browse files
authored
enhance: delegate.getEntities() returns restricted EntitiesInterface (#3501)
1 parent ac05f9d commit 5699005

File tree

19 files changed

+332
-126
lines changed

19 files changed

+332
-126
lines changed

.changeset/proud-taxes-bake.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,40 @@
33
'@data-client/endpoint': minor
44
---
55

6-
delegate.getEntity(this.key) -> delegate.getEntities(this.key)
6+
delegate.getEntity(key) -> delegate.getEntities(this.key)
77

8-
This applies to both schema.queryKey and schema.normalize method delegates.
8+
Return value is a restricted interface with keys() and entries() iterator methods.
9+
This applies to both schema.queryKey and schema.normalize method delegates.
10+
11+
```ts
12+
const entities = delegate.getEntities(key);
13+
14+
// foreach on keys
15+
for (const key of entities.keys()) {}
16+
// Object.keys() (convert to array)
17+
return [...entities.keys()];
18+
// foreach on full entry
19+
for (const [key, entity] of entities.entries()) {}
20+
```
21+
22+
#### Before
23+
24+
```ts
25+
const entities = delegate.getEntity(this.key);
26+
if (entities)
27+
Object.keys(entities).forEach(collectionPk => {
28+
if (!filterCollections(JSON.parse(collectionPk))) return;
29+
delegate.mergeEntity(this, collectionPk, normalizedValue);
30+
});
31+
```
32+
33+
#### After
34+
35+
```ts
36+
const entities = delegate.getEntities(this.key);
37+
if (entities)
38+
for (const collectionKey of entities.keys()) {
39+
if (!filterCollections(JSON.parse(collectionKey))) continue;
40+
delegate.mergeEntity(this, collectionKey, normalizedValue);
41+
}
42+
```

.changeset/thirty-islands-hope.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@data-client/endpoint': patch
3+
---
4+
5+
Fix: schema.All() polymorphic handling of Invalidated entities
6+
7+
In case an Entity is invalidated, schema.All will continue to properly
8+
filter it out of its list.

packages/endpoint/src/interface.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,12 @@ export interface CheckLoop {
127127
(entityKey: string, pk: string, input: object): boolean;
128128
}
129129

130-
/** Get all normalized entities of one type from store */
131-
export interface GetEntities {
132-
(key: string): { readonly [pk: string]: any } | undefined;
130+
/** Interface specification for entities state accessor */
131+
export interface EntitiesInterface {
132+
keys(): IterableIterator<string>;
133+
entries(): IterableIterator<[string, any]>;
133134
}
135+
134136
/** Get normalized Entity from store */
135137
export interface GetEntity {
136138
(key: string, pk: string): any;
@@ -143,8 +145,11 @@ export interface GetIndex {
143145

144146
/** Accessors to the currently processing state while building query */
145147
export interface IQueryDelegate {
146-
getEntities: GetEntities;
148+
/** Get all entities for a given schema key */
149+
getEntities(key: string): EntitiesInterface | undefined;
150+
/** Gets any previously normalized entity from store */
147151
getEntity: GetEntity;
152+
/** Get PK using an Entity Index */
148153
getIndex: GetIndex;
149154
/** Return to consider results invalid */
150155
INVALID: symbol;
@@ -154,8 +159,8 @@ export interface IQueryDelegate {
154159
export interface INormalizeDelegate {
155160
/** Action meta-data for this normalize call */
156161
readonly meta: { fetchedAt: number; date: number; expiresAt: number };
157-
/** Get all normalized entities of one type from store */
158-
getEntities: GetEntities;
162+
/** Get all entities for a given schema key */
163+
getEntities(key: string): EntitiesInterface | undefined;
159164
/** Gets any previously normalized entity from store */
160165
getEntity: GetEntity;
161166
/** Updates an entity using merge lifecycles when it has previously been set */

packages/endpoint/src/schemas/All.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import ArraySchema from './Array.js';
22
import { IQueryDelegate, Visit } from '../interface.js';
3-
import { EntityInterface, EntityMap, SchemaFunction } from '../schema.js';
3+
import {
4+
Entity,
5+
EntityInterface,
6+
EntityMap,
7+
SchemaFunction,
8+
} from '../schema.js';
49

510
/**
611
* Retrieves all entities in cache
@@ -26,23 +31,25 @@ export default class AllSchema<
2631

2732
queryKey(args: any, unvisit: any, delegate: IQueryDelegate): any {
2833
if (this.isSingleSchema) {
29-
const entitiesEntry = delegate.getEntities(this.schema.key);
30-
// we must wait until there are entries for any 'All' query to be Valid
31-
if (entitiesEntry === undefined) return delegate.INVALID;
32-
return Object.values(entitiesEntry).map(
33-
entity => entity && this.schema.pk(entity),
34-
);
34+
const entities = delegate.getEntities(this.schema.key);
35+
if (!entities) return delegate.INVALID;
36+
return [...entities.keys()];
3537
}
3638
let found = false;
3739
const list = Object.values(this.schema as Record<string, any>).flatMap(
3840
(schema: EntityInterface) => {
39-
const entitiesEntry = delegate.getEntities(schema.key);
40-
if (entitiesEntry === undefined) return [];
41+
const entities = delegate.getEntities(schema.key);
42+
if (!entities) return [];
4143
found = true;
42-
return Object.entries(entitiesEntry).map(([key, entity]) => ({
43-
id: entity && schema.pk(entity, undefined, key, []),
44-
schema: this.getSchemaAttribute(entity, undefined, key),
45-
}));
44+
const normEntities: any[] = [];
45+
for (const [key, entity] of entities.entries()) {
46+
if (!entity || typeof entity === 'symbol') continue;
47+
normEntities.push({
48+
id: schema.pk(entity, undefined, key, []),
49+
schema: this.getSchemaAttribute(entity, undefined, key),
50+
});
51+
}
52+
return normEntities;
4653
},
4754
);
4855
// we need at least one table entry of the Union for this to count as Valid.

packages/endpoint/src/schemas/Collection.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,10 @@ function normalizeCreate(
328328
// add to any collections that match this
329329
const entities = delegate.getEntities(this.key);
330330
if (entities)
331-
Object.keys(entities).forEach(collectionPk => {
332-
if (!filterCollections(JSON.parse(collectionPk))) return;
333-
delegate.mergeEntity(this, collectionPk, normalizedValue);
334-
});
331+
for (const collectionKey of entities.keys()) {
332+
if (!filterCollections(JSON.parse(collectionKey))) continue;
333+
delegate.mergeEntity(this, collectionKey, normalizedValue);
334+
}
335335
return normalizedValue as any;
336336
}
337337

packages/endpoint/src/schemas/__tests__/All.test.ts

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -200,21 +200,65 @@ describe.each([
200200
expect(createOutput(value)).toMatchSnapshot();
201201
});
202202

203+
test('denormalizes removes undefined or INVALID entities with polymorphism', () => {
204+
class Cat extends IDEntity {
205+
readonly type = 'Cat';
206+
}
207+
class Dog extends IDEntity {
208+
readonly type = 'dogs';
209+
}
210+
class Person extends IDEntity {
211+
readonly type = 'people';
212+
}
213+
const listSchema = new schema.All(
214+
{
215+
Cat: Cat,
216+
dogs: Dog,
217+
people: Person,
218+
},
219+
input => input.type || 'dogs',
220+
);
221+
222+
const state = createInput({
223+
entities: {
224+
Cat: {
225+
'1': { id: '1', name: 'Milo', type: 'Cat' },
226+
'2': { id: '2', name: 'Jake', type: 'Cat' },
227+
'3': undefined,
228+
'4': INVALID,
229+
},
230+
Dog: {
231+
'1': { id: '1', name: 'Rex', type: 'dogs' },
232+
'2': INVALID,
233+
},
234+
Person: {
235+
'1': { id: '1', name: 'Alice', type: 'people' },
236+
'2': undefined,
237+
},
238+
},
239+
indexes: {},
240+
});
241+
const value = new MemoCache(MyDelegate).query(listSchema, [], state).data;
242+
expect(value).not.toEqual(expect.any(Symbol));
243+
if (typeof value === 'symbol' || value === undefined) return;
244+
expect(createOutput(value).length).toBe(4);
245+
expect(createOutput(value)).toMatchSnapshot();
246+
});
247+
203248
test('denormalize maintains referential equality until entities are added', () => {
204249
class Cat extends IDEntity {}
205250
(Cat as any).defaults;
206251
const catSchema = { results: new schema.All(Cat), nextPage: '' };
207-
let state: State<unknown> = {
252+
let state: State<unknown> = createInput({
208253
...initialState,
209254
entities: {
210255
Cat: {
211-
1: { id: '1', name: 'Milo' },
212-
2: { id: '2', name: 'Jake' },
256+
'1': { id: '1', name: 'Milo' },
257+
'2': { id: '2', name: 'Jake' },
213258
},
214259
},
215-
indexes: {},
216-
};
217-
const memo = new MemoCache();
260+
}) as any;
261+
const memo = new MemoCache(MyDelegate);
218262
const value = memo.query(catSchema, [], state).data;
219263

220264
expect(createOutput(value).results?.length).toBe(2);
@@ -225,16 +269,27 @@ describe.each([
225269
);
226270
expect(value).toBe(value2);
227271

228-
state = {
229-
...state,
230-
entities: {
231-
...state.entities,
232-
Cat: {
233-
...state.entities.Cat,
234-
3: { id: '3', name: 'Jelico' },
272+
if (ImmDelegate === MyDelegate) {
273+
state = {
274+
...state,
275+
entities: (state.entities as any).setIn(['Cat', '3'], {
276+
id: '3',
277+
name: 'Jelico',
278+
}),
279+
} as any;
280+
} else {
281+
state = {
282+
...state,
283+
entities: {
284+
...state.entities,
285+
Cat: {
286+
...state.entities.Cat,
287+
'3': { id: '3', name: 'Jelico' },
288+
},
235289
},
236-
},
237-
};
290+
} as any;
291+
}
292+
238293
const value3 = memo.query(catSchema, [], state).data;
239294
expect(createOutput(value3).results?.length).toBe(3);
240295
expect(createOutput(value3).results).toMatchSnapshot();

packages/endpoint/src/schemas/__tests__/Object.test.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import {
66
import { denormalize as immDenormalize } from '@data-client/normalizr/imm';
77
import { Temporal } from '@js-temporal/polyfill';
88
import { IDEntity } from '__tests__/new';
9-
import { fromJS, Map } from 'immutable';
9+
import { fromJS } from 'immutable';
1010

11+
import { fromJSEntities } from './denormalize';
1112
import { schema } from '../../';
1213
import Entity from '../Entity';
1314

@@ -22,10 +23,6 @@ afterAll(() => {
2223
dateSpy.mockRestore();
2324
});
2425

25-
function fromJSEntities(entities) {
26-
return Map(entities).map(v => Map(v));
27-
}
28-
2926
describe(`${schema.Object.name} normalization`, () => {
3027
test('normalizes an object', () => {
3128
class User extends IDEntity {}

packages/endpoint/src/schemas/__tests__/__snapshots__/All.test.ts.snap

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,31 @@ exports[`ArraySchema denormalization (direct) denormalizes removes undefined or
436436
}
437437
`;
438438

439+
exports[`ArraySchema denormalization (direct) denormalizes removes undefined or INVALID entities with polymorphism 1`] = `
440+
[
441+
Cat {
442+
"id": "1",
443+
"name": "Milo",
444+
"type": "Cat",
445+
},
446+
Cat {
447+
"id": "2",
448+
"name": "Jake",
449+
"type": "Cat",
450+
},
451+
Dog {
452+
"id": "1",
453+
"name": "Rex",
454+
"type": "dogs",
455+
},
456+
Person {
457+
"id": "1",
458+
"name": "Alice",
459+
"type": "people",
460+
},
461+
]
462+
`;
463+
439464
exports[`ArraySchema denormalization (direct) returns the input value if is null 1`] = `
440465
Taco {
441466
"fillings": null,
@@ -576,6 +601,31 @@ exports[`ArraySchema denormalization (immutable) denormalizes removes undefined
576601
}
577602
`;
578603

604+
exports[`ArraySchema denormalization (immutable) denormalizes removes undefined or INVALID entities with polymorphism 1`] = `
605+
[
606+
Cat {
607+
"id": "1",
608+
"name": "Milo",
609+
"type": "Cat",
610+
},
611+
Cat {
612+
"id": "2",
613+
"name": "Jake",
614+
"type": "Cat",
615+
},
616+
Dog {
617+
"id": "1",
618+
"name": "Rex",
619+
"type": "dogs",
620+
},
621+
Person {
622+
"id": "1",
623+
"name": "Alice",
624+
"type": "people",
625+
},
626+
]
627+
`;
628+
579629
exports[`ArraySchema denormalization (immutable) returns the input value if is null 1`] = `
580630
Taco {
581631
"fillings": null,

0 commit comments

Comments
 (0)