Skip to content

Commit f9f24b4

Browse files
committed
fix(db-sqlite-persistence-core): add schema-aware overloads to persistedCollectionOptions
persistedCollectionOptions was missing schema-aware overloads, causing TSchema to default to `never` when a schema was passed. This made the result incompatible with createCollection's schema overloads. Add four overloads matching the pattern used by localOnlyCollectionOptions and localStorageCollectionOptions: - schema provided + sync present - schema provided + sync absent - no schema + sync present - no schema + sync absent
1 parent e8e029e commit f9f24b4

2 files changed

Lines changed: 249 additions & 3 deletions

File tree

packages/db-sqlite-persistence-core/src/persisted.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
CollectionConfig,
1515
CollectionIndexMetadata,
1616
DeleteMutationFnParams,
17+
InferSchemaOutput,
1718
InsertMutationFnParams,
1819
LoadSubsetOptions,
1920
PendingMutation,
@@ -2572,22 +2573,76 @@ function createLoopbackSyncConfig<
25722573
}
25732574
}
25742575

2576+
// Overload for when schema is provided and sync is present
2577+
export function persistedCollectionOptions<
2578+
TSchema extends StandardSchemaV1,
2579+
TKey extends string | number,
2580+
TUtils extends UtilsRecord = UtilsRecord,
2581+
>(
2582+
options: PersistedSyncWrappedOptions<
2583+
InferSchemaOutput<TSchema>,
2584+
TKey,
2585+
TSchema,
2586+
TUtils
2587+
> & {
2588+
schema: TSchema
2589+
},
2590+
): PersistedSyncOptionsResult<
2591+
InferSchemaOutput<TSchema>,
2592+
TKey,
2593+
TSchema,
2594+
TUtils
2595+
> & {
2596+
schema: TSchema
2597+
}
2598+
2599+
// Overload for when schema is provided and sync is absent
2600+
export function persistedCollectionOptions<
2601+
TSchema extends StandardSchemaV1,
2602+
TKey extends string | number,
2603+
TUtils extends UtilsRecord = UtilsRecord,
2604+
>(
2605+
options: PersistedLocalOnlyOptions<
2606+
InferSchemaOutput<TSchema>,
2607+
TKey,
2608+
TSchema,
2609+
TUtils
2610+
> & {
2611+
schema: TSchema
2612+
},
2613+
): PersistedLocalOnlyOptionsResult<
2614+
InferSchemaOutput<TSchema>,
2615+
TKey,
2616+
TSchema,
2617+
TUtils
2618+
> & {
2619+
schema: TSchema
2620+
}
2621+
2622+
// Overload for when no schema is provided and sync is present
2623+
// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
25752624
export function persistedCollectionOptions<
25762625
T extends object,
25772626
TKey extends string | number,
25782627
TSchema extends StandardSchemaV1 = never,
25792628
TUtils extends UtilsRecord = UtilsRecord,
25802629
>(
2581-
options: PersistedSyncWrappedOptions<T, TKey, TSchema, TUtils>,
2630+
options: PersistedSyncWrappedOptions<T, TKey, TSchema, TUtils> & {
2631+
schema?: never // prohibit schema
2632+
},
25822633
): PersistedSyncOptionsResult<T, TKey, TSchema, TUtils>
25832634

2635+
// Overload for when no schema is provided and sync is absent
2636+
// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
25842637
export function persistedCollectionOptions<
25852638
T extends object,
25862639
TKey extends string | number,
25872640
TSchema extends StandardSchemaV1 = never,
25882641
TUtils extends UtilsRecord = UtilsRecord,
25892642
>(
2590-
options: PersistedLocalOnlyOptions<T, TKey, TSchema, TUtils>,
2643+
options: PersistedLocalOnlyOptions<T, TKey, TSchema, TUtils> & {
2644+
schema?: never // prohibit schema
2645+
},
25912646
): PersistedLocalOnlyOptionsResult<T, TKey, TSchema, TUtils>
25922647

25932648
export function persistedCollectionOptions<

packages/db-sqlite-persistence-core/tests/persisted.test-d.ts

Lines changed: 192 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import { describe, expectTypeOf, it } from 'vitest'
2+
import { z } from 'zod'
23
import { createCollection } from '@tanstack/db'
34
import { persistedCollectionOptions } from '../src'
45
import type { PersistedCollectionUtils, PersistenceAdapter } from '../src'
5-
import type { SyncConfig, UtilsRecord } from '@tanstack/db'
6+
import type { SyncConfig, UtilsRecord, WithVirtualProps } from '@tanstack/db'
7+
8+
type OutputWithVirtual<
9+
T extends object,
10+
TKey extends string | number = string | number,
11+
> = WithVirtualProps<T, TKey>
12+
13+
type ItemOf<T> = T extends Array<infer U> ? U : T
614

715
type Todo = {
816
id: string
@@ -90,6 +98,11 @@ describe(`persisted collection types`, () => {
9098
// @ts-expect-error persistedCollectionOptions requires a persistence config
9199
persistedCollectionOptions({
92100
getKey: (item: Todo) => item.id,
101+
})
102+
103+
persistedCollectionOptions({
104+
getKey: (item: Todo) => item.id,
105+
// @ts-expect-error persistedCollectionOptions requires a persistence config when sync is provided
93106
sync: {
94107
sync: ({ markReady }: { markReady: () => void }) => {
95108
markReady()
@@ -108,4 +121,182 @@ describe(`persisted collection types`, () => {
108121
},
109122
})
110123
})
124+
125+
it(`should work with schema and infer correct types when saved to a variable in sync-absent mode`, () => {
126+
const testSchema = z.object({
127+
id: z.string(),
128+
title: z.string(),
129+
createdAt: z.date().optional().default(new Date()),
130+
})
131+
132+
type ExpectedType = z.infer<typeof testSchema>
133+
type ExpectedInput = z.input<typeof testSchema>
134+
135+
const schemaAdapter: PersistenceAdapter<ExpectedType, string> = {
136+
loadSubset: () => Promise.resolve([]),
137+
applyCommittedTx: () => Promise.resolve(),
138+
ensureIndex: () => Promise.resolve(),
139+
}
140+
141+
const options = persistedCollectionOptions({
142+
id: `test-local-schema`,
143+
schema: testSchema,
144+
schemaVersion: 1,
145+
getKey: (item) => item.id,
146+
persistence: { adapter: schemaAdapter },
147+
})
148+
149+
expectTypeOf(options.schema).toEqualTypeOf<typeof testSchema>()
150+
151+
const collection = createCollection(options)
152+
153+
// Test that the collection has the correct inferred type from schema
154+
expectTypeOf(collection.toArray).toEqualTypeOf<
155+
Array<OutputWithVirtual<ExpectedType, string>>
156+
>()
157+
158+
// Test insert parameter type
159+
type InsertParam = Parameters<typeof collection.insert>[0]
160+
expectTypeOf<ItemOf<InsertParam>>().toEqualTypeOf<ExpectedInput>()
161+
162+
// Check that the update method accepts the expected input type
163+
collection.update(`1`, (draft) => {
164+
expectTypeOf(draft).toEqualTypeOf<ExpectedInput>()
165+
})
166+
})
167+
168+
it(`should work with schema and infer correct types when nested in createCollection in sync-absent mode`, () => {
169+
const testSchema = z.object({
170+
id: z.string(),
171+
title: z.string(),
172+
createdAt: z.date().optional().default(new Date()),
173+
})
174+
175+
type ExpectedType = z.infer<typeof testSchema>
176+
type ExpectedInput = z.input<typeof testSchema>
177+
178+
const schemaAdapter: PersistenceAdapter<ExpectedType, string> = {
179+
loadSubset: () => Promise.resolve([]),
180+
applyCommittedTx: () => Promise.resolve(),
181+
ensureIndex: () => Promise.resolve(),
182+
}
183+
184+
const collection = createCollection(
185+
persistedCollectionOptions({
186+
id: `test-local-schema-nested`,
187+
schema: testSchema,
188+
schemaVersion: 1,
189+
getKey: (item) => item.id,
190+
persistence: { adapter: schemaAdapter },
191+
}),
192+
)
193+
194+
// Test that the collection has the correct inferred type from schema
195+
expectTypeOf(collection.toArray).toEqualTypeOf<
196+
Array<OutputWithVirtual<ExpectedType, string>>
197+
>()
198+
199+
// Test insert parameter type
200+
type InsertParam = Parameters<typeof collection.insert>[0]
201+
expectTypeOf<ItemOf<InsertParam>>().toEqualTypeOf<ExpectedInput>()
202+
203+
// Check that the update method accepts the expected input type
204+
collection.update(`1`, (draft) => {
205+
expectTypeOf(draft).toEqualTypeOf<ExpectedInput>()
206+
})
207+
})
208+
209+
it(`should work with schema and infer correct types when saved to a variable in sync-present mode`, () => {
210+
const testSchema = z.object({
211+
id: z.string(),
212+
title: z.string(),
213+
createdAt: z.date().optional().default(new Date()),
214+
})
215+
216+
type ExpectedType = z.infer<typeof testSchema>
217+
type ExpectedInput = z.input<typeof testSchema>
218+
219+
const schemaAdapter: PersistenceAdapter<ExpectedType, string> = {
220+
loadSubset: () => Promise.resolve([]),
221+
applyCommittedTx: () => Promise.resolve(),
222+
ensureIndex: () => Promise.resolve(),
223+
}
224+
225+
const options = persistedCollectionOptions({
226+
id: `test-sync-schema`,
227+
schema: testSchema,
228+
schemaVersion: 1,
229+
getKey: (item) => item.id,
230+
sync: {
231+
sync: ({ markReady }) => {
232+
markReady()
233+
},
234+
},
235+
persistence: { adapter: schemaAdapter },
236+
})
237+
238+
expectTypeOf(options.schema).toEqualTypeOf<typeof testSchema>()
239+
240+
const collection = createCollection(options)
241+
242+
// Test that the collection has the correct inferred type from schema
243+
expectTypeOf(collection.toArray).toEqualTypeOf<
244+
Array<OutputWithVirtual<ExpectedType, string>>
245+
>()
246+
247+
// Test insert parameter type
248+
type InsertParam = Parameters<typeof collection.insert>[0]
249+
expectTypeOf<ItemOf<InsertParam>>().toEqualTypeOf<ExpectedInput>()
250+
251+
// Check that the update method accepts the expected input type
252+
collection.update(`1`, (draft) => {
253+
expectTypeOf(draft).toEqualTypeOf<ExpectedInput>()
254+
})
255+
})
256+
257+
it(`should work with schema and infer correct types when nested in createCollection in sync-present mode`, () => {
258+
const testSchema = z.object({
259+
id: z.string(),
260+
title: z.string(),
261+
createdAt: z.date().optional().default(new Date()),
262+
})
263+
264+
type ExpectedType = z.infer<typeof testSchema>
265+
type ExpectedInput = z.input<typeof testSchema>
266+
267+
const schemaAdapter: PersistenceAdapter<ExpectedType, string> = {
268+
loadSubset: () => Promise.resolve([]),
269+
applyCommittedTx: () => Promise.resolve(),
270+
ensureIndex: () => Promise.resolve(),
271+
}
272+
273+
const collection = createCollection(
274+
persistedCollectionOptions({
275+
id: `test-sync-schema-nested`,
276+
schema: testSchema,
277+
schemaVersion: 1,
278+
getKey: (item) => item.id,
279+
sync: {
280+
sync: ({ markReady }) => {
281+
markReady()
282+
},
283+
},
284+
persistence: { adapter: schemaAdapter },
285+
}),
286+
)
287+
288+
// Test that the collection has the correct inferred type from schema
289+
expectTypeOf(collection.toArray).toEqualTypeOf<
290+
Array<OutputWithVirtual<ExpectedType, string>>
291+
>()
292+
293+
// Test insert parameter type
294+
type InsertParam = Parameters<typeof collection.insert>[0]
295+
expectTypeOf<ItemOf<InsertParam>>().toEqualTypeOf<ExpectedInput>()
296+
297+
// Check that the update method accepts the expected input type
298+
collection.update(`1`, (draft) => {
299+
expectTypeOf(draft).toEqualTypeOf<ExpectedInput>()
300+
})
301+
})
111302
})

0 commit comments

Comments
 (0)