@livestore/common 0.3.2-dev.13 → 0.3.2-dev.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ClientSessionLeaderThreadProxy.d.ts +2 -2
  3. package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
  4. package/dist/adapter-types.d.ts +1 -1
  5. package/dist/adapter-types.d.ts.map +1 -1
  6. package/dist/debug-info.d.ts +17 -17
  7. package/dist/devtools/devtools-messages-client-session.d.ts +38 -38
  8. package/dist/devtools/devtools-messages-common.d.ts +6 -6
  9. package/dist/devtools/devtools-messages-leader.d.ts +28 -28
  10. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  11. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  12. package/dist/leader-thread/types.d.ts +1 -1
  13. package/dist/leader-thread/types.d.ts.map +1 -1
  14. package/dist/schema/LiveStoreEvent.d.ts +5 -5
  15. package/dist/schema/LiveStoreEvent.js.map +1 -1
  16. package/dist/schema/state/sqlite/column-annotations.d.ts +34 -0
  17. package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -0
  18. package/dist/schema/state/sqlite/column-annotations.js +50 -0
  19. package/dist/schema/state/sqlite/column-annotations.js.map +1 -0
  20. package/dist/schema/state/sqlite/column-annotations.test.d.ts +2 -0
  21. package/dist/schema/state/sqlite/column-annotations.test.d.ts.map +1 -0
  22. package/dist/schema/state/sqlite/column-annotations.test.js +179 -0
  23. package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -0
  24. package/dist/schema/state/sqlite/column-spec.d.ts +11 -0
  25. package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -0
  26. package/dist/schema/state/sqlite/column-spec.js +39 -0
  27. package/dist/schema/state/sqlite/column-spec.js.map +1 -0
  28. package/dist/schema/state/sqlite/column-spec.test.d.ts +2 -0
  29. package/dist/schema/state/sqlite/column-spec.test.d.ts.map +1 -0
  30. package/dist/schema/state/sqlite/column-spec.test.js +146 -0
  31. package/dist/schema/state/sqlite/column-spec.test.js.map +1 -0
  32. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +1 -0
  33. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
  34. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +1 -0
  35. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  36. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +17 -4
  37. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
  38. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +2 -0
  39. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  40. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  41. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -0
  42. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
  43. package/dist/schema/state/sqlite/mod.d.ts +2 -0
  44. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  45. package/dist/schema/state/sqlite/mod.js +2 -0
  46. package/dist/schema/state/sqlite/mod.js.map +1 -1
  47. package/dist/schema/state/sqlite/system-tables.d.ts +464 -46
  48. package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
  49. package/dist/schema/state/sqlite/table-def.d.ts +100 -9
  50. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  51. package/dist/schema/state/sqlite/table-def.js +227 -5
  52. package/dist/schema/state/sqlite/table-def.js.map +1 -1
  53. package/dist/schema/state/sqlite/table-def.test.d.ts +2 -0
  54. package/dist/schema/state/sqlite/table-def.test.d.ts.map +1 -0
  55. package/dist/schema/state/sqlite/table-def.test.js +625 -0
  56. package/dist/schema/state/sqlite/table-def.test.js.map +1 -0
  57. package/dist/schema-management/migrations.d.ts +0 -1
  58. package/dist/schema-management/migrations.d.ts.map +1 -1
  59. package/dist/schema-management/migrations.js +3 -29
  60. package/dist/schema-management/migrations.js.map +1 -1
  61. package/dist/sqlite-db-helper.d.ts +1 -1
  62. package/dist/sqlite-db-helper.d.ts.map +1 -1
  63. package/dist/sqlite-db-helper.js.map +1 -1
  64. package/dist/sqlite-types.d.ts +4 -4
  65. package/dist/sqlite-types.d.ts.map +1 -1
  66. package/dist/sync/ClientSessionSyncProcessor.d.ts +2 -2
  67. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  68. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  69. package/dist/util.d.ts +3 -3
  70. package/dist/util.d.ts.map +1 -1
  71. package/dist/util.js.map +1 -1
  72. package/dist/version.d.ts +1 -1
  73. package/dist/version.js +1 -1
  74. package/package.json +4 -4
  75. package/src/ClientSessionLeaderThreadProxy.ts +2 -2
  76. package/src/adapter-types.ts +3 -1
  77. package/src/devtools/devtools-messages-leader.ts +3 -3
  78. package/src/leader-thread/types.ts +1 -1
  79. package/src/schema/LiveStoreEvent.ts +1 -1
  80. package/src/schema/state/sqlite/column-annotations.test.ts +212 -0
  81. package/src/schema/state/sqlite/column-annotations.ts +77 -0
  82. package/src/schema/state/sqlite/column-spec.test.ts +223 -0
  83. package/src/schema/state/sqlite/column-spec.ts +42 -0
  84. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -0
  85. package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +15 -0
  86. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +20 -2
  87. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +1 -0
  88. package/src/schema/state/sqlite/mod.ts +2 -0
  89. package/src/schema/state/sqlite/table-def.test.ts +783 -0
  90. package/src/schema/state/sqlite/table-def.ts +440 -16
  91. package/src/schema-management/migrations.ts +3 -32
  92. package/src/sqlite-db-helper.ts +1 -1
  93. package/src/sqlite-types.ts +4 -4
  94. package/src/sync/ClientSessionSyncProcessor.ts +5 -2
  95. package/src/util.ts +7 -2
  96. package/src/version.ts +1 -1
  97. package/src/schema-management/migrations.test.ts +0 -65
@@ -1,6 +1,7 @@
1
- import type { Nullable } from '@livestore/utils'
2
- import type { Schema, Types } from '@livestore/utils/effect'
1
+ import { type Nullable, shouldNeverHappen, type Writeable } from '@livestore/utils'
2
+ import { Option, Schema, SchemaAST, type Types } from '@livestore/utils/effect'
3
3
 
4
+ import { AutoIncrement, ColumnType, Default, PrimaryKeyId, Unique } from './column-annotations.ts'
4
5
  import { SqliteDsl } from './db-schema/mod.ts'
5
6
  import type { QueryBuilder } from './query-builder/mod.ts'
6
7
  import { makeQueryBuilder, QueryBuilderAstSymbol, QueryBuilderTypeId } from './query-builder/mod.ts'
@@ -17,6 +18,7 @@ export const TableDefInternalsSymbol = Symbol('TableDefInternals')
17
18
  export type TableDefInternalsSymbol = typeof TableDefInternalsSymbol
18
19
 
19
20
  export type TableDefBase<
21
+ // TODO replace SqliteDef type param with Effect Schema
20
22
  TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDefConstrained,
21
23
  TOptions extends TableOptions = TableOptions,
22
24
  > = {
@@ -28,10 +30,12 @@ export type TableDefBase<
28
30
  }
29
31
 
30
32
  export type TableDef<
33
+ // TODO replace SqliteDef type param with Effect Schema
31
34
  TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDefConstrained,
32
35
  TOptions extends TableOptions = TableOptions,
33
36
  // NOTE we're not using `SqliteDsl.StructSchemaForColumns<TSqliteDef['columns']>`
34
37
  // as we don't want the alias type for users to show up, so we're redefining it here
38
+ // TODO adjust this to `TSchema = Schema.TypeLiteral<` but requires some advance type-level work
35
39
  TSchema = Schema.Schema<
36
40
  SqliteDsl.AnyIfConstained<
37
41
  TSqliteDef['columns'],
@@ -66,28 +70,191 @@ export type TableOptions = {
66
70
  readonly isClientDocumentTable: boolean
67
71
  }
68
72
 
69
- export const table = <
73
+ /**
74
+ * Creates a SQLite table definition from columns or an Effect Schema.
75
+ *
76
+ * This function supports two main ways to define a table:
77
+ * 1. Using explicit column definitions
78
+ * 2. Using an Effect Schema (either the `name` property needs to be provided or the schema needs to have a title/identifier)
79
+ *
80
+ * ```ts
81
+ * // Using explicit columns
82
+ * const usersTable = State.SQLite.table({
83
+ * name: 'users',
84
+ * columns: {
85
+ * id: State.SQLite.text({ primaryKey: true }),
86
+ * name: State.SQLite.text({ nullable: false }),
87
+ * email: State.SQLite.text({ nullable: false }),
88
+ * age: State.SQLite.integer({ nullable: true }),
89
+ * },
90
+ * })
91
+ * ```
92
+ *
93
+ * ```ts
94
+ * // Using Effect Schema with annotations
95
+ * import { Schema } from '@livestore/utils/effect'
96
+ *
97
+ * const UserSchema = Schema.Struct({
98
+ * id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement),
99
+ * email: Schema.String.pipe(State.SQLite.withUnique),
100
+ * name: Schema.String,
101
+ * active: Schema.Boolean.pipe(State.SQLite.withDefault(true)),
102
+ * createdAt: Schema.optional(Schema.Date),
103
+ * })
104
+ *
105
+ * // Option 1: With explicit name
106
+ * const usersTable = State.SQLite.table({
107
+ * name: 'users',
108
+ * schema: UserSchema,
109
+ * })
110
+ *
111
+ * // Option 2: With name from schema annotation (title or identifier)
112
+ * const AnnotatedUserSchema = UserSchema.annotations({ title: 'users' })
113
+ * const usersTable2 = State.SQLite.table({
114
+ * schema: AnnotatedUserSchema,
115
+ * })
116
+ * ```
117
+ *
118
+ * ```ts
119
+ * // Adding indexes
120
+ * const PostSchema = Schema.Struct({
121
+ * id: Schema.String.pipe(State.SQLite.withPrimaryKey),
122
+ * title: Schema.String,
123
+ * authorId: Schema.String,
124
+ * createdAt: Schema.Date,
125
+ * }).annotations({ identifier: 'posts' })
126
+ *
127
+ * const postsTable = State.SQLite.table({
128
+ * schema: PostSchema,
129
+ * indexes: [
130
+ * { name: 'idx_posts_author', columns: ['authorId'] },
131
+ * { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false },
132
+ * ],
133
+ * })
134
+ * ```
135
+ *
136
+ * @remarks
137
+ * - Primary key columns are automatically non-nullable
138
+ * - Columns with `State.SQLite.withUnique` annotation automatically get unique indexes
139
+ * - The `State.SQLite.withAutoIncrement` annotation only works with integer primary keys
140
+ * - Default values can be literal values or SQL expressions
141
+ * - When using Effect Schema without explicit name, the schema must have a title or identifier annotation
142
+ */
143
+ // Overload 1: With columns
144
+ export function table<
70
145
  TName extends string,
71
- TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>,
146
+ TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any,
72
147
  const TOptionsInput extends TableOptionsInput = TableOptionsInput,
73
148
  >(
74
149
  args: {
75
150
  name: TName
76
151
  columns: TColumns
77
152
  } & Partial<TOptionsInput>,
78
- ): TableDef<SqliteTableDefForInput<TName, TColumns>, WithDefaults<TColumns>> => {
79
- const { name, columns: columnOrColumns, ...options } = args
80
- const tablePath = name
153
+ ): TableDef<SqliteTableDefForInput<TName, TColumns>, WithDefaults<TColumns>>
154
+
155
+ // Overload 2: With schema and explicit name
156
+ export function table<
157
+ TName extends string,
158
+ TSchema extends Schema.Schema.AnyNoContext,
159
+ const TOptionsInput extends TableOptionsInput = TableOptionsInput,
160
+ >(
161
+ args: {
162
+ name: TName
163
+ schema: TSchema
164
+ } & Partial<TOptionsInput>,
165
+ ): TableDef<
166
+ SqliteTableDefForSchemaInput<TName, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>,
167
+ TableOptions
168
+ >
169
+
170
+ // Overload 3: With schema and no name (uses schema annotations)
171
+ export function table<
172
+ TSchema extends Schema.Schema.AnyNoContext,
173
+ const TOptionsInput extends TableOptionsInput = TableOptionsInput,
174
+ >(
175
+ args: {
176
+ schema: TSchema
177
+ } & Partial<TOptionsInput>,
178
+ ): TableDef<
179
+ SqliteTableDefForSchemaInput<string, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>,
180
+ TableOptions
181
+ >
182
+
183
+ // Implementation
184
+ export function table<
185
+ TName extends string,
186
+ TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any,
187
+ const TOptionsInput extends TableOptionsInput = TableOptionsInput,
188
+ >(
189
+ args: (
190
+ | {
191
+ name: TName
192
+ columns: TColumns
193
+ }
194
+ | {
195
+ name: TName
196
+ schema: Schema.Schema.AnyNoContext
197
+ }
198
+ | {
199
+ schema: Schema.Schema.AnyNoContext
200
+ }
201
+ ) &
202
+ Partial<TOptionsInput>,
203
+ ): TableDef<any, any> {
204
+ const { ...options } = args
205
+
206
+ let tableName: string
207
+ let columns: SqliteDsl.Columns
208
+ let additionalIndexes: SqliteDsl.Index[] = []
209
+
210
+ if ('columns' in args) {
211
+ tableName = args.name
212
+ const columnOrColumns = args.columns
213
+ columns = (
214
+ SqliteDsl.isColumnDefinition(columnOrColumns) ? { value: columnOrColumns } : columnOrColumns
215
+ ) as SqliteDsl.Columns
216
+ additionalIndexes = []
217
+ } else if ('schema' in args) {
218
+ const result = schemaFieldsToColumns(SchemaAST.getPropertySignatures(args.schema.ast))
219
+ columns = result.columns
220
+
221
+ // We'll set tableName first, then use it for index names
222
+ let tempTableName: string
223
+
224
+ // If name is provided, use it; otherwise extract from schema annotations
225
+ if ('name' in args) {
226
+ tempTableName = args.name
227
+ } else {
228
+ // Use title or identifier, with preference for title
229
+ tempTableName = SchemaAST.getTitleAnnotation(args.schema.ast).pipe(
230
+ Option.orElse(() => SchemaAST.getIdentifierAnnotation(args.schema.ast)),
231
+ Option.getOrElse(() =>
232
+ shouldNeverHappen(
233
+ 'When using schema without explicit name, the schema must have a title or identifier annotation',
234
+ ),
235
+ ),
236
+ )
237
+ }
238
+
239
+ tableName = tempTableName
240
+
241
+ // Create unique indexes for columns with unique annotation
242
+ additionalIndexes = (result.uniqueColumns || []).map((columnName) => ({
243
+ name: `idx_${tableName}_${columnName}_unique`,
244
+ columns: [columnName],
245
+ isUnique: true,
246
+ }))
247
+ } else {
248
+ return shouldNeverHappen('Either `columns` or `schema` must be provided when calling `table()`')
249
+ }
81
250
 
82
251
  const options_: TableOptions = {
83
252
  isClientDocumentTable: false,
84
253
  }
85
254
 
86
- const columns = (
87
- SqliteDsl.isColumnDefinition(columnOrColumns) ? { value: columnOrColumns } : columnOrColumns
88
- ) as SqliteDsl.Columns
89
-
90
- const sqliteDef = SqliteDsl.table(tablePath, columns, options?.indexes ?? [])
255
+ // Combine user-provided indexes with unique column indexes
256
+ const allIndexes = [...(options?.indexes ?? []), ...additionalIndexes]
257
+ const sqliteDef = SqliteDsl.table(tableName, columns, allIndexes)
91
258
 
92
259
  const rowSchema = SqliteDsl.structSchemaForTable(sqliteDef)
93
260
  const insertSchema = SqliteDsl.insertStructSchemaForTable(sqliteDef)
@@ -179,19 +346,276 @@ export namespace FromColumns {
179
346
 
180
347
  export type SqliteTableDefForInput<
181
348
  TName extends string,
182
- TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>,
349
+ TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any,
183
350
  > = SqliteDsl.TableDefinition<TName, PrettifyFlat<ToColumns<TColumns>>>
184
351
 
185
- type WithDefaults<TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>> = {
352
+ export type SqliteTableDefForSchemaInput<
353
+ TName extends string,
354
+ TType,
355
+ TEncoded,
356
+ _TSchema = any,
357
+ > = TableDefInput.ForSchema<TName, TType, TEncoded, _TSchema>
358
+
359
+ export type WithDefaults<TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any> = {
186
360
  isClientDocumentTable: false
187
361
  requiredInsertColumnNames: SqliteDsl.FromColumns.RequiredInsertColumnNames<ToColumns<TColumns>>
188
362
  }
189
363
 
190
364
  export type PrettifyFlat<T> = T extends infer U ? { [K in keyof U]: U[K] } : never
191
365
 
192
- type ToColumns<TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>> =
366
+ export type ToColumns<TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any> =
193
367
  TColumns extends SqliteDsl.Columns
194
368
  ? TColumns
195
- : TColumns extends SqliteDsl.ColumnDefinition<any, any>
369
+ : TColumns extends SqliteDsl.ColumnDefinition.Any
196
370
  ? { value: TColumns }
197
371
  : never
372
+
373
+ export declare namespace SchemaToColumns {
374
+ // Type helper to create column definition with proper schema
375
+ export type ColumnDefForType<TEncoded, TType> = SqliteDsl.ColumnDefinition<TEncoded, TType>
376
+
377
+ // Create columns type from schema Type and Encoded
378
+ export type FromTypes<TType, TEncoded> = TType extends Record<string, any>
379
+ ? TEncoded extends Record<string, any>
380
+ ? {
381
+ [K in keyof TType & keyof TEncoded]: ColumnDefForType<TEncoded[K], TType[K]>
382
+ }
383
+ : SqliteDsl.Columns
384
+ : SqliteDsl.Columns
385
+ }
386
+
387
+ export declare namespace TableDefInput {
388
+ export type ForColumns<
389
+ TName extends string,
390
+ TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any,
391
+ > = SqliteDsl.TableDefinition<TName, PrettifyFlat<ToColumns<TColumns>>>
392
+
393
+ export type ForSchema<TName extends string, TType, TEncoded, _TSchema = any> = SqliteDsl.TableDefinition<
394
+ TName,
395
+ SchemaToColumns.FromTypes<TType, TEncoded>
396
+ >
397
+ }
398
+
399
+ /**
400
+ * Checks if a property signature has a specific annotation, checking both
401
+ * the property signature itself and its type AST.
402
+ */
403
+ const hasPropertyAnnotation = <T>(
404
+ propertySignature: SchemaAST.PropertySignature,
405
+ annotationId: symbol,
406
+ ): Option.Option<T> => {
407
+ // When using Schema.optional(Schema.String).pipe(withPrimaryKey) in a struct,
408
+ // the annotation ends up on a PropertySignatureDeclaration, not the Union type
409
+ // Check if this is a PropertySignatureDeclaration with annotations
410
+ if ('annotations' in propertySignature && propertySignature.annotations) {
411
+ const annotation = SchemaAST.getAnnotation<T>(annotationId)(propertySignature as any)
412
+ if (Option.isSome(annotation)) {
413
+ return annotation
414
+ }
415
+ }
416
+
417
+ // Otherwise check the type AST
418
+ return SchemaAST.getAnnotation<T>(annotationId)(propertySignature.type)
419
+ }
420
+
421
+ /**
422
+ * Maps schema property signatures to SQLite column definitions.
423
+ * Returns both columns and unique column names for index creation.
424
+ */
425
+ const schemaFieldsToColumns = (
426
+ propertySignatures: ReadonlyArray<SchemaAST.PropertySignature>,
427
+ ): { columns: SqliteDsl.Columns; uniqueColumns: string[] } => {
428
+ const columns: SqliteDsl.Columns = {}
429
+ const uniqueColumns: string[] = []
430
+
431
+ for (const prop of propertySignatures) {
432
+ if (typeof prop.name === 'string') {
433
+ // Create a schema from the AST
434
+ const fieldSchema = Schema.make(prop.type)
435
+ // Check if property has primary key annotation
436
+ const hasPrimaryKey = hasPropertyAnnotation<boolean>(prop, PrimaryKeyId).pipe(Option.getOrElse(() => false))
437
+ // Check if property has unique annotation
438
+ const hasUnique = hasPropertyAnnotation<boolean>(prop, Unique).pipe(Option.getOrElse(() => false))
439
+
440
+ columns[prop.name] = schemaFieldToColumn(fieldSchema, prop, hasPrimaryKey)
441
+
442
+ if (hasUnique) {
443
+ uniqueColumns.push(prop.name)
444
+ }
445
+ }
446
+ }
447
+
448
+ return { columns, uniqueColumns }
449
+ }
450
+
451
+ /**
452
+ * Converts a schema field and its property signature to a SQLite column definition.
453
+ */
454
+ const schemaFieldToColumn = (
455
+ fieldSchema: Schema.Schema.AnyNoContext,
456
+ propertySignature: SchemaAST.PropertySignature,
457
+ forceHasPrimaryKey?: boolean,
458
+ ): SqliteDsl.ColumnDefinition.Any => {
459
+ // Determine column type based on schema type
460
+ const columnDef = getColumnDefForSchema(fieldSchema, propertySignature)
461
+
462
+ // Create a new object with appropriate properties
463
+ const result: Writeable<SqliteDsl.ColumnDefinition.Any> = {
464
+ columnType: columnDef.columnType,
465
+ schema: columnDef.schema,
466
+ default: columnDef.default,
467
+ nullable: columnDef.nullable,
468
+ primaryKey: columnDef.primaryKey,
469
+ autoIncrement: columnDef.autoIncrement,
470
+ }
471
+
472
+ // Set nullable property explicitly
473
+ if (propertySignature.isOptional && !forceHasPrimaryKey && !columnDef.primaryKey) {
474
+ result.nullable = true
475
+ } else if (columnDef.nullable) {
476
+ result.nullable = true
477
+ } else {
478
+ result.nullable = false
479
+ }
480
+
481
+ // Set primaryKey property explicitly
482
+ if (forceHasPrimaryKey || columnDef.primaryKey) {
483
+ result.primaryKey = true
484
+ } else {
485
+ result.primaryKey = false
486
+ }
487
+
488
+ // Only add autoIncrement if it's true
489
+ if (columnDef.autoIncrement) {
490
+ result.autoIncrement = true
491
+ }
492
+
493
+ return result as SqliteDsl.ColumnDefinition.Any
494
+ }
495
+
496
+ /**
497
+ * Maps a schema to a SQLite column definition, respecting column annotations.
498
+ */
499
+ export const getColumnDefForSchema = (
500
+ schema: Schema.Schema.AnyNoContext,
501
+ propertySignature?: SchemaAST.PropertySignature,
502
+ ): SqliteDsl.ColumnDefinition.Any => {
503
+ const ast = schema.ast
504
+
505
+ // Check for annotations
506
+ const hasPrimaryKey = propertySignature
507
+ ? hasPropertyAnnotation<boolean>(propertySignature, PrimaryKeyId).pipe(Option.getOrElse(() => false))
508
+ : SchemaAST.getAnnotation<boolean>(PrimaryKeyId)(ast).pipe(Option.getOrElse(() => false))
509
+
510
+ const hasAutoIncrement = propertySignature
511
+ ? hasPropertyAnnotation<boolean>(propertySignature, AutoIncrement).pipe(Option.getOrElse(() => false))
512
+ : SchemaAST.getAnnotation<boolean>(AutoIncrement)(ast).pipe(Option.getOrElse(() => false))
513
+
514
+ const defaultValue = propertySignature
515
+ ? hasPropertyAnnotation<unknown>(propertySignature, Default)
516
+ : SchemaAST.getAnnotation<unknown>(Default)(ast)
517
+
518
+ /** Adds annotations to a column definition if they are present. */
519
+ const withAnnotationsIfNeeded = (columnDef: SqliteDsl.ColumnDefinition.Any): SqliteDsl.ColumnDefinition.Any => {
520
+ const result = { ...columnDef }
521
+
522
+ if (hasPrimaryKey) {
523
+ result.primaryKey = true
524
+ }
525
+
526
+ if (hasAutoIncrement) {
527
+ result.autoIncrement = true
528
+ }
529
+
530
+ if (Option.isSome(defaultValue)) {
531
+ result.default = Option.some(defaultValue.value)
532
+ }
533
+
534
+ return result
535
+ }
536
+
537
+ // Check for custom column type annotation
538
+ const columnTypeAnnotation = SchemaAST.getAnnotation<SqliteDsl.FieldColumnType>(ColumnType)(ast)
539
+ if (Option.isSome(columnTypeAnnotation)) {
540
+ const columnType = columnTypeAnnotation.value
541
+ let columnDef: SqliteDsl.ColumnDefinition.Any
542
+ switch (columnType) {
543
+ case 'text':
544
+ columnDef = SqliteDsl.text()
545
+ break
546
+ case 'integer':
547
+ columnDef = SqliteDsl.integer()
548
+ break
549
+ case 'real':
550
+ columnDef = SqliteDsl.real()
551
+ break
552
+ case 'blob':
553
+ columnDef = SqliteDsl.blob()
554
+ break
555
+ default:
556
+ return shouldNeverHappen(`Unsupported column type annotation: ${columnType}`)
557
+ }
558
+
559
+ return withAnnotationsIfNeeded(columnDef)
560
+ }
561
+
562
+ // Check for refinements (e.g., Schema.Int)
563
+ if (SchemaAST.isRefinement(ast)) {
564
+ // Check if this is specifically Schema.Int by looking at the identifier annotation
565
+ const identifier = SchemaAST.getIdentifierAnnotation(ast).pipe(Option.getOrElse(() => ''))
566
+ if (identifier === 'Int') {
567
+ return withAnnotationsIfNeeded(SqliteDsl.integer())
568
+ }
569
+ // For other refinements, check the underlying type
570
+ return getColumnDefForSchema(Schema.make(ast.from), propertySignature)
571
+ }
572
+
573
+ // Check for string types
574
+ if (SchemaAST.isStringKeyword(ast)) {
575
+ return withAnnotationsIfNeeded(SqliteDsl.text())
576
+ }
577
+
578
+ // Check for number types
579
+ if (SchemaAST.isNumberKeyword(ast)) {
580
+ return withAnnotationsIfNeeded(SqliteDsl.real())
581
+ }
582
+
583
+ // Check for boolean types
584
+ if (SchemaAST.isBooleanKeyword(ast)) {
585
+ return withAnnotationsIfNeeded(SqliteDsl.boolean())
586
+ }
587
+
588
+ // Check for unions (like optional)
589
+ if (SchemaAST.isUnion(ast)) {
590
+ // For optional schemas, find the non-undefined type and use that
591
+ for (const type of ast.types) {
592
+ if (!SchemaAST.isUndefinedKeyword(type)) {
593
+ // Create a new schema with this type but preserve the primary key annotation
594
+ const innerSchema = Schema.make(type)
595
+ const innerColumnDef = getColumnDefForSchema(innerSchema, propertySignature)
596
+ return withAnnotationsIfNeeded(innerColumnDef)
597
+ }
598
+ }
599
+ }
600
+
601
+ // Check for Date types
602
+ if (SchemaAST.isTransformation(ast)) {
603
+ // Try to map the transformation's target type
604
+ return getColumnDefForSchema(Schema.make(ast.to), propertySignature)
605
+ }
606
+
607
+ // Check for literal types
608
+ if (SchemaAST.isLiteral(ast)) {
609
+ const value = ast.literal
610
+ if (typeof value === 'string') {
611
+ return withAnnotationsIfNeeded(SqliteDsl.text())
612
+ } else if (typeof value === 'number') {
613
+ return withAnnotationsIfNeeded(SqliteDsl.real())
614
+ } else if (typeof value === 'boolean') {
615
+ return withAnnotationsIfNeeded(SqliteDsl.boolean())
616
+ }
617
+ }
618
+
619
+ // Default to JSON column for complex types
620
+ return withAnnotationsIfNeeded(SqliteDsl.json({ schema }))
621
+ }
@@ -1,11 +1,12 @@
1
1
  import { memoizeByStringifyArgs } from '@livestore/utils'
2
- import { Effect, Schema as EffectSchema } from '@livestore/utils/effect'
2
+ import { Effect } from '@livestore/utils/effect'
3
3
 
4
4
  import type { SqliteDb } from '../adapter-types.ts'
5
5
  import type { MigrationsReport, MigrationsReportEntry } from '../defs.ts'
6
6
  import type { UnexpectedError } from '../errors.ts'
7
7
  import type { LiveStoreSchema } from '../schema/mod.ts'
8
- import { SqliteAst, SqliteDsl } from '../schema/state/sqlite/db-schema/mod.ts'
8
+ import { makeColumnSpec } from '../schema/state/sqlite/column-spec.ts'
9
+ import { SqliteAst } from '../schema/state/sqlite/db-schema/mod.ts'
9
10
  import type { SchemaEventDefsMetaRow, SchemaMetaRow } from '../schema/state/sqlite/system-tables.ts'
10
11
  import {
11
12
  isStateSystemTable,
@@ -171,33 +172,3 @@ const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) =>
171
172
  const uniqueStr = index.unique ? 'UNIQUE' : ''
172
173
  return sql`create ${uniqueStr} index if not exists '${index.name}' on '${tableName}' (${index.columns.map((col) => `'${col}'`).join(', ')})`
173
174
  }
174
-
175
- export const makeColumnSpec = (tableAst: SqliteAst.Table) => {
176
- const primaryKeys = tableAst.columns.filter((_) => _.primaryKey).map((_) => `'${_.name}'`)
177
- const columnDefStrs = tableAst.columns.map(toSqliteColumnSpec)
178
- if (primaryKeys.length > 0) {
179
- columnDefStrs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`)
180
- }
181
-
182
- return columnDefStrs.join(', ')
183
- }
184
-
185
- /** NOTE primary keys are applied on a table level not on a column level to account for multi-column primary keys */
186
- const toSqliteColumnSpec = (column: SqliteAst.Column) => {
187
- const columnTypeStr = column.type._tag
188
- const nullableStr = column.nullable === false ? 'not null' : ''
189
- const defaultValueStr = (() => {
190
- if (column.default._tag === 'None') return ''
191
-
192
- if (column.default.value === null) return 'default null'
193
- if (SqliteDsl.isSqlDefaultValue(column.default.value)) return `default ${column.default.value.sql}`
194
-
195
- const encodeValue = EffectSchema.encodeSync(column.schema)
196
- const encodedDefaultValue = encodeValue(column.default.value)
197
-
198
- if (columnTypeStr === 'text') return `default '${encodedDefaultValue}'`
199
- return `default ${encodedDefaultValue}`
200
- })()
201
-
202
- return `'${column.name}' ${columnTypeStr} ${nullableStr} ${defaultValueStr}`
203
- }
@@ -52,7 +52,7 @@ export const validateSnapshot = (snapshot: Uint8Array) => {
52
52
  }
53
53
  }
54
54
 
55
- export const makeExport = (exportFn: () => Uint8Array) => () => {
55
+ export const makeExport = (exportFn: () => Uint8Array<ArrayBuffer>) => () => {
56
56
  const snapshot = exportFn()
57
57
  validateSnapshot(snapshot)
58
58
  return snapshot
@@ -25,12 +25,12 @@ export interface SqliteDb<TReq = any, TMetadata extends TReq = TReq> {
25
25
  select<T>(queryStr: string, bindValues?: PreparedBindValues | undefined): ReadonlyArray<T>
26
26
  select<T>(queryBuilder: QueryBuilder<T, any, any>): T
27
27
 
28
- export(): Uint8Array
29
- import: (data: Uint8Array | SqliteDb<TReq>) => void
28
+ export(): Uint8Array<ArrayBuffer>
29
+ import: (data: Uint8Array<ArrayBuffer> | SqliteDb<TReq>) => void
30
30
  close(): void
31
31
  destroy(): void
32
32
  session(): SqliteDbSession
33
- makeChangeset: (data: Uint8Array) => SqliteDbChangeset
33
+ makeChangeset: (data: Uint8Array<ArrayBuffer>) => SqliteDbChangeset
34
34
  }
35
35
 
36
36
  export type SqliteDebugInfo = { head: EventSequenceNumber.EventSequenceNumber }
@@ -56,7 +56,7 @@ export interface PreparedStatement {
56
56
  }
57
57
 
58
58
  export type SqliteDbSession = {
59
- changeset: () => Uint8Array | undefined
59
+ changeset: () => Uint8Array<ArrayBuffer> | undefined
60
60
  finish: () => void
61
61
  }
62
62
 
@@ -55,10 +55,13 @@ export const makeClientSessionSyncProcessor = ({
55
55
  options: { otelContext: otel.Context; withChangeset: boolean; materializerHashLeader: Option.Option<number> },
56
56
  ) => {
57
57
  writeTables: Set<string>
58
- sessionChangeset: { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } | { _tag: 'no-op' } | { _tag: 'unset' }
58
+ sessionChangeset:
59
+ | { _tag: 'sessionChangeset'; data: Uint8Array<ArrayBuffer>; debug: any }
60
+ | { _tag: 'no-op' }
61
+ | { _tag: 'unset' }
59
62
  materializerHash: Option.Option<number>
60
63
  }
61
- rollback: (changeset: Uint8Array) => void
64
+ rollback: (changeset: Uint8Array<ArrayBuffer>) => void
62
65
  refreshTables: (tables: Set<string>) => void
63
66
  span: otel.Span
64
67
  params: {
package/src/util.ts CHANGED
@@ -4,11 +4,16 @@ import type { Brand } from '@livestore/utils/effect'
4
4
  import { Schema } from '@livestore/utils/effect'
5
5
 
6
6
  export type ParamsObject = Record<string, SqlValue>
7
- export type SqlValue = string | number | Uint8Array | null
7
+ export type SqlValue = string | number | Uint8Array<ArrayBuffer> | null
8
8
 
9
9
  export type Bindable = ReadonlyArray<SqlValue> | ParamsObject
10
10
 
11
- export const SqlValueSchema = Schema.Union(Schema.String, Schema.Number, Schema.Uint8Array, Schema.Null)
11
+ export const SqlValueSchema = Schema.Union(
12
+ Schema.String,
13
+ Schema.Number,
14
+ Schema.Uint8Array as any as Schema.Schema<Uint8Array<ArrayBuffer>>,
15
+ Schema.Null,
16
+ )
12
17
 
13
18
  export const PreparedBindValues = Schema.Union(
14
19
  Schema.Array(SqlValueSchema),
package/src/version.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // import packageJson from '../package.json' with { type: 'json' }
3
3
  // export const liveStoreVersion = packageJson.version
4
4
 
5
- export const liveStoreVersion = '0.3.2-dev.13' as const
5
+ export const liveStoreVersion = '0.3.2-dev.14' as const
6
6
 
7
7
  /**
8
8
  * This version number is incremented whenever the internal storage format changes in a breaking way.