Skip to content

Commit c8acf13

Browse files
committed
enhance: Use getEntities with abstract interface return value
1 parent 0861b4b commit c8acf13

File tree

17 files changed

+183
-258
lines changed

17 files changed

+183
-258
lines changed

.changeset/proud-taxes-bake.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,22 @@
33
'@data-client/endpoint': minor
44
---
55

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

8+
Return value is a restricted interface with keys() and entries() iterator methods.
89
This applies to both schema.queryKey and schema.normalize method delegates.
910

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+
1022
#### Before
1123

1224
```ts
@@ -21,8 +33,10 @@ if (entities)
2133
#### After
2234

2335
```ts
24-
delegate.forEntities(this.key, ([collectionKey]) => {
25-
if (!filterCollections(JSON.parse(collectionKey))) return;
26-
delegate.mergeEntity(this, collectionKey, normalizedValue);
27-
});
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+
}
2842
```

packages/endpoint/src/interface.ts

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

130-
/** Return all entity PKs for a given entity key or INVALID if no entity entries */
131-
export interface GetEntityKeys {
132-
(key: string): string[] | symbol;
130+
/** Interface specification for entities state accessor */
131+
export interface EntitiesInterface {
132+
keys(): IterableIterator<string>;
133+
entries(): IterableIterator<[string, any]>;
133134
}
134135

135-
/** Loop over all entities of a given key */
136-
export interface ForEntities {
137-
(key: string, callbackfn: (value: [string, unknown]) => void): boolean;
138-
}
139136
/** Get normalized Entity from store */
140137
export interface GetEntity {
141138
(key: string, pk: string): any;
@@ -148,10 +145,8 @@ export interface GetIndex {
148145

149146
/** Accessors to the currently processing state while building query */
150147
export interface IQueryDelegate {
151-
/** Return all entity PKs for a given entity key or INVALID if no entity entries */
152-
getEntityKeys: GetEntityKeys;
153-
/** Loop over all entities of a given key */
154-
forEntities: ForEntities;
148+
/** Get all entities for a given schema key */
149+
getEntities(key: string): EntitiesInterface | undefined;
155150
/** Gets any previously normalized entity from store */
156151
getEntity: GetEntity;
157152
/** Get PK using an Entity Index */
@@ -164,10 +159,8 @@ export interface IQueryDelegate {
164159
export interface INormalizeDelegate {
165160
/** Action meta-data for this normalize call */
166161
readonly meta: { fetchedAt: number; date: number; expiresAt: number };
167-
/** Return all entity PKs for a given entity key or INVALID if no entity entries */
168-
getEntityKeys: GetEntityKeys;
169-
/** Loop over all entities of a given key */
170-
forEntities: ForEntities;
162+
/** Get all entities for a given schema key */
163+
getEntities(key: string): EntitiesInterface | undefined;
171164
/** Gets any previously normalized entity from store */
172165
getEntity: GetEntity;
173166
/** Updates an entity using merge lifecycles when it has previously been set */

packages/endpoint/src/schemas/All.ts

Lines changed: 21 additions & 14 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-
return delegate.getEntityKeys(this.schema.key);
34+
const entities = delegate.getEntities(this.schema.key);
35+
if (!entities) return delegate.INVALID;
36+
return [...entities.keys()];
3037
}
3138
let found = false;
3239
const list = Object.values(this.schema as Record<string, any>).flatMap(
3340
(schema: EntityInterface) => {
34-
const entities: any[] = [];
35-
if (
36-
delegate.forEntities(schema.key, ([pk, entity]) => {
37-
if (!entity) return;
38-
entities.push({
39-
id: schema.pk(entity, undefined, pk, []),
40-
schema: this.getSchemaAttribute(entity, undefined, pk),
41-
});
42-
})
43-
)
44-
found = true;
45-
return entities;
41+
const entities = delegate.getEntities(schema.key);
42+
if (!entities) return [];
43+
found = true;
44+
const normEntities: any[] = [];
45+
for (const [key, entity] of entities.entries()) {
46+
if (!entity) 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: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -326,13 +326,12 @@ function normalizeCreate(
326326
// parent is args when not nested
327327
const filterCollections = (this.createCollectionFilter as any)(...args);
328328
// add to any collections that match this
329-
const keys = delegate.getEntityKeys(this.key);
330-
if (typeof keys !== 'symbol') {
331-
keys.forEach(collectionKey => {
332-
if (!filterCollections(JSON.parse(collectionKey))) return;
329+
const entities = delegate.getEntities(this.key);
330+
if (entities)
331+
for (const collectionKey of entities.keys()) {
332+
if (!filterCollections(JSON.parse(collectionKey))) continue;
333333
delegate.mergeEntity(this, collectionKey, normalizedValue);
334-
});
335-
}
334+
}
336335
return normalizedValue as any;
337336
}
338337

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

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -204,17 +204,16 @@ describe.each([
204204
class Cat extends IDEntity {}
205205
(Cat as any).defaults;
206206
const catSchema = { results: new schema.All(Cat), nextPage: '' };
207-
let state: State<unknown> = {
207+
let state: State<unknown> = createInput({
208208
...initialState,
209209
entities: {
210210
Cat: {
211-
1: { id: '1', name: 'Milo' },
212-
2: { id: '2', name: 'Jake' },
211+
'1': { id: '1', name: 'Milo' },
212+
'2': { id: '2', name: 'Jake' },
213213
},
214214
},
215-
indexes: {},
216-
};
217-
const memo = new MemoCache();
215+
}) as any;
216+
const memo = new MemoCache(MyDelegate);
218217
const value = memo.query(catSchema, [], state).data;
219218

220219
expect(createOutput(value).results?.length).toBe(2);
@@ -225,16 +224,27 @@ describe.each([
225224
);
226225
expect(value).toBe(value2);
227226

228-
state = {
229-
...state,
230-
entities: {
231-
...state.entities,
232-
Cat: {
233-
...state.entities.Cat,
234-
3: { id: '3', name: 'Jelico' },
227+
if (ImmDelegate === MyDelegate) {
228+
state = {
229+
...state,
230+
entities: (state.entities as any).setIn(['Cat', '3'], {
231+
id: '3',
232+
name: 'Jelico',
233+
}),
234+
} as any;
235+
} else {
236+
state = {
237+
...state,
238+
entities: {
239+
...state.entities,
240+
Cat: {
241+
...state.entities.Cat,
242+
'3': { id: '3', name: 'Jelico' },
243+
},
235244
},
236-
},
237-
};
245+
} as any;
246+
}
247+
238248
const value3 = memo.query(catSchema, [], state).data;
239249
expect(createOutput(value3).results?.length).toBe(3);
240250
expect(createOutput(value3).results).toMatchSnapshot();

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Temporal } from '@js-temporal/polyfill';
88
import { IDEntity } from '__tests__/new';
99
import { fromJS, Map } 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/normalizr/src/delegate/BaseDelegate.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { INVALID } from '../denormalize/symbol.js';
22
import type {
33
QueryPath,
44
IndexPath,
5-
EntitiesPath,
65
IQueryDelegate,
6+
EntitiesInterface,
77
} from '../interface.js';
88
import type { Dep } from '../memo/WeakDependencyMap.js';
99

@@ -17,19 +17,15 @@ export abstract class BaseDelegate {
1717
this.indexes = indexes;
1818
}
1919

20-
abstract forEntities(
21-
key: string,
22-
callbackfn: (value: [string, unknown]) => void,
23-
): boolean;
24-
25-
// we must expose the entities object to track in our WeakDependencyMap
26-
// however, this should not be part of the public API
27-
protected abstract getEntities(key: string): object | undefined;
28-
abstract getEntityKeys(key: string): string[] | symbol;
20+
abstract getEntities(key: string): EntitiesInterface | undefined;
2921
abstract getEntity(key: string, pk: string): object | undefined;
3022
abstract getIndex(...path: IndexPath): object | undefined;
3123
abstract getIndexEnd(entity: any, value: string): string | undefined;
24+
// we must expose the entities object to track in our WeakDependencyMap
25+
// however, this should not be part of the public API
26+
protected abstract getEntitiesObject(key: string): object | undefined;
3227

28+
// only used in buildQueryKey
3329
tracked(
3430
schema: any,
3531
): [delegate: IQueryDelegate, dependencies: Dep<QueryPath>[]] {
@@ -48,19 +44,10 @@ export abstract class BaseDelegate {
4844
dependencies.push({ path, entity });
4945
return entity;
5046
},
51-
forEntities(
52-
key: string,
53-
callbackfn: (value: [string, any]) => void,
54-
): boolean {
55-
const entity = base.getEntities(key);
56-
// always push even if entity is undefined
57-
dependencies.push({ path: [key], entity });
58-
return base.forEntities(key, callbackfn);
59-
},
60-
getEntityKeys(key: string): string[] | symbol {
61-
const entity = base.getEntities(key);
47+
getEntities(key: string): EntitiesInterface | undefined {
48+
const entity = base.getEntitiesObject(key);
6249
dependencies.push({ path: [key], entity });
63-
return base.getEntityKeys(key);
50+
return base.getEntities(key);
6451
},
6552
};
6653
return [delegate, dependencies];

packages/normalizr/src/delegate/Delegate.imm.ts

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
import { BaseDelegate } from './BaseDelegate.js';
2-
import { INVALID } from '../denormalize/symbol.js';
2+
import { EntitiesInterface } from '../interface.js';
33

44
export type ImmutableJSEntityTable = {
5-
get(key: string):
6-
| {
7-
forEach(
8-
sideEffect: (value: any, key: string, iter: any) => unknown,
9-
context?: unknown,
10-
): number;
11-
keySeq(): { toArray(): string[] };
12-
}
13-
| undefined;
5+
get(key: string): EntitiesInterface | undefined;
146
getIn(k: [key: string, pk: string]): { toJS(): any } | undefined;
157
setIn(k: [key: string, pk: string], value: any);
168
};
@@ -27,33 +19,13 @@ export class ImmDelegate extends BaseDelegate {
2719
super(state);
2820
}
2921

30-
forEntities(
31-
key: string,
32-
callbackfn: (value: [string, unknown]) => void,
33-
): boolean {
34-
const entities = this.getEntities(key);
35-
if (!entities) return false;
36-
entities.forEach((entity: any, pk: string) => {
37-
callbackfn([pk, entity]);
38-
});
39-
return true;
40-
}
41-
42-
getEntityKeys(key: string): string[] | symbol {
43-
const entities = this.getEntities(key);
44-
if (entities === undefined) return INVALID;
45-
return entities.keySeq().toArray();
22+
// we must expose the entities object to track in our WeakDependencyMap
23+
// however, this should not be part of the public API
24+
protected getEntitiesObject(key: string): object | undefined {
25+
return this.entities.get(key);
4626
}
4727

48-
protected getEntities(key: string):
49-
| {
50-
forEach(
51-
sideEffect: (value: any, key: string, iter: any) => unknown,
52-
context?: unknown,
53-
): number;
54-
keySeq(): { toArray(): string[] };
55-
}
56-
| undefined {
28+
getEntities(key: string): EntitiesInterface | undefined {
5729
return this.entities.get(key);
5830
}
5931

0 commit comments

Comments
 (0)