You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There are two ways to create a collection: using type inference or using schema validation.
84
+
There are two main ways to create a collection: using type inference or using schema validation. Type inference will infer collection types from the underlying PowerSync SQLite tables. Schema validation can be used for additional input/output validations and type transforms.
85
85
86
86
#### Option 1: Using Table Type Inference
87
87
88
88
The collection types are automatically inferred from the PowerSync schema table definition. The table is used to construct a default standard schema validator which is used internally to validate collection operations.
89
89
90
+
Collection mutations accept SQLite types and queries report data with SQLite types.
|`column.text`|`string \| null`| Text values, commonly used for strings, JSON, dates (as ISO strings) |
111
+
|`column.integer`|`number \| null`| Integer values, also used for booleans (0/1) |
112
+
|`column.real`|`number \| null`| Floating point numbers |
103
113
104
-
Additional validations can be performed by supplying a compatible validation schema (such as a Zod schema). The output typing of the validator is constrained to match the typing of the SQLite table. The input typing can be arbitrary.
114
+
Note: All PowerSync column types are nullable by default, as SQLite allows null values in any column. Your schema should always handle null values appropriately by using `.nullable()` in your Zod schemas and handling null cases in your transformations.
115
+
116
+
Additional validations for collection mutations can be performed with a custom schema. The Schema below asserts that
117
+
the `name`, `author` and `created_at` fields are required as input. `name` also has an additional string length check.
118
+
119
+
Note: The input and output types specified in this example still satisfy the underlying SQLite types. An additional `deserializationSchema` is required if the typing differs. See the examples below for more details.
/** Note: The types for input and output are defined as this */
192
+
// Used for mutations like `insert` or `update`
193
+
typeDocumentCollectionInput= {
194
+
id:string
195
+
name:string|null
196
+
author:string|null
197
+
created_at:string|null// SQLite TEXT
198
+
archived:number|null
199
+
}
200
+
// The type of query/data results
201
+
typeDocumentCollectionOutput= {
202
+
id:string
203
+
name:string|null
204
+
author:string|null
205
+
created_at:Date|null// JS Date instance
206
+
archived:boolean|null// JS boolean
207
+
}
208
+
```
209
+
210
+
#### Option 4: Custom Input/Output Types with Deserialization
211
+
212
+
The input and output types can be completely decoupled from the internal SQLite types. This can be used to accept rich values for input mutations.
213
+
We require an additional `deserializationSchema` in order to validate and transform incoming synced (SQLite) updates. This schema should convert the incoming SQLite update to the output type.
214
+
215
+
The application logic (including the backend) should enforce that all incoming synced data passes validation with the `deserializationSchema`. Failing to validate data will result in inconsistency of the collection data. This is a fatal error! An `onDeserializationError` handler must be provided to react to this case.
216
+
217
+
```ts
218
+
// Our input/output types use Date and boolean
219
+
const schema =z.object({
220
+
id: z.string(),
221
+
name: z.string(),
222
+
author: z.string(),
223
+
created_at: z.date(), // Accept Date objects as input
224
+
})
225
+
226
+
// Schema to transform from SQLite types to our output types
227
+
const deserializationSchema =z.object({
228
+
id: z.string(),
229
+
name: z.string(),
230
+
author: z.string(),
231
+
created_at: z
232
+
.string()
233
+
.nullable()
234
+
.transform((val) => (val?newDate(val) :null)), // SQLite TEXT to Date
115
235
})
116
236
117
237
const documentsCollection =createCollection(
118
238
powerSyncCollectionOptions({
119
239
database: db,
120
240
table: APP_SCHEMA.props.documents,
121
241
schema,
242
+
deserializationSchema,
243
+
onDeserializationError: (error) => {
244
+
// Present fatal error
245
+
},
122
246
})
123
247
)
124
248
```
@@ -138,6 +262,102 @@ When connected to a PowerSync backend, changes are automatically synchronized in
138
262
- Queue management for offline changes
139
263
- Automatic retries on connection loss
140
264
265
+
### Working with Rich JavaScript Types
266
+
267
+
PowerSync collections support rich JavaScript types like `Date`, `Boolean`, and custom objects while maintaining SQLite compatibility. The collection handles serialization and deserialization automatically:
due_date: "2025-10-30T10:00:00Z", // String input is automatically converted to Date
317
+
completed: 0, // Number input is automatically converted to boolean
318
+
metadata: JSON.stringify({ priority: "high" }),
319
+
})
320
+
321
+
// Query returns rich types
322
+
const task =tasksCollection.get("task-1")
323
+
console.log(task.due_dateinstanceofDate) // true
324
+
console.log(typeoftask.completed) // "boolean"
325
+
console.log(task.metadata.priority) // "high"
326
+
```
327
+
328
+
### Type Safety with Rich Types
329
+
330
+
The collection maintains type safety throughout:
331
+
332
+
```typescript
333
+
typeTaskInput= {
334
+
id:string
335
+
title:string|null
336
+
due_date:string|null// Accept ISO string for mutations
337
+
completed:number|null// Accept 0/1 for mutations
338
+
metadata:string|null// Accept JSON string for mutations
339
+
}
340
+
341
+
typeTaskOutput= {
342
+
id:string
343
+
title:string|null
344
+
due_date:Date|null// Get Date object in queries
345
+
completed:boolean|null// Get boolean in queries
346
+
metadata: {
347
+
priority:string
348
+
[key: string]:any
349
+
} |null
350
+
}
351
+
352
+
// TypeScript enforces correct types:
353
+
tasksCollection.insert({
354
+
due_date: newDate(), // Error: Type 'Date' is not assignable to type 'string'
355
+
})
356
+
357
+
const task =tasksCollection.get("task-1")
358
+
task.due_date.getTime() // OK - TypeScript knows this is a Date
359
+
```
360
+
141
361
### Optimistic Updates
142
362
143
363
Updates to the collection are applied optimistically to the local state first, then synchronized with PowerSync and the backend. If an error occurs during sync, the changes are automatically rolled back.
@@ -147,10 +367,23 @@ Updates to the collection are applied optimistically to the local state first, t
147
367
The `powerSyncCollectionOptions` function accepts the following options:
0 commit comments