@livestore/common 0.3.2-dev.9 → 0.4.0-dev.1

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 (172) 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 +4 -4
  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/LeaderSyncProcessor.js +3 -1
  13. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  14. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  15. package/dist/leader-thread/make-leader-thread-layer.js +21 -4
  16. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  17. package/dist/leader-thread/shutdown-channel.d.ts +2 -2
  18. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  19. package/dist/leader-thread/shutdown-channel.js +2 -2
  20. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  21. package/dist/leader-thread/types.d.ts +1 -1
  22. package/dist/leader-thread/types.d.ts.map +1 -1
  23. package/dist/materializer-helper.d.ts +3 -3
  24. package/dist/materializer-helper.d.ts.map +1 -1
  25. package/dist/materializer-helper.js +2 -2
  26. package/dist/materializer-helper.js.map +1 -1
  27. package/dist/rematerialize-from-eventlog.js +1 -1
  28. package/dist/rematerialize-from-eventlog.js.map +1 -1
  29. package/dist/schema/EventDef.d.ts +104 -178
  30. package/dist/schema/EventSequenceNumber.d.ts +5 -0
  31. package/dist/schema/EventSequenceNumber.d.ts.map +1 -1
  32. package/dist/schema/EventSequenceNumber.js +7 -2
  33. package/dist/schema/EventSequenceNumber.js.map +1 -1
  34. package/dist/schema/EventSequenceNumber.test.js +2 -2
  35. package/dist/schema/LiveStoreEvent.d.ts +6 -5
  36. package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
  37. package/dist/schema/LiveStoreEvent.js +5 -0
  38. package/dist/schema/LiveStoreEvent.js.map +1 -1
  39. package/dist/schema/schema.d.ts +3 -0
  40. package/dist/schema/schema.d.ts.map +1 -1
  41. package/dist/schema/schema.js.map +1 -1
  42. package/dist/schema/state/sqlite/client-document-def.d.ts +3 -2
  43. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
  44. package/dist/schema/state/sqlite/client-document-def.js +6 -4
  45. package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
  46. package/dist/schema/state/sqlite/client-document-def.test.js +76 -1
  47. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  48. package/dist/schema/state/sqlite/column-annotations.d.ts +34 -0
  49. package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -0
  50. package/dist/schema/state/sqlite/column-annotations.js +50 -0
  51. package/dist/schema/state/sqlite/column-annotations.js.map +1 -0
  52. package/dist/schema/state/sqlite/column-annotations.test.d.ts +2 -0
  53. package/dist/schema/state/sqlite/column-annotations.test.d.ts.map +1 -0
  54. package/dist/schema/state/sqlite/column-annotations.test.js +179 -0
  55. package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -0
  56. package/dist/schema/state/sqlite/column-def.d.ts +15 -0
  57. package/dist/schema/state/sqlite/column-def.d.ts.map +1 -0
  58. package/dist/schema/state/sqlite/column-def.js +242 -0
  59. package/dist/schema/state/sqlite/column-def.js.map +1 -0
  60. package/dist/schema/state/sqlite/column-def.test.d.ts +2 -0
  61. package/dist/schema/state/sqlite/column-def.test.d.ts.map +1 -0
  62. package/dist/schema/state/sqlite/column-def.test.js +529 -0
  63. package/dist/schema/state/sqlite/column-def.test.js.map +1 -0
  64. package/dist/schema/state/sqlite/column-spec.d.ts +11 -0
  65. package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -0
  66. package/dist/schema/state/sqlite/column-spec.js +39 -0
  67. package/dist/schema/state/sqlite/column-spec.js.map +1 -0
  68. package/dist/schema/state/sqlite/column-spec.test.d.ts +2 -0
  69. package/dist/schema/state/sqlite/column-spec.test.d.ts.map +1 -0
  70. package/dist/schema/state/sqlite/column-spec.test.js +146 -0
  71. package/dist/schema/state/sqlite/column-spec.test.js.map +1 -0
  72. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +1 -0
  73. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
  74. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +1 -0
  75. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  76. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +17 -4
  77. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
  78. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +2 -0
  79. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  80. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +65 -165
  81. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  82. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -0
  83. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
  84. package/dist/schema/state/sqlite/mod.d.ts +2 -0
  85. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  86. package/dist/schema/state/sqlite/mod.js +2 -0
  87. package/dist/schema/state/sqlite/mod.js.map +1 -1
  88. package/dist/schema/state/sqlite/query-builder/api.d.ts +309 -560
  89. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  90. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts +1 -0
  91. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
  92. package/dist/schema/state/sqlite/query-builder/astToSql.js +8 -6
  93. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
  94. package/dist/schema/state/sqlite/system-tables.d.ts +464 -46
  95. package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
  96. package/dist/schema/state/sqlite/table-def.d.ts +159 -152
  97. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  98. package/dist/schema/state/sqlite/table-def.js +45 -6
  99. package/dist/schema/state/sqlite/table-def.js.map +1 -1
  100. package/dist/schema/state/sqlite/table-def.test.d.ts +2 -0
  101. package/dist/schema/state/sqlite/table-def.test.d.ts.map +1 -0
  102. package/dist/schema/state/sqlite/table-def.test.js +192 -0
  103. package/dist/schema/state/sqlite/table-def.test.js.map +1 -0
  104. package/dist/schema-management/common.d.ts +1 -1
  105. package/dist/schema-management/common.d.ts.map +1 -1
  106. package/dist/schema-management/common.js +11 -2
  107. package/dist/schema-management/common.js.map +1 -1
  108. package/dist/schema-management/migrations.d.ts +0 -1
  109. package/dist/schema-management/migrations.d.ts.map +1 -1
  110. package/dist/schema-management/migrations.js +4 -30
  111. package/dist/schema-management/migrations.js.map +1 -1
  112. package/dist/schema-management/migrations.test.d.ts +2 -0
  113. package/dist/schema-management/migrations.test.d.ts.map +1 -0
  114. package/dist/schema-management/migrations.test.js +52 -0
  115. package/dist/schema-management/migrations.test.js.map +1 -0
  116. package/dist/sql-queries/types.d.ts +37 -133
  117. package/dist/sqlite-db-helper.d.ts +3 -1
  118. package/dist/sqlite-db-helper.d.ts.map +1 -1
  119. package/dist/sqlite-db-helper.js +16 -0
  120. package/dist/sqlite-db-helper.js.map +1 -1
  121. package/dist/sqlite-types.d.ts +4 -4
  122. package/dist/sqlite-types.d.ts.map +1 -1
  123. package/dist/sync/ClientSessionSyncProcessor.d.ts +2 -2
  124. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  125. package/dist/sync/ClientSessionSyncProcessor.js +8 -7
  126. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  127. package/dist/sync/sync.d.ts.map +1 -1
  128. package/dist/sync/sync.js.map +1 -1
  129. package/dist/util.d.ts +3 -3
  130. package/dist/util.d.ts.map +1 -1
  131. package/dist/util.js.map +1 -1
  132. package/dist/version.d.ts +1 -1
  133. package/dist/version.js +1 -1
  134. package/package.json +4 -4
  135. package/src/ClientSessionLeaderThreadProxy.ts +2 -2
  136. package/src/adapter-types.ts +6 -4
  137. package/src/devtools/devtools-messages-leader.ts +3 -3
  138. package/src/leader-thread/LeaderSyncProcessor.ts +3 -1
  139. package/src/leader-thread/make-leader-thread-layer.ts +26 -7
  140. package/src/leader-thread/shutdown-channel.ts +2 -2
  141. package/src/leader-thread/types.ts +1 -1
  142. package/src/materializer-helper.ts +5 -11
  143. package/src/rematerialize-from-eventlog.ts +2 -2
  144. package/src/schema/EventSequenceNumber.test.ts +2 -2
  145. package/src/schema/EventSequenceNumber.ts +8 -2
  146. package/src/schema/LiveStoreEvent.ts +7 -1
  147. package/src/schema/schema.ts +4 -0
  148. package/src/schema/state/sqlite/client-document-def.test.ts +89 -1
  149. package/src/schema/state/sqlite/client-document-def.ts +7 -4
  150. package/src/schema/state/sqlite/column-annotations.test.ts +212 -0
  151. package/src/schema/state/sqlite/column-annotations.ts +77 -0
  152. package/src/schema/state/sqlite/column-def.test.ts +665 -0
  153. package/src/schema/state/sqlite/column-def.ts +290 -0
  154. package/src/schema/state/sqlite/column-spec.test.ts +223 -0
  155. package/src/schema/state/sqlite/column-spec.ts +42 -0
  156. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -0
  157. package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +15 -0
  158. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +20 -2
  159. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +1 -0
  160. package/src/schema/state/sqlite/mod.ts +2 -0
  161. package/src/schema/state/sqlite/query-builder/api.ts +4 -3
  162. package/src/schema/state/sqlite/query-builder/astToSql.ts +9 -7
  163. package/src/schema/state/sqlite/table-def.test.ts +241 -0
  164. package/src/schema/state/sqlite/table-def.ts +222 -16
  165. package/src/schema-management/common.ts +10 -3
  166. package/src/schema-management/migrations.ts +4 -33
  167. package/src/sqlite-db-helper.ts +19 -1
  168. package/src/sqlite-types.ts +4 -4
  169. package/src/sync/ClientSessionSyncProcessor.ts +13 -8
  170. package/src/sync/sync.ts +2 -0
  171. package/src/util.ts +7 -2
  172. package/src/version.ts +1 -1
@@ -1,12 +1,16 @@
1
- import type { Nullable } from '@livestore/utils'
2
- import type { Schema, Types } from '@livestore/utils/effect'
1
+ import { type Nullable, shouldNeverHappen } from '@livestore/utils'
2
+ import { Option, type Schema, SchemaAST, type Types } from '@livestore/utils/effect'
3
3
 
4
+ import { getColumnDefForSchema, schemaFieldsToColumns } from './column-def.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'
7
8
 
8
9
  export const { blob, boolean, column, datetime, integer, isColumnDefinition, json, real, text } = SqliteDsl
9
10
 
11
+ // Re-export the column definition function
12
+ export { getColumnDefForSchema }
13
+
10
14
  export type StateType = 'singleton' | 'dynamic'
11
15
 
12
16
  export type DefaultSqliteTableDef = SqliteDsl.TableDefinition<string, SqliteDsl.Columns>
@@ -17,6 +21,7 @@ export const TableDefInternalsSymbol = Symbol('TableDefInternals')
17
21
  export type TableDefInternalsSymbol = typeof TableDefInternalsSymbol
18
22
 
19
23
  export type TableDefBase<
24
+ // TODO replace SqliteDef type param with Effect Schema (see below)
20
25
  TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDefConstrained,
21
26
  TOptions extends TableOptions = TableOptions,
22
27
  > = {
@@ -28,10 +33,14 @@ export type TableDefBase<
28
33
  }
29
34
 
30
35
  export type TableDef<
36
+ // TODO replace SqliteDef type param with Effect Schema
37
+ // We can only do this with Effect Schema v4 once the default values are tracked on the type level
38
+ // https://github.com/livestorejs/livestore/issues/382
31
39
  TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDefConstrained,
32
40
  TOptions extends TableOptions = TableOptions,
33
41
  // NOTE we're not using `SqliteDsl.StructSchemaForColumns<TSqliteDef['columns']>`
34
42
  // as we don't want the alias type for users to show up, so we're redefining it here
43
+ // TODO adjust this to `TSchema = Schema.TypeLiteral<` but requires some advance type-level work
35
44
  TSchema = Schema.Schema<
36
45
  SqliteDsl.AnyIfConstained<
37
46
  TSqliteDef['columns'],
@@ -66,28 +75,192 @@ export type TableOptions = {
66
75
  readonly isClientDocumentTable: boolean
67
76
  }
68
77
 
69
- export const table = <
78
+ /**
79
+ * Creates a SQLite table definition from columns or an Effect Schema.
80
+ *
81
+ * This function supports two main ways to define a table:
82
+ * 1. Using explicit column definitions
83
+ * 2. Using an Effect Schema (either the `name` property needs to be provided or the schema needs to have a title/identifier)
84
+ *
85
+ * ```ts
86
+ * // Using explicit columns
87
+ * const usersTable = State.SQLite.table({
88
+ * name: 'users',
89
+ * columns: {
90
+ * id: State.SQLite.text({ primaryKey: true }),
91
+ * name: State.SQLite.text({ nullable: false }),
92
+ * email: State.SQLite.text({ nullable: false }),
93
+ * age: State.SQLite.integer({ nullable: true }),
94
+ * },
95
+ * })
96
+ * ```
97
+ *
98
+ * ```ts
99
+ * // Using Effect Schema with annotations
100
+ * import { Schema } from '@livestore/utils/effect'
101
+ *
102
+ * const UserSchema = Schema.Struct({
103
+ * id: Schema.Int.pipe(State.SQLite.withPrimaryKey).pipe(State.SQLite.withAutoIncrement),
104
+ * email: Schema.String.pipe(State.SQLite.withUnique),
105
+ * name: Schema.String,
106
+ * active: Schema.Boolean.pipe(State.SQLite.withDefault(true)),
107
+ * createdAt: Schema.optional(Schema.Date),
108
+ * })
109
+ *
110
+ * // Option 1: With explicit name
111
+ * const usersTable = State.SQLite.table({
112
+ * name: 'users',
113
+ * schema: UserSchema,
114
+ * })
115
+ *
116
+ * // Option 2: With name from schema annotation (title or identifier)
117
+ * const AnnotatedUserSchema = UserSchema.annotations({ title: 'users' })
118
+ * const usersTable2 = State.SQLite.table({
119
+ * schema: AnnotatedUserSchema,
120
+ * })
121
+ * ```
122
+ *
123
+ * ```ts
124
+ * // Adding indexes
125
+ * const PostSchema = Schema.Struct({
126
+ * id: Schema.String.pipe(State.SQLite.withPrimaryKey),
127
+ * title: Schema.String,
128
+ * authorId: Schema.String,
129
+ * createdAt: Schema.Date,
130
+ * }).annotations({ identifier: 'posts' })
131
+ *
132
+ * const postsTable = State.SQLite.table({
133
+ * schema: PostSchema,
134
+ * indexes: [
135
+ * { name: 'idx_posts_author', columns: ['authorId'] },
136
+ * { name: 'idx_posts_created', columns: ['createdAt'], isUnique: false },
137
+ * ],
138
+ * })
139
+ * ```
140
+ *
141
+ * @remarks
142
+ * - Primary key columns are automatically non-nullable
143
+ * - Columns with `State.SQLite.withUnique` annotation automatically get unique indexes
144
+ * - The `State.SQLite.withAutoIncrement` annotation only works with integer primary keys
145
+ * - Default values can be literal values or SQL expressions
146
+ * - When using Effect Schema without explicit name, the schema must have a title or identifier annotation
147
+ */
148
+ // Overload 1: With columns
149
+ // TODO drop support for `column` when Effect Schema v4 is released
150
+ export function table<
70
151
  TName extends string,
71
- TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>,
152
+ TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any,
72
153
  const TOptionsInput extends TableOptionsInput = TableOptionsInput,
73
154
  >(
74
155
  args: {
75
156
  name: TName
76
157
  columns: TColumns
77
158
  } & Partial<TOptionsInput>,
78
- ): TableDef<SqliteTableDefForInput<TName, TColumns>, WithDefaults<TColumns>> => {
79
- const { name, columns: columnOrColumns, ...options } = args
80
- const tablePath = name
159
+ ): TableDef<SqliteTableDefForInput<TName, TColumns>, WithDefaults<TColumns>>
160
+
161
+ // Overload 2: With schema and explicit name
162
+ export function table<
163
+ TName extends string,
164
+ TSchema extends Schema.Schema.AnyNoContext,
165
+ const TOptionsInput extends TableOptionsInput = TableOptionsInput,
166
+ >(
167
+ args: {
168
+ name: TName
169
+ schema: TSchema
170
+ } & Partial<TOptionsInput>,
171
+ ): TableDef<
172
+ SqliteTableDefForSchemaInput<TName, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>,
173
+ TableOptions
174
+ >
175
+
176
+ // Overload 3: With schema and no name (uses schema annotations)
177
+ export function table<
178
+ TSchema extends Schema.Schema.AnyNoContext,
179
+ const TOptionsInput extends TableOptionsInput = TableOptionsInput,
180
+ >(
181
+ args: {
182
+ schema: TSchema
183
+ } & Partial<TOptionsInput>,
184
+ ): TableDef<
185
+ SqliteTableDefForSchemaInput<string, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>,
186
+ TableOptions
187
+ >
188
+
189
+ // Implementation
190
+ export function table<
191
+ TName extends string,
192
+ TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any,
193
+ const TOptionsInput extends TableOptionsInput = TableOptionsInput,
194
+ >(
195
+ args: (
196
+ | {
197
+ name: TName
198
+ columns: TColumns
199
+ }
200
+ | {
201
+ name: TName
202
+ schema: Schema.Schema.AnyNoContext
203
+ }
204
+ | {
205
+ schema: Schema.Schema.AnyNoContext
206
+ }
207
+ ) &
208
+ Partial<TOptionsInput>,
209
+ ): TableDef<any, any> {
210
+ const { ...options } = args
211
+
212
+ let tableName: string
213
+ let columns: SqliteDsl.Columns
214
+ let additionalIndexes: SqliteDsl.Index[] = []
215
+
216
+ if ('columns' in args) {
217
+ tableName = args.name
218
+ const columnOrColumns = args.columns
219
+ columns = (
220
+ SqliteDsl.isColumnDefinition(columnOrColumns) ? { value: columnOrColumns } : columnOrColumns
221
+ ) as SqliteDsl.Columns
222
+ additionalIndexes = []
223
+ } else if ('schema' in args) {
224
+ const result = schemaFieldsToColumns(SchemaAST.getPropertySignatures(args.schema.ast))
225
+ columns = result.columns
226
+
227
+ // We'll set tableName first, then use it for index names
228
+ let tempTableName: string
229
+
230
+ // If name is provided, use it; otherwise extract from schema annotations
231
+ if ('name' in args) {
232
+ tempTableName = args.name
233
+ } else {
234
+ // Use title or identifier, with preference for title
235
+ tempTableName = SchemaAST.getTitleAnnotation(args.schema.ast).pipe(
236
+ Option.orElse(() => SchemaAST.getIdentifierAnnotation(args.schema.ast)),
237
+ Option.getOrElse(() =>
238
+ shouldNeverHappen(
239
+ 'When using schema without explicit name, the schema must have a title or identifier annotation',
240
+ ),
241
+ ),
242
+ )
243
+ }
244
+
245
+ tableName = tempTableName
246
+
247
+ // Create unique indexes for columns with unique annotation
248
+ additionalIndexes = (result.uniqueColumns || []).map((columnName) => ({
249
+ name: `idx_${tableName}_${columnName}_unique`,
250
+ columns: [columnName],
251
+ isUnique: true,
252
+ }))
253
+ } else {
254
+ return shouldNeverHappen('Either `columns` or `schema` must be provided when calling `table()`')
255
+ }
81
256
 
82
257
  const options_: TableOptions = {
83
258
  isClientDocumentTable: false,
84
259
  }
85
260
 
86
- const columns = (
87
- SqliteDsl.isColumnDefinition(columnOrColumns) ? { value: columnOrColumns } : columnOrColumns
88
- ) as SqliteDsl.Columns
89
-
90
- const sqliteDef = SqliteDsl.table(tablePath, columns, options?.indexes ?? [])
261
+ // Combine user-provided indexes with unique column indexes
262
+ const allIndexes = [...(options?.indexes ?? []), ...additionalIndexes]
263
+ const sqliteDef = SqliteDsl.table(tableName, columns, allIndexes)
91
264
 
92
265
  const rowSchema = SqliteDsl.structSchemaForTable(sqliteDef)
93
266
  const insertSchema = SqliteDsl.insertStructSchemaForTable(sqliteDef)
@@ -179,19 +352,52 @@ export namespace FromColumns {
179
352
 
180
353
  export type SqliteTableDefForInput<
181
354
  TName extends string,
182
- TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>,
355
+ TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any,
183
356
  > = SqliteDsl.TableDefinition<TName, PrettifyFlat<ToColumns<TColumns>>>
184
357
 
185
- type WithDefaults<TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>> = {
358
+ export type SqliteTableDefForSchemaInput<
359
+ TName extends string,
360
+ TType,
361
+ TEncoded,
362
+ _TSchema = any,
363
+ > = TableDefInput.ForSchema<TName, TType, TEncoded, _TSchema>
364
+
365
+ export type WithDefaults<TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any> = {
186
366
  isClientDocumentTable: false
187
367
  requiredInsertColumnNames: SqliteDsl.FromColumns.RequiredInsertColumnNames<ToColumns<TColumns>>
188
368
  }
189
369
 
190
370
  export type PrettifyFlat<T> = T extends infer U ? { [K in keyof U]: U[K] } : never
191
371
 
192
- type ToColumns<TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition<any, any>> =
372
+ export type ToColumns<TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any> =
193
373
  TColumns extends SqliteDsl.Columns
194
374
  ? TColumns
195
- : TColumns extends SqliteDsl.ColumnDefinition<any, any>
375
+ : TColumns extends SqliteDsl.ColumnDefinition.Any
196
376
  ? { value: TColumns }
197
377
  : never
378
+
379
+ export declare namespace SchemaToColumns {
380
+ // Type helper to create column definition with proper schema
381
+ export type ColumnDefForType<TEncoded, TType> = SqliteDsl.ColumnDefinition<TEncoded, TType>
382
+
383
+ // Create columns type from schema Type and Encoded
384
+ export type FromTypes<TType, TEncoded> = TType extends Record<string, any>
385
+ ? TEncoded extends Record<string, any>
386
+ ? {
387
+ [K in keyof TType & keyof TEncoded]: ColumnDefForType<TEncoded[K], TType[K]>
388
+ }
389
+ : SqliteDsl.Columns
390
+ : SqliteDsl.Columns
391
+ }
392
+
393
+ export declare namespace TableDefInput {
394
+ export type ForColumns<
395
+ TName extends string,
396
+ TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any,
397
+ > = SqliteDsl.TableDefinition<TName, PrettifyFlat<ToColumns<TColumns>>>
398
+
399
+ export type ForSchema<TName extends string, TType, TEncoded, _TSchema = any> = SqliteDsl.TableDefinition<
400
+ TName,
401
+ SchemaToColumns.FromTypes<TType, TEncoded>
402
+ >
403
+ }
@@ -1,4 +1,4 @@
1
- import type { SqliteDb } from '../adapter-types.ts'
1
+ import { type SqliteDb, SqliteError } from '../adapter-types.ts'
2
2
  import type { ParamsObject } from '../util.ts'
3
3
  import { prepareBindValues } from '../util.ts'
4
4
 
@@ -15,9 +15,16 @@ export const dbExecute = (db: SqliteDb, queryStr: string, bindValues?: ParamsObj
15
15
 
16
16
  const preparedBindValues = bindValues ? prepareBindValues(bindValues, queryStr) : undefined
17
17
 
18
- stmt.execute(preparedBindValues)
18
+ try {
19
+ stmt.execute(preparedBindValues)
19
20
 
20
- stmt.finalize()
21
+ stmt.finalize()
22
+ } catch (cause) {
23
+ throw new SqliteError({
24
+ cause,
25
+ query: { sql: queryStr, bindValues: preparedBindValues ?? {} },
26
+ })
27
+ }
21
28
  }
22
29
 
23
30
  export const dbSelect = <T>(db: SqliteDb, queryStr: string, bindValues?: ParamsObject) => {
@@ -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,
@@ -169,35 +170,5 @@ export const migrateTable = ({
169
170
 
170
171
  const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
171
172
  const uniqueStr = index.unique ? 'UNIQUE' : ''
172
- return sql`create ${uniqueStr} index if not exists '${index.name}' on '${tableName}' (${index.columns.join(', ')})`
173
- }
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}`
173
+ return sql`create ${uniqueStr} index if not exists '${index.name}' on '${tableName}' (${index.columns.map((col) => `'${col}'`).join(', ')})`
203
174
  }
@@ -1,6 +1,6 @@
1
1
  import { Schema } from '@livestore/utils/effect'
2
2
 
3
- import type { SqliteDb } from './adapter-types.ts'
3
+ import { type SqliteDb, SqliteError } from './adapter-types.ts'
4
4
  import { getResultSchema, isQueryBuilder } from './schema/state/sqlite/query-builder/mod.ts'
5
5
  import type { PreparedBindValues } from './util.ts'
6
6
 
@@ -39,3 +39,21 @@ export const makeSelect = <T>(
39
39
  }
40
40
  }
41
41
  }
42
+
43
+ export const validateSnapshot = (snapshot: Uint8Array) => {
44
+ const headerBytes = new TextDecoder().decode(snapshot.slice(0, 16))
45
+ const hasValidHeader = headerBytes.startsWith('SQLite format 3')
46
+
47
+ if (!hasValidHeader) {
48
+ throw new SqliteError({
49
+ cause: 'Invalid SQLite header',
50
+ note: `Expected header to start with 'SQLite format 3', but got: ${headerBytes}`,
51
+ })
52
+ }
53
+ }
54
+
55
+ export const makeExport = (exportFn: () => Uint8Array<ArrayBuffer>) => () => {
56
+ const snapshot = exportFn()
57
+ validateSnapshot(snapshot)
58
+ return snapshot
59
+ }
@@ -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
 
@@ -3,6 +3,7 @@ import { LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
3
3
  import {
4
4
  BucketQueue,
5
5
  Effect,
6
+ Exit,
6
7
  FiberHandle,
7
8
  Option,
8
9
  Queue,
@@ -54,10 +55,13 @@ export const makeClientSessionSyncProcessor = ({
54
55
  options: { otelContext: otel.Context; withChangeset: boolean; materializerHashLeader: Option.Option<number> },
55
56
  ) => {
56
57
  writeTables: Set<string>
57
- 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' }
58
62
  materializerHash: Option.Option<number>
59
63
  }
60
- rollback: (changeset: Uint8Array) => void
64
+ rollback: (changeset: Uint8Array<ArrayBuffer>) => void
61
65
  refreshTables: (tables: Set<string>) => void
62
66
  span: otel.Span
63
67
  params: {
@@ -316,7 +320,7 @@ export const makeClientSessionSyncProcessor = ({
316
320
  refreshTables(writeTables)
317
321
  }).pipe(
318
322
  Effect.tapCauseLogPretty,
319
- Effect.catchAllCause((cause) => clientSession.shutdown(cause)),
323
+ Effect.catchAllCause((cause) => clientSession.shutdown(Exit.failCause(cause))),
320
324
  ),
321
325
  ),
322
326
  Stream.runDrain,
@@ -381,13 +385,14 @@ export interface ClientSessionSyncProcessor {
381
385
  // TODO turn this into a build-time "macro" so all simulation snippets are removed for production builds
382
386
  const SIMULATION_ENABLED = true
383
387
 
388
+ // Warning: High values for the simulation params can lead to very long test runs since those get multiplied with the number of events
384
389
  export const ClientSessionSyncProcessorSimulationParams = Schema.Struct({
385
390
  pull: Schema.Struct({
386
- '1_before_leader_push_fiber_interrupt': Schema.Int.pipe(Schema.between(0, 1000)),
387
- '2_before_leader_push_queue_clear': Schema.Int.pipe(Schema.between(0, 1000)),
388
- '3_before_rebase_rollback': Schema.Int.pipe(Schema.between(0, 1000)),
389
- '4_before_leader_push_queue_offer': Schema.Int.pipe(Schema.between(0, 1000)),
390
- '5_before_leader_push_fiber_run': Schema.Int.pipe(Schema.between(0, 1000)),
391
+ '1_before_leader_push_fiber_interrupt': Schema.Int.pipe(Schema.between(0, 25)),
392
+ '2_before_leader_push_queue_clear': Schema.Int.pipe(Schema.between(0, 25)),
393
+ '3_before_rebase_rollback': Schema.Int.pipe(Schema.between(0, 25)),
394
+ '4_before_leader_push_queue_offer': Schema.Int.pipe(Schema.between(0, 25)),
395
+ '5_before_leader_push_fiber_run': Schema.Int.pipe(Schema.between(0, 25)),
391
396
  }),
392
397
  })
393
398
  type ClientSessionSyncProcessorSimulationParams = typeof ClientSessionSyncProcessorSimulationParams.Type
package/src/sync/sync.ts CHANGED
@@ -31,6 +31,7 @@ export type SyncOptions = {
31
31
  onSyncError?: 'shutdown' | 'ignore'
32
32
  }
33
33
 
34
+ // TODO rename to `SyncProviderClientConstructor`
34
35
  export type SyncBackendConstructor<TSyncMetadata = Schema.JsonValue> = (
35
36
  args: MakeBackendArgs,
36
37
  ) => Effect.Effect<SyncBackend<TSyncMetadata>, UnexpectedError, Scope.Scope | HttpClient.HttpClient>
@@ -41,6 +42,7 @@ export type SyncBackendConstructor<TSyncMetadata = Schema.JsonValue> = (
41
42
  // - dynamic sync backend data;
42
43
  // - data center location (e.g. colo on CF workers)
43
44
 
45
+ // TODO rename to `SyncProviderClient`
44
46
  export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
45
47
  /**
46
48
  * Can be implemented to prepare a connection to the sync backend to speed up the first pull/push.
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.9' as const
5
+ export const liveStoreVersion = '0.4.0-dev.1' as const
6
6
 
7
7
  /**
8
8
  * This version number is incremented whenever the internal storage format changes in a breaking way.