Skip to content

Commit 3f3f649

Browse files
kevin-dpUziniii
authored andcommitted
Base collection config interface (TanStack#531)
1 parent 27df19c commit 3f3f649

File tree

8 files changed

+52
-399
lines changed

8 files changed

+52
-399
lines changed

.changeset/evil-regions-stop.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@tanstack/trailbase-db-collection": patch
3+
"@tanstack/electric-db-collection": patch
4+
"@tanstack/query-db-collection": patch
5+
"@tanstack/db": patch
6+
---
7+
8+
Define BaseCollectionConfig interface and let all collections extend it.

packages/db/src/local-only.ts

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
BaseCollectionConfig,
23
CollectionConfig,
34
DeleteMutationFnParams,
45
InferSchemaOutput,
@@ -20,46 +21,15 @@ export interface LocalOnlyCollectionConfig<
2021
T extends object = object,
2122
TSchema extends StandardSchemaV1 = never,
2223
TKey extends string | number = string | number,
23-
> {
24-
/**
25-
* Standard Collection configuration properties
26-
*/
27-
id?: string
28-
schema?: TSchema
29-
getKey: (item: T) => TKey
30-
24+
> extends Omit<
25+
BaseCollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils>,
26+
`gcTime` | `startSync`
27+
> {
3128
/**
3229
* Optional initial data to populate the collection with on creation
3330
* This data will be applied during the initial sync process
3431
*/
3532
initialData?: Array<T>
36-
37-
/**
38-
* Optional asynchronous handler function called after an insert operation
39-
* @param params Object containing transaction and collection information
40-
* @returns Promise resolving to any value
41-
*/
42-
onInsert?: (
43-
params: InsertMutationFnParams<T, TKey, LocalOnlyCollectionUtils>
44-
) => Promise<any>
45-
46-
/**
47-
* Optional asynchronous handler function called after an update operation
48-
* @param params Object containing transaction and collection information
49-
* @returns Promise resolving to any value
50-
*/
51-
onUpdate?: (
52-
params: UpdateMutationFnParams<T, TKey, LocalOnlyCollectionUtils>
53-
) => Promise<any>
54-
55-
/**
56-
* Optional asynchronous handler function called after a delete operation
57-
* @param params Object containing transaction and collection information
58-
* @returns Promise resolving to any value
59-
*/
60-
onDelete?: (
61-
params: DeleteMutationFnParams<T, TKey, LocalOnlyCollectionUtils>
62-
) => Promise<any>
6333
}
6434

6535
/**

packages/db/src/local-storage.ts

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
StorageKeyRequiredError,
88
} from "./errors"
99
import type {
10+
BaseCollectionConfig,
1011
CollectionConfig,
1112
DeleteMutationFnParams,
1213
InferSchemaOutput,
@@ -54,7 +55,7 @@ export interface LocalStorageCollectionConfig<
5455
T extends object = object,
5556
TSchema extends StandardSchemaV1 = never,
5657
TKey extends string | number = string | number,
57-
> {
58+
> extends BaseCollectionConfig<T, TKey, TSchema> {
5859
/**
5960
* The key to use for storing the collection data in localStorage/sessionStorage
6061
*/
@@ -71,35 +72,6 @@ export interface LocalStorageCollectionConfig<
7172
* Can be any object that implements addEventListener/removeEventListener for storage events
7273
*/
7374
storageEventApi?: StorageEventApi
74-
75-
/**
76-
* Collection identifier (defaults to "local-collection:{storageKey}" if not provided)
77-
*/
78-
id?: string
79-
schema?: TSchema
80-
getKey: CollectionConfig<T, TKey, TSchema>[`getKey`]
81-
sync?: CollectionConfig<T, TKey, TSchema>[`sync`]
82-
83-
/**
84-
* Optional asynchronous handler function called before an insert operation
85-
* @param params Object containing transaction and collection information
86-
* @returns Promise resolving to any value
87-
*/
88-
onInsert?: (params: InsertMutationFnParams<T>) => Promise<any>
89-
90-
/**
91-
* Optional asynchronous handler function called before an update operation
92-
* @param params Object containing transaction and collection information
93-
* @returns Promise resolving to any value
94-
*/
95-
onUpdate?: (params: UpdateMutationFnParams<T>) => Promise<any>
96-
97-
/**
98-
* Optional asynchronous handler function called before a delete operation
99-
* @param params Object containing transaction and collection information
100-
* @returns Promise resolving to any value
101-
*/
102-
onDelete?: (params: DeleteMutationFnParams<T>) => Promise<any>
10375
}
10476

10577
/**

packages/db/src/types.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -259,19 +259,22 @@ export type InsertMutationFn<
259259
T extends object = Record<string, unknown>,
260260
TKey extends string | number = string | number,
261261
TUtils extends UtilsRecord = Record<string, Fn>,
262-
> = (params: InsertMutationFnParams<T, TKey, TUtils>) => Promise<any>
262+
TReturn = any,
263+
> = (params: InsertMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>
263264

264265
export type UpdateMutationFn<
265266
T extends object = Record<string, unknown>,
266267
TKey extends string | number = string | number,
267268
TUtils extends UtilsRecord = Record<string, Fn>,
268-
> = (params: UpdateMutationFnParams<T, TKey, TUtils>) => Promise<any>
269+
TReturn = any,
270+
> = (params: UpdateMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>
269271

270272
export type DeleteMutationFn<
271273
T extends object = Record<string, unknown>,
272274
TKey extends string | number = string | number,
273275
TUtils extends UtilsRecord = Record<string, Fn>,
274-
> = (params: DeleteMutationFnParams<T, TKey, TUtils>) => Promise<any>
276+
TReturn = any,
277+
> = (params: DeleteMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>
275278

276279
/**
277280
* Collection status values for lifecycle management
@@ -302,19 +305,20 @@ export type CollectionStatus =
302305
/** Collection has been cleaned up and resources freed */
303306
| `cleaned-up`
304307

305-
export interface CollectionConfig<
308+
export interface BaseCollectionConfig<
306309
T extends object = Record<string, unknown>,
307310
TKey extends string | number = string | number,
308311
// Let TSchema default to `never` such that if a user provides T explicitly and no schema
309312
// then TSchema will be `never` otherwise if it would default to StandardSchemaV1
310313
// then it would conflict with the overloads of createCollection which
311314
// requires either T to be provided or a schema to be provided but not both!
312315
TSchema extends StandardSchemaV1 = never,
316+
TUtils extends UtilsRecord = Record<string, Fn>,
317+
TReturn = any,
313318
> {
314319
// If an id isn't passed in, a UUID will be
315320
// generated for it.
316321
id?: string
317-
sync: SyncConfig<T, TKey>
318322
schema?: TSchema
319323
/**
320324
* Function to extract the ID from an object
@@ -397,7 +401,7 @@ export interface CollectionConfig<
397401
* })
398402
* }
399403
*/
400-
onInsert?: InsertMutationFn<T, TKey>
404+
onInsert?: InsertMutationFn<T, TKey, TUtils, TReturn>
401405

402406
/**
403407
* Optional asynchronous handler function called before an update operation
@@ -441,7 +445,7 @@ export interface CollectionConfig<
441445
* }
442446
* }
443447
*/
444-
onUpdate?: UpdateMutationFn<T, TKey>
448+
onUpdate?: UpdateMutationFn<T, TKey, TUtils, TReturn>
445449
/**
446450
* Optional asynchronous handler function called before a delete operation
447451
* @param params Object containing transaction and collection information
@@ -484,7 +488,15 @@ export interface CollectionConfig<
484488
* }
485489
* }
486490
*/
487-
onDelete?: DeleteMutationFn<T, TKey>
491+
onDelete?: DeleteMutationFn<T, TKey, TUtils, TReturn>
492+
}
493+
494+
export interface CollectionConfig<
495+
T extends object = Record<string, unknown>,
496+
TKey extends string | number = string | number,
497+
TSchema extends StandardSchemaV1 = never,
498+
> extends BaseCollectionConfig<T, TKey, TSchema> {
499+
sync: SyncConfig<T, TKey>
488500
}
489501

490502
export type ChangesPayload<T extends object = Record<string, unknown>> = Array<

packages/electric-db-collection/src/electric.ts

Lines changed: 9 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import {
1313
TimeoutWaitingForTxIdError,
1414
} from "./errors"
1515
import type {
16+
BaseCollectionConfig,
1617
CollectionConfig,
1718
DeleteMutationFnParams,
19+
Fn,
1820
InsertMutationFnParams,
1921
SyncConfig,
2022
UpdateMutationFnParams,
@@ -53,176 +55,17 @@ type InferSchemaOutput<T> = T extends StandardSchemaV1
5355
export interface ElectricCollectionConfig<
5456
T extends Row<unknown> = Row<unknown>,
5557
TSchema extends StandardSchemaV1 = never,
56-
> {
58+
> extends BaseCollectionConfig<
59+
T,
60+
string | number,
61+
TSchema,
62+
Record<string, Fn>,
63+
{ txid: Txid | Array<Txid> }
64+
> {
5765
/**
5866
* Configuration options for the ElectricSQL ShapeStream
5967
*/
6068
shapeOptions: ShapeStreamOptions<GetExtensions<T>>
61-
62-
/**
63-
* All standard Collection configuration properties
64-
*/
65-
id?: string
66-
schema?: TSchema
67-
getKey: CollectionConfig<T, string | number, TSchema>[`getKey`]
68-
sync?: CollectionConfig<T, string | number, TSchema>[`sync`]
69-
70-
/**
71-
* Optional asynchronous handler function called before an insert operation
72-
* Must return an object containing a txid number or array of txids
73-
* @param params Object containing transaction and collection information
74-
* @returns Promise resolving to an object with txid or txids
75-
* @example
76-
* // Basic Electric insert handler - MUST return { txid: number }
77-
* onInsert: async ({ transaction }) => {
78-
* const newItem = transaction.mutations[0].modified
79-
* const result = await api.todos.create({
80-
* data: newItem
81-
* })
82-
* return { txid: result.txid } // Required for Electric sync matching
83-
* }
84-
*
85-
* @example
86-
* // Insert handler with multiple items - return array of txids
87-
* onInsert: async ({ transaction }) => {
88-
* const items = transaction.mutations.map(m => m.modified)
89-
* const results = await Promise.all(
90-
* items.map(item => api.todos.create({ data: item }))
91-
* )
92-
* return { txid: results.map(r => r.txid) } // Array of txids
93-
* }
94-
*
95-
* @example
96-
* // Insert handler with error handling
97-
* onInsert: async ({ transaction }) => {
98-
* try {
99-
* const newItem = transaction.mutations[0].modified
100-
* const result = await api.createTodo(newItem)
101-
* return { txid: result.txid }
102-
* } catch (error) {
103-
* console.error('Insert failed:', error)
104-
* throw error // This will cause the transaction to fail
105-
* }
106-
* }
107-
*
108-
* @example
109-
* // Insert handler with batch operation - single txid
110-
* onInsert: async ({ transaction }) => {
111-
* const items = transaction.mutations.map(m => m.modified)
112-
* const result = await api.todos.createMany({
113-
* data: items
114-
* })
115-
* return { txid: result.txid } // Single txid for batch operation
116-
* }
117-
*/
118-
onInsert?: (
119-
params: InsertMutationFnParams<T>
120-
) => Promise<{ txid: Txid | Array<Txid> }>
121-
122-
/**
123-
* Optional asynchronous handler function called before an update operation
124-
* Must return an object containing a txid number or array of txids
125-
* @param params Object containing transaction and collection information
126-
* @returns Promise resolving to an object with txid or txids
127-
* @example
128-
* // Basic Electric update handler - MUST return { txid: number }
129-
* onUpdate: async ({ transaction }) => {
130-
* const { original, changes } = transaction.mutations[0]
131-
* const result = await api.todos.update({
132-
* where: { id: original.id },
133-
* data: changes // Only the changed fields
134-
* })
135-
* return { txid: result.txid } // Required for Electric sync matching
136-
* }
137-
*
138-
* @example
139-
* // Update handler with multiple items - return array of txids
140-
* onUpdate: async ({ transaction }) => {
141-
* const updates = await Promise.all(
142-
* transaction.mutations.map(m =>
143-
* api.todos.update({
144-
* where: { id: m.original.id },
145-
* data: m.changes
146-
* })
147-
* )
148-
* )
149-
* return { txid: updates.map(u => u.txid) } // Array of txids
150-
* }
151-
*
152-
* @example
153-
* // Update handler with optimistic rollback
154-
* onUpdate: async ({ transaction }) => {
155-
* const mutation = transaction.mutations[0]
156-
* try {
157-
* const result = await api.updateTodo(mutation.original.id, mutation.changes)
158-
* return { txid: result.txid }
159-
* } catch (error) {
160-
* // Transaction will automatically rollback optimistic changes
161-
* console.error('Update failed, rolling back:', error)
162-
* throw error
163-
* }
164-
* }
165-
*/
166-
onUpdate?: (
167-
params: UpdateMutationFnParams<T>
168-
) => Promise<{ txid: Txid | Array<Txid> }>
169-
170-
/**
171-
* Optional asynchronous handler function called before a delete operation
172-
* Must return an object containing a txid number or array of txids
173-
* @param params Object containing transaction and collection information
174-
* @returns Promise resolving to an object with txid or txids
175-
* @example
176-
* // Basic Electric delete handler - MUST return { txid: number }
177-
* onDelete: async ({ transaction }) => {
178-
* const mutation = transaction.mutations[0]
179-
* const result = await api.todos.delete({
180-
* id: mutation.original.id
181-
* })
182-
* return { txid: result.txid } // Required for Electric sync matching
183-
* }
184-
*
185-
* @example
186-
* // Delete handler with multiple items - return array of txids
187-
* onDelete: async ({ transaction }) => {
188-
* const deletes = await Promise.all(
189-
* transaction.mutations.map(m =>
190-
* api.todos.delete({
191-
* where: { id: m.key }
192-
* })
193-
* )
194-
* )
195-
* return { txid: deletes.map(d => d.txid) } // Array of txids
196-
* }
197-
*
198-
* @example
199-
* // Delete handler with batch operation - single txid
200-
* onDelete: async ({ transaction }) => {
201-
* const idsToDelete = transaction.mutations.map(m => m.original.id)
202-
* const result = await api.todos.deleteMany({
203-
* ids: idsToDelete
204-
* })
205-
* return { txid: result.txid } // Single txid for batch operation
206-
* }
207-
*
208-
* @example
209-
* // Delete handler with optimistic rollback
210-
* onDelete: async ({ transaction }) => {
211-
* const mutation = transaction.mutations[0]
212-
* try {
213-
* const result = await api.deleteTodo(mutation.original.id)
214-
* return { txid: result.txid }
215-
* } catch (error) {
216-
* // Transaction will automatically rollback optimistic changes
217-
* console.error('Delete failed, rolling back:', error)
218-
* throw error
219-
* }
220-
* }
221-
*
222-
*/
223-
onDelete?: (
224-
params: DeleteMutationFnParams<T>
225-
) => Promise<{ txid: Txid | Array<Txid> }>
22669
}
22770

22871
function isUpToDateMessage<T extends Row<unknown>>(

0 commit comments

Comments
 (0)