@livestore/common 0.4.0-dev.1 → 0.4.0-dev.10
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 +7 -2
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/ClientSessionLeaderThreadProxy.js.map +1 -1
- package/dist/adapter-types.d.ts +9 -3
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
- package/dist/devtools/devtools-messages-common.d.ts +7 -14
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +1 -6
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +27 -25
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/errors.d.ts +47 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +22 -3
- package/dist/errors.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +7 -3
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +122 -49
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts +4 -10
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +4 -6
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +6 -2
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +1 -2
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +68 -19
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.d.ts +2 -0
- package/dist/leader-thread/make-leader-thread-layer.test.d.ts.map +1 -0
- package/dist/leader-thread/make-leader-thread-layer.test.js +32 -0
- package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -0
- package/dist/leader-thread/materialize-event.d.ts +2 -2
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +23 -9
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts +2 -3
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +1 -1
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -2
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -2
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/types.d.ts +7 -5
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/materializer-helper.d.ts +1 -1
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +20 -4
- package/dist/materializer-helper.js.map +1 -1
- package/dist/rematerialize-from-eventlog.d.ts +1 -1
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +25 -16
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef.d.ts +3 -0
- package/dist/schema/EventDef.d.ts.map +1 -1
- package/dist/schema/EventDef.js.map +1 -1
- package/dist/schema/LiveStoreEvent.d.ts +1 -1
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent.js +1 -2
- package/dist/schema/LiveStoreEvent.js.map +1 -1
- package/dist/schema/mod.d.ts +2 -0
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +1 -0
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/schema.d.ts +15 -0
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +26 -1
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts +35 -5
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +95 -4
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +16 -0
- package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.js +14 -6
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts +6 -2
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-def.js +122 -185
- package/dist/schema/state/sqlite/column-def.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.test.js +116 -73
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +23 -6
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.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 +2 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts +1 -1
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +1 -1
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.d.ts +5 -2
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +6 -2
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +137 -2
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables.d.ts +42 -6
- package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables.js +2 -0
- package/dist/schema/state/sqlite/system-tables.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +4 -4
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +2 -2
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +51 -2
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/schema/unknown-events.d.ts +47 -0
- package/dist/schema/unknown-events.d.ts.map +1 -0
- package/dist/schema/unknown-events.js +69 -0
- package/dist/schema/unknown-events.js.map +1 -0
- package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
- package/dist/sql-queries/sql-query-builder.js +2 -1
- package/dist/sql-queries/sql-query-builder.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +9 -11
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +35 -33
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/errors.d.ts +61 -0
- package/dist/sync/errors.d.ts.map +1 -0
- package/dist/sync/errors.js +36 -0
- package/dist/sync/errors.js.map +1 -0
- package/dist/sync/index.d.ts +3 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +3 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/mock-sync-backend.d.ts +23 -0
- package/dist/sync/mock-sync-backend.d.ts.map +1 -0
- package/dist/sync/mock-sync-backend.js +114 -0
- package/dist/sync/mock-sync-backend.js.map +1 -0
- package/dist/sync/next/compact-events.d.ts.map +1 -1
- package/dist/sync/next/compact-events.js +4 -5
- package/dist/sync/next/compact-events.js.map +1 -1
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +1 -2
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +50 -11
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +193 -4
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/history-dag.js +3 -1
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/sync-backend-kv.d.ts +7 -0
- package/dist/sync/sync-backend-kv.d.ts.map +1 -0
- package/dist/sync/sync-backend-kv.js +18 -0
- package/dist/sync/sync-backend-kv.js.map +1 -0
- package/dist/sync/sync-backend.d.ts +105 -0
- package/dist/sync/sync-backend.d.ts.map +1 -0
- package/dist/sync/sync-backend.js +61 -0
- package/dist/sync/sync-backend.js.map +1 -0
- package/dist/sync/sync.d.ts +6 -84
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js +2 -27
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/transport-chunking.d.ts +36 -0
- package/dist/sync/transport-chunking.d.ts.map +1 -0
- package/dist/sync/transport-chunking.js +56 -0
- package/dist/sync/transport-chunking.js.map +1 -0
- package/dist/sync/validate-push-payload.d.ts +1 -1
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +6 -6
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/testing/event-factory.d.ts +68 -0
- package/dist/testing/event-factory.d.ts.map +1 -0
- package/dist/testing/event-factory.js +80 -0
- package/dist/testing/event-factory.js.map +1 -0
- package/dist/testing/mod.d.ts +2 -0
- package/dist/testing/mod.d.ts.map +1 -0
- package/dist/testing/mod.js +2 -0
- package/dist/testing/mod.js.map +1 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +7 -8
- package/src/ClientSessionLeaderThreadProxy.ts +7 -2
- package/src/adapter-types.ts +13 -3
- package/src/devtools/devtools-messages-common.ts +1 -8
- package/src/errors.ts +33 -4
- package/src/leader-thread/LeaderSyncProcessor.ts +179 -57
- package/src/leader-thread/eventlog.ts +10 -6
- package/src/leader-thread/leader-worker-devtools.ts +6 -2
- package/src/leader-thread/make-leader-thread-layer.test.ts +44 -0
- package/src/leader-thread/make-leader-thread-layer.ts +137 -26
- package/src/leader-thread/materialize-event.ts +34 -9
- package/src/leader-thread/recreate-db.ts +11 -3
- package/src/leader-thread/shutdown-channel.ts +16 -2
- package/src/leader-thread/types.ts +7 -5
- package/src/materializer-helper.ts +22 -5
- package/src/rematerialize-from-eventlog.ts +33 -23
- package/src/schema/EventDef.ts +3 -0
- package/src/schema/LiveStoreEvent.ts +1 -2
- package/src/schema/mod.ts +2 -0
- package/src/schema/schema.ts +37 -1
- package/src/schema/state/sqlite/client-document-def.test.ts +17 -0
- package/src/schema/state/sqlite/client-document-def.ts +117 -5
- package/src/schema/state/sqlite/column-annotations.ts +16 -6
- package/src/schema/state/sqlite/column-def.test.ts +150 -93
- package/src/schema/state/sqlite/column-def.ts +128 -203
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +26 -6
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +2 -1
- package/src/schema/state/sqlite/mod.ts +1 -0
- package/src/schema/state/sqlite/query-builder/api.ts +7 -2
- package/src/schema/state/sqlite/query-builder/impl.test.ts +187 -6
- package/src/schema/state/sqlite/query-builder/impl.ts +8 -2
- package/src/schema/state/sqlite/system-tables.ts +2 -0
- package/src/schema/state/sqlite/table-def.test.ts +64 -2
- package/src/schema/state/sqlite/table-def.ts +9 -8
- package/src/schema/unknown-events.ts +131 -0
- package/src/sql-queries/sql-query-builder.ts +2 -1
- package/src/sync/ClientSessionSyncProcessor.ts +55 -49
- package/src/sync/errors.ts +38 -0
- package/src/sync/index.ts +3 -0
- package/src/sync/mock-sync-backend.ts +184 -0
- package/src/sync/next/compact-events.ts +4 -5
- package/src/sync/next/facts.ts +1 -3
- package/src/sync/next/history-dag-common.ts +272 -21
- package/src/sync/next/history-dag.ts +3 -1
- package/src/sync/sync-backend-kv.ts +22 -0
- package/src/sync/sync-backend.ts +185 -0
- package/src/sync/sync.ts +6 -89
- package/src/sync/transport-chunking.ts +90 -0
- package/src/sync/validate-push-payload.ts +6 -7
- package/src/testing/event-factory.ts +133 -0
- package/src/testing/mod.ts +1 -0
- package/src/version.ts +2 -2
- package/dist/schema-management/migrations.test.d.ts +0 -2
- package/dist/schema-management/migrations.test.d.ts.map +0 -1
- package/dist/schema-management/migrations.test.js +0 -52
- package/dist/schema-management/migrations.test.js.map +0 -1
- package/dist/sync/next/graphology.d.ts +0 -8
- package/dist/sync/next/graphology.d.ts.map +0 -1
- package/dist/sync/next/graphology.js +0 -30
- package/dist/sync/next/graphology.js.map +0 -1
- package/dist/sync/next/graphology_.d.ts +0 -3
- package/dist/sync/next/graphology_.d.ts.map +0 -1
- package/dist/sync/next/graphology_.js +0 -3
- package/dist/sync/next/graphology_.js.map +0 -1
- package/src/sync/next/ambient.d.ts +0 -3
- package/src/sync/next/graphology.ts +0 -41
- package/src/sync/next/graphology_.ts +0 -2
@@ -1,4 +1,4 @@
|
|
1
|
-
import { shouldNeverHappen
|
1
|
+
import { shouldNeverHappen } from '@livestore/utils'
|
2
2
|
import { Option, Schema, SchemaAST } from '@livestore/utils/effect'
|
3
3
|
|
4
4
|
import { AutoIncrement, ColumnType, Default, PrimaryKeyId, Unique } from './column-annotations.ts'
|
@@ -6,96 +6,61 @@ import { SqliteDsl } from './db-schema/mod.ts'
|
|
6
6
|
|
7
7
|
/**
|
8
8
|
* Maps a schema to a SQLite column definition, respecting column annotations.
|
9
|
+
*
|
10
|
+
* Note: When used with schema-based table definitions, optional fields (| undefined)
|
11
|
+
* are transformed to nullable fields (| null) to match SQLite's NULL semantics.
|
12
|
+
* Fields with both null and undefined will emit a warning as this is a lossy conversion.
|
9
13
|
*/
|
10
14
|
export const getColumnDefForSchema = (
|
11
15
|
schema: Schema.Schema.AnyNoContext,
|
12
16
|
propertySignature?: SchemaAST.PropertySignature,
|
17
|
+
forceNullable = false,
|
13
18
|
): SqliteDsl.ColumnDefinition.Any => {
|
14
19
|
const ast = schema.ast
|
15
20
|
|
16
|
-
//
|
21
|
+
// Extract annotations
|
17
22
|
const getAnnotation = <T>(annotationId: symbol): Option.Option<T> =>
|
18
23
|
propertySignature
|
19
24
|
? hasPropertyAnnotation<T>(propertySignature, annotationId)
|
20
25
|
: SchemaAST.getAnnotation<T>(annotationId)(ast)
|
21
26
|
|
22
|
-
const
|
23
|
-
primaryKey: getAnnotation<boolean>(PrimaryKeyId).pipe(Option.getOrElse(() => false)),
|
24
|
-
autoIncrement: getAnnotation<boolean>(AutoIncrement).pipe(Option.getOrElse(() => false)),
|
25
|
-
defaultValue: getAnnotation<unknown>(Default),
|
26
|
-
columnType: SchemaAST.getAnnotation<SqliteDsl.FieldColumnType>(ColumnType)(ast),
|
27
|
-
}
|
27
|
+
const columnType = SchemaAST.getAnnotation<SqliteDsl.FieldColumnType>(ColumnType)(ast)
|
28
28
|
|
29
|
-
//
|
30
|
-
const
|
29
|
+
// Check if schema has null (e.g., Schema.NullOr) or undefined or if it's forced nullable (optional field)
|
30
|
+
const isNullable = forceNullable || hasNull(ast) || hasUndefined(ast)
|
31
31
|
|
32
|
-
//
|
33
|
-
|
32
|
+
// Get base column definition with nullable flag
|
33
|
+
const baseColumn = Option.isSome(columnType)
|
34
|
+
? getColumnForType(columnType.value, isNullable)
|
35
|
+
: getColumnForSchema(schema, isNullable)
|
34
36
|
|
35
|
-
//
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
// Lossy case: both null and undefined need JSON
|
40
|
-
else if (typeInfo.hasNull && typeInfo.hasUndefined) {
|
41
|
-
columnDef = {
|
42
|
-
...SqliteDsl.text(),
|
43
|
-
nullable: true,
|
44
|
-
schema: Schema.parseJson(schema),
|
45
|
-
}
|
46
|
-
}
|
47
|
-
// Regular nullable/optional case
|
48
|
-
else if (typeInfo.hasNull || typeInfo.hasUndefined) {
|
49
|
-
const baseColumnDef = createColumnFromAST(typeInfo.coreType, Schema.make(typeInfo.coreType))
|
50
|
-
const isComplexOptional = typeInfo.hasUndefined && !isPrimitiveAST(typeInfo.coreType)
|
51
|
-
|
52
|
-
columnDef = {
|
53
|
-
...baseColumnDef,
|
54
|
-
nullable: true,
|
55
|
-
schema: isComplexOptional ? Schema.parseJson(schema) : schema,
|
56
|
-
}
|
57
|
-
}
|
58
|
-
// Non-nullable type
|
59
|
-
else {
|
60
|
-
columnDef = createColumnFromAST(ast, schema)
|
61
|
-
}
|
37
|
+
// Apply annotations
|
38
|
+
const primaryKey = getAnnotation<boolean>(PrimaryKeyId).pipe(Option.getOrElse(() => false))
|
39
|
+
const autoIncrement = getAnnotation<boolean>(AutoIncrement).pipe(Option.getOrElse(() => false))
|
40
|
+
const defaultValue = getAnnotation<unknown>(Default)
|
62
41
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
result.default = Option.some(annotations.defaultValue.value)
|
42
|
+
return {
|
43
|
+
...baseColumn,
|
44
|
+
...(primaryKey && { primaryKey: true }),
|
45
|
+
...(autoIncrement && { autoIncrement: true }),
|
46
|
+
...(Option.isSome(defaultValue) && { default: Option.some(defaultValue.value) }),
|
69
47
|
}
|
70
|
-
|
71
|
-
return result
|
72
48
|
}
|
73
49
|
|
74
|
-
/**
|
75
|
-
* Checks if a property signature has a specific annotation, checking both
|
76
|
-
* the property signature itself and its type AST.
|
77
|
-
*/
|
78
50
|
const hasPropertyAnnotation = <T>(
|
79
51
|
propertySignature: SchemaAST.PropertySignature,
|
80
52
|
annotationId: symbol,
|
81
53
|
): Option.Option<T> => {
|
82
|
-
// When using Schema.optional(Schema.String).pipe(withPrimaryKey) in a struct,
|
83
|
-
// the annotation ends up on a PropertySignatureDeclaration, not the Union type
|
84
|
-
// Check if this is a PropertySignatureDeclaration with annotations
|
85
54
|
if ('annotations' in propertySignature && propertySignature.annotations) {
|
86
55
|
const annotation = SchemaAST.getAnnotation<T>(annotationId)(propertySignature as any)
|
87
|
-
if (Option.isSome(annotation))
|
88
|
-
return annotation
|
89
|
-
}
|
56
|
+
if (Option.isSome(annotation)) return annotation
|
90
57
|
}
|
91
|
-
|
92
|
-
// Otherwise check the type AST
|
93
58
|
return SchemaAST.getAnnotation<T>(annotationId)(propertySignature.type)
|
94
59
|
}
|
95
60
|
|
96
61
|
/**
|
97
62
|
* Maps schema property signatures to SQLite column definitions.
|
98
|
-
*
|
63
|
+
* Optional fields (| undefined) become nullable columns (| null).
|
99
64
|
*/
|
100
65
|
export const schemaFieldsToColumns = (
|
101
66
|
propertySignatures: ReadonlyArray<SchemaAST.PropertySignature>,
|
@@ -104,187 +69,147 @@ export const schemaFieldsToColumns = (
|
|
104
69
|
const uniqueColumns: string[] = []
|
105
70
|
|
106
71
|
for (const prop of propertySignatures) {
|
107
|
-
if (typeof prop.name
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
const
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
if (hasUnique) {
|
118
|
-
uniqueColumns.push(prop.name)
|
72
|
+
if (typeof prop.name !== 'string') continue
|
73
|
+
|
74
|
+
const fieldSchema = Schema.make(prop.type)
|
75
|
+
|
76
|
+
// Warn about lossy conversion for fields with both null and undefined
|
77
|
+
if (prop.isOptional) {
|
78
|
+
const { hasNull, hasUndefined } = checkNullUndefined(fieldSchema.ast)
|
79
|
+
if (hasNull && hasUndefined) {
|
80
|
+
console.warn(`Field '${prop.name}' has both null and undefined - treating | undefined as | null`)
|
119
81
|
}
|
120
82
|
}
|
121
|
-
}
|
122
|
-
|
123
|
-
return { columns, uniqueColumns }
|
124
|
-
}
|
125
83
|
|
126
|
-
|
127
|
-
|
128
|
-
*/
|
129
|
-
const schemaFieldToColumn = (
|
130
|
-
fieldSchema: Schema.Schema.AnyNoContext,
|
131
|
-
propertySignature: SchemaAST.PropertySignature,
|
132
|
-
forceHasPrimaryKey?: boolean,
|
133
|
-
): SqliteDsl.ColumnDefinition.Any => {
|
134
|
-
// Determine column type based on schema type
|
135
|
-
const columnDef = getColumnDefForSchema(fieldSchema, propertySignature)
|
136
|
-
|
137
|
-
// Create a new object with appropriate properties
|
138
|
-
const result: Writeable<SqliteDsl.ColumnDefinition.Any> = {
|
139
|
-
columnType: columnDef.columnType,
|
140
|
-
schema: columnDef.schema,
|
141
|
-
default: columnDef.default,
|
142
|
-
nullable: columnDef.nullable,
|
143
|
-
primaryKey: columnDef.primaryKey,
|
144
|
-
autoIncrement: columnDef.autoIncrement,
|
145
|
-
}
|
84
|
+
// Get column definition - pass nullable flag for optional fields
|
85
|
+
const columnDef = getColumnDefForSchema(fieldSchema, prop, prop.isOptional)
|
146
86
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
} else {
|
151
|
-
result.primaryKey = false
|
152
|
-
}
|
87
|
+
// Check for primary key and unique annotations
|
88
|
+
const hasPrimaryKey = hasPropertyAnnotation<boolean>(prop, PrimaryKeyId).pipe(Option.getOrElse(() => false))
|
89
|
+
const hasUnique = hasPropertyAnnotation<boolean>(prop, Unique).pipe(Option.getOrElse(() => false))
|
153
90
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
)
|
160
|
-
}
|
91
|
+
// Build final column
|
92
|
+
columns[prop.name] = {
|
93
|
+
...columnDef,
|
94
|
+
...(hasPrimaryKey && { primaryKey: true }),
|
95
|
+
}
|
161
96
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
} else {
|
168
|
-
result.nullable = false
|
169
|
-
}
|
97
|
+
// Validate primary key + nullable
|
98
|
+
const column = columns[prop.name]
|
99
|
+
if (column?.primaryKey && column.nullable) {
|
100
|
+
throw new Error('Primary key columns cannot be nullable')
|
101
|
+
}
|
170
102
|
|
171
|
-
|
172
|
-
if (columnDef.autoIncrement) {
|
173
|
-
result.autoIncrement = true
|
103
|
+
if (hasUnique) uniqueColumns.push(prop.name)
|
174
104
|
}
|
175
105
|
|
176
|
-
return
|
106
|
+
return { columns, uniqueColumns }
|
177
107
|
}
|
178
108
|
|
179
|
-
|
180
|
-
* Resolves type information from an AST, unwrapping unions and tracking nullability.
|
181
|
-
*/
|
182
|
-
const resolveType = (
|
183
|
-
ast: SchemaAST.AST,
|
184
|
-
): {
|
185
|
-
coreType: SchemaAST.AST
|
186
|
-
hasNull: boolean
|
187
|
-
hasUndefined: boolean
|
188
|
-
} => {
|
189
|
-
if (!SchemaAST.isUnion(ast)) {
|
190
|
-
return { coreType: ast, hasNull: false, hasUndefined: false }
|
191
|
-
}
|
192
|
-
|
109
|
+
const checkNullUndefined = (ast: SchemaAST.AST): { hasNull: boolean; hasUndefined: boolean } => {
|
193
110
|
let hasNull = false
|
194
111
|
let hasUndefined = false
|
195
|
-
let coreType: SchemaAST.AST | undefined
|
196
112
|
|
197
113
|
const visit = (type: SchemaAST.AST): void => {
|
198
|
-
if (SchemaAST.isUndefinedKeyword(type))
|
199
|
-
|
200
|
-
|
201
|
-
hasNull = true
|
202
|
-
} else if (SchemaAST.isUnion(type)) {
|
203
|
-
type.types.forEach(visit)
|
204
|
-
} else if (!coreType) {
|
205
|
-
coreType = type
|
206
|
-
}
|
114
|
+
if (SchemaAST.isUndefinedKeyword(type)) hasUndefined = true
|
115
|
+
else if (SchemaAST.isLiteral(type) && type.literal === null) hasNull = true
|
116
|
+
else if (SchemaAST.isUnion(type)) type.types.forEach(visit)
|
207
117
|
}
|
208
118
|
|
209
|
-
ast
|
210
|
-
return {
|
119
|
+
visit(ast)
|
120
|
+
return { hasNull, hasUndefined }
|
211
121
|
}
|
212
122
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
ast: SchemaAST.AST,
|
218
|
-
schema: Schema.Schema.AnyNoContext,
|
219
|
-
): SqliteDsl.ColumnDefinition.Any => {
|
220
|
-
// Follow refinements and transformations to their core type
|
221
|
-
if (SchemaAST.isRefinement(ast)) {
|
222
|
-
// Special case for Schema.Int
|
223
|
-
const identifier = SchemaAST.getIdentifierAnnotation(ast).pipe(Option.getOrElse(() => ''))
|
224
|
-
if (identifier === 'Int') return SqliteDsl.integer()
|
225
|
-
return createColumnFromAST(ast.from, Schema.make(ast.from))
|
226
|
-
}
|
227
|
-
|
228
|
-
if (SchemaAST.isTransformation(ast)) {
|
229
|
-
return createColumnFromAST(ast.to, Schema.make(ast.to))
|
123
|
+
const hasNull = (ast: SchemaAST.AST): boolean => {
|
124
|
+
if (SchemaAST.isLiteral(ast) && ast.literal === null) return true
|
125
|
+
if (SchemaAST.isUnion(ast)) {
|
126
|
+
return ast.types.some((type) => hasNull(type))
|
230
127
|
}
|
128
|
+
return false
|
129
|
+
}
|
231
130
|
|
232
|
-
|
233
|
-
if (SchemaAST.
|
234
|
-
if (SchemaAST.
|
235
|
-
|
236
|
-
|
237
|
-
// Literals
|
238
|
-
if (SchemaAST.isLiteral(ast)) {
|
239
|
-
const value = ast.literal
|
240
|
-
if (typeof value === 'string') return SqliteDsl.text()
|
241
|
-
if (typeof value === 'number') return SqliteDsl.real()
|
242
|
-
if (typeof value === 'boolean') return SqliteDsl.boolean()
|
131
|
+
const hasUndefined = (ast: SchemaAST.AST): boolean => {
|
132
|
+
if (SchemaAST.isUndefinedKeyword(ast)) return true
|
133
|
+
if (SchemaAST.isUnion(ast)) {
|
134
|
+
return ast.types.some((type) => hasUndefined(type))
|
243
135
|
}
|
244
|
-
|
245
|
-
// Everything else is complex
|
246
|
-
return SqliteDsl.json({ schema })
|
136
|
+
return false
|
247
137
|
}
|
248
138
|
|
249
|
-
|
250
|
-
* Creates a column from a specific column type string.
|
251
|
-
*/
|
252
|
-
const createColumnFromType = (columnType: string, ast: SchemaAST.AST): SqliteDsl.ColumnDefinition.Any => {
|
139
|
+
const getColumnForType = (columnType: string, nullable = false): SqliteDsl.ColumnDefinition.Any => {
|
253
140
|
switch (columnType) {
|
254
141
|
case 'text':
|
255
|
-
return SqliteDsl.text()
|
142
|
+
return SqliteDsl.text({ nullable })
|
256
143
|
case 'integer':
|
257
|
-
|
258
|
-
return SchemaAST.isBooleanKeyword(ast) ? SqliteDsl.boolean() : SqliteDsl.integer()
|
144
|
+
return SqliteDsl.integer({ nullable })
|
259
145
|
case 'real':
|
260
|
-
return SqliteDsl.real()
|
146
|
+
return SqliteDsl.real({ nullable })
|
261
147
|
case 'blob':
|
262
|
-
return SqliteDsl.blob()
|
148
|
+
return SqliteDsl.blob({ nullable })
|
263
149
|
default:
|
264
150
|
return shouldNeverHappen(`Unsupported column type: ${columnType}`)
|
265
151
|
}
|
266
152
|
}
|
267
153
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
const
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
154
|
+
const getColumnForSchema = (schema: Schema.Schema.AnyNoContext, nullable = false): SqliteDsl.ColumnDefinition.Any => {
|
155
|
+
const ast = schema.ast
|
156
|
+
// Strip nullable wrapper to get core type
|
157
|
+
const coreAst = stripNullable(ast)
|
158
|
+
const coreSchema = stripNullable(ast) === ast ? schema : Schema.make(coreAst)
|
159
|
+
|
160
|
+
// Special case: Boolean is transformed to integer in SQLite
|
161
|
+
if (SchemaAST.isBooleanKeyword(coreAst)) {
|
162
|
+
return SqliteDsl.boolean({ nullable })
|
163
|
+
}
|
164
|
+
|
165
|
+
// Get the encoded AST - what actually gets stored in SQLite
|
166
|
+
const encodedAst = Schema.encodedSchema(coreSchema).ast
|
167
|
+
|
168
|
+
// Check if the encoded type matches SQLite native types
|
169
|
+
if (SchemaAST.isStringKeyword(encodedAst)) {
|
170
|
+
return SqliteDsl.text({ schema: coreSchema, nullable })
|
279
171
|
}
|
280
172
|
|
281
|
-
if (SchemaAST.
|
282
|
-
|
173
|
+
if (SchemaAST.isNumberKeyword(encodedAst)) {
|
174
|
+
// Special cases for integer columns
|
175
|
+
const id = SchemaAST.getIdentifierAnnotation(coreAst).pipe(Option.getOrElse(() => ''))
|
176
|
+
if (id === 'Int' || id === 'DateFromNumber') {
|
177
|
+
return SqliteDsl.integer({ schema: coreSchema, nullable })
|
178
|
+
}
|
179
|
+
return SqliteDsl.real({ schema: coreSchema, nullable })
|
283
180
|
}
|
284
181
|
|
285
|
-
|
286
|
-
|
182
|
+
// Literals based on their type
|
183
|
+
if (SchemaAST.isLiteral(coreAst)) {
|
184
|
+
const value = coreAst.literal
|
185
|
+
if (typeof value === 'boolean') return SqliteDsl.boolean({ nullable })
|
287
186
|
}
|
288
187
|
|
289
|
-
|
188
|
+
// Literals based on their encoded type
|
189
|
+
if (SchemaAST.isLiteral(encodedAst)) {
|
190
|
+
const value = encodedAst.literal
|
191
|
+
if (typeof value === 'string') return SqliteDsl.text({ schema: coreSchema, nullable })
|
192
|
+
if (typeof value === 'number') {
|
193
|
+
// Check if the original schema is Int
|
194
|
+
const id = SchemaAST.getIdentifierAnnotation(coreAst).pipe(Option.getOrElse(() => ''))
|
195
|
+
if (id === 'Int') {
|
196
|
+
return SqliteDsl.integer({ schema: coreSchema, nullable })
|
197
|
+
}
|
198
|
+
return SqliteDsl.real({ schema: coreSchema, nullable })
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
// Everything else needs JSON encoding
|
203
|
+
return SqliteDsl.json({ schema: coreSchema, nullable })
|
204
|
+
}
|
205
|
+
|
206
|
+
const stripNullable = (ast: SchemaAST.AST): SchemaAST.AST => {
|
207
|
+
if (!SchemaAST.isUnion(ast)) return ast
|
208
|
+
|
209
|
+
// Find non-null/undefined type
|
210
|
+
const core = ast.types.find(
|
211
|
+
(type) => !(SchemaAST.isLiteral(type) && type.literal === null) && !SchemaAST.isUndefinedKeyword(type),
|
212
|
+
)
|
213
|
+
|
214
|
+
return core || ast
|
290
215
|
}
|
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
1
|
+
import { omitUndefineds } from '@livestore/utils'
|
2
|
+
import { type Option, Schema, SchemaAST } from '@livestore/utils/effect'
|
2
3
|
|
3
4
|
import { hashCode } from '../hash.ts'
|
4
5
|
|
@@ -45,9 +46,7 @@ export const index = (
|
|
45
46
|
): Index => ({
|
46
47
|
_tag: 'index',
|
47
48
|
columns,
|
48
|
-
name,
|
49
|
-
unique,
|
50
|
-
primaryKey,
|
49
|
+
...omitUndefineds({ name, unique, primaryKey }),
|
51
50
|
})
|
52
51
|
|
53
52
|
export type ForeignKey = {
|
@@ -85,7 +84,19 @@ export type DbSchema = {
|
|
85
84
|
export const dbSchema = (tables: Table[]): DbSchema => ({ _tag: 'dbSchema', tables })
|
86
85
|
|
87
86
|
/**
|
88
|
-
*
|
87
|
+
* Helper to detect if a column is a JSON column (has parseJson transformation)
|
88
|
+
*/
|
89
|
+
const isJsonColumn = (column: Column): boolean => {
|
90
|
+
if (column.type._tag !== 'text') return false
|
91
|
+
|
92
|
+
// Check if the schema AST is a parseJson transformation
|
93
|
+
const ast = column.schema.ast
|
94
|
+
return ast._tag === 'Transformation' && ast.annotations.schemaId === SchemaAST.ParseJsonSchemaId
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* NOTE we're now including JSON schema information for JSON columns
|
99
|
+
* to detect client document schema changes
|
89
100
|
*/
|
90
101
|
export const hash = (obj: Table | Column | Index | ForeignKey | DbSchema): number =>
|
91
102
|
hashCode(JSON.stringify(trimInfoForHasing(obj)))
|
@@ -101,7 +112,7 @@ const trimInfoForHasing = (obj: Table | Column | Index | ForeignKey | DbSchema):
|
|
101
112
|
}
|
102
113
|
}
|
103
114
|
case 'column': {
|
104
|
-
|
115
|
+
const baseInfo: Record<string, any> = {
|
105
116
|
_tag: 'column',
|
106
117
|
name: obj.name,
|
107
118
|
type: obj.type._tag,
|
@@ -110,6 +121,15 @@ const trimInfoForHasing = (obj: Table | Column | Index | ForeignKey | DbSchema):
|
|
110
121
|
autoIncrement: obj.autoIncrement,
|
111
122
|
default: obj.default,
|
112
123
|
}
|
124
|
+
|
125
|
+
// NEW: Include schema hash for JSON columns
|
126
|
+
// This ensures that changes to the JSON schema are detected
|
127
|
+
if (isJsonColumn(obj) && obj.schema) {
|
128
|
+
// Use Effect's Schema.hash for consistent hashing
|
129
|
+
baseInfo.jsonSchemaHash = Schema.hash(obj.schema)
|
130
|
+
}
|
131
|
+
|
132
|
+
return baseInfo
|
113
133
|
}
|
114
134
|
case 'index': {
|
115
135
|
return {
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import type { Nullable } from '@livestore/utils'
|
2
|
+
import { omitUndefineds } from '@livestore/utils'
|
2
3
|
import type { Option, Types } from '@livestore/utils/effect'
|
3
4
|
import { Schema } from '@livestore/utils/effect'
|
4
5
|
|
@@ -46,7 +47,7 @@ export const table = <TTableName extends string, TColumns extends Columns, TInde
|
|
46
47
|
indexes: indexesToAst(indexes ?? []),
|
47
48
|
}
|
48
49
|
|
49
|
-
return { name, columns, indexes, ast }
|
50
|
+
return { name, columns, ...omitUndefineds({ indexes }), ast }
|
50
51
|
}
|
51
52
|
|
52
53
|
export type AnyIfConstained<In, Out> = '__constrained' extends keyof In ? any : Out
|
@@ -3,7 +3,7 @@ import { type Option, Predicate, type Schema } from '@livestore/utils/effect'
|
|
3
3
|
|
4
4
|
import type { SessionIdSymbol } from '../../../../adapter-types.ts'
|
5
5
|
import type { SqlValue } from '../../../../util.ts'
|
6
|
-
import type { ClientDocumentTableDef } from '../client-document-def.ts'
|
6
|
+
import type { ClientDocumentTableDef, ClientDocumentTableDefSymbol } from '../client-document-def.ts'
|
7
7
|
import type { SqliteDsl } from '../db-schema/mod.ts'
|
8
8
|
import type { TableDefBase } from '../table-def.ts'
|
9
9
|
|
@@ -437,7 +437,12 @@ export namespace QueryBuilder {
|
|
437
437
|
|
438
438
|
export namespace RowQuery {
|
439
439
|
export type GetOrCreateOptions<TTableDef extends ClientDocumentTableDef.TraitAny> = {
|
440
|
-
|
440
|
+
/**
|
441
|
+
* Default value to use instead of the default value from the table definition
|
442
|
+
*/
|
443
|
+
default: TTableDef[ClientDocumentTableDefSymbol]['options']['partialSet'] extends false
|
444
|
+
? TTableDef['Value']
|
445
|
+
: Partial<TTableDef['Value']>
|
441
446
|
}
|
442
447
|
|
443
448
|
// TODO get rid of this
|