@livestore/common 0.4.0-dev.22 → 0.4.0-dev.23
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 +9 -9
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/WorkerTransportError.d.ts +11 -0
- package/dist/WorkerTransportError.d.ts.map +1 -0
- package/dist/WorkerTransportError.js +11 -0
- package/dist/WorkerTransportError.js.map +1 -0
- package/dist/adapter-types.d.ts +3 -3
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/bounded-collections.d.ts.map +1 -1
- package/dist/bounded-collections.js +6 -4
- package/dist/bounded-collections.js.map +1 -1
- package/dist/debug-info.js +4 -4
- package/dist/debug-info.js.map +1 -1
- package/dist/devtools/devtools-messages-common.js +1 -1
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/mod.js +1 -1
- package/dist/devtools/mod.js.map +1 -1
- package/dist/errors.d.ts +15 -15
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -11
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +20 -6
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +287 -257
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/RejectedPushError.d.ts +107 -0
- package/dist/leader-thread/RejectedPushError.d.ts.map +1 -0
- package/dist/leader-thread/RejectedPushError.js +78 -0
- package/dist/leader-thread/RejectedPushError.js.map +1 -0
- package/dist/leader-thread/connection.js +1 -1
- package/dist/leader-thread/connection.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +12 -11
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -2
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +25 -14
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +8 -3
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +7 -10
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -1
- package/dist/leader-thread/materialize-event.js +4 -4
- package/dist/leader-thread/materialize-event.js.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/stream-events.d.ts.map +1 -1
- package/dist/leader-thread/stream-events.js +4 -3
- package/dist/leader-thread/stream-events.js.map +1 -1
- package/dist/leader-thread/types.d.ts +7 -6
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/logging.js +4 -4
- package/dist/logging.js.map +1 -1
- package/dist/make-client-session.js +2 -2
- package/dist/make-client-session.js.map +1 -1
- package/dist/materializer-helper.js +6 -6
- package/dist/materializer-helper.js.map +1 -1
- package/dist/otel.d.ts +1 -1
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +2 -2
- package/dist/otel.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 +11 -9
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef/define.d.ts +2 -2
- package/dist/schema/EventDef/define.d.ts.map +1 -1
- package/dist/schema/EventDef/define.js +4 -4
- package/dist/schema/EventDef/define.js.map +1 -1
- package/dist/schema/EventDef/deprecated.js +3 -3
- package/dist/schema/EventDef/deprecated.js.map +1 -1
- package/dist/schema/EventDef/deprecated.test.js +1 -1
- package/dist/schema/EventDef/deprecated.test.js.map +1 -1
- package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/client.js +11 -11
- package/dist/schema/EventSequenceNumber/client.js.map +1 -1
- package/dist/schema/EventSequenceNumber.test.js +1 -1
- package/dist/schema/EventSequenceNumber.test.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/client.js +6 -3
- package/dist/schema/LiveStoreEvent/client.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.test.d.ts +2 -0
- package/dist/schema/LiveStoreEvent/client.test.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent/client.test.js +83 -0
- package/dist/schema/LiveStoreEvent/client.test.js.map +1 -0
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +7 -4
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +18 -6
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +1 -1
- 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 +1 -1
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-def.js +36 -34
- package/dist/schema/state/sqlite/column-def.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.test.js +7 -6
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-spec.js +8 -8
- package/dist/schema/state/sqlite/column-spec.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +2 -2
- 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 +2 -2
- 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 +11 -2
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +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 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +3 -5
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.d.ts +10 -2
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/astToSql.js +11 -11
- package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +28 -14
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +3 -2
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js +2 -2
- package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +5 -3
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +1 -1
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +57 -4
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/schema/unknown-events.d.ts +1 -1
- package/dist/schema/unknown-events.d.ts.map +1 -1
- package/dist/schema/unknown-events.js +1 -1
- package/dist/schema/unknown-events.js.map +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -1
- package/dist/schema-management/common.js +2 -2
- package/dist/schema-management/common.js.map +1 -1
- package/dist/schema-management/migrations.js +1 -1
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sql-queries/sql-queries.js +8 -6
- package/dist/sql-queries/sql-queries.js.map +1 -1
- package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
- package/dist/sql-queries/sql-query-builder.js.map +1 -1
- package/dist/sqlite-db-helper.js +3 -3
- package/dist/sqlite-db-helper.js.map +1 -1
- package/dist/sqlite-types.d.ts +2 -2
- package/dist/sqlite-types.d.ts.map +1 -1
- package/dist/sqlite-types.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +8 -9
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +95 -113
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/errors.d.ts +0 -38
- package/dist/sync/errors.d.ts.map +1 -1
- package/dist/sync/errors.js +3 -20
- package/dist/sync/errors.js.map +1 -1
- package/dist/sync/mock-sync-backend.d.ts +5 -3
- package/dist/sync/mock-sync-backend.d.ts.map +1 -1
- package/dist/sync/mock-sync-backend.js +70 -68
- package/dist/sync/mock-sync-backend.js.map +1 -1
- package/dist/sync/next/compact-events.js +6 -6
- 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 +6 -6
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +6 -6
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.js +3 -3
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.js +1 -1
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +2 -2
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.d.ts.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +2 -2
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/event-fixtures.js +2 -2
- package/dist/sync/next/test/event-fixtures.js.map +1 -1
- package/dist/sync/sync-backend-kv.d.ts.map +1 -1
- package/dist/sync/sync-backend-kv.js.map +1 -1
- package/dist/sync/sync-backend.d.ts +3 -3
- package/dist/sync/sync-backend.d.ts.map +1 -1
- package/dist/sync/sync-backend.js +1 -1
- package/dist/sync/sync-backend.js.map +1 -1
- package/dist/sync/sync.d.ts +20 -0
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/syncstate.d.ts +4 -17
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +51 -74
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +112 -96
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/transport-chunking.js +3 -3
- package/dist/sync/transport-chunking.js.map +1 -1
- package/dist/sync/validate-push-payload.d.ts +2 -2
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +4 -6
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/util.js +2 -2
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -5
- package/dist/version.js.map +1 -1
- package/package.json +66 -12
- package/src/ClientSessionLeaderThreadProxy.ts +9 -9
- package/src/WorkerTransportError.ts +12 -0
- package/src/adapter-types.ts +9 -3
- package/src/bounded-collections.ts +6 -5
- package/src/debug-info.ts +4 -4
- package/src/devtools/devtools-messages-common.ts +1 -1
- package/src/devtools/mod.ts +1 -1
- package/src/errors.ts +18 -17
- package/src/index.ts +2 -0
- package/src/leader-thread/LeaderSyncProcessor.ts +421 -392
- package/src/leader-thread/RejectedPushError.ts +106 -0
- package/src/leader-thread/connection.ts +1 -1
- package/src/leader-thread/eventlog.ts +16 -14
- package/src/leader-thread/leader-worker-devtools.ts +96 -66
- package/src/leader-thread/make-leader-thread-layer.test.ts +1 -1
- package/src/leader-thread/make-leader-thread-layer.ts +33 -31
- package/src/leader-thread/materialize-event.ts +4 -4
- package/src/leader-thread/recreate-db.ts +1 -1
- package/src/leader-thread/shutdown-channel.ts +2 -6
- package/src/leader-thread/stream-events.ts +10 -5
- package/src/leader-thread/types.ts +7 -6
- package/src/logging.ts +4 -4
- package/src/make-client-session.ts +2 -2
- package/src/materializer-helper.ts +9 -9
- package/src/otel.ts +3 -2
- package/src/rematerialize-from-eventlog.ts +60 -60
- package/src/schema/EventDef/define.ts +6 -6
- package/src/schema/EventDef/deprecated.test.ts +2 -1
- package/src/schema/EventDef/deprecated.ts +3 -3
- package/src/schema/EventSequenceNumber/client.ts +11 -11
- package/src/schema/EventSequenceNumber.test.ts +2 -1
- package/src/schema/LiveStoreEvent/client.test.ts +97 -0
- package/src/schema/LiveStoreEvent/client.ts +6 -3
- package/src/schema/schema.ts +9 -4
- package/src/schema/state/sqlite/client-document-def.test.ts +2 -1
- package/src/schema/state/sqlite/client-document-def.ts +20 -6
- package/src/schema/state/sqlite/column-annotations.test.ts +2 -1
- package/src/schema/state/sqlite/column-annotations.ts +2 -1
- package/src/schema/state/sqlite/column-def.test.ts +8 -6
- package/src/schema/state/sqlite/column-def.ts +41 -36
- package/src/schema/state/sqlite/column-spec.test.ts +3 -1
- package/src/schema/state/sqlite/column-spec.ts +9 -8
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -2
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +2 -1
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +13 -4
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +3 -3
- package/src/schema/state/sqlite/mod.ts +4 -5
- package/src/schema/state/sqlite/query-builder/api.ts +12 -5
- package/src/schema/state/sqlite/query-builder/astToSql.ts +11 -11
- package/src/schema/state/sqlite/query-builder/impl.test.ts +4 -2
- package/src/schema/state/sqlite/query-builder/impl.ts +26 -12
- package/src/schema/state/sqlite/schema-helpers.ts +2 -2
- package/src/schema/state/sqlite/table-def.test.ts +67 -4
- package/src/schema/state/sqlite/table-def.ts +8 -15
- package/src/schema/unknown-events.ts +2 -2
- package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +3 -1
- package/src/schema-management/common.ts +2 -2
- package/src/schema-management/migrations.ts +1 -1
- package/src/sql-queries/sql-queries.ts +10 -6
- package/src/sql-queries/sql-query-builder.ts +1 -0
- package/src/sqlite-db-helper.ts +3 -3
- package/src/sqlite-types.ts +3 -2
- package/src/sync/ClientSessionSyncProcessor.ts +148 -152
- package/src/sync/errors.ts +10 -22
- package/src/sync/mock-sync-backend.ts +139 -97
- package/src/sync/next/compact-events.ts +5 -5
- package/src/sync/next/facts.ts +7 -6
- package/src/sync/next/history-dag-common.ts +9 -6
- package/src/sync/next/history-dag.ts +3 -3
- package/src/sync/next/rebase-events.ts +1 -1
- package/src/sync/next/test/compact-events.calculator.test.ts +3 -2
- package/src/sync/next/test/compact-events.test.ts +4 -3
- package/src/sync/next/test/event-fixtures.ts +2 -2
- package/src/sync/sync-backend-kv.ts +1 -0
- package/src/sync/sync-backend.ts +5 -4
- package/src/sync/sync.ts +21 -0
- package/src/sync/syncstate.test.ts +513 -435
- package/src/sync/syncstate.ts +80 -86
- package/src/sync/transport-chunking.ts +3 -3
- package/src/sync/validate-push-payload.ts +4 -6
- package/src/util.ts +2 -2
- package/src/version.ts +2 -6
|
@@ -31,7 +31,7 @@ export type DbSchemaFromInputSchema<TSchemaInput extends DbSchemaInput> =
|
|
|
31
31
|
export const makeDbSchema = <TDbSchemaInput extends DbSchemaInput>(
|
|
32
32
|
schema: TDbSchemaInput,
|
|
33
33
|
): DbSchemaFromInputSchema<TDbSchemaInput> => {
|
|
34
|
-
return Array.isArray(schema) ? Object.fromEntries(schema.map((_) => [_.name, _])) : (schema as any)
|
|
34
|
+
return Array.isArray(schema) === true ? Object.fromEntries(schema.map((_) => [_.name, _])) : (schema as any)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export const table = <TTableName extends string, TColumns extends Columns, TIndexes extends Index[]>(
|
|
@@ -76,6 +76,7 @@ export const insertStructSchemaForTable = <TTableDefinition extends TableDefinit
|
|
|
76
76
|
Object.fromEntries(
|
|
77
77
|
tableDef.ast.columns.map((column) => [
|
|
78
78
|
column.name,
|
|
79
|
+
|
|
79
80
|
column.nullable === true || column.default._tag === 'Some' ? Schema.optional(column.schema) : column.schema,
|
|
80
81
|
]),
|
|
81
82
|
),
|
|
@@ -195,8 +196,7 @@ export namespace FromColumns {
|
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
export type NullableColumnNames<TColumns extends Columns> = keyof {
|
|
198
|
-
|
|
199
|
-
[K in keyof TColumns as TColumns[K] extends ColumnDefinition<any, true> ? K : never]: {}
|
|
199
|
+
[K in keyof TColumns as TColumns[K]['nullable'] extends true ? K : never]: {}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
export type RequiredInsertColumns<TColumns extends Columns> = {
|
|
@@ -22,16 +22,15 @@ export * from './column-spec.ts'
|
|
|
22
22
|
export * from './table-def.ts'
|
|
23
23
|
|
|
24
24
|
export const makeState = <TStateInput extends InputState>(inputSchema: TStateInput): InternalState => {
|
|
25
|
-
const inputTables: ReadonlyArray<TableDef> =
|
|
26
|
-
? inputSchema.tables
|
|
27
|
-
: Object.values(inputSchema.tables)
|
|
25
|
+
const inputTables: ReadonlyArray<TableDef> =
|
|
26
|
+
Array.isArray(inputSchema.tables) === true ? inputSchema.tables : Object.values(inputSchema.tables)
|
|
28
27
|
|
|
29
28
|
const tables = new Map<string, TableDef.Any>()
|
|
30
29
|
|
|
31
30
|
for (const tableDef of inputTables) {
|
|
32
31
|
const sqliteDef = tableDef.sqliteDef
|
|
33
32
|
// TODO validate tables (e.g. index names are unique)
|
|
34
|
-
if (tables.has(sqliteDef.ast.name)) {
|
|
33
|
+
if (tables.has(sqliteDef.ast.name) === true) {
|
|
35
34
|
shouldNeverHappen(`Duplicate table name: ${sqliteDef.ast.name}. Please use unique names for tables.`)
|
|
36
35
|
}
|
|
37
36
|
tables.set(sqliteDef.ast.name, tableDef)
|
|
@@ -48,7 +47,7 @@ export const makeState = <TStateInput extends InputState>(inputSchema: TStateInp
|
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
for (const tableDef of inputTables) {
|
|
51
|
-
if (tableIsClientDocumentTable(tableDef)) {
|
|
50
|
+
if (tableIsClientDocumentTable(tableDef) === true) {
|
|
52
51
|
materializers.set(
|
|
53
52
|
tableDef[ClientDocumentTableDefSymbol].derived.setEventDef.name,
|
|
54
53
|
tableDef[ClientDocumentTableDefSymbol].derived.setMaterializer,
|
|
@@ -230,9 +230,8 @@ export namespace QueryBuilder {
|
|
|
230
230
|
/** Select multiple columns */
|
|
231
231
|
<TColumns extends keyof TTableDef['sqliteDef']['columns'] & string>(
|
|
232
232
|
...columns: TColumns[]
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
): QueryBuilder<
|
|
233
|
+
)// params: QueryBuilderSelectParams, // TODO also support arbitrary SQL selects
|
|
234
|
+
: QueryBuilder<
|
|
236
235
|
ReadonlyArray<{
|
|
237
236
|
readonly [K in TColumns]: TTableDef['sqliteDef']['columns'][K]['schema']['Type']
|
|
238
237
|
}>,
|
|
@@ -358,12 +357,20 @@ export namespace QueryBuilder {
|
|
|
358
357
|
>
|
|
359
358
|
|
|
360
359
|
/**
|
|
361
|
-
* Insert a new row into the table
|
|
360
|
+
* Insert a new row into the table.
|
|
361
|
+
*
|
|
362
|
+
* @remarks
|
|
363
|
+
*
|
|
364
|
+
* Follows SQL semantics: nullable columns and columns with defaults are omittable.
|
|
365
|
+
* `NullOr(S)` and `optional(NullOr(S))` both produce nullable columns, so both are omittable.
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
362
368
|
*
|
|
363
|
-
* Example:
|
|
364
369
|
* ```ts
|
|
365
370
|
* db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' })
|
|
366
371
|
* ```
|
|
372
|
+
*
|
|
373
|
+
* @param values - The row values to insert.
|
|
367
374
|
*/
|
|
368
375
|
readonly insert: (
|
|
369
376
|
values: TTableDef['insertSchema']['Type'],
|
|
@@ -11,11 +11,11 @@ import type { QueryBuilderAst } from './api.ts'
|
|
|
11
11
|
* Returns the element schema, or undefined if not a JSON array transformation.
|
|
12
12
|
*/
|
|
13
13
|
const extractArrayElementFromTransformation = (ast: SchemaAST.AST): Schema.Schema.Any | undefined => {
|
|
14
|
-
if (
|
|
14
|
+
if (SchemaAST.isTransformation(ast) === false) return undefined
|
|
15
15
|
|
|
16
16
|
const toAst = ast.to
|
|
17
17
|
// Check if the "to" side is a TupleType (Effect's internal representation of Array)
|
|
18
|
-
if (
|
|
18
|
+
if (SchemaAST.isTupleType(toAst) === false) return undefined
|
|
19
19
|
|
|
20
20
|
// For Schema.Array, rest contains { type: AST } elements - get the first one's type
|
|
21
21
|
const restElement = toAst.rest[0]
|
|
@@ -34,13 +34,13 @@ const getJsonArrayElementSchema = (colSchema: Schema.Schema.Any): Schema.Schema.
|
|
|
34
34
|
|
|
35
35
|
// Case 1: Direct transformation (non-nullable JSON array)
|
|
36
36
|
// Schema.parseJson(Schema.Array(ElementSchema)) creates a Transformation AST
|
|
37
|
-
if (SchemaAST.isTransformation(ast)) {
|
|
37
|
+
if (SchemaAST.isTransformation(ast) === true) {
|
|
38
38
|
return extractArrayElementFromTransformation(ast)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// Case 2: Nullable JSON array - Schema.NullOr wraps the parseJson in a Union
|
|
42
42
|
// Structure: Union([Transformation (JSON array), Literal (null)])
|
|
43
|
-
if (SchemaAST.isUnion(ast)) {
|
|
43
|
+
if (SchemaAST.isUnion(ast) === true) {
|
|
44
44
|
for (const member of ast.types) {
|
|
45
45
|
const result = extractArrayElementFromTransformation(member)
|
|
46
46
|
if (result !== undefined) return result
|
|
@@ -63,7 +63,7 @@ const encodeJsonArrayElementValue = (elementSchema: Schema.Schema.Any, value: un
|
|
|
63
63
|
return JSON.stringify(encoded)
|
|
64
64
|
}
|
|
65
65
|
if (typeof encoded === 'boolean') {
|
|
66
|
-
return encoded ? 1 : 0
|
|
66
|
+
return encoded === true ? 1 : 0
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
return encoded
|
|
@@ -118,9 +118,9 @@ const formatWhereClause = (
|
|
|
118
118
|
// Handle array values for IN/NOT IN operators
|
|
119
119
|
const isArray = op === 'IN' || op === 'NOT IN'
|
|
120
120
|
|
|
121
|
-
if (isArray) {
|
|
121
|
+
if (isArray === true) {
|
|
122
122
|
// Verify value is an array
|
|
123
|
-
if (
|
|
123
|
+
if (Array.isArray(value) === false) {
|
|
124
124
|
return shouldNeverHappen(`Expected array value for ${op} operator but got`, value)
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -145,7 +145,7 @@ const formatWhereClause = (
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
const formatReturningClause = (returning?: string[]): string => {
|
|
148
|
-
if (
|
|
148
|
+
if (returning == null || returning.length === 0) return ''
|
|
149
149
|
return ` RETURNING ${returning.map(quoteIdentifier).join(', ')}`
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -169,7 +169,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
|
|
|
169
169
|
let conflictClause = '' // Store the ON CONFLICT clause separately
|
|
170
170
|
|
|
171
171
|
// Handle ON CONFLICT clause
|
|
172
|
-
if (ast.onConflict) {
|
|
172
|
+
if (ast.onConflict !== undefined) {
|
|
173
173
|
// Handle REPLACE specifically as it changes the INSERT verb
|
|
174
174
|
if (ast.onConflict.action._tag === 'replace') {
|
|
175
175
|
insertVerb = 'INSERT OR REPLACE'
|
|
@@ -249,7 +249,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
|
|
|
249
249
|
.join(', ')}`
|
|
250
250
|
|
|
251
251
|
const whereClause = formatWhereClause(ast.where, ast.tableDef, bindValues)
|
|
252
|
-
if (whereClause) query += ` ${whereClause}`
|
|
252
|
+
if (whereClause !== undefined) query += ` ${whereClause}`
|
|
253
253
|
|
|
254
254
|
query += formatReturningClause(ast.returning)
|
|
255
255
|
return { query, bindValues, usedTables }
|
|
@@ -260,7 +260,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
|
|
|
260
260
|
let query = `DELETE FROM '${ast.tableDef.sqliteDef.name}'`
|
|
261
261
|
|
|
262
262
|
const whereClause = formatWhereClause(ast.where, ast.tableDef, bindValues)
|
|
263
|
-
if (whereClause) query += ` ${whereClause}`
|
|
263
|
+
if (whereClause !== undefined) query += ` ${whereClause}`
|
|
264
264
|
|
|
265
265
|
query += formatReturningClause(ast.returning)
|
|
266
266
|
return { query, bindValues, usedTables }
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { Schema } from '@livestore/utils/effect'
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import { Schema } from '@livestore/utils/effect'
|
|
4
|
+
import { objectToString } from '@livestore/utils'
|
|
5
|
+
|
|
4
6
|
import { State } from '../../../mod.ts'
|
|
5
7
|
import type { QueryBuilder } from './api.ts'
|
|
6
8
|
import { getResultSchema } from './impl.ts'
|
|
@@ -101,7 +103,7 @@ const db = { todos, todosWithIntId, comments, issue, selections, UiState, UiStat
|
|
|
101
103
|
const dump = (qb: QueryBuilder<any, any, any>) => ({
|
|
102
104
|
bindValues: qb.asSql().bindValues,
|
|
103
105
|
query: qb.asSql().query,
|
|
104
|
-
schema: getResultSchema(qb)
|
|
106
|
+
schema: objectToString(getResultSchema(qb)),
|
|
105
107
|
})
|
|
106
108
|
|
|
107
109
|
describe('query builder', () => {
|
|
@@ -9,7 +9,7 @@ import { astToSql } from './astToSql.ts'
|
|
|
9
9
|
export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
10
10
|
tableDef: TTableDef,
|
|
11
11
|
ast: QueryBuilderAst = emptyAst(tableDef),
|
|
12
|
-
): QueryBuilder<TResult, TTableDef
|
|
12
|
+
): QueryBuilder<TResult, TTableDef> => {
|
|
13
13
|
const api = {
|
|
14
14
|
select() {
|
|
15
15
|
assertSelectQueryBuilderAst(ast)
|
|
@@ -18,6 +18,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
18
18
|
|
|
19
19
|
// Pluck if there's only one column selected
|
|
20
20
|
if (params.length === 1) {
|
|
21
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- arguments-based overload dispatch requires manual narrowing
|
|
21
22
|
const [col] = params as any as [string]
|
|
22
23
|
return makeQueryBuilder(tableDef, {
|
|
23
24
|
...ast,
|
|
@@ -26,8 +27,10 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
26
27
|
})
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- arguments-based overload dispatch requires manual narrowing
|
|
29
31
|
const columns = params as unknown as ReadonlyArray<string>
|
|
30
32
|
|
|
33
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
|
|
31
34
|
return makeQueryBuilder(tableDef, {
|
|
32
35
|
...ast,
|
|
33
36
|
resultSchemaSingle:
|
|
@@ -44,7 +47,8 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
44
47
|
const newOps = Object.entries(params)
|
|
45
48
|
.filter(([, value]) => value !== undefined)
|
|
46
49
|
.map<QueryBuilderAst.Where>(([col, value]) =>
|
|
47
|
-
Predicate.hasProperty(value, 'op') && Predicate.hasProperty(value, 'value')
|
|
50
|
+
Predicate.hasProperty(value, 'op') === true && Predicate.hasProperty(value, 'value') === true
|
|
51
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- where clause construction; shape validated at runtime
|
|
48
52
|
? ({ col, op: value.op, value: value.value } as any)
|
|
49
53
|
: { col, op: '=', value },
|
|
50
54
|
)
|
|
@@ -54,6 +58,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
54
58
|
case 'SelectQuery':
|
|
55
59
|
case 'UpdateQuery':
|
|
56
60
|
case 'DeleteQuery': {
|
|
61
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
|
|
57
62
|
return makeQueryBuilder(tableDef, {
|
|
58
63
|
...ast,
|
|
59
64
|
where: [...ast.where, ...newOps],
|
|
@@ -74,6 +79,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
74
79
|
case 'SelectQuery':
|
|
75
80
|
case 'UpdateQuery':
|
|
76
81
|
case 'DeleteQuery': {
|
|
82
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
|
|
77
83
|
return makeQueryBuilder(tableDef, {
|
|
78
84
|
...ast,
|
|
79
85
|
where: [...ast.where, { col, op, value }],
|
|
@@ -91,6 +97,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
91
97
|
if (arguments.length === 0 || arguments.length > 2) return invalidQueryBuilder()
|
|
92
98
|
|
|
93
99
|
if (arguments.length === 1) {
|
|
100
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- arguments-based overload dispatch requires manual narrowing
|
|
94
101
|
const params = arguments[0] as QueryBuilder.OrderByParams<TTableDef>
|
|
95
102
|
return makeQueryBuilder(tableDef, {
|
|
96
103
|
...ast,
|
|
@@ -98,8 +105,10 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
98
105
|
})
|
|
99
106
|
}
|
|
100
107
|
|
|
108
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- arguments-based overload dispatch requires manual narrowing
|
|
101
109
|
const [col, direction] = arguments as any as [keyof TTableDef['sqliteDef']['columns'] & string, 'asc' | 'desc']
|
|
102
110
|
|
|
111
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
|
|
103
112
|
return makeQueryBuilder(tableDef, {
|
|
104
113
|
...ast,
|
|
105
114
|
orderBy: [...ast.orderBy, { col, direction }],
|
|
@@ -116,7 +125,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
116
125
|
return makeQueryBuilder(tableDef, { ...ast, offset: Option.some(offset) })
|
|
117
126
|
},
|
|
118
127
|
count: () => {
|
|
119
|
-
if (isRowQuery(ast) || ast._tag === 'InsertQuery' || ast._tag === 'UpdateQuery' || ast._tag === 'DeleteQuery')
|
|
128
|
+
if (isRowQuery(ast) === true || ast._tag === 'InsertQuery' || ast._tag === 'UpdateQuery' || ast._tag === 'DeleteQuery')
|
|
120
129
|
return invalidQueryBuilder()
|
|
121
130
|
|
|
122
131
|
return makeQueryBuilder(tableDef, {
|
|
@@ -171,6 +180,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
171
180
|
insert: (values) => {
|
|
172
181
|
const filteredValues = Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined))
|
|
173
182
|
|
|
183
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
|
|
174
184
|
return makeQueryBuilder(tableDef, {
|
|
175
185
|
_tag: 'InsertQuery',
|
|
176
186
|
tableDef,
|
|
@@ -185,7 +195,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
185
195
|
action: 'ignore' | 'replace' | 'update',
|
|
186
196
|
updateValues?: Record<string, unknown>,
|
|
187
197
|
) => {
|
|
188
|
-
const targets = Array.isArray(targetOrTargets) ? targetOrTargets : [targetOrTargets]
|
|
198
|
+
const targets = Array.isArray(targetOrTargets) === true ? targetOrTargets : [targetOrTargets]
|
|
189
199
|
|
|
190
200
|
assertInsertQueryBuilderAst(ast)
|
|
191
201
|
|
|
@@ -199,6 +209,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
199
209
|
Match.exhaustive,
|
|
200
210
|
)
|
|
201
211
|
|
|
212
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
|
|
202
213
|
return makeQueryBuilder(tableDef, {
|
|
203
214
|
...ast,
|
|
204
215
|
onConflict,
|
|
@@ -208,6 +219,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
208
219
|
returning: (...columns) => {
|
|
209
220
|
assertWriteQueryBuilderAst(ast)
|
|
210
221
|
|
|
222
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
|
|
211
223
|
return makeQueryBuilder(tableDef, {
|
|
212
224
|
...ast,
|
|
213
225
|
returning: columns,
|
|
@@ -221,6 +233,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
221
233
|
// Preserve where clauses if coming from a SelectQuery
|
|
222
234
|
const whereClause = ast._tag === 'SelectQuery' ? ast.where : []
|
|
223
235
|
|
|
236
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
|
|
224
237
|
return makeQueryBuilder(tableDef, {
|
|
225
238
|
_tag: 'UpdateQuery',
|
|
226
239
|
tableDef,
|
|
@@ -235,6 +248,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
235
248
|
// Preserve where clauses if coming from a SelectQuery
|
|
236
249
|
const whereClause = ast._tag === 'SelectQuery' ? ast.where : []
|
|
237
250
|
|
|
251
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- query builder return type depends on AST state; consumer type safety enforced by api.ts
|
|
238
252
|
return makeQueryBuilder(tableDef, {
|
|
239
253
|
_tag: 'DeleteQuery',
|
|
240
254
|
tableDef,
|
|
@@ -248,6 +262,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
|
248
262
|
return {
|
|
249
263
|
[QueryBuilderTypeId]: QueryBuilderTypeId,
|
|
250
264
|
[QueryBuilderAstSymbol]: ast,
|
|
265
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- phantom type field for generic inference only
|
|
251
266
|
ResultType: 'only-for-type-inference' as TResult,
|
|
252
267
|
asSql: () => astToSql(ast),
|
|
253
268
|
toString: () => {
|
|
@@ -277,19 +292,19 @@ const emptyAst = (tableDef: TableDefBase): QueryBuilderAst.SelectQuery => ({
|
|
|
277
292
|
|
|
278
293
|
// Helper functions
|
|
279
294
|
|
|
280
|
-
|
|
295
|
+
const assertSelectQueryBuilderAst: (ast: QueryBuilderAst) => asserts ast is QueryBuilderAst.SelectQuery = (ast) => {
|
|
281
296
|
if (ast._tag !== 'SelectQuery') {
|
|
282
297
|
return shouldNeverHappen(`Expected SelectQuery but got ${ast._tag}`)
|
|
283
298
|
}
|
|
284
299
|
}
|
|
285
300
|
|
|
286
|
-
|
|
301
|
+
const assertInsertQueryBuilderAst: (ast: QueryBuilderAst) => asserts ast is QueryBuilderAst.InsertQuery = (ast) => {
|
|
287
302
|
if (ast._tag !== 'InsertQuery') {
|
|
288
303
|
return shouldNeverHappen(`Expected InsertQuery but got ${ast._tag}`)
|
|
289
304
|
}
|
|
290
305
|
}
|
|
291
306
|
|
|
292
|
-
|
|
307
|
+
const assertWriteQueryBuilderAst: (ast: QueryBuilderAst) => asserts ast is QueryBuilderAst.WriteQuery = (ast) => {
|
|
293
308
|
if (ast._tag !== 'InsertQuery' && ast._tag !== 'UpdateQuery' && ast._tag !== 'DeleteQuery') {
|
|
294
309
|
return shouldNeverHappen(`Expected WriteQuery but got ${ast._tag}`)
|
|
295
310
|
}
|
|
@@ -298,7 +313,7 @@ function assertWriteQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryB
|
|
|
298
313
|
const isRowQuery = (ast: QueryBuilderAst): ast is QueryBuilderAst.RowQuery => ast._tag === 'RowQuery'
|
|
299
314
|
|
|
300
315
|
export const invalidQueryBuilder = (msg?: string) => {
|
|
301
|
-
return shouldNeverHappen(`Invalid query builder${msg ? `: ${msg}` : ''}`)
|
|
316
|
+
return shouldNeverHappen(`Invalid query builder${msg !== undefined ? `: ${msg}` : ''}`)
|
|
302
317
|
}
|
|
303
318
|
|
|
304
319
|
export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<any> => {
|
|
@@ -328,7 +343,7 @@ export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<
|
|
|
328
343
|
case 'UpdateQuery':
|
|
329
344
|
case 'DeleteQuery': {
|
|
330
345
|
// For write operations with RETURNING clause, we need to return the appropriate schema
|
|
331
|
-
if (queryAst.returning && queryAst.returning.length > 0) {
|
|
346
|
+
if (queryAst.returning !== undefined && queryAst.returning.length > 0) {
|
|
332
347
|
// Create a schema for the returned columns
|
|
333
348
|
return queryAst.tableDef.rowSchema.pipe(Schema.pick(...queryAst.returning), Schema.Array)
|
|
334
349
|
}
|
|
@@ -344,8 +359,7 @@ export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<
|
|
|
344
359
|
Schema.headOrElse(),
|
|
345
360
|
)
|
|
346
361
|
}
|
|
347
|
-
default:
|
|
348
|
-
casesHandled(queryAst)
|
|
349
|
-
}
|
|
362
|
+
default:
|
|
363
|
+
return casesHandled(queryAst)
|
|
350
364
|
}
|
|
351
365
|
}
|
|
@@ -13,7 +13,7 @@ export const getDefaultValuesEncoded = <TTableDef extends TableDefBase>(
|
|
|
13
13
|
ReadonlyRecord.filter((col, key) => {
|
|
14
14
|
if (fallbackValues?.[key] !== undefined) return true
|
|
15
15
|
if (key === 'id') return false
|
|
16
|
-
return col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value)
|
|
16
|
+
return col!.default._tag === 'None' || !SqliteDsl.isSqlDefaultValue(col!.default.value)
|
|
17
17
|
}),
|
|
18
18
|
ReadonlyRecord.map((column, columnName) => {
|
|
19
19
|
if (fallbackValues?.[columnName] !== undefined) return fallbackValues[columnName]
|
|
@@ -39,7 +39,7 @@ export const getDefaultValuesDecoded = <TTableDef extends TableDefBase>(
|
|
|
39
39
|
ReadonlyRecord.filter((col, key) => {
|
|
40
40
|
if (fallbackValues?.[key] !== undefined) return true
|
|
41
41
|
if (key === 'id') return false
|
|
42
|
-
return col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value)
|
|
42
|
+
return col!.default._tag === 'None' || !SqliteDsl.isSqlDefaultValue(col!.default.value)
|
|
43
43
|
}),
|
|
44
44
|
ReadonlyRecord.map((column, columnName) => {
|
|
45
45
|
if (fallbackValues?.[columnName] !== undefined) return fallbackValues[columnName]
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { objectToString } from '@livestore/utils'
|
|
2
|
+
|
|
1
3
|
import { Schema } from '@livestore/utils/effect'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import { describe, expect, expectTypeOf, it } from 'vitest'
|
|
5
|
+
|
|
3
6
|
import { State } from '../../mod.ts'
|
|
4
7
|
|
|
5
8
|
describe('table function overloads', () => {
|
|
@@ -79,11 +82,11 @@ describe('table function overloads', () => {
|
|
|
79
82
|
expect(todosTable.sqliteDef.columns).toHaveProperty('optionalComplex')
|
|
80
83
|
|
|
81
84
|
expect(todosTable.sqliteDef.columns.optionalBoolean.nullable).toBe(true)
|
|
82
|
-
expect(todosTable.sqliteDef.columns.optionalBoolean.schema
|
|
85
|
+
expect(objectToString(todosTable.sqliteDef.columns.optionalBoolean.schema)).toBe('(number <-> boolean) | null')
|
|
83
86
|
expect((todosTable.rowSchema as any).fields.optionalBoolean.toString()).toBe('(number <-> boolean) | null')
|
|
84
87
|
|
|
85
88
|
expect(todosTable.sqliteDef.columns.optionalComplex.nullable).toBe(true)
|
|
86
|
-
expect(todosTable.sqliteDef.columns.optionalComplex.schema
|
|
89
|
+
expect(objectToString(todosTable.sqliteDef.columns.optionalComplex.schema)).toBe(
|
|
87
90
|
'(parseJson <-> { readonly color: string } | undefined) | null',
|
|
88
91
|
)
|
|
89
92
|
expect((todosTable.rowSchema as any).fields.optionalComplex.toString()).toBe(
|
|
@@ -91,6 +94,22 @@ describe('table function overloads', () => {
|
|
|
91
94
|
)
|
|
92
95
|
})
|
|
93
96
|
|
|
97
|
+
it('should allow explicit first two generic arguments without options generic', () => {
|
|
98
|
+
const columns = {
|
|
99
|
+
id: State.SQLite.text({ primaryKey: true }),
|
|
100
|
+
text: State.SQLite.text({ default: '' }),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const todosTable = State.SQLite.table<'todos', typeof columns>({
|
|
104
|
+
name: 'todos',
|
|
105
|
+
columns,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
expect(todosTable.sqliteDef.name).toBe('todos')
|
|
109
|
+
expect(todosTable.sqliteDef.columns).toHaveProperty('id')
|
|
110
|
+
expect(todosTable.sqliteDef.columns).toHaveProperty('text')
|
|
111
|
+
})
|
|
112
|
+
|
|
94
113
|
it('should work with schema parameter', () => {
|
|
95
114
|
const TodoSchema = Schema.Struct({
|
|
96
115
|
id: Schema.String,
|
|
@@ -301,6 +320,50 @@ describe('table function overloads', () => {
|
|
|
301
320
|
expect(userTable.sqliteDef.columns.metadata.nullable).toBe(true)
|
|
302
321
|
})
|
|
303
322
|
|
|
323
|
+
it('should allow omitting nullable fields in insert()', () => {
|
|
324
|
+
const UserSchema = Schema.Struct({
|
|
325
|
+
id: Schema.String.pipe(State.SQLite.withPrimaryKey),
|
|
326
|
+
undefined: Schema.Undefined,
|
|
327
|
+
null: Schema.Null,
|
|
328
|
+
undefinedOrString: Schema.UndefinedOr(Schema.String),
|
|
329
|
+
nullOrString: Schema.NullOr(Schema.String),
|
|
330
|
+
optionalString: Schema.optional(Schema.String),
|
|
331
|
+
optionalNullOrString: Schema.optional(Schema.NullOr(Schema.String)),
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
const usersTable = State.SQLite.table({
|
|
335
|
+
name: 'users',
|
|
336
|
+
schema: UserSchema,
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
// Non-nullable fields (id) are required — omitting id should be rejected
|
|
340
|
+
expectTypeOf<{ undefined: undefined }>().not.toExtend<Parameters<typeof usersTable.insert>[0]>()
|
|
341
|
+
|
|
342
|
+
// Nullable fields (NullOr, optional+NullOr) are omittable — SQL defaults to NULL
|
|
343
|
+
expectTypeOf(usersTable.insert)
|
|
344
|
+
.toBeCallableWith({ id: '1' })
|
|
345
|
+
.toBeCallableWith({ id: '1', undefined: undefined })
|
|
346
|
+
.toBeCallableWith({ id: '1', undefined: undefined, null: null })
|
|
347
|
+
.toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: undefined })
|
|
348
|
+
.toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string' })
|
|
349
|
+
.toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: null })
|
|
350
|
+
.toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string' })
|
|
351
|
+
.toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string' })
|
|
352
|
+
.toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string', optionalNullOrString: null })
|
|
353
|
+
.toBeCallableWith({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string', optionalNullOrString: 'string' })
|
|
354
|
+
|
|
355
|
+
expect(() => usersTable.insert({ id: '1' }).asSql()).not.toThrow()
|
|
356
|
+
expect(() => usersTable.insert({ id: '1', undefined: undefined }).asSql()).not.toThrow()
|
|
357
|
+
expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null }).asSql()).not.toThrow()
|
|
358
|
+
expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: undefined }).asSql()).not.toThrow()
|
|
359
|
+
expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string' }).asSql()).not.toThrow()
|
|
360
|
+
expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: null }).asSql()).not.toThrow()
|
|
361
|
+
expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string' }).asSql()).not.toThrow()
|
|
362
|
+
expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string' }).asSql()).not.toThrow()
|
|
363
|
+
expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string', optionalNullOrString: null }).asSql()).not.toThrow()
|
|
364
|
+
expect(() => usersTable.insert({ id: '1', undefined: undefined, null: null, undefinedOrString: 'string', nullOrString: 'string', optionalString: 'string', optionalNullOrString: 'string' }).asSql()).not.toThrow()
|
|
365
|
+
})
|
|
366
|
+
|
|
304
367
|
it('supports discriminated unions with parsed JSON payloads', () => {
|
|
305
368
|
const CircleDataSchema = Schema.Struct({
|
|
306
369
|
radius: Schema.Number,
|
|
@@ -327,7 +390,7 @@ describe('table function overloads', () => {
|
|
|
327
390
|
|
|
328
391
|
expect(shapes.sqliteDef.columns.kind.columnType).toBe('text')
|
|
329
392
|
|
|
330
|
-
const kindSchema = shapes.sqliteDef.columns.kind.schema
|
|
393
|
+
const kindSchema = objectToString(shapes.sqliteDef.columns.kind.schema)
|
|
331
394
|
expect(kindSchema).toContain('"circle" | "square"')
|
|
332
395
|
|
|
333
396
|
expect(() =>
|
|
@@ -168,10 +168,7 @@ export function table<
|
|
|
168
168
|
name: TName
|
|
169
169
|
schema: TSchema
|
|
170
170
|
} & Partial<TOptionsInput>,
|
|
171
|
-
): TableDef<
|
|
172
|
-
SqliteTableDefForSchemaInput<TName, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>,
|
|
173
|
-
TableOptions
|
|
174
|
-
>
|
|
171
|
+
): TableDef<SqliteTableDefForSchemaInput<TName, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>>
|
|
175
172
|
|
|
176
173
|
// Overload 3: With schema and no name (uses schema annotations)
|
|
177
174
|
export function table<
|
|
@@ -181,10 +178,7 @@ export function table<
|
|
|
181
178
|
args: {
|
|
182
179
|
schema: TSchema
|
|
183
180
|
} & Partial<TOptionsInput>,
|
|
184
|
-
): TableDef<
|
|
185
|
-
SqliteTableDefForSchemaInput<string, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>,
|
|
186
|
-
TableOptions
|
|
187
|
-
>
|
|
181
|
+
): TableDef<SqliteTableDefForSchemaInput<string, Schema.Schema.Type<TSchema>, Schema.Schema.Encoded<TSchema>, TSchema>>
|
|
188
182
|
|
|
189
183
|
// Implementation
|
|
190
184
|
export function table<
|
|
@@ -216,9 +210,7 @@ export function table<
|
|
|
216
210
|
if ('columns' in args) {
|
|
217
211
|
tableName = args.name
|
|
218
212
|
const columnOrColumns = args.columns
|
|
219
|
-
columns = (
|
|
220
|
-
SqliteDsl.isColumnDefinition(columnOrColumns) ? { value: columnOrColumns } : columnOrColumns
|
|
221
|
-
) as SqliteDsl.Columns
|
|
213
|
+
columns = SqliteDsl.isColumnDefinition(columnOrColumns) === true ? { value: columnOrColumns } : columnOrColumns
|
|
222
214
|
additionalIndexes = []
|
|
223
215
|
} else if ('schema' in args) {
|
|
224
216
|
const result = schemaFieldsToColumns(Schema.getResolvedPropertySignatures(args.schema))
|
|
@@ -377,15 +369,16 @@ export type ToColumns<TColumns extends SqliteDsl.Columns | SqliteDsl.ColumnDefin
|
|
|
377
369
|
: never
|
|
378
370
|
|
|
379
371
|
export declare namespace SchemaToColumns {
|
|
372
|
+
/** Checks if `null` or `undefined` is assignable to `T`, matching the runtime nullable detection. */
|
|
373
|
+
type IsNullable<T> = null extends T ? true : undefined extends T ? true : false
|
|
374
|
+
|
|
380
375
|
// Type helper to create column definition with proper schema
|
|
381
|
-
export type ColumnDefForType<TEncoded, TType> = SqliteDsl.ColumnDefinition<TEncoded, TType
|
|
376
|
+
export type ColumnDefForType<TEncoded, TType> = SqliteDsl.ColumnDefinition<TEncoded, TType, IsNullable<TEncoded>>
|
|
382
377
|
|
|
383
|
-
// Create columns type from schema Type and Encoded
|
|
384
378
|
export type FromTypes<TType, TEncoded> =
|
|
385
379
|
TEncoded extends Record<string, any>
|
|
386
380
|
? {
|
|
387
|
-
[K in keyof TEncoded]-?: ColumnDefForType<
|
|
388
|
-
TEncoded[K],
|
|
381
|
+
[K in keyof TEncoded]-?: ColumnDefForType<TEncoded[K],
|
|
389
382
|
TType extends Record<string, any> ? (K extends keyof TType ? TType[K] : TEncoded[K]) : TEncoded[K]
|
|
390
383
|
>
|
|
391
384
|
}
|
|
@@ -17,7 +17,7 @@ export namespace UnknownEvents {
|
|
|
17
17
|
export type Callback = (
|
|
18
18
|
context: UnknownEventContext,
|
|
19
19
|
error: UnknownEventError,
|
|
20
|
-
) => Effect.SyncOrPromiseOrEffect<void, unknown
|
|
20
|
+
) => Effect.SyncOrPromiseOrEffect<void, unknown>
|
|
21
21
|
|
|
22
22
|
export type HandlingConfig =
|
|
23
23
|
| { readonly strategy: 'warn' }
|
|
@@ -60,7 +60,7 @@ const handleUnknownEvent = ({
|
|
|
60
60
|
|
|
61
61
|
switch (config.strategy) {
|
|
62
62
|
case 'fail': {
|
|
63
|
-
return yield*
|
|
63
|
+
return yield* error
|
|
64
64
|
}
|
|
65
65
|
case 'warn': {
|
|
66
66
|
yield* Effect.logWarning('@livestore/common:schema:unknown-event', context)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Effect, Option, Schema } from '@livestore/utils/effect'
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { Effect, Option, Schema } from '@livestore/utils/effect'
|
|
4
|
+
|
|
3
5
|
import { SqliteAst } from '../../schema/state/sqlite/db-schema/mod.ts'
|
|
4
6
|
import type { PreparedStatement, SqliteDb } from '../../sqlite-types.ts'
|
|
5
7
|
import type { PreparedBindValues } from '../../util.ts'
|
|
@@ -13,7 +13,7 @@ export const dbExecute = (db: SqliteDb, queryStr: string, bindValues?: ParamsObj
|
|
|
13
13
|
// cachedStmts.set(queryStr, stmt)
|
|
14
14
|
// }
|
|
15
15
|
|
|
16
|
-
const preparedBindValues = bindValues ? prepareBindValues(bindValues, queryStr) : undefined
|
|
16
|
+
const preparedBindValues = bindValues !== undefined ? prepareBindValues(bindValues, queryStr) : undefined
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
19
|
stmt.execute(preparedBindValues)
|
|
@@ -34,7 +34,7 @@ export const dbSelect = <T>(db: SqliteDb, queryStr: string, bindValues?: ParamsO
|
|
|
34
34
|
// cachedStmts.set(queryStr, stmt)
|
|
35
35
|
// }
|
|
36
36
|
|
|
37
|
-
const res = stmt.select<T>(bindValues ? prepareBindValues(bindValues, queryStr) : undefined)
|
|
37
|
+
const res = stmt.select<T>(bindValues !== undefined ? prepareBindValues(bindValues, queryStr) : undefined)
|
|
38
38
|
stmt.finalize()
|
|
39
39
|
return res
|
|
40
40
|
}
|
|
@@ -200,7 +200,7 @@ export const migrateTable = ({
|
|
|
200
200
|
)
|
|
201
201
|
|
|
202
202
|
const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
|
|
203
|
-
const uniqueStr = index.unique ? 'UNIQUE' : ''
|
|
203
|
+
const uniqueStr = index.unique === true ? 'UNIQUE' : ''
|
|
204
204
|
return sql`create ${uniqueStr} index if not exists "${index.name}" on "${tableName}" (${index.columns
|
|
205
205
|
.map((col) => `"${col}"`)
|
|
206
206
|
.join(', ')})`
|