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
21 changes: 11 additions & 10 deletions packages/endpoint/src/schemas/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ function CreateMover<C extends CollectionSchema<any, any>>(
addMerge: (existing: any, incoming: any) => any,
removeMerge: (existing: any, incoming: any) => any,
) {
return Object.create(
const mover = Object.create(
collection,
derivedProperties(
collection,
Expand All @@ -412,8 +412,7 @@ function CreateMover<C extends CollectionSchema<any, any>>(
) {
return normalizeMove.call(
this,
addMerge,
removeMerge,
(this as any)._removeSchema,
input,
parent,
key,
Expand All @@ -424,12 +423,17 @@ function CreateMover<C extends CollectionSchema<any, any>>(
},
),
);
// Pre-create the remove schema once so normalizeMove avoids
// per-call Object.create (which causes V8 hidden-class polymorphism).
mover._removeSchema = Object.create(mover, {
merge: { value: removeMerge },
});
return mover;
}

function normalizeMove(
this: CollectionSchema<any, any>,
addMerge: (existing: any, incoming: any) => any,
removeMergeFunc: (existing: any, incoming: any) => any,
removeSchema: CollectionSchema<any, any>,
input: any,
parent: any,
key: string,
Expand Down Expand Up @@ -470,10 +474,6 @@ function normalizeMove(
);

const lastArg = args[args.length - 1];
const addSchema = Object.create(this, { merge: { value: addMerge } });
const removeSchema = Object.create(this, {
merge: { value: removeMergeFunc },
});
const collections = delegate.getEntities(this.key);

// Process each entity's collection membership individually
Expand Down Expand Up @@ -507,7 +507,8 @@ function normalizeMove(
if (shouldRemove && !shouldAdd) {
delegate.mergeEntity(removeSchema, collectionKey, value);
} else if (shouldAdd && !shouldRemove) {
delegate.mergeEntity(addSchema, collectionKey, value);
// this.merge is already the addMerge function
delegate.mergeEntity(this, collectionKey, value);
}
}
}
Expand Down
36 changes: 25 additions & 11 deletions packages/normalizr/src/memo/globalCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export default class GlobalCache implements Cache {
computeValue: (localCacheKey: Map<string, any>) => void,
): object | undefined | typeof INVALID {
const key = schema.key;
const { localCacheKey, cycleCacheKey } = this.getCacheKey(key);
// cycleCache is deferred to the branch that actually needs it
// to avoid unnecessary allocations.
const localCacheKey = this.getOrCreateLocalCache(key);

if (!localCacheKey.get(pk)) {
const globalCache: WeakDependencyMap<
Expand All @@ -54,12 +56,17 @@ export default class GlobalCache implements Cache {
localCacheKey.set(pk, cacheValue.value);
// TODO: can we store the cache values instead of tracking *all* their sources?
// this is only used for setting endpoints cache correctly. if we got this far we will def need to set as we would have already tried getting it
this.dependencies.push(...cacheValue.dependencies);
// Indexed loop avoids spread-into-push overhead for large dep arrays
const cdeps = cacheValue.dependencies;
for (let i = 0; i < cdeps.length; i++) {
this.dependencies.push(cdeps[i]);
}
return cacheValue.value;
}
// if we don't find in denormalize cache then do full denormalize
else {
const trackingIndex = this.dependencies.length;
const cycleCacheKey = this.getOrCreateCycleCache(key);
cycleCacheKey.set(pk, trackingIndex);
this.dependencies.push({ path: { key, pk }, entity });

Expand All @@ -85,8 +92,9 @@ export default class GlobalCache implements Cache {
}
}
} else {
const cycleCacheKey = this.cycleCache.get(key);
// cycle detected
if (cycleCacheKey.has(pk)) {
if (cycleCacheKey?.has(pk)) {
this.cycleIndex = cycleCacheKey.get(pk)!;
} else {
// with no cycle, globalCacheEntry will have already been set
Expand All @@ -96,16 +104,22 @@ export default class GlobalCache implements Cache {
return localCacheKey.get(pk);
}

private getCacheKey(key: string) {
if (!this.localCache.has(key)) {
this.localCache.set(key, new Map());
private getOrCreateLocalCache(key: string): Map<string, any> {
let localCacheKey = this.localCache.get(key);
if (!localCacheKey) {
localCacheKey = new Map();
this.localCache.set(key, localCacheKey);
}
if (!this.cycleCache.has(key)) {
this.cycleCache.set(key, new Map());
return localCacheKey;
}

private getOrCreateCycleCache(key: string): Map<string, number> {
let cycleCacheKey = this.cycleCache.get(key);
if (!cycleCacheKey) {
cycleCacheKey = new Map();
this.cycleCache.set(key, cycleCacheKey);
}
const localCacheKey = this.localCache.get(key)!;
const cycleCacheKey = this.cycleCache.get(key)!;
return { localCacheKey, cycleCacheKey };
return cycleCacheKey;
}

/** Cache varies based on input (=== aka reference) */
Expand Down
Loading