Skip to content

Commit 1cbc57d

Browse files
committed
change update to use the schema
1 parent 43ac7e7 commit 1cbc57d

File tree

2 files changed

+272
-3
lines changed

2 files changed

+272
-3
lines changed

packages/db/src/collection.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
7272
* @template T - The type of items in the collection
7373
* @template TKey - The type of the key for the collection
7474
* @template TUtils - The utilities record type
75+
* @template TSchema - The schema type for validation and type inference
7576
* @template TInsertInput - The type for insert operations (can be different from T for schemas with defaults)
7677
*/
7778
export interface Collection<
@@ -1613,9 +1614,14 @@ export class CollectionImpl<
16131614
throw new SchemaValidationError(type, typedIssues)
16141615
}
16151616

1616-
// Return the original update data, not the merged data
1617-
// We only used the merged data for validation
1618-
return data as T
1617+
// Extract only the modified keys from the validated result
1618+
const validatedMergedData = result.value as T
1619+
const modifiedKeys = Object.keys(data)
1620+
const extractedChanges = Object.fromEntries(
1621+
modifiedKeys.map((k) => [k, validatedMergedData[k as keyof T]])
1622+
) as T
1623+
1624+
return extractedChanges
16191625
}
16201626
}
16211627

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import { describe, expect, it } from "vitest"
2+
import { z } from "zod"
3+
import { createCollection } from "../src/collection"
4+
5+
describe(`Collection Schema Validation`, () => {
6+
it(`should apply transformations for both insert and update operations`, () => {
7+
// Create a schema with transformations
8+
const userSchema = z.object({
9+
id: z.string(),
10+
name: z.string(),
11+
email: z.string().email(),
12+
created_at: z.string().transform((val) => new Date(val)),
13+
updated_at: z.string().transform((val) => new Date(val)),
14+
})
15+
16+
const collection = createCollection({
17+
getKey: (item) => item.id,
18+
schema: userSchema,
19+
sync: { sync: () => {} },
20+
})
21+
22+
// Test insert validation
23+
const insertData = {
24+
id: `1`,
25+
name: `John Doe`,
26+
27+
created_at: `2023-01-01T00:00:00.000Z`,
28+
updated_at: `2023-01-01T00:00:00.000Z`,
29+
}
30+
31+
const validatedInsert = collection.validateData(insertData, `insert`)
32+
33+
// Verify that the inserted data has been transformed
34+
expect(validatedInsert.created_at).toBeInstanceOf(Date)
35+
expect(validatedInsert.updated_at).toBeInstanceOf(Date)
36+
expect(validatedInsert.name).toBe(`John Doe`)
37+
expect(validatedInsert.email).toBe(`[email protected]`)
38+
39+
// Test update validation - use a schema that accepts both string and Date for existing data
40+
const updateSchema = z.object({
41+
id: z.string(),
42+
name: z.string(),
43+
email: z.string().email(),
44+
created_at: z
45+
.union([z.string(), z.date()])
46+
.transform((val) => (typeof val === `string` ? new Date(val) : val)),
47+
updated_at: z
48+
.union([z.string(), z.date()])
49+
.transform((val) => (typeof val === `string` ? new Date(val) : val)),
50+
})
51+
52+
const updateCollection = createCollection({
53+
getKey: (item) => item.id,
54+
schema: updateSchema,
55+
sync: { sync: () => {} },
56+
})
57+
58+
// Add the validated insert data to the update collection
59+
;(updateCollection as any).syncedData.set(`1`, validatedInsert)
60+
61+
const updateData = {
62+
name: `Jane Doe`,
63+
64+
updated_at: `2023-01-02T00:00:00.000Z`,
65+
}
66+
67+
const validatedUpdate = updateCollection.validateData(
68+
updateData,
69+
`update`,
70+
`1`
71+
)
72+
73+
// Verify that the updated data has been transformed
74+
expect(validatedUpdate.updated_at).toBeInstanceOf(Date)
75+
expect(validatedUpdate.name).toBe(`Jane Doe`)
76+
expect(validatedUpdate.email).toBe(`[email protected]`)
77+
})
78+
79+
it(`should extract only modified keys from validated update result`, () => {
80+
// Create a schema with transformations that can handle both string and Date inputs
81+
const userSchema = z.object({
82+
id: z.string(),
83+
name: z.string(),
84+
email: z.string().email(),
85+
created_at: z
86+
.union([z.string(), z.date()])
87+
.transform((val) => (typeof val === `string` ? new Date(val) : val)),
88+
updated_at: z
89+
.union([z.string(), z.date()])
90+
.transform((val) => (typeof val === `string` ? new Date(val) : val)),
91+
})
92+
93+
const collection = createCollection({
94+
getKey: (item) => item.id,
95+
schema: userSchema,
96+
sync: { sync: () => {} },
97+
})
98+
99+
// First, we need to add an item to the collection for update validation
100+
const insertData = {
101+
id: `1`,
102+
name: `John Doe`,
103+
104+
created_at: `2023-01-01T00:00:00.000Z`,
105+
updated_at: `2023-01-01T00:00:00.000Z`,
106+
}
107+
108+
const validatedInsert = collection.validateData(insertData, `insert`)
109+
110+
// Manually add the item to the collection's synced data for testing
111+
;(collection as any).syncedData.set(`1`, validatedInsert)
112+
113+
// Test update validation with only modified fields
114+
const updateData = {
115+
name: `Jane Doe`,
116+
updated_at: `2023-01-02T00:00:00.000Z`,
117+
}
118+
119+
const validatedUpdate = collection.validateData(updateData, `update`, `1`)
120+
121+
// Verify that only the modified fields are returned
122+
expect(validatedUpdate).toHaveProperty(`name`)
123+
expect(validatedUpdate).toHaveProperty(`updated_at`)
124+
expect(validatedUpdate).not.toHaveProperty(`id`)
125+
expect(validatedUpdate).not.toHaveProperty(`email`)
126+
expect(validatedUpdate).not.toHaveProperty(`created_at`)
127+
128+
// Verify the changes contain the transformed values
129+
expect(validatedUpdate.name).toBe(`Jane Doe`)
130+
expect(validatedUpdate.updated_at).toBeInstanceOf(Date)
131+
})
132+
133+
it(`should handle schemas with default values correctly`, () => {
134+
// Create a schema with default values that can handle both existing Date objects and new string inputs
135+
const userSchema = z.object({
136+
id: z.string(),
137+
name: z.string(),
138+
email: z.string().email(),
139+
created_at: z
140+
.union([z.date(), z.string()])
141+
.transform((val) => (typeof val === `string` ? new Date(val) : val))
142+
.default(() => new Date()),
143+
updated_at: z
144+
.union([z.date(), z.string()])
145+
.transform((val) => (typeof val === `string` ? new Date(val) : val))
146+
.default(() => new Date()),
147+
})
148+
149+
const collection = createCollection({
150+
getKey: (item) => item.id,
151+
schema: userSchema,
152+
sync: { sync: () => {} },
153+
})
154+
155+
// Test insert validation without providing defaulted fields
156+
const insertData = {
157+
id: `1`,
158+
name: `John Doe`,
159+
160+
}
161+
162+
const validatedInsert = collection.validateData(insertData, `insert`)
163+
164+
// Verify that default values are applied
165+
expect(validatedInsert.created_at).toBeInstanceOf(Date)
166+
expect(validatedInsert.updated_at).toBeInstanceOf(Date)
167+
expect(validatedInsert.name).toBe(`John Doe`)
168+
expect(validatedInsert.email).toBe(`[email protected]`)
169+
170+
// Manually add the item to the collection's synced data for testing
171+
;(collection as any).syncedData.set(`1`, validatedInsert)
172+
173+
// Test update validation without providing defaulted fields
174+
const updateData = {
175+
name: `Jane Doe`,
176+
}
177+
178+
const validatedUpdate = collection.validateData(updateData, `update`, `1`)
179+
180+
// Verify that only the modified field is returned
181+
expect(validatedUpdate).toHaveProperty(`name`)
182+
expect(validatedUpdate).not.toHaveProperty(`updated_at`)
183+
expect(validatedUpdate.name).toBe(`Jane Doe`)
184+
185+
// Test update validation with explicit updated_at field
186+
const updateDataWithTimestamp = {
187+
name: `Jane Smith`,
188+
updated_at: `2023-01-02T00:00:00.000Z`,
189+
}
190+
191+
const validatedUpdateWithTimestamp = collection.validateData(
192+
updateDataWithTimestamp,
193+
`update`,
194+
`1`
195+
)
196+
197+
// Verify that both modified fields are returned with transformations applied
198+
expect(validatedUpdateWithTimestamp).toHaveProperty(`name`)
199+
expect(validatedUpdateWithTimestamp).toHaveProperty(`updated_at`)
200+
expect(validatedUpdateWithTimestamp.name).toBe(`Jane Smith`)
201+
expect(validatedUpdateWithTimestamp.updated_at).toBeInstanceOf(Date)
202+
})
203+
204+
it(`should validate schema input types for both insert and update`, () => {
205+
// Create a schema with different input and output types that can handle both string and Date inputs
206+
const userSchema = z.object({
207+
id: z.string(),
208+
name: z.string(),
209+
email: z.string().email(),
210+
age: z.number().int().positive(),
211+
created_at: z
212+
.union([z.string(), z.date()])
213+
.transform((val) => (typeof val === `string` ? new Date(val) : val)),
214+
updated_at: z
215+
.union([z.string(), z.date()])
216+
.transform((val) => (typeof val === `string` ? new Date(val) : val)),
217+
})
218+
219+
const collection = createCollection({
220+
getKey: (item) => item.id,
221+
schema: userSchema,
222+
sync: { sync: () => {} },
223+
})
224+
225+
// Test that insert validation accepts input type (with string dates)
226+
const insertData = {
227+
id: `1`,
228+
name: `John Doe`,
229+
230+
age: 30,
231+
created_at: `2023-01-01T00:00:00.000Z`,
232+
updated_at: `2023-01-01T00:00:00.000Z`,
233+
}
234+
235+
const validatedInsert = collection.validateData(insertData, `insert`)
236+
237+
// Verify that the output type has Date objects
238+
expect(validatedInsert.created_at).toBeInstanceOf(Date)
239+
expect(validatedInsert.updated_at).toBeInstanceOf(Date)
240+
expect(typeof validatedInsert.age).toBe(`number`)
241+
242+
// Add to collection for update testing
243+
;(collection as any).syncedData.set(`1`, validatedInsert)
244+
245+
// Test that update validation accepts input type for new fields
246+
const updateData = {
247+
name: `Jane Doe`,
248+
age: 31,
249+
updated_at: `2023-01-02T00:00:00.000Z`,
250+
}
251+
252+
const validatedUpdate = collection.validateData(updateData, `update`, `1`)
253+
254+
// Verify that the output type has Date objects and only modified fields
255+
expect(validatedUpdate).toHaveProperty(`name`)
256+
expect(validatedUpdate).toHaveProperty(`age`)
257+
expect(validatedUpdate).toHaveProperty(`updated_at`)
258+
expect(validatedUpdate.updated_at).toBeInstanceOf(Date)
259+
expect(typeof validatedUpdate.age).toBe(`number`)
260+
expect(validatedUpdate.name).toBe(`Jane Doe`)
261+
expect(validatedUpdate.age).toBe(31)
262+
})
263+
})

0 commit comments

Comments
 (0)