@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/ClientSessionLeaderThreadProxy.d.ts +2 -2
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/adapter-types.d.ts +1 -1
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/debug-info.d.ts +17 -17
- package/dist/devtools/devtools-messages-client-session.d.ts +38 -38
- package/dist/devtools/devtools-messages-common.d.ts +6 -6
- package/dist/devtools/devtools-messages-leader.d.ts +28 -28
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/leader-thread/types.d.ts +1 -1
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent.d.ts +5 -5
- package/dist/schema/LiveStoreEvent.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.d.ts +34 -0
- package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-annotations.js +50 -0
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -0
- package/dist/schema/state/sqlite/column-annotations.test.d.ts +2 -0
- package/dist/schema/state/sqlite/column-annotations.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-annotations.test.js +179 -0
- package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -0
- package/dist/schema/state/sqlite/column-spec.d.ts +11 -0
- package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-spec.js +39 -0
- package/dist/schema/state/sqlite/column-spec.js.map +1 -0
- package/dist/schema/state/sqlite/column-spec.test.d.ts +2 -0
- package/dist/schema/state/sqlite/column-spec.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-spec.test.js +146 -0
- package/dist/schema/state/sqlite/column-spec.test.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +17 -4
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +2 -0
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -0
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts +2 -0
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +2 -0
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables.d.ts +464 -46
- package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +100 -9
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +227 -5
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.d.ts +2 -0
- package/dist/schema/state/sqlite/table-def.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/table-def.test.js +625 -0
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -0
- package/dist/schema-management/migrations.d.ts +0 -1
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js +3 -29
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sqlite-db-helper.d.ts +1 -1
- package/dist/sqlite-db-helper.d.ts.map +1 -1
- package/dist/sqlite-db-helper.js.map +1 -1
- package/dist/sqlite-types.d.ts +4 -4
- package/dist/sqlite-types.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +2 -2
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/util.d.ts +3 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/src/ClientSessionLeaderThreadProxy.ts +2 -2
- package/src/adapter-types.ts +3 -1
- package/src/devtools/devtools-messages-leader.ts +3 -3
- package/src/leader-thread/types.ts +1 -1
- package/src/schema/LiveStoreEvent.ts +1 -1
- package/src/schema/state/sqlite/column-annotations.test.ts +212 -0
- package/src/schema/state/sqlite/column-annotations.ts +77 -0
- package/src/schema/state/sqlite/column-spec.test.ts +223 -0
- package/src/schema/state/sqlite/column-spec.ts +42 -0
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -0
- package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +15 -0
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +20 -2
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +1 -0
- package/src/schema/state/sqlite/mod.ts +2 -0
- package/src/schema/state/sqlite/table-def.test.ts +783 -0
- package/src/schema/state/sqlite/table-def.ts +440 -16
- package/src/schema-management/migrations.ts +3 -32
- package/src/sqlite-db-helper.ts +1 -1
- package/src/sqlite-types.ts +4 -4
- package/src/sync/ClientSessionSyncProcessor.ts +5 -2
- package/src/util.ts +7 -2
- package/src/version.ts +1 -1
- package/src/schema-management/migrations.test.ts +0 -65
@@ -1,6 +1,7 @@
|
|
1
|
-
import type
|
2
|
-
import
|
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
|
-
|
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
|
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
|
-
|
80
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
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
|
349
|
+
TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefinition.Any,
|
183
350
|
> = SqliteDsl.TableDefinition<TName, PrettifyFlat<ToColumns<TColumns>>>
|
184
351
|
|
185
|
-
type
|
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
|
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
|
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
|
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 {
|
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
|
-
}
|
package/src/sqlite-db-helper.ts
CHANGED
@@ -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
|
package/src/sqlite-types.ts
CHANGED
@@ -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:
|
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(
|
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.
|
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.
|