Skip to content

Commit 2ad167b

Browse files
authored
fix: support deeply nested relations (#349)
1 parent d3656d8 commit 2ad167b

35 files changed

Lines changed: 93 additions & 36 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ You start by defining a data _collection_.
3434

3535
```ts
3636
import { Collection } from '@msw/data'
37-
import z from 'zod'
37+
import { z } from 'zod'
3838

3939
const users = new Collection({
4040
schema: z.object({

decisions/standard-schema.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ I chose to adopt [Standard Schema](https://standardschema.dev/) as the way to de
44

55
```ts
66
import { Collection } from '@msw/data'
7-
import z from 'zod'
7+
import { z } from 'zod'
88

99
const users = new Collection({
1010
schema: z.object({

src/collection.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,12 @@ export class Collection<Schema extends StandardSchemaV1> {
535535
descriptor: PropertyDescriptor
536536
}> = []
537537

538+
// Track visited records by primary key to detect cycles
539+
// in self-referencing relations. Only strip relation values
540+
// when revisiting a record (i.e. an actual cycle), not for
541+
// all nested records indiscriminately.
542+
const visited = new Set<string>()
543+
538544
const sanitize = (
539545
value: unknown,
540546
path: Array<string | number | symbol> = [],
@@ -544,7 +550,14 @@ export class Collection<Schema extends StandardSchemaV1> {
544550
}
545551

546552
if (isObject(value)) {
547-
const relations = isRecord(value) ? value[kRelationMap] : undefined
553+
const record = isRecord(value) ? value : undefined
554+
const isRevisit = record != null && visited.has(record[kPrimaryKey])
555+
556+
if (record && !isRevisit) {
557+
visited.add(record[kPrimaryKey])
558+
}
559+
560+
const relations = record ? record[kRelationMap] : undefined
548561

549562
return Object.fromEntries(
550563
Reflect.ownKeys(value).map((key) => {
@@ -569,7 +582,10 @@ export class Collection<Schema extends StandardSchemaV1> {
569582

570583
const relation = relations?.get(key)
571584

572-
if (relation && childValue != null) {
585+
// Only strip relation values when revisiting a record
586+
// to break self-referencing cycles. Non-circular nested
587+
// relations are left intact for proper schema validation.
588+
if (isRevisit && relation && childValue != null) {
573589
propertiesToRestore.push({
574590
path: childPath,
575591
descriptor: Object.getOwnPropertyDescriptor(value, key)!,
@@ -586,6 +602,7 @@ export class Collection<Schema extends StandardSchemaV1> {
586602
}
587603

588604
const sanitizedInitialValues = sanitize(initialValues)
605+
589606
return {
590607
sanitizedInitialValues,
591608
/**

tests/clear.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Collection } from '#/src/collection.js'
2-
import z from 'zod'
2+
import { z } from 'zod'
33

44
const schema = z.object({ id: z.number() })
55

tests/count.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Collection } from '#/src/collection.js'
2-
import z from 'zod'
2+
import { z } from 'zod'
33

44
const schema = z.object({
55
id: z.number(),

tests/create-derived-property.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Collection } from '#/src/index.js'
2-
import z from 'zod'
2+
import { z } from 'zod'
33

44
it('derives a value from other values', async () => {
55
const users = new Collection({

tests/create-many.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Collection } from '#/src/collection.js'
2-
import z from 'zod'
2+
import { z } from 'zod'
33

44
const schema = z.object({
55
id: z.number(),

tests/delete-many.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Collection } from '#/src/collection.js'
22
import { Query } from '#/src/query.js'
3-
import z from 'zod'
3+
import { z } from 'zod'
44

55
const schema = z.object({
66
id: z.number(),

tests/delete.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Collection } from '#/src/collection.js'
22
import { Query } from '#/src/query.js'
3-
import z from 'zod'
3+
import { z } from 'zod'
44

55
const schema = z.object({
66
id: z.number(),

tests/find-many.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Collection, Query } from '#/src/index.js'
2-
import z from 'zod'
2+
import { z } from 'zod'
33

44
const schema = z.object({
55
id: z.number(),

0 commit comments

Comments
 (0)