Skip to content

Commit 3b02402

Browse files
committed
fix(js-client)!: enrich circular link dependencies
Instead of manually setting a flag to stop enriching, we now check if an object has already been enriched. This ensures that all linked objects are enriched, even in cases of circular dependencies. A `WeakSet` is used to track enriched objects, ensuring they can be garbage collected when no longer referenced. BREAKING CHANGE: The client's output is no longer guaranteed to be serializable (e.g., with `JSON.stringify`). Due to the full resolution of circular dependencies, the resulting object graph may contain cycles. Fixes WDX-146
1 parent bf6fdde commit 3b02402

File tree

2 files changed

+13
-27
lines changed

2 files changed

+13
-27
lines changed

packages/js-client/src/index.test.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,6 @@ describe('storyblokClient', () => {
921921
name: 'Test Event Type',
922922
component: 'event_type',
923923
},
924-
_stopResolving: true,
925924
});
926925

927926
// Verify that get was called two times
@@ -997,7 +996,6 @@ describe('storyblokClient', () => {
997996
name: 'Tag 1',
998997
component: 'tag',
999998
},
1000-
_stopResolving: true,
1001999
},
10021000
{
10031001
_uid: 'tag-2-uid',
@@ -1006,7 +1004,6 @@ describe('storyblokClient', () => {
10061004
name: 'Tag 2',
10071005
component: 'tag',
10081006
},
1009-
_stopResolving: true,
10101007
},
10111008
]);
10121009
});
@@ -1081,7 +1078,6 @@ describe('storyblokClient', () => {
10811078
name: 'John Doe',
10821079
component: 'author',
10831080
},
1084-
_stopResolving: true,
10851081
});
10861082

10871083
expect(result.data.story.content.category).toEqual({
@@ -1091,7 +1087,6 @@ describe('storyblokClient', () => {
10911087
name: 'Technology',
10921088
component: 'category',
10931089
},
1094-
_stopResolving: true,
10951090
});
10961091
});
10971092

packages/js-client/src/index.ts

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export class Storyblok {
7474
private accessToken: string;
7575
private cache: ISbCache;
7676
private resolveCounter: number;
77+
private enriched: WeakSet<ISbStoriesParams | ISbStoriesParams[]>;
7778
public relations: RelationsType;
7879
public links: LinksType;
7980
public version: StoryblokContentVersionKeys | undefined;
@@ -153,6 +154,7 @@ export class Storyblok {
153154
this.links = {} as LinksType;
154155
this.cache = config.cache || { clear: 'manual' };
155156
this.resolveCounter = 0;
157+
this.enriched = new WeakSet();
156158
this.resolveNestedRelations = config.resolveNestedRelations || true;
157159
this.stringifiedStoriesCache = {} as Record<string, string>;
158160
this.version = config.version || StoryblokContentVersion.PUBLISHED; // the default version is published as per API documentation
@@ -343,10 +345,6 @@ export class Storyblok {
343345
}
344346
}
345347

346-
private _cleanCopy(value: LinksType): JSON {
347-
return JSON.parse(JSON.stringify(value));
348-
}
349-
350348
private _insertLinks(
351349
jtree: ISbStoriesParams,
352350
treeItem: keyof ISbStoriesParams,
@@ -361,15 +359,15 @@ export class Storyblok {
361359
&& typeof node.id === 'string'
362360
&& this.links[resolveId][node.id]
363361
) {
364-
node.story = this._cleanCopy(this.links[resolveId][node.id]);
362+
node.story = this.links[resolveId][node.id];
365363
}
366364
else if (
367365
node
368366
&& node.linktype === 'story'
369367
&& typeof node.uuid === 'string'
370368
&& this.links[resolveId][node.uuid]
371369
) {
372-
node.story = this._cleanCopy(this.links[resolveId][node.uuid]);
370+
node.story = this.links[resolveId][node.uuid];
373371
}
374372
}
375373

@@ -467,24 +465,23 @@ export class Storyblok {
467465
resolveId: string,
468466
): void {
469467
// Internal recursive function to process each node in the tree
470-
const enrich = (jtree: ISbStoriesParams | any, path = '') => {
471-
// Skip processing if node is null/undefined or marked to stop resolving
472-
if (!jtree || jtree._stopResolving) {
468+
const enrich = (jtree: ISbStoriesParams | any) => {
469+
// Skip processing if node is null/undefined or already enriched
470+
if (!jtree || this.enriched.has(jtree)) {
473471
return;
474472
}
475473

476474
// Handle arrays by recursively processing each element
477475
// Maintains path context by adding array indices
478476
if (Array.isArray(jtree)) {
479-
jtree.forEach((item, index) => enrich(item, `${path}[${index}]`));
477+
jtree.forEach(item => enrich(item));
478+
this.enriched.add(jtree);
480479
}
481480
// Handle object nodes
482481
else if (typeof jtree === 'object') {
482+
this.enriched.add(jtree);
483483
// Process each property in the object
484484
for (const key in jtree) {
485-
// Build the current path for the context
486-
const newPath = path ? `${path}.${key}` : key;
487-
488485
// If this is a component (has component and _uid) or a link,
489486
// attempt to resolve its relations and links
490487
if ((jtree.component && jtree._uid) || jtree.type === 'link') {
@@ -494,7 +491,7 @@ export class Storyblok {
494491

495492
// Continue traversing deeper into the tree
496493
// This ensures we process nested components and their relations
497-
enrich(jtree[key], newPath);
494+
enrich(jtree[key]);
498495
}
499496
}
500497
};
@@ -541,10 +538,7 @@ export class Storyblok {
541538
}
542539

543540
links.forEach((story: ISbStoryData | any) => {
544-
this.links[resolveId][story.uuid] = {
545-
...story,
546-
...{ _stopResolving: true },
547-
};
541+
this.links[resolveId][story.uuid] = story;
548542
});
549543
}
550544

@@ -592,10 +586,7 @@ export class Storyblok {
592586

593587
if (relations && relations.length > 0) {
594588
relations.forEach((story: ISbStoryData) => {
595-
this.relations[resolveId][story.uuid] = {
596-
...story,
597-
...{ _stopResolving: true },
598-
};
589+
this.relations[resolveId][story.uuid] = story;
599590
});
600591
}
601592
}

0 commit comments

Comments
 (0)