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
13 changes: 13 additions & 0 deletions .changeset/fix-denorm-depth-limit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@data-client/normalizr': patch
'@data-client/core': patch
'@data-client/react': patch
'@data-client/vue': patch
---

Fix stack overflow during denormalization of large bidirectional entity graphs.

Add entity depth limit (128) to prevent `RangeError: Maximum call stack size exceeded`
when denormalizing cross-type chains with thousands of unique entities
(e.g., Department → Building → Department → ...). Entities beyond the depth limit
are returned with unresolved ids instead of fully denormalized nested objects.
14 changes: 14 additions & 0 deletions examples/benchmark/normalizr.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
ProjectWithBuildTypesDescription,
ProjectSchemaMixin,
User,
Department,
buildBidirectionalChain,
} from './schemas.js';
import userData from './user.json' with { type: 'json' };

Expand Down Expand Up @@ -147,6 +149,18 @@ export default function addNormlizrSuite(suite, filter) {
memo.denormalize(User, 'gnoff', githubState.entities);
});

const chain50 = buildBidirectionalChain(50);
add('denormalize bidirectional 50', () => {
return new MemoCache().denormalize(
Department,
chain50.result,
chain50.entities,
);
});
add('denormalize bidirectional 50 donotcache', () => {
return denormalize(Department, chain50.result, chain50.entities);
});

return suite.on('complete', function () {
if (process.env.SHOW_OPTIMIZATION) {
printStatus(memo.denormalize);
Expand Down
50 changes: 50 additions & 0 deletions examples/benchmark/schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,56 @@ export const getSortedProjects = new Query(
},
);

// Degenerate bidirectional chain for #3822 stack overflow testing
export class Department extends Entity {
id = '';
name = '';
buildings = [];

static key = 'Department';
pk() {
return this.id;
}
}
export class Building extends Entity {
id = '';
name = '';
departments = [];

static schema = {
departments: [Department],
};

static key = 'Building';
pk() {
return this.id;
}
}
Department.schema = {
buildings: [Building],
};

export function buildBidirectionalChain(length) {
const departmentEntities = {};
const buildingEntities = {};
for (let i = 0; i < length; i++) {
departmentEntities[`dept-${i}`] = {
id: `dept-${i}`,
name: `Department ${i}`,
buildings: [`bldg-${i}`],
};
buildingEntities[`bldg-${i}`] = {
id: `bldg-${i}`,
name: `Building ${i}`,
departments: i < length - 1 ? [`dept-${i + 1}`] : [],
};
}
return {
entities: { Department: departmentEntities, Building: buildingEntities },
result: 'dept-0',
};
}

class BuildTypeDescriptionSimpleMerge extends Entity {
static merge(existing, incoming) {
return incoming;
Expand Down
49 changes: 23 additions & 26 deletions examples/github-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions examples/todo-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading