@livestore/common 0.4.0-dev.21 → 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 +16 -9
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/ClientSessionLeaderThreadProxy.js.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 +26 -3
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +27 -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-client-session.d.ts +42 -22
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.js +12 -1
- package/dist/devtools/devtools-messages-client-session.js.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +12 -6
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +8 -3
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +45 -25
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +12 -1
- package/dist/devtools/devtools-messages-leader.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 +283 -253
- 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 +34 -14
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +12 -5
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +12 -11
- 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.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +7 -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 +16 -2
- package/dist/schema/EventDef/define.d.ts.map +1 -1
- package/dist/schema/EventDef/define.js +5 -4
- package/dist/schema/EventDef/define.js.map +1 -1
- package/dist/schema/EventDef/deprecated.d.ts +99 -0
- package/dist/schema/EventDef/deprecated.d.ts.map +1 -0
- package/dist/schema/EventDef/deprecated.js +144 -0
- package/dist/schema/EventDef/deprecated.js.map +1 -0
- package/dist/schema/EventDef/deprecated.test.d.ts +2 -0
- package/dist/schema/EventDef/deprecated.test.d.ts.map +1 -0
- package/dist/schema/EventDef/deprecated.test.js +95 -0
- package/dist/schema/EventDef/deprecated.test.js.map +1 -0
- package/dist/schema/EventDef/event-def.d.ts +4 -0
- package/dist/schema/EventDef/event-def.d.ts.map +1 -1
- package/dist/schema/EventDef/mod.d.ts +1 -0
- package/dist/schema/EventDef/mod.d.ts.map +1 -1
- package/dist/schema/EventDef/mod.js +1 -0
- package/dist/schema/EventDef/mod.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 +6 -6
- 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 +1 -0
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +34 -13
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +121 -2
- 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 +37 -13
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/astToSql.js +77 -7
- 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 +112 -3
- 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 +93 -107
- 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 +7 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +8 -4
- package/dist/version.js.map +1 -1
- package/package.json +66 -12
- package/src/ClientSessionLeaderThreadProxy.ts +16 -9
- package/src/WorkerTransportError.ts +12 -0
- package/src/adapter-types.ts +39 -3
- package/src/bounded-collections.ts +6 -5
- package/src/debug-info.ts +4 -4
- package/src/devtools/devtools-messages-client-session.ts +12 -0
- package/src/devtools/devtools-messages-common.ts +8 -4
- package/src/devtools/devtools-messages-leader.ts +12 -0
- 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 +417 -347
- 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 +107 -66
- package/src/leader-thread/make-leader-thread-layer.test.ts +1 -1
- package/src/leader-thread/make-leader-thread-layer.ts +41 -31
- package/src/leader-thread/materialize-event.ts +8 -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 +22 -6
- package/src/schema/EventDef/deprecated.test.ts +129 -0
- package/src/schema/EventDef/deprecated.ts +175 -0
- package/src/schema/EventDef/event-def.ts +5 -0
- package/src/schema/EventDef/mod.ts +1 -0
- 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 +142 -3
- package/src/schema/state/sqlite/client-document-def.ts +37 -14
- 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 +37 -8
- package/src/schema/state/sqlite/query-builder/astToSql.ts +87 -7
- package/src/schema/state/sqlite/query-builder/impl.test.ts +145 -3
- 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 +142 -133
- 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 +8 -4
|
@@ -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,
|
|
@@ -128,15 +128,25 @@ export type QueryBuilder<
|
|
|
128
128
|
|
|
129
129
|
export namespace QueryBuilder {
|
|
130
130
|
export type Any = QueryBuilder<any, any, any>
|
|
131
|
-
export type WhereOps = WhereOps.Equality | WhereOps.Order | WhereOps.Like | WhereOps.In
|
|
131
|
+
export type WhereOps = WhereOps.Equality | WhereOps.Order | WhereOps.Like | WhereOps.In | WhereOps.JsonArray
|
|
132
132
|
|
|
133
133
|
export namespace WhereOps {
|
|
134
134
|
export type Equality = '=' | '!='
|
|
135
135
|
export type Order = '<' | '>' | '<=' | '>='
|
|
136
136
|
export type Like = 'LIKE' | 'NOT LIKE' | 'ILIKE' | 'NOT ILIKE'
|
|
137
137
|
export type In = 'IN' | 'NOT IN'
|
|
138
|
+
/**
|
|
139
|
+
* Operators for checking if a JSON array column contains a value.
|
|
140
|
+
*
|
|
141
|
+
* ⚠️ **Performance note**: These operators use SQLite's `json_each()` table-valued function
|
|
142
|
+
* which **cannot be indexed** and requires a full table scan. For large tables with frequent
|
|
143
|
+
* lookups, consider denormalizing the data into a separate indexed table.
|
|
144
|
+
*
|
|
145
|
+
* @see https://sqlite.org/json1.html#jeach
|
|
146
|
+
*/
|
|
147
|
+
export type JsonArray = 'JSON_CONTAINS' | 'JSON_NOT_CONTAINS'
|
|
138
148
|
|
|
139
|
-
export type SingleValue = Equality | Order | Like
|
|
149
|
+
export type SingleValue = Equality | Order | Like | JsonArray
|
|
140
150
|
export type MultiValue = In
|
|
141
151
|
}
|
|
142
152
|
|
|
@@ -155,14 +165,26 @@ export namespace QueryBuilder {
|
|
|
155
165
|
| 'returning'
|
|
156
166
|
| 'onConflict'
|
|
157
167
|
|
|
168
|
+
/** Extracts the element type from an array type, or returns never if not an array */
|
|
169
|
+
type ArrayElement<T> = T extends ReadonlyArray<infer E> ? E : never
|
|
170
|
+
|
|
158
171
|
export type WhereParams<TTableDef extends TableDefBase> = Partial<{
|
|
159
172
|
[K in keyof TTableDef['sqliteDef']['columns']]:
|
|
160
173
|
| TTableDef['sqliteDef']['columns'][K]['schema']['Type']
|
|
161
|
-
| {
|
|
174
|
+
| {
|
|
175
|
+
op: Exclude<QueryBuilder.WhereOps.SingleValue, QueryBuilder.WhereOps.JsonArray>
|
|
176
|
+
value: TTableDef['sqliteDef']['columns'][K]['schema']['Type']
|
|
177
|
+
}
|
|
162
178
|
| {
|
|
163
179
|
op: QueryBuilder.WhereOps.MultiValue
|
|
164
180
|
value: ReadonlyArray<TTableDef['sqliteDef']['columns'][K]['schema']['Type']>
|
|
165
181
|
}
|
|
182
|
+
| (ArrayElement<TTableDef['sqliteDef']['columns'][K]['schema']['Type']> extends never
|
|
183
|
+
? never
|
|
184
|
+
: {
|
|
185
|
+
op: QueryBuilder.WhereOps.JsonArray
|
|
186
|
+
value: ArrayElement<TTableDef['sqliteDef']['columns'][K]['schema']['Type']>
|
|
187
|
+
})
|
|
166
188
|
| undefined
|
|
167
189
|
}>
|
|
168
190
|
|
|
@@ -208,9 +230,8 @@ export namespace QueryBuilder {
|
|
|
208
230
|
/** Select multiple columns */
|
|
209
231
|
<TColumns extends keyof TTableDef['sqliteDef']['columns'] & string>(
|
|
210
232
|
...columns: TColumns[]
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
): QueryBuilder<
|
|
233
|
+
)// params: QueryBuilderSelectParams, // TODO also support arbitrary SQL selects
|
|
234
|
+
: QueryBuilder<
|
|
214
235
|
ReadonlyArray<{
|
|
215
236
|
readonly [K in TColumns]: TTableDef['sqliteDef']['columns'][K]['schema']['Type']
|
|
216
237
|
}>,
|
|
@@ -336,12 +357,20 @@ export namespace QueryBuilder {
|
|
|
336
357
|
>
|
|
337
358
|
|
|
338
359
|
/**
|
|
339
|
-
* 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
|
|
340
368
|
*
|
|
341
|
-
* Example:
|
|
342
369
|
* ```ts
|
|
343
370
|
* db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' })
|
|
344
371
|
* ```
|
|
372
|
+
*
|
|
373
|
+
* @param values - The row values to insert.
|
|
345
374
|
*/
|
|
346
375
|
readonly insert: (
|
|
347
376
|
values: TTableDef['insertSchema']['Type'],
|
|
@@ -1,11 +1,74 @@
|
|
|
1
1
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
2
|
-
import { Schema } from '@livestore/utils/effect'
|
|
2
|
+
import { Schema, SchemaAST } from '@livestore/utils/effect'
|
|
3
3
|
|
|
4
4
|
import { SessionIdSymbol } from '../../../../adapter-types.ts'
|
|
5
5
|
import type { SqlValue } from '../../../../util.ts'
|
|
6
6
|
import type { State } from '../../../mod.ts'
|
|
7
7
|
import type { QueryBuilderAst } from './api.ts'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Extracts array element schema from a JSON array transformation AST.
|
|
11
|
+
* Returns the element schema, or undefined if not a JSON array transformation.
|
|
12
|
+
*/
|
|
13
|
+
const extractArrayElementFromTransformation = (ast: SchemaAST.AST): Schema.Schema.Any | undefined => {
|
|
14
|
+
if (SchemaAST.isTransformation(ast) === false) return undefined
|
|
15
|
+
|
|
16
|
+
const toAst = ast.to
|
|
17
|
+
// Check if the "to" side is a TupleType (Effect's internal representation of Array)
|
|
18
|
+
if (SchemaAST.isTupleType(toAst) === false) return undefined
|
|
19
|
+
|
|
20
|
+
// For Schema.Array, rest contains { type: AST } elements - get the first one's type
|
|
21
|
+
const restElement = toAst.rest[0]
|
|
22
|
+
if (restElement === undefined) return undefined
|
|
23
|
+
|
|
24
|
+
return Schema.make(restElement.type)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* For JSON array columns, extracts the element schema from Schema.parseJson(Schema.Array(ElementSchema)).
|
|
29
|
+
* Also handles nullable JSON arrays (Schema.NullOr(Schema.parseJson(Schema.Array(...)))).
|
|
30
|
+
* Returns the element schema, or undefined if the column is not a JSON array.
|
|
31
|
+
*/
|
|
32
|
+
const getJsonArrayElementSchema = (colSchema: Schema.Schema.Any): Schema.Schema.Any | undefined => {
|
|
33
|
+
const ast = colSchema.ast
|
|
34
|
+
|
|
35
|
+
// Case 1: Direct transformation (non-nullable JSON array)
|
|
36
|
+
// Schema.parseJson(Schema.Array(ElementSchema)) creates a Transformation AST
|
|
37
|
+
if (SchemaAST.isTransformation(ast) === true) {
|
|
38
|
+
return extractArrayElementFromTransformation(ast)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Case 2: Nullable JSON array - Schema.NullOr wraps the parseJson in a Union
|
|
42
|
+
// Structure: Union([Transformation (JSON array), Literal (null)])
|
|
43
|
+
if (SchemaAST.isUnion(ast) === true) {
|
|
44
|
+
for (const member of ast.types) {
|
|
45
|
+
const result = extractArrayElementFromTransformation(member)
|
|
46
|
+
if (result !== undefined) return result
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Encodes a JSON array element to the representation returned by SQLite's json_each().
|
|
55
|
+
* Objects/arrays are stringified so they match json_each's TEXT representation.
|
|
56
|
+
*/
|
|
57
|
+
const encodeJsonArrayElementValue = (elementSchema: Schema.Schema.Any, value: unknown): SqlValue => {
|
|
58
|
+
const encoded = Schema.encodeSync(elementSchema as Schema.Schema<unknown, SqlValue>)(value)
|
|
59
|
+
|
|
60
|
+
if (encoded === null) return null
|
|
61
|
+
if (typeof encoded === 'object') {
|
|
62
|
+
// Objects and arrays need to be JSON-stringified to match json_each() output
|
|
63
|
+
return JSON.stringify(encoded)
|
|
64
|
+
}
|
|
65
|
+
if (typeof encoded === 'boolean') {
|
|
66
|
+
return encoded === true ? 1 : 0
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return encoded
|
|
70
|
+
}
|
|
71
|
+
|
|
9
72
|
// Helper functions for SQL generation
|
|
10
73
|
const quoteIdentifier = (identifier: string): string => `"${identifier.replace(/"/g, '""')}"`
|
|
11
74
|
|
|
@@ -35,12 +98,29 @@ const formatWhereClause = (
|
|
|
35
98
|
throw new Error(`Column ${col} not found`)
|
|
36
99
|
}
|
|
37
100
|
|
|
101
|
+
// Handle JSON array containment operators
|
|
102
|
+
if (op === 'JSON_CONTAINS' || op === 'JSON_NOT_CONTAINS') {
|
|
103
|
+
const elementSchema = getJsonArrayElementSchema(colDef.schema)
|
|
104
|
+
if (elementSchema === undefined) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`${op} operator can only be used on JSON array columns, but column "${col}" is not a JSON array`,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const existsOp = op === 'JSON_CONTAINS' ? 'EXISTS' : 'NOT EXISTS'
|
|
111
|
+
// Encode the element value using the element schema
|
|
112
|
+
// Objects are JSON-stringified to match json_each() output
|
|
113
|
+
const encodedValue = encodeJsonArrayElementValue(elementSchema, value)
|
|
114
|
+
bindValues.push(encodedValue)
|
|
115
|
+
return `${existsOp} (SELECT 1 FROM json_each(${quotedCol}) WHERE value = ?)`
|
|
116
|
+
}
|
|
117
|
+
|
|
38
118
|
// Handle array values for IN/NOT IN operators
|
|
39
119
|
const isArray = op === 'IN' || op === 'NOT IN'
|
|
40
120
|
|
|
41
|
-
if (isArray) {
|
|
121
|
+
if (isArray === true) {
|
|
42
122
|
// Verify value is an array
|
|
43
|
-
if (
|
|
123
|
+
if (Array.isArray(value) === false) {
|
|
44
124
|
return shouldNeverHappen(`Expected array value for ${op} operator but got`, value)
|
|
45
125
|
}
|
|
46
126
|
|
|
@@ -65,7 +145,7 @@ const formatWhereClause = (
|
|
|
65
145
|
}
|
|
66
146
|
|
|
67
147
|
const formatReturningClause = (returning?: string[]): string => {
|
|
68
|
-
if (
|
|
148
|
+
if (returning == null || returning.length === 0) return ''
|
|
69
149
|
return ` RETURNING ${returning.map(quoteIdentifier).join(', ')}`
|
|
70
150
|
}
|
|
71
151
|
|
|
@@ -89,7 +169,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
|
|
|
89
169
|
let conflictClause = '' // Store the ON CONFLICT clause separately
|
|
90
170
|
|
|
91
171
|
// Handle ON CONFLICT clause
|
|
92
|
-
if (ast.onConflict) {
|
|
172
|
+
if (ast.onConflict !== undefined) {
|
|
93
173
|
// Handle REPLACE specifically as it changes the INSERT verb
|
|
94
174
|
if (ast.onConflict.action._tag === 'replace') {
|
|
95
175
|
insertVerb = 'INSERT OR REPLACE'
|
|
@@ -169,7 +249,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
|
|
|
169
249
|
.join(', ')}`
|
|
170
250
|
|
|
171
251
|
const whereClause = formatWhereClause(ast.where, ast.tableDef, bindValues)
|
|
172
|
-
if (whereClause) query += ` ${whereClause}`
|
|
252
|
+
if (whereClause !== undefined) query += ` ${whereClause}`
|
|
173
253
|
|
|
174
254
|
query += formatReturningClause(ast.returning)
|
|
175
255
|
return { query, bindValues, usedTables }
|
|
@@ -180,7 +260,7 @@ export const astToSql = (ast: QueryBuilderAst): { query: string; bindValues: Sql
|
|
|
180
260
|
let query = `DELETE FROM '${ast.tableDef.sqliteDef.name}'`
|
|
181
261
|
|
|
182
262
|
const whereClause = formatWhereClause(ast.where, ast.tableDef, bindValues)
|
|
183
|
-
if (whereClause) query += ` ${whereClause}`
|
|
263
|
+
if (whereClause !== undefined) query += ` ${whereClause}`
|
|
184
264
|
|
|
185
265
|
query += formatReturningClause(ast.returning)
|
|
186
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'
|
|
@@ -81,12 +83,27 @@ const selections = State.SQLite.table({
|
|
|
81
83
|
},
|
|
82
84
|
})
|
|
83
85
|
|
|
84
|
-
const
|
|
86
|
+
const Source = Schema.Literal('google', 'linkedin', 'facebook')
|
|
87
|
+
const ProfileAttribute = Schema.Struct({ key: Schema.String, value: Schema.String })
|
|
88
|
+
|
|
89
|
+
const personProfiles = State.SQLite.table({
|
|
90
|
+
name: 'person_profiles',
|
|
91
|
+
columns: {
|
|
92
|
+
personId: State.SQLite.text({ primaryKey: true }),
|
|
93
|
+
sources: State.SQLite.json({ schema: Schema.Array(Source), default: [] }),
|
|
94
|
+
tags: State.SQLite.json({ schema: Schema.Array(Schema.String), default: [] }),
|
|
95
|
+
attributes: State.SQLite.json({ schema: Schema.Array(ProfileAttribute), default: [] }),
|
|
96
|
+
/** Nullable JSON array column for testing JSON_CONTAINS on nullable columns */
|
|
97
|
+
optionalTags: State.SQLite.json({ schema: Schema.Array(Schema.String), nullable: true }),
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const db = { todos, todosWithIntId, comments, issue, selections, UiState, UiStateWithDefaultId, personProfiles }
|
|
85
102
|
|
|
86
103
|
const dump = (qb: QueryBuilder<any, any, any>) => ({
|
|
87
104
|
bindValues: qb.asSql().bindValues,
|
|
88
105
|
query: qb.asSql().query,
|
|
89
|
-
schema: getResultSchema(qb)
|
|
106
|
+
schema: objectToString(getResultSchema(qb)),
|
|
90
107
|
})
|
|
91
108
|
|
|
92
109
|
describe('query builder', () => {
|
|
@@ -364,6 +381,131 @@ describe('query builder', () => {
|
|
|
364
381
|
}
|
|
365
382
|
`)
|
|
366
383
|
})
|
|
384
|
+
|
|
385
|
+
it('should handle JSON_CONTAINS operator for JSON array columns', () => {
|
|
386
|
+
expect(
|
|
387
|
+
dump(db.personProfiles.where({ sources: { op: 'JSON_CONTAINS', value: 'google' } })),
|
|
388
|
+
).toMatchInlineSnapshot(`
|
|
389
|
+
{
|
|
390
|
+
"bindValues": [
|
|
391
|
+
"google",
|
|
392
|
+
],
|
|
393
|
+
"query": "SELECT * FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)",
|
|
394
|
+
"schema": "ReadonlyArray<person_profiles>",
|
|
395
|
+
}
|
|
396
|
+
`)
|
|
397
|
+
|
|
398
|
+
// With select
|
|
399
|
+
expect(
|
|
400
|
+
dump(db.personProfiles.select('personId').where({ sources: { op: 'JSON_CONTAINS', value: 'linkedin' } })),
|
|
401
|
+
).toMatchInlineSnapshot(`
|
|
402
|
+
{
|
|
403
|
+
"bindValues": [
|
|
404
|
+
"linkedin",
|
|
405
|
+
],
|
|
406
|
+
"query": "SELECT "personId" FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)",
|
|
407
|
+
"schema": "ReadonlyArray<({ readonly personId: string } <-> string)>",
|
|
408
|
+
}
|
|
409
|
+
`)
|
|
410
|
+
|
|
411
|
+
// With plain string array column
|
|
412
|
+
expect(
|
|
413
|
+
dump(db.personProfiles.where({ tags: { op: 'JSON_CONTAINS', value: 'important' } })),
|
|
414
|
+
).toMatchInlineSnapshot(`
|
|
415
|
+
{
|
|
416
|
+
"bindValues": [
|
|
417
|
+
"important",
|
|
418
|
+
],
|
|
419
|
+
"query": "SELECT * FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("tags") WHERE value = ?)",
|
|
420
|
+
"schema": "ReadonlyArray<person_profiles>",
|
|
421
|
+
}
|
|
422
|
+
`)
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
it('should handle JSON_NOT_CONTAINS operator for JSON array columns', () => {
|
|
426
|
+
expect(
|
|
427
|
+
dump(db.personProfiles.where({ sources: { op: 'JSON_NOT_CONTAINS', value: 'google' } })),
|
|
428
|
+
).toMatchInlineSnapshot(`
|
|
429
|
+
{
|
|
430
|
+
"bindValues": [
|
|
431
|
+
"google",
|
|
432
|
+
],
|
|
433
|
+
"query": "SELECT * FROM 'person_profiles' WHERE NOT EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)",
|
|
434
|
+
"schema": "ReadonlyArray<person_profiles>",
|
|
435
|
+
}
|
|
436
|
+
`)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it('should JSON-stringify object elements for JSON_CONTAINS', () => {
|
|
440
|
+
expect(
|
|
441
|
+
dump(
|
|
442
|
+
db.personProfiles.where({
|
|
443
|
+
attributes: { op: 'JSON_CONTAINS', value: { key: 'language', value: 'typescript' } },
|
|
444
|
+
}),
|
|
445
|
+
),
|
|
446
|
+
).toMatchInlineSnapshot(`
|
|
447
|
+
{
|
|
448
|
+
"bindValues": [
|
|
449
|
+
"{"key":"language","value":"typescript"}",
|
|
450
|
+
],
|
|
451
|
+
"query": "SELECT * FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("attributes") WHERE value = ?)",
|
|
452
|
+
"schema": "ReadonlyArray<person_profiles>",
|
|
453
|
+
}
|
|
454
|
+
`)
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('should handle combining JSON_CONTAINS with other WHERE clauses', () => {
|
|
458
|
+
expect(
|
|
459
|
+
dump(
|
|
460
|
+
db.personProfiles
|
|
461
|
+
.where({ sources: { op: 'JSON_CONTAINS', value: 'google' } })
|
|
462
|
+
.where({ sources: { op: 'JSON_NOT_CONTAINS', value: 'facebook' } }),
|
|
463
|
+
),
|
|
464
|
+
).toMatchInlineSnapshot(`
|
|
465
|
+
{
|
|
466
|
+
"bindValues": [
|
|
467
|
+
"google",
|
|
468
|
+
"facebook",
|
|
469
|
+
],
|
|
470
|
+
"query": "SELECT * FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?) AND NOT EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)",
|
|
471
|
+
"schema": "ReadonlyArray<person_profiles>",
|
|
472
|
+
}
|
|
473
|
+
`)
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
it('should handle JSON_CONTAINS on nullable JSON array columns', () => {
|
|
477
|
+
expect(
|
|
478
|
+
dump(db.personProfiles.where({ optionalTags: { op: 'JSON_CONTAINS', value: 'important' } })),
|
|
479
|
+
).toMatchInlineSnapshot(`
|
|
480
|
+
{
|
|
481
|
+
"bindValues": [
|
|
482
|
+
"important",
|
|
483
|
+
],
|
|
484
|
+
"query": "SELECT * FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)",
|
|
485
|
+
"schema": "ReadonlyArray<person_profiles>",
|
|
486
|
+
}
|
|
487
|
+
`)
|
|
488
|
+
|
|
489
|
+
// With JSON_NOT_CONTAINS
|
|
490
|
+
expect(
|
|
491
|
+
dump(db.personProfiles.where({ optionalTags: { op: 'JSON_NOT_CONTAINS', value: 'archived' } })),
|
|
492
|
+
).toMatchInlineSnapshot(`
|
|
493
|
+
{
|
|
494
|
+
"bindValues": [
|
|
495
|
+
"archived",
|
|
496
|
+
],
|
|
497
|
+
"query": "SELECT * FROM 'person_profiles' WHERE NOT EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)",
|
|
498
|
+
"schema": "ReadonlyArray<person_profiles>",
|
|
499
|
+
}
|
|
500
|
+
`)
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
it('should throw error when using JSON_CONTAINS on non-JSON array column', () => {
|
|
504
|
+
expect(() =>
|
|
505
|
+
// Type system prevents this at compile time for non-array columns, but test runtime check
|
|
506
|
+
dump(db.todos.where({ status: { op: 'JSON_CONTAINS', value: 'active' } } as any)),
|
|
507
|
+
).toThrow('JSON_CONTAINS operator can only be used on JSON array columns')
|
|
508
|
+
})
|
|
367
509
|
})
|
|
368
510
|
|
|
369
511
|
// describe('getOrCreate queries', () => {
|
|
@@ -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]
|