@livestore/common 0.4.0 → 0.5.0-dev.0
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 +1 -1
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/MaterializationJournal.d.ts +44 -0
- package/dist/MaterializationJournal.d.ts.map +1 -0
- package/dist/MaterializationJournal.js +112 -0
- package/dist/MaterializationJournal.js.map +1 -0
- package/dist/StateHead.d.ts +26 -0
- package/dist/StateHead.d.ts.map +1 -0
- package/dist/StateHead.js +50 -0
- package/dist/StateHead.js.map +1 -0
- package/dist/WorkerTransportError.d.ts +3 -3
- package/dist/WorkerTransportError.d.ts.map +1 -1
- package/dist/WorkerTransportError.js +3 -3
- package/dist/WorkerTransportError.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +1 -1
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +3 -3
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/adapter-types.d.ts +29 -57
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +20 -37
- package/dist/adapter-types.js.map +1 -1
- package/dist/debug-info.d.ts +56 -56
- package/dist/debug-info.d.ts.map +1 -1
- package/dist/debug-info.js +28 -41
- package/dist/debug-info.js.map +1 -1
- package/dist/defs.d.ts +9 -9
- package/dist/defs.js +2 -2
- package/dist/devtools/devtools-compatibility.test.js +1 -0
- package/dist/devtools/devtools-compatibility.test.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +520 -332
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.js +76 -74
- package/dist/devtools/devtools-messages-client-session.js.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +27 -27
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +5 -5
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +405 -343
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +101 -89
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/devtools/devtools-sessioninfo.d.ts +18 -19
- package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -1
- package/dist/devtools/devtools-sessioninfo.js +8 -5
- package/dist/devtools/devtools-sessioninfo.js.map +1 -1
- package/dist/devtools/mod.d.ts +4 -5
- package/dist/devtools/mod.d.ts.map +1 -1
- package/dist/devtools/mod.js +9 -5
- package/dist/devtools/mod.js.map +1 -1
- package/dist/errors.d.ts +45 -69
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +18 -16
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +66 -18
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +519 -461
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/RejectedPushError.d.ts +67 -36
- package/dist/leader-thread/RejectedPushError.d.ts.map +1 -1
- package/dist/leader-thread/RejectedPushError.js +24 -5
- package/dist/leader-thread/RejectedPushError.js.map +1 -1
- package/dist/leader-thread/connection.d.ts +1 -0
- package/dist/leader-thread/connection.d.ts.map +1 -1
- package/dist/leader-thread/connection.js +9 -8
- package/dist/leader-thread/connection.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts +4 -4
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +6 -6
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +33 -17
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +9 -5
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +25 -35
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js +7 -7
- package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -1
- package/dist/leader-thread/materialize-event.d.ts +2 -1
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +4 -0
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +18 -39
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -6
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -3
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/stream-events.d.ts +4 -7
- package/dist/leader-thread/stream-events.d.ts.map +1 -1
- package/dist/leader-thread/stream-events.js +13 -16
- package/dist/leader-thread/stream-events.js.map +1 -1
- package/dist/leader-thread/types.d.ts +32 -69
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js +5 -5
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/logging.d.ts +5 -30
- package/dist/logging.d.ts.map +1 -1
- package/dist/logging.js +1 -32
- package/dist/logging.js.map +1 -1
- package/dist/make-client-session.d.ts +1 -2
- package/dist/make-client-session.d.ts.map +1 -1
- package/dist/make-client-session.js +3 -3
- package/dist/make-client-session.js.map +1 -1
- package/dist/materializer-helper.d.ts +0 -1
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +7 -35
- package/dist/materializer-helper.js.map +1 -1
- package/dist/otel.d.ts +2 -3
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +2 -7
- package/dist/otel.js.map +1 -1
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +28 -26
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef/define.d.ts +4 -4
- package/dist/schema/EventDef/define.d.ts.map +1 -1
- package/dist/schema/EventDef/define.js +5 -5
- package/dist/schema/EventDef/define.js.map +1 -1
- package/dist/schema/EventDef/define.test.d.ts +2 -0
- package/dist/schema/EventDef/define.test.d.ts.map +1 -0
- package/dist/schema/EventDef/define.test.js +23 -0
- package/dist/schema/EventDef/define.test.js.map +1 -0
- package/dist/schema/EventDef/deprecated.d.ts +8 -13
- package/dist/schema/EventDef/deprecated.d.ts.map +1 -1
- package/dist/schema/EventDef/deprecated.js +12 -13
- 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/EventDef/event-def.d.ts +1 -1
- package/dist/schema/EventDef/event-def.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/client.d.ts +12 -12
- package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/client.js +8 -9
- package/dist/schema/EventSequenceNumber/client.js.map +1 -1
- package/dist/schema/EventSequenceNumber/global.d.ts +2 -2
- package/dist/schema/EventSequenceNumber/global.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/global.js +1 -1
- package/dist/schema/EventSequenceNumber/global.js.map +1 -1
- package/dist/schema/EventSequenceNumber.test.js +5 -5
- package/dist/schema/EventSequenceNumber.test.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.d.ts +50 -179
- package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/client.js +32 -29
- package/dist/schema/LiveStoreEvent/client.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.test.js +11 -2
- package/dist/schema/LiveStoreEvent/client.test.js.map +1 -1
- package/dist/schema/LiveStoreEvent/for-event-def.d.ts +7 -7
- package/dist/schema/LiveStoreEvent/for-event-def.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/global.d.ts +6 -6
- package/dist/schema/LiveStoreEvent/global.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/global.js +1 -1
- package/dist/schema/LiveStoreEvent/global.js.map +1 -1
- package/dist/schema/LiveStoreEvent/input.d.ts +5 -5
- package/dist/schema/LiveStoreEvent/input.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/input.js +3 -3
- package/dist/schema/LiveStoreEvent/input.js.map +1 -1
- package/dist/schema/mod.d.ts +1 -0
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +1 -0
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +3 -2
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts +12 -17
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +23 -20
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +29 -8
- package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.d.ts +13 -13
- package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.js +13 -15
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js +19 -43
- package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-def.js +57 -31
- package/dist/schema/state/sqlite/column-def.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.test.js +118 -72
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js +2 -2
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -2
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +13 -4
- 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 +9 -9
- 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 +8 -13
- 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 +27 -16
- 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 +13 -11
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js +5 -3
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts +1 -3
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +1 -1
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/persistence.d.ts +11 -0
- package/dist/schema/state/sqlite/persistence.d.ts.map +1 -0
- package/dist/schema/state/sqlite/persistence.js +10 -0
- package/dist/schema/state/sqlite/persistence.js.map +1 -0
- package/dist/schema/state/sqlite/query-builder/api.d.ts +7 -7
- 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 +27 -23
- package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts +3 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +16 -14
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +362 -596
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts +102 -134
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.js +2 -2
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables/state-tables.d.ts +328 -106
- package/dist/schema/state/sqlite/system-tables/state-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables/state-tables.js +33 -1
- package/dist/schema/state/sqlite/system-tables/state-tables.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +16 -19
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +53 -3
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +153 -36
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/schema/unknown-events.d.ts.map +1 -1
- package/dist/schema/unknown-events.js +2 -1
- package/dist/schema/unknown-events.js.map +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +2 -2
- package/dist/session-id-symbol.d.ts +29 -0
- package/dist/session-id-symbol.d.ts.map +1 -0
- package/dist/session-id-symbol.js +44 -0
- package/dist/session-id-symbol.js.map +1 -0
- package/dist/sql-queries/sql-queries.d.ts.map +1 -1
- package/dist/sql-queries/sql-queries.js +8 -10
- package/dist/sql-queries/sql-queries.js.map +1 -1
- package/dist/sql-queries/types.d.ts +6 -7
- package/dist/sql-queries/types.d.ts.map +1 -1
- package/dist/sql-queries/types.js.map +1 -1
- package/dist/sqlite-db-helper.d.ts +21 -1
- package/dist/sqlite-db-helper.d.ts.map +1 -1
- package/dist/sqlite-db-helper.js +37 -2
- package/dist/sqlite-db-helper.js.map +1 -1
- package/dist/sqlite-types.d.ts +3 -6
- package/dist/sqlite-types.d.ts.map +1 -1
- package/dist/sqlite-types.js +3 -3
- package/dist/sqlite-types.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +38 -16
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +196 -66
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/errors.d.ts +12 -18
- package/dist/sync/errors.d.ts.map +1 -1
- package/dist/sync/errors.js +2 -2
- package/dist/sync/errors.js.map +1 -1
- package/dist/sync/mock-sync-backend.d.ts +2 -3
- package/dist/sync/mock-sync-backend.d.ts.map +1 -1
- package/dist/sync/mock-sync-backend.js +54 -33
- package/dist/sync/mock-sync-backend.js.map +1 -1
- package/dist/sync/mock-sync-backend.test.d.ts +2 -0
- package/dist/sync/mock-sync-backend.test.d.ts.map +1 -0
- package/dist/sync/mock-sync-backend.test.js +49 -0
- package/dist/sync/mock-sync-backend.test.js.map +1 -0
- package/dist/sync/next/history-dag-common.d.ts +4 -0
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +52 -31
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/history-dag.js +2 -1
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +2 -2
- package/dist/sync/next/test/event-fixtures.d.ts +7 -7
- package/dist/sync/next/test/event-fixtures.js +1 -1
- package/dist/sync/next/test/event-fixtures.js.map +1 -1
- package/dist/sync/next/test/history-dag-common.test.d.ts +2 -0
- package/dist/sync/next/test/history-dag-common.test.d.ts.map +1 -0
- package/dist/sync/next/test/history-dag-common.test.js +73 -0
- package/dist/sync/next/test/history-dag-common.test.js.map +1 -0
- package/dist/sync/sync-backend-kv.d.ts.map +1 -1
- package/dist/sync/sync-backend-kv.js +4 -2
- package/dist/sync/sync-backend-kv.js.map +1 -1
- package/dist/sync/sync-backend.d.ts +15 -15
- package/dist/sync/sync-backend.d.ts.map +1 -1
- package/dist/sync/sync-backend.js +9 -5
- package/dist/sync/sync-backend.js.map +1 -1
- package/dist/sync/sync.d.ts +1 -1
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/syncstate.d.ts +97 -173
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +34 -37
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +100 -61
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/transport-chunking.d.ts +15 -16
- package/dist/sync/transport-chunking.d.ts.map +1 -1
- package/dist/sync/transport-chunking.js +23 -32
- package/dist/sync/transport-chunking.js.map +1 -1
- package/dist/util.d.ts +11 -4
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +19 -2
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +19 -37
- package/src/ClientSessionLeaderThreadProxy.ts +1 -1
- package/src/MaterializationJournal.ts +184 -0
- package/src/StateHead.ts +80 -0
- package/src/WorkerTransportError.ts +4 -4
- package/src/__tests__/fixture.ts +3 -3
- package/src/adapter-types.ts +24 -61
- package/src/debug-info.ts +50 -70
- package/src/defs.ts +2 -2
- package/src/devtools/devtools-compatibility.test.ts +1 -0
- package/src/devtools/devtools-messages-client-session.ts +56 -65
- package/src/devtools/devtools-messages-common.ts +12 -11
- package/src/devtools/devtools-messages-leader.ts +71 -69
- package/src/devtools/devtools-sessioninfo.ts +13 -11
- package/src/devtools/mod.ts +4 -5
- package/src/errors.ts +46 -34
- package/src/index.ts +8 -1
- package/src/leader-thread/LeaderSyncProcessor.ts +794 -691
- package/src/leader-thread/RejectedPushError.ts +40 -27
- package/src/leader-thread/connection.ts +12 -8
- package/src/leader-thread/eventlog.ts +7 -7
- package/src/leader-thread/leader-worker-devtools.ts +37 -19
- package/src/leader-thread/make-leader-thread-layer.test.ts +7 -7
- package/src/leader-thread/make-leader-thread-layer.ts +56 -53
- package/src/leader-thread/materialize-event.ts +13 -8
- package/src/leader-thread/recreate-db.ts +24 -59
- package/src/leader-thread/shutdown-channel.ts +3 -14
- package/src/leader-thread/stream-events.ts +104 -101
- package/src/leader-thread/types.ts +24 -62
- package/src/logging.ts +5 -52
- package/src/make-client-session.ts +6 -6
- package/src/materializer-helper.ts +8 -37
- package/src/otel.ts +5 -14
- package/src/rematerialize-from-eventlog.ts +97 -94
- package/src/schema/EventDef/define.test.ts +28 -0
- package/src/schema/EventDef/define.ts +20 -19
- package/src/schema/EventDef/deprecated.test.ts +1 -8
- package/src/schema/EventDef/deprecated.ts +17 -20
- package/src/schema/EventDef/event-def.ts +2 -4
- package/src/schema/EventSequenceNumber/client.ts +8 -9
- package/src/schema/EventSequenceNumber/global.ts +1 -1
- package/src/schema/EventSequenceNumber.test.ts +5 -5
- package/src/schema/LiveStoreEvent/client.test.ts +17 -4
- package/src/schema/LiveStoreEvent/client.ts +36 -35
- package/src/schema/LiveStoreEvent/for-event-def.ts +7 -7
- package/src/schema/LiveStoreEvent/global.ts +1 -1
- package/src/schema/LiveStoreEvent/input.ts +6 -6
- package/src/schema/mod.ts +1 -0
- package/src/schema/schema.ts +3 -2
- package/src/schema/state/sqlite/client-document-def.test.ts +32 -13
- package/src/schema/state/sqlite/client-document-def.ts +95 -80
- package/src/schema/state/sqlite/column-annotations.test.ts +26 -45
- package/src/schema/state/sqlite/column-annotations.ts +26 -30
- package/src/schema/state/sqlite/column-def.test.ts +132 -77
- package/src/schema/state/sqlite/column-def.ts +72 -37
- package/src/schema/state/sqlite/column-spec.test.ts +2 -2
- package/src/schema/state/sqlite/column-spec.ts +3 -2
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +15 -5
- package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +123 -63
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +35 -16
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +25 -31
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +35 -21
- package/src/schema/state/sqlite/mod.ts +2 -4
- package/src/schema/state/sqlite/persistence.ts +11 -0
- package/src/schema/state/sqlite/query-builder/api.ts +9 -9
- package/src/schema/state/sqlite/query-builder/astToSql.ts +31 -26
- package/src/schema/state/sqlite/query-builder/impl.test.ts +433 -669
- package/src/schema/state/sqlite/query-builder/impl.ts +19 -15
- package/src/schema/state/sqlite/schema-helpers.ts +1 -1
- package/src/schema/state/sqlite/system-tables/eventlog-tables.ts +2 -2
- package/src/schema/state/sqlite/system-tables/state-tables.ts +41 -1
- package/src/schema/state/sqlite/table-def.test.ts +204 -66
- package/src/schema/state/sqlite/table-def.ts +96 -45
- package/src/schema/unknown-events.ts +3 -2
- package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +2 -2
- package/src/session-id-symbol.ts +59 -0
- package/src/sql-queries/sql-queries.ts +8 -12
- package/src/sql-queries/types.ts +6 -7
- package/src/sqlite-db-helper.ts +64 -2
- package/src/sqlite-types.ts +5 -8
- package/src/sync/ClientSessionSyncProcessor.ts +303 -119
- package/src/sync/errors.ts +14 -12
- package/src/sync/mock-sync-backend.test.ts +78 -0
- package/src/sync/mock-sync-backend.ts +93 -47
- package/src/sync/next/history-dag-common.ts +60 -34
- package/src/sync/next/history-dag.ts +4 -1
- package/src/sync/next/test/compact-events.calculator.test.ts +2 -2
- package/src/sync/next/test/event-fixtures.ts +1 -1
- package/src/sync/next/test/history-dag-common.test.ts +87 -0
- package/src/sync/sync-backend-kv.ts +4 -2
- package/src/sync/sync-backend.ts +15 -15
- package/src/sync/sync.ts +1 -1
- package/src/sync/syncstate.test.ts +191 -140
- package/src/sync/syncstate.ts +63 -53
- package/src/sync/transport-chunking.ts +34 -39
- package/src/util.ts +18 -9
- package/src/version.ts +1 -2
- package/dist/sql-queries/misc.d.ts +0 -2
- package/dist/sql-queries/misc.d.ts.map +0 -1
- package/dist/sql-queries/misc.js +0 -2
- package/dist/sql-queries/misc.js.map +0 -1
- package/src/sql-queries/misc.ts +0 -2
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { TestSchema } from 'effect/testing'
|
|
1
2
|
import { describe, expect, it } from 'vitest'
|
|
2
3
|
|
|
3
|
-
import { Schema } from '@livestore/utils/effect'
|
|
4
|
-
import { objectToString } from '@livestore/utils'
|
|
4
|
+
import { Schema, SchemaTransformation } from '@livestore/utils/effect'
|
|
5
5
|
|
|
6
6
|
import { State } from '../../../mod.ts'
|
|
7
|
-
import type { QueryBuilder } from './api.ts'
|
|
8
7
|
import { getResultSchema } from './impl.ts'
|
|
9
8
|
|
|
10
9
|
const todos = State.SQLite.table({
|
|
@@ -13,7 +12,7 @@ const todos = State.SQLite.table({
|
|
|
13
12
|
id: State.SQLite.text({ primaryKey: true }),
|
|
14
13
|
text: State.SQLite.text({ default: '', nullable: false }),
|
|
15
14
|
completed: State.SQLite.boolean({ default: false, nullable: false }),
|
|
16
|
-
status: State.SQLite.text({ schema: Schema.
|
|
15
|
+
status: State.SQLite.text({ schema: Schema.Literals(['active', 'completed']) }),
|
|
17
16
|
deletedAt: State.SQLite.datetime({ nullable: true }),
|
|
18
17
|
// TODO consider leaning more into Effect schema
|
|
19
18
|
// other: Schema.Number.pipe(State.SQLite.asInteger),
|
|
@@ -25,7 +24,7 @@ const todosWithIntId = State.SQLite.table({
|
|
|
25
24
|
columns: {
|
|
26
25
|
id: State.SQLite.integer({ primaryKey: true }),
|
|
27
26
|
text: State.SQLite.text({ default: '', nullable: false }),
|
|
28
|
-
status: State.SQLite.text({ schema: Schema.
|
|
27
|
+
status: State.SQLite.text({ schema: Schema.Literals(['active', 'completed']) }),
|
|
29
28
|
},
|
|
30
29
|
})
|
|
31
30
|
|
|
@@ -41,7 +40,7 @@ const comments = State.SQLite.table({
|
|
|
41
40
|
const UiState = State.SQLite.clientDocument({
|
|
42
41
|
name: 'UiState',
|
|
43
42
|
schema: Schema.Struct({
|
|
44
|
-
filter: Schema.
|
|
43
|
+
filter: Schema.Literals(['all', 'active', 'completed']),
|
|
45
44
|
}),
|
|
46
45
|
default: { value: { filter: 'all' } },
|
|
47
46
|
})
|
|
@@ -49,7 +48,7 @@ const UiState = State.SQLite.clientDocument({
|
|
|
49
48
|
const UiStateWithDefaultId = State.SQLite.clientDocument({
|
|
50
49
|
name: 'UiState',
|
|
51
50
|
schema: Schema.Struct({
|
|
52
|
-
filter: Schema.
|
|
51
|
+
filter: Schema.Literals(['all', 'active', 'completed']),
|
|
53
52
|
}),
|
|
54
53
|
default: {
|
|
55
54
|
id: 'static',
|
|
@@ -63,10 +62,10 @@ const issue = State.SQLite.table({
|
|
|
63
62
|
id: State.SQLite.integer({ primaryKey: true }),
|
|
64
63
|
title: State.SQLite.text({ default: '' }),
|
|
65
64
|
creator: State.SQLite.text({ default: '' }),
|
|
66
|
-
priority: State.SQLite.integer({ schema: Schema.
|
|
67
|
-
created: State.SQLite.integer({ schema: Schema.
|
|
68
|
-
deleted: State.SQLite.integer({ nullable: true, schema: Schema.
|
|
69
|
-
modified: State.SQLite.integer({ schema: Schema.
|
|
65
|
+
priority: State.SQLite.integer({ schema: Schema.Literals([0, 1, 2, 3, 4]), default: 0 }),
|
|
66
|
+
created: State.SQLite.integer({ schema: Schema.DateFromMillis }),
|
|
67
|
+
deleted: State.SQLite.integer({ nullable: true, schema: Schema.DateFromMillis }),
|
|
68
|
+
modified: State.SQLite.integer({ schema: Schema.DateFromMillis }),
|
|
70
69
|
kanbanorder: State.SQLite.text({ nullable: false, default: '' }),
|
|
71
70
|
},
|
|
72
71
|
indexes: [
|
|
@@ -83,7 +82,7 @@ const selections = State.SQLite.table({
|
|
|
83
82
|
},
|
|
84
83
|
})
|
|
85
84
|
|
|
86
|
-
const Source = Schema.
|
|
85
|
+
const Source = Schema.Literals(['google', 'linkedin', 'facebook'])
|
|
87
86
|
const ProfileAttribute = Schema.Struct({ key: Schema.String, value: Schema.String })
|
|
88
87
|
|
|
89
88
|
const personProfiles = State.SQLite.table({
|
|
@@ -100,483 +99,352 @@ const personProfiles = State.SQLite.table({
|
|
|
100
99
|
|
|
101
100
|
const db = { todos, todosWithIntId, comments, issue, selections, UiState, UiStateWithDefaultId, personProfiles }
|
|
102
101
|
|
|
103
|
-
const dump = (qb: QueryBuilder<any, any, any>) => ({
|
|
104
|
-
bindValues: qb.asSql().bindValues,
|
|
105
|
-
query: qb.asSql().query,
|
|
106
|
-
schema: objectToString(getResultSchema(qb)),
|
|
107
|
-
})
|
|
108
|
-
|
|
109
102
|
describe('query builder', () => {
|
|
110
103
|
describe('basic queries', () => {
|
|
111
104
|
it('should handle simple SELECT queries', () => {
|
|
112
|
-
expect(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
`)
|
|
105
|
+
expect(db.todos.asSql()).toEqual({
|
|
106
|
+
bindValues: [],
|
|
107
|
+
query: "SELECT * FROM 'todos'",
|
|
108
|
+
usedTables: new Set(['todos']),
|
|
109
|
+
})
|
|
119
110
|
|
|
120
|
-
expect(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
`)
|
|
111
|
+
expect(db.todos.select('id').asSql()).toEqual({
|
|
112
|
+
bindValues: [],
|
|
113
|
+
query: 'SELECT "id" FROM \'todos\'',
|
|
114
|
+
usedTables: new Set(['todos']),
|
|
115
|
+
})
|
|
127
116
|
|
|
128
|
-
expect(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
117
|
+
expect(db.todos.select('id', 'text').asSql()).toEqual({
|
|
118
|
+
bindValues: [],
|
|
119
|
+
query: 'SELECT "id", "text" FROM \'todos\'',
|
|
120
|
+
usedTables: new Set(['todos']),
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('derives result schemas for SELECT queries', async () => {
|
|
125
|
+
const selectedRows = new TestSchema.Asserts(getResultSchema(db.todos.select('id', 'text')))
|
|
126
|
+
await selectedRows.decoding().succeed([{ id: '123', text: 'Buy milk' }])
|
|
127
|
+
|
|
128
|
+
const selectedColumn = new TestSchema.Asserts(getResultSchema(db.todos.select('id')))
|
|
129
|
+
await selectedColumn.decoding().succeed([{ id: '123' }], ['123'])
|
|
135
130
|
})
|
|
136
131
|
|
|
137
132
|
it('should handle .first()', () => {
|
|
138
|
-
expect(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
{
|
|
150
|
-
"bindValues": [
|
|
151
|
-
1,
|
|
152
|
-
],
|
|
153
|
-
"query": "SELECT "id", "text" FROM 'todos' LIMIT ?",
|
|
154
|
-
"schema": "(ReadonlyArray<{ readonly id: string; readonly text: string }> <-> { readonly id: string; readonly text: string })",
|
|
155
|
-
}
|
|
156
|
-
`)
|
|
133
|
+
expect(db.todos.select('id', 'text').first().asSql()).toEqual({
|
|
134
|
+
bindValues: [1],
|
|
135
|
+
query: 'SELECT "id", "text" FROM \'todos\' LIMIT ?',
|
|
136
|
+
usedTables: new Set(['todos']),
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
expect(db.todos.select('id', 'text').first({ behaviour: 'error' }).asSql()).toEqual({
|
|
140
|
+
bindValues: [1],
|
|
141
|
+
query: 'SELECT "id", "text" FROM \'todos\' LIMIT ?',
|
|
142
|
+
usedTables: new Set(['todos']),
|
|
143
|
+
})
|
|
157
144
|
|
|
158
145
|
expect(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
146
|
+
db.todos
|
|
147
|
+
.select('id', 'text')
|
|
148
|
+
.first({ behaviour: 'fallback', fallback: () => undefined })
|
|
149
|
+
.asSql(),
|
|
150
|
+
).toEqual({
|
|
151
|
+
bindValues: [1],
|
|
152
|
+
query: 'SELECT "id", "text" FROM \'todos\' LIMIT ?',
|
|
153
|
+
usedTables: new Set(['todos']),
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('derives result schemas for .first()', async () => {
|
|
158
|
+
const firstOrUndefined = new TestSchema.Asserts(getResultSchema(db.todos.select('id', 'text').first()))
|
|
159
|
+
await firstOrUndefined.decoding().succeed([], undefined)
|
|
160
|
+
await firstOrUndefined.decoding().succeed([{ id: '123', text: 'Buy milk' }], { id: '123', text: 'Buy milk' })
|
|
161
|
+
|
|
162
|
+
const firstOrError = new TestSchema.Asserts(
|
|
163
|
+
getResultSchema(db.todos.select('id', 'text').first({ behaviour: 'error' })),
|
|
164
|
+
)
|
|
165
|
+
await firstOrError.decoding().succeed([{ id: '123', text: 'Buy milk' }], { id: '123', text: 'Buy milk' })
|
|
166
|
+
await firstOrError.decoding().fail([], 'Unable to retrieve the first element of an empty array')
|
|
167
|
+
|
|
168
|
+
const firstOrFallback = new TestSchema.Asserts(
|
|
169
|
+
getResultSchema(db.todos.select('id', 'text').first({ behaviour: 'fallback', fallback: () => undefined })),
|
|
170
|
+
)
|
|
171
|
+
await firstOrFallback.decoding().succeed([], undefined)
|
|
169
172
|
})
|
|
170
173
|
|
|
171
174
|
it('should handle WHERE clauses', () => {
|
|
172
|
-
expect(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
"bindValues": [
|
|
193
|
-
1,
|
|
194
|
-
],
|
|
195
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ?",
|
|
196
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
197
|
-
}
|
|
198
|
-
`)
|
|
199
|
-
expect(dump(db.todos.select('id', 'text').where({ completed: undefined }))).toMatchInlineSnapshot(`
|
|
200
|
-
{
|
|
201
|
-
"bindValues": [],
|
|
202
|
-
"query": "SELECT "id", "text" FROM 'todos'",
|
|
203
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
204
|
-
}
|
|
205
|
-
`)
|
|
175
|
+
expect(db.todos.select('id', 'text').where('completed', true).asSql()).toEqual({
|
|
176
|
+
bindValues: [1],
|
|
177
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ?',
|
|
178
|
+
usedTables: new Set(['todos']),
|
|
179
|
+
})
|
|
180
|
+
expect(db.todos.select('id', 'text').where('completed', '!=', true).asSql()).toEqual({
|
|
181
|
+
bindValues: [1],
|
|
182
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" != ?',
|
|
183
|
+
usedTables: new Set(['todos']),
|
|
184
|
+
})
|
|
185
|
+
expect(db.todos.select('id', 'text').where({ completed: true }).asSql()).toEqual({
|
|
186
|
+
bindValues: [1],
|
|
187
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ?',
|
|
188
|
+
usedTables: new Set(['todos']),
|
|
189
|
+
})
|
|
190
|
+
expect(db.todos.select('id', 'text').where({ completed: undefined }).asSql()).toEqual({
|
|
191
|
+
bindValues: [],
|
|
192
|
+
query: 'SELECT "id", "text" FROM \'todos\'',
|
|
193
|
+
usedTables: new Set(['todos']),
|
|
194
|
+
})
|
|
206
195
|
expect(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
`)
|
|
196
|
+
db.todos
|
|
197
|
+
.select('id', 'text')
|
|
198
|
+
.where({ deletedAt: { op: '<=', value: new Date('2024-01-01') } })
|
|
199
|
+
.asSql(),
|
|
200
|
+
).toEqual({
|
|
201
|
+
bindValues: ['2024-01-01T00:00:00.000Z'],
|
|
202
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "deletedAt" <= ?',
|
|
203
|
+
usedTables: new Set(['todos']),
|
|
204
|
+
})
|
|
217
205
|
expect(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
`)
|
|
206
|
+
db.todos
|
|
207
|
+
.select('id', 'text')
|
|
208
|
+
.where({ status: { op: 'IN', value: ['active'] } })
|
|
209
|
+
.asSql(),
|
|
210
|
+
).toEqual({
|
|
211
|
+
bindValues: ['active'],
|
|
212
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "status" IN (?)',
|
|
213
|
+
usedTables: new Set(['todos']),
|
|
214
|
+
})
|
|
228
215
|
expect(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
`)
|
|
216
|
+
db.todos
|
|
217
|
+
.select('id', 'text')
|
|
218
|
+
.where({ status: { op: 'NOT IN', value: ['active', 'completed'] } })
|
|
219
|
+
.asSql(),
|
|
220
|
+
).toEqual({
|
|
221
|
+
bindValues: ['active', 'completed'],
|
|
222
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "status" NOT IN (?, ?)',
|
|
223
|
+
usedTables: new Set(['todos']),
|
|
224
|
+
})
|
|
240
225
|
|
|
241
226
|
expect(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
"active",
|
|
254
|
-
],
|
|
255
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ? AND "status" IN (?)",
|
|
256
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
257
|
-
}
|
|
258
|
-
`)
|
|
227
|
+
db.todos
|
|
228
|
+
.select('id', 'text')
|
|
229
|
+
.where({ completed: false })
|
|
230
|
+
.where({ status: { op: 'IN', value: ['active'] } })
|
|
231
|
+
.where({ deletedAt: undefined })
|
|
232
|
+
.asSql(),
|
|
233
|
+
).toEqual({
|
|
234
|
+
bindValues: [0, 'active'],
|
|
235
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? AND "status" IN (?)',
|
|
236
|
+
usedTables: new Set(['todos']),
|
|
237
|
+
})
|
|
259
238
|
})
|
|
260
239
|
|
|
261
240
|
it('should handle OFFSET and LIMIT clauses', () => {
|
|
262
|
-
expect(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
10,
|
|
268
|
-
],
|
|
269
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ? LIMIT ? OFFSET ?",
|
|
270
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
271
|
-
}
|
|
272
|
-
`)
|
|
241
|
+
expect(db.todos.select('id', 'text').where('completed', true).offset(10).limit(10).asSql()).toEqual({
|
|
242
|
+
bindValues: [1, 10, 10],
|
|
243
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? LIMIT ? OFFSET ?',
|
|
244
|
+
usedTables: new Set(['todos']),
|
|
245
|
+
})
|
|
273
246
|
})
|
|
274
247
|
|
|
275
248
|
it('should handle OFFSET and LIMIT clauses correctly', () => {
|
|
276
249
|
// Test with both offset and limit
|
|
277
|
-
expect(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
5,
|
|
283
|
-
],
|
|
284
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ? LIMIT ? OFFSET ?",
|
|
285
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
286
|
-
}
|
|
287
|
-
`)
|
|
250
|
+
expect(db.todos.select('id', 'text').where('completed', true).offset(5).limit(10).asSql()).toEqual({
|
|
251
|
+
bindValues: [1, 10, 5],
|
|
252
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? LIMIT ? OFFSET ?',
|
|
253
|
+
usedTables: new Set(['todos']),
|
|
254
|
+
})
|
|
288
255
|
|
|
289
256
|
// Test with only offset
|
|
290
|
-
expect(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
],
|
|
296
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ? OFFSET ?",
|
|
297
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
298
|
-
}
|
|
299
|
-
`)
|
|
257
|
+
expect(db.todos.select('id', 'text').where('completed', true).offset(5).asSql()).toEqual({
|
|
258
|
+
bindValues: [1, 5],
|
|
259
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? OFFSET ?',
|
|
260
|
+
usedTables: new Set(['todos']),
|
|
261
|
+
})
|
|
300
262
|
|
|
301
263
|
// Test with only limit
|
|
302
|
-
expect(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
],
|
|
308
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ? LIMIT ?",
|
|
309
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
310
|
-
}
|
|
311
|
-
`)
|
|
264
|
+
expect(db.todos.select('id', 'text').where('completed', true).limit(10).asSql()).toEqual({
|
|
265
|
+
bindValues: [1, 10],
|
|
266
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? LIMIT ?',
|
|
267
|
+
usedTables: new Set(['todos']),
|
|
268
|
+
})
|
|
312
269
|
})
|
|
313
270
|
|
|
314
271
|
it('should handle COUNT queries', () => {
|
|
315
|
-
expect(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
],
|
|
336
|
-
"query": "SELECT COUNT(*) as count FROM 'todos' WHERE "completed" = ?",
|
|
337
|
-
"schema": "(ReadonlyArray<({ readonly count: number } <-> number)> <-> number)",
|
|
338
|
-
}
|
|
339
|
-
`)
|
|
272
|
+
expect(db.todos.count().asSql()).toEqual({
|
|
273
|
+
bindValues: [],
|
|
274
|
+
query: "SELECT COUNT(*) as count FROM 'todos'",
|
|
275
|
+
usedTables: new Set(['todos']),
|
|
276
|
+
})
|
|
277
|
+
expect(db.todos.count().where('completed', true).asSql()).toEqual({
|
|
278
|
+
bindValues: [1],
|
|
279
|
+
query: 'SELECT COUNT(*) as count FROM \'todos\' WHERE "completed" = ?',
|
|
280
|
+
usedTables: new Set(['todos']),
|
|
281
|
+
})
|
|
282
|
+
expect(db.todos.where('completed', true).count().asSql()).toEqual({
|
|
283
|
+
bindValues: [1],
|
|
284
|
+
query: 'SELECT COUNT(*) as count FROM \'todos\' WHERE "completed" = ?',
|
|
285
|
+
usedTables: new Set(['todos']),
|
|
286
|
+
})
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('derives result schemas for COUNT queries', async () => {
|
|
290
|
+
const countResult = new TestSchema.Asserts(getResultSchema(db.todos.count()))
|
|
291
|
+
await countResult.decoding().succeed([{ count: 3 }], 3)
|
|
340
292
|
})
|
|
341
293
|
|
|
342
294
|
it('should handle NULL comparisons', () => {
|
|
343
|
-
expect(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "deletedAt" IS NOT NULL",
|
|
354
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
355
|
-
}
|
|
356
|
-
`)
|
|
295
|
+
expect(db.todos.select('id', 'text').where('deletedAt', '=', null).asSql()).toEqual({
|
|
296
|
+
bindValues: [],
|
|
297
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "deletedAt" IS NULL',
|
|
298
|
+
usedTables: new Set(['todos']),
|
|
299
|
+
})
|
|
300
|
+
expect(db.todos.select('id', 'text').where('deletedAt', '!=', null).asSql()).toEqual({
|
|
301
|
+
bindValues: [],
|
|
302
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "deletedAt" IS NOT NULL',
|
|
303
|
+
usedTables: new Set(['todos']),
|
|
304
|
+
})
|
|
357
305
|
})
|
|
358
306
|
|
|
359
307
|
it('should handle orderBy', () => {
|
|
360
|
-
expect(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
`)
|
|
308
|
+
expect(db.todos.orderBy('completed', 'desc').asSql()).toEqual({
|
|
309
|
+
bindValues: [],
|
|
310
|
+
query: 'SELECT * FROM \'todos\' ORDER BY "completed" desc',
|
|
311
|
+
usedTables: new Set(['todos']),
|
|
312
|
+
})
|
|
367
313
|
|
|
368
|
-
expect(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
`)
|
|
314
|
+
expect(db.todos.orderBy([{ col: 'completed', direction: 'desc' }]).asSql()).toEqual({
|
|
315
|
+
bindValues: [],
|
|
316
|
+
query: 'SELECT * FROM \'todos\' ORDER BY "completed" desc',
|
|
317
|
+
usedTables: new Set(['todos']),
|
|
318
|
+
})
|
|
375
319
|
|
|
376
|
-
expect(
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
`)
|
|
320
|
+
expect(db.todos.orderBy([]).asSql()).toEqual({
|
|
321
|
+
bindValues: [],
|
|
322
|
+
query: "SELECT * FROM 'todos'",
|
|
323
|
+
usedTables: new Set(['todos']),
|
|
324
|
+
})
|
|
383
325
|
})
|
|
384
326
|
|
|
385
327
|
it('should handle JSON_CONTAINS operator for JSON array columns', () => {
|
|
386
|
-
expect(
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
`)
|
|
328
|
+
expect(db.personProfiles.where({ sources: { op: 'JSON_CONTAINS', value: 'google' } }).asSql()).toEqual({
|
|
329
|
+
bindValues: ['google'],
|
|
330
|
+
query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)',
|
|
331
|
+
usedTables: new Set(['person_profiles']),
|
|
332
|
+
})
|
|
397
333
|
|
|
398
334
|
// With select
|
|
399
335
|
expect(
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
"
|
|
408
|
-
|
|
409
|
-
|
|
336
|
+
db.personProfiles
|
|
337
|
+
.select('personId')
|
|
338
|
+
.where({ sources: { op: 'JSON_CONTAINS', value: 'linkedin' } })
|
|
339
|
+
.asSql(),
|
|
340
|
+
).toEqual({
|
|
341
|
+
bindValues: ['linkedin'],
|
|
342
|
+
query:
|
|
343
|
+
'SELECT "personId" FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)',
|
|
344
|
+
usedTables: new Set(['person_profiles']),
|
|
345
|
+
})
|
|
410
346
|
|
|
411
347
|
// With plain string array column
|
|
412
|
-
expect(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
`)
|
|
348
|
+
expect(db.personProfiles.where({ tags: { op: 'JSON_CONTAINS', value: 'important' } }).asSql()).toEqual({
|
|
349
|
+
bindValues: ['important'],
|
|
350
|
+
query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("tags") WHERE value = ?)',
|
|
351
|
+
usedTables: new Set(['person_profiles']),
|
|
352
|
+
})
|
|
423
353
|
})
|
|
424
354
|
|
|
425
355
|
it('should handle JSON_NOT_CONTAINS operator for JSON array columns', () => {
|
|
426
|
-
expect(
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
`)
|
|
356
|
+
expect(db.personProfiles.where({ sources: { op: 'JSON_NOT_CONTAINS', value: 'google' } }).asSql()).toEqual({
|
|
357
|
+
bindValues: ['google'],
|
|
358
|
+
query:
|
|
359
|
+
'SELECT * FROM \'person_profiles\' WHERE NOT EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)',
|
|
360
|
+
usedTables: new Set(['person_profiles']),
|
|
361
|
+
})
|
|
437
362
|
})
|
|
438
363
|
|
|
439
364
|
it('should JSON-stringify object elements for JSON_CONTAINS', () => {
|
|
440
365
|
expect(
|
|
441
|
-
|
|
442
|
-
|
|
366
|
+
db.personProfiles
|
|
367
|
+
.where({
|
|
443
368
|
attributes: { op: 'JSON_CONTAINS', value: { key: 'language', value: 'typescript' } },
|
|
444
|
-
})
|
|
445
|
-
|
|
446
|
-
).
|
|
447
|
-
{
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
"query": "SELECT * FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("attributes") WHERE value = ?)",
|
|
452
|
-
"schema": "ReadonlyArray<person_profiles>",
|
|
453
|
-
}
|
|
454
|
-
`)
|
|
369
|
+
})
|
|
370
|
+
.asSql(),
|
|
371
|
+
).toEqual({
|
|
372
|
+
bindValues: ['{"key":"language","value":"typescript"}'],
|
|
373
|
+
query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("attributes") WHERE value = ?)',
|
|
374
|
+
usedTables: new Set(['person_profiles']),
|
|
375
|
+
})
|
|
455
376
|
})
|
|
456
377
|
|
|
457
378
|
it('should handle combining JSON_CONTAINS with other WHERE clauses', () => {
|
|
458
379
|
expect(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
"
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
`)
|
|
380
|
+
db.personProfiles
|
|
381
|
+
.where({ sources: { op: 'JSON_CONTAINS', value: 'google' } })
|
|
382
|
+
.where({ sources: { op: 'JSON_NOT_CONTAINS', value: 'facebook' } })
|
|
383
|
+
.asSql(),
|
|
384
|
+
).toEqual({
|
|
385
|
+
bindValues: ['google', 'facebook'],
|
|
386
|
+
query:
|
|
387
|
+
'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 = ?)',
|
|
388
|
+
usedTables: new Set(['person_profiles']),
|
|
389
|
+
})
|
|
474
390
|
})
|
|
475
391
|
|
|
476
392
|
it('should handle JSON_CONTAINS on nullable JSON array columns', () => {
|
|
477
|
-
expect(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
],
|
|
484
|
-
"query": "SELECT * FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)",
|
|
485
|
-
"schema": "ReadonlyArray<person_profiles>",
|
|
486
|
-
}
|
|
487
|
-
`)
|
|
393
|
+
expect(db.personProfiles.where({ optionalTags: { op: 'JSON_CONTAINS', value: 'important' } }).asSql()).toEqual({
|
|
394
|
+
bindValues: ['important'],
|
|
395
|
+
query:
|
|
396
|
+
'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)',
|
|
397
|
+
usedTables: new Set(['person_profiles']),
|
|
398
|
+
})
|
|
488
399
|
|
|
489
400
|
// With JSON_NOT_CONTAINS
|
|
490
|
-
expect(
|
|
491
|
-
dump(db.personProfiles.where({ optionalTags: { op: 'JSON_NOT_CONTAINS', value: 'archived' } })),
|
|
492
|
-
).toMatchInlineSnapshot(`
|
|
401
|
+
expect(db.personProfiles.where({ optionalTags: { op: 'JSON_NOT_CONTAINS', value: 'archived' } }).asSql()).toEqual(
|
|
493
402
|
{
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
`)
|
|
403
|
+
bindValues: ['archived'],
|
|
404
|
+
query:
|
|
405
|
+
'SELECT * FROM \'person_profiles\' WHERE NOT EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)',
|
|
406
|
+
usedTables: new Set(['person_profiles']),
|
|
407
|
+
},
|
|
408
|
+
)
|
|
501
409
|
})
|
|
502
410
|
|
|
503
411
|
it('should throw error when using JSON_CONTAINS on non-JSON array column', () => {
|
|
504
412
|
expect(() =>
|
|
505
413
|
// Type system prevents this at compile time for non-array columns, but test runtime check
|
|
506
|
-
|
|
414
|
+
db.todos.where({ status: { op: 'JSON_CONTAINS', value: 'active' } } as any).asSql(),
|
|
507
415
|
).toThrow('JSON_CONTAINS operator can only be used on JSON array columns')
|
|
508
416
|
})
|
|
509
417
|
})
|
|
510
418
|
|
|
511
|
-
// describe('getOrCreate queries', () => {
|
|
512
|
-
// it('should handle getOrCreate queries', () => {
|
|
513
|
-
// expect(dump(db.UiState.getOrCreate('sessionid-1'))).toMatchInlineSnapshot(`
|
|
514
|
-
// {
|
|
515
|
-
// "bindValues": [
|
|
516
|
-
// "sessionid-1",
|
|
517
|
-
// ],
|
|
518
|
-
// "query": "SELECT * FROM 'UiState' WHERE id = ?",
|
|
519
|
-
// "schema": "...", // TODO determine schema
|
|
520
|
-
// }
|
|
521
|
-
// `)
|
|
522
|
-
// })
|
|
523
|
-
|
|
524
|
-
// it('should handle getOrCreate queries with default id', () => {
|
|
525
|
-
// expect(dump(db.UiStateWithDefaultId.getOrCreate())).toMatchInlineSnapshot(`
|
|
526
|
-
// {
|
|
527
|
-
// "bindValues": [],
|
|
528
|
-
// "query": "SELECT * FROM 'UiState' WHERE id = ?",
|
|
529
|
-
// "schema": "...", // TODO determine schema
|
|
530
|
-
// }
|
|
531
|
-
// `)
|
|
532
|
-
// })
|
|
533
|
-
// // it('should handle row queries with numbers', () => {
|
|
534
|
-
// // expect(dump(db.todosWithIntId.getOrCreate(123, { insertValues: { status: 'active' } }))).toMatchInlineSnapshot(`
|
|
535
|
-
// // {
|
|
536
|
-
// // "bindValues": [
|
|
537
|
-
// // 123,
|
|
538
|
-
// // ],
|
|
539
|
-
// // "query": "SELECT * FROM 'todos_with_int_id' WHERE id = ?",
|
|
540
|
-
// // "schema": "...", // TODO determine schema
|
|
541
|
-
// // }
|
|
542
|
-
// // `)
|
|
543
|
-
// // })
|
|
544
|
-
// })
|
|
545
|
-
|
|
546
419
|
describe('write operations', () => {
|
|
547
420
|
it('should handle INSERT queries', () => {
|
|
548
|
-
expect(
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
|
|
421
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).asSql()).toEqual({
|
|
422
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
423
|
+
query: `INSERT INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?)`,
|
|
424
|
+
usedTables: new Set(['todos']),
|
|
425
|
+
})
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('derives result schemas for write queries', async () => {
|
|
429
|
+
const insertResult = new TestSchema.Asserts(
|
|
430
|
+
getResultSchema(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' })),
|
|
431
|
+
)
|
|
432
|
+
await insertResult.decoding().succeed(1)
|
|
559
433
|
})
|
|
560
434
|
|
|
561
435
|
it('should handle INSERT queries with undefined values', () => {
|
|
562
|
-
expect(
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
"active",
|
|
568
|
-
],
|
|
569
|
-
"query": "INSERT INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?)",
|
|
570
|
-
"schema": "number",
|
|
571
|
-
}
|
|
572
|
-
`)
|
|
436
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).asSql()).toEqual({
|
|
437
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
438
|
+
query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?)',
|
|
439
|
+
usedTables: new Set(['todos']),
|
|
440
|
+
})
|
|
573
441
|
})
|
|
574
442
|
|
|
575
443
|
// Test helped to catch a bindValues ordering bug
|
|
576
444
|
it('should handle INSERT queries (issue)', () => {
|
|
577
445
|
expect(
|
|
578
|
-
|
|
579
|
-
|
|
446
|
+
db.issue
|
|
447
|
+
.insert({
|
|
580
448
|
id: 1,
|
|
581
449
|
title: 'Revert the user profile page',
|
|
582
450
|
priority: 2,
|
|
@@ -584,275 +452,180 @@ describe('query builder', () => {
|
|
|
584
452
|
modified: new Date('2024-12-29T17:15:20.507Z'),
|
|
585
453
|
kanbanorder: 'a2',
|
|
586
454
|
creator: 'John Doe',
|
|
587
|
-
})
|
|
588
|
-
|
|
589
|
-
).
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
1722532520507,
|
|
596
|
-
1735492520507,
|
|
597
|
-
"a2",
|
|
598
|
-
"John Doe",
|
|
599
|
-
],
|
|
600
|
-
"query": "INSERT INTO 'issue' ("id", "title", "priority", "created", "modified", "kanbanorder", "creator") VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
601
|
-
"schema": "number",
|
|
602
|
-
}
|
|
603
|
-
`)
|
|
455
|
+
})
|
|
456
|
+
.asSql(),
|
|
457
|
+
).toEqual({
|
|
458
|
+
bindValues: [1, 'Revert the user profile page', 2, 1722532520507, 1735492520507, 'a2', 'John Doe'],
|
|
459
|
+
query:
|
|
460
|
+
'INSERT INTO \'issue\' ("id", "title", "priority", "created", "modified", "kanbanorder", "creator") VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
461
|
+
usedTables: new Set(['issue']),
|
|
462
|
+
})
|
|
604
463
|
})
|
|
605
464
|
|
|
606
465
|
it('should handle UPDATE queries', () => {
|
|
607
|
-
expect(
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
],
|
|
613
|
-
"query": "UPDATE 'todos' SET "status" = ? WHERE "id" = ?",
|
|
614
|
-
"schema": "number",
|
|
615
|
-
}
|
|
616
|
-
`)
|
|
466
|
+
expect(db.todos.update({ status: 'completed' }).where({ id: '123' }).asSql()).toEqual({
|
|
467
|
+
bindValues: ['completed', '123'],
|
|
468
|
+
query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ?',
|
|
469
|
+
usedTables: new Set(['todos']),
|
|
470
|
+
})
|
|
617
471
|
|
|
618
472
|
// empty update set
|
|
619
|
-
expect(
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
}
|
|
625
|
-
`)
|
|
473
|
+
expect(db.todos.update({}).where({ id: '123' }).asSql()).toEqual({
|
|
474
|
+
bindValues: [],
|
|
475
|
+
query: 'SELECT 1',
|
|
476
|
+
usedTables: new Set(['todos']),
|
|
477
|
+
})
|
|
626
478
|
})
|
|
627
479
|
|
|
628
480
|
it('should handle UPDATE queries with undefined values', () => {
|
|
629
|
-
expect(
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
],
|
|
635
|
-
"query": "UPDATE 'todos' SET "text" = ? WHERE "id" = ?",
|
|
636
|
-
"schema": "number",
|
|
637
|
-
}
|
|
638
|
-
`)
|
|
481
|
+
expect(db.todos.update({ text: 'some text' }).where({ id: '123' }).asSql()).toEqual({
|
|
482
|
+
bindValues: ['some text', '123'],
|
|
483
|
+
query: 'UPDATE \'todos\' SET "text" = ? WHERE "id" = ?',
|
|
484
|
+
usedTables: new Set(['todos']),
|
|
485
|
+
})
|
|
639
486
|
})
|
|
640
487
|
|
|
641
488
|
it('should handle UPDATE queries with undefined values (issue)', () => {
|
|
642
|
-
expect(
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
1,
|
|
648
|
-
],
|
|
649
|
-
"query": "UPDATE 'issue' SET "priority" = ?, "creator" = ? WHERE "id" = ?",
|
|
650
|
-
"schema": "number",
|
|
651
|
-
}
|
|
652
|
-
`)
|
|
489
|
+
expect(db.issue.update({ priority: 2, creator: 'John Doe' }).where({ id: 1 }).asSql()).toEqual({
|
|
490
|
+
bindValues: [2, 'John Doe', 1],
|
|
491
|
+
query: 'UPDATE \'issue\' SET "priority" = ?, "creator" = ? WHERE "id" = ?',
|
|
492
|
+
usedTables: new Set(['issue']),
|
|
493
|
+
})
|
|
653
494
|
})
|
|
654
495
|
|
|
655
496
|
it('should handle DELETE queries', () => {
|
|
656
|
-
expect(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
"query": "DELETE FROM 'todos' WHERE "status" = ?",
|
|
662
|
-
"schema": "number",
|
|
663
|
-
}
|
|
664
|
-
`)
|
|
497
|
+
expect(db.todos.delete().where({ status: 'completed' }).asSql()).toEqual({
|
|
498
|
+
bindValues: ['completed'],
|
|
499
|
+
query: 'DELETE FROM \'todos\' WHERE "status" = ?',
|
|
500
|
+
usedTables: new Set(['todos']),
|
|
501
|
+
})
|
|
665
502
|
})
|
|
666
503
|
|
|
667
504
|
it('should handle INSERT with ON CONFLICT', () => {
|
|
668
505
|
expect(
|
|
669
|
-
|
|
670
|
-
).
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
"active",
|
|
676
|
-
],
|
|
677
|
-
"query": "INSERT INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id") DO NOTHING",
|
|
678
|
-
"schema": "number",
|
|
679
|
-
}
|
|
680
|
-
`)
|
|
506
|
+
db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'ignore').asSql(),
|
|
507
|
+
).toEqual({
|
|
508
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
509
|
+
query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id") DO NOTHING',
|
|
510
|
+
usedTables: new Set(['todos']),
|
|
511
|
+
})
|
|
681
512
|
|
|
682
513
|
expect(
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
"
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
"active",
|
|
694
|
-
"Buy soy milk",
|
|
695
|
-
"active",
|
|
696
|
-
],
|
|
697
|
-
"query": "INSERT INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id") DO UPDATE SET "text" = ?, "status" = ?",
|
|
698
|
-
"schema": "number",
|
|
699
|
-
}
|
|
700
|
-
`)
|
|
514
|
+
db.todos
|
|
515
|
+
.insert({ id: '123', text: 'Buy milk', status: 'active' })
|
|
516
|
+
.onConflict('id', 'update', { text: 'Buy soy milk', status: 'active' })
|
|
517
|
+
.asSql(),
|
|
518
|
+
).toEqual({
|
|
519
|
+
bindValues: ['123', 'Buy milk', 'active', 'Buy soy milk', 'active'],
|
|
520
|
+
query:
|
|
521
|
+
'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id") DO UPDATE SET "text" = ?, "status" = ?',
|
|
522
|
+
usedTables: new Set(['todos']),
|
|
523
|
+
})
|
|
701
524
|
|
|
702
525
|
expect(
|
|
703
|
-
|
|
704
|
-
).
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
"active",
|
|
710
|
-
],
|
|
711
|
-
"query": "INSERT OR REPLACE INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?)",
|
|
712
|
-
"schema": "number",
|
|
713
|
-
}
|
|
714
|
-
`)
|
|
526
|
+
db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'replace').asSql(),
|
|
527
|
+
).toEqual({
|
|
528
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
529
|
+
query: 'INSERT OR REPLACE INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?)',
|
|
530
|
+
usedTables: new Set(['todos']),
|
|
531
|
+
})
|
|
715
532
|
})
|
|
716
533
|
|
|
717
534
|
it('should quote reserved column names', () => {
|
|
718
|
-
expect(
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
expect(dump(db.selections.update({ group: 'beta' }).where({ id: 1 }))).toMatchInlineSnapshot(`
|
|
730
|
-
{
|
|
731
|
-
"bindValues": [
|
|
732
|
-
"beta",
|
|
733
|
-
1,
|
|
734
|
-
],
|
|
735
|
-
"query": "UPDATE 'selections' SET "group" = ? WHERE "id" = ?",
|
|
736
|
-
"schema": "number",
|
|
737
|
-
}
|
|
738
|
-
`)
|
|
535
|
+
expect(db.selections.insert({ id: 1, group: 'alpha' }).onConflict('id', 'ignore').asSql()).toEqual({
|
|
536
|
+
bindValues: [1, 'alpha'],
|
|
537
|
+
query: 'INSERT INTO \'selections\' ("id", "group") VALUES (?, ?) ON CONFLICT ("id") DO NOTHING',
|
|
538
|
+
usedTables: new Set(['selections']),
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
expect(db.selections.update({ group: 'beta' }).where({ id: 1 }).asSql()).toEqual({
|
|
542
|
+
bindValues: ['beta', 1],
|
|
543
|
+
query: 'UPDATE \'selections\' SET "group" = ? WHERE "id" = ?',
|
|
544
|
+
usedTables: new Set(['selections']),
|
|
545
|
+
})
|
|
739
546
|
})
|
|
740
547
|
|
|
741
548
|
it('should handle ON CONFLICT with multiple columns', () => {
|
|
742
549
|
expect(
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
}
|
|
754
|
-
`)
|
|
550
|
+
db.todos
|
|
551
|
+
.insert({ id: '123', text: 'Buy milk', status: 'active' })
|
|
552
|
+
.onConflict(['id', 'status'], 'ignore')
|
|
553
|
+
.asSql(),
|
|
554
|
+
).toEqual({
|
|
555
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
556
|
+
query:
|
|
557
|
+
'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id", "status") DO NOTHING',
|
|
558
|
+
usedTables: new Set(['todos']),
|
|
559
|
+
})
|
|
755
560
|
})
|
|
756
561
|
|
|
757
562
|
it('should handle RETURNING clause', () => {
|
|
758
|
-
expect(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
"123",
|
|
764
|
-
"Buy milk",
|
|
765
|
-
"active",
|
|
766
|
-
],
|
|
767
|
-
"query": "INSERT INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?) RETURNING "id"",
|
|
768
|
-
"schema": "ReadonlyArray<{ readonly id: string }>",
|
|
769
|
-
}
|
|
770
|
-
`)
|
|
563
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).returning('id').asSql()).toEqual({
|
|
564
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
565
|
+
query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) RETURNING "id"',
|
|
566
|
+
usedTables: new Set(['todos']),
|
|
567
|
+
})
|
|
771
568
|
|
|
772
|
-
expect(
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
"query": "DELETE FROM 'todos' WHERE "status" = ? RETURNING "id"",
|
|
791
|
-
"schema": "ReadonlyArray<{ readonly id: string }>",
|
|
792
|
-
}
|
|
793
|
-
`)
|
|
569
|
+
expect(db.todos.update({ status: 'completed' }).where({ id: '123' }).returning('id').asSql()).toEqual({
|
|
570
|
+
bindValues: ['completed', '123'],
|
|
571
|
+
query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ? RETURNING "id"',
|
|
572
|
+
usedTables: new Set(['todos']),
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
expect(db.todos.delete().where({ status: 'completed' }).returning('id').asSql()).toEqual({
|
|
576
|
+
bindValues: ['completed'],
|
|
577
|
+
query: 'DELETE FROM \'todos\' WHERE "status" = ? RETURNING "id"',
|
|
578
|
+
usedTables: new Set(['todos']),
|
|
579
|
+
})
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
it('derives result schemas for RETURNING clauses', async () => {
|
|
583
|
+
const returningId = new TestSchema.Asserts(
|
|
584
|
+
getResultSchema(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).returning('id')),
|
|
585
|
+
)
|
|
586
|
+
await returningId.decoding().succeed([{ id: '123' }])
|
|
794
587
|
})
|
|
795
588
|
|
|
796
589
|
it('should handle where().delete() - preserving where clauses', () => {
|
|
797
|
-
expect(
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
"query": "DELETE FROM 'todos' WHERE "status" = ?",
|
|
803
|
-
"schema": "number",
|
|
804
|
-
}
|
|
805
|
-
`)
|
|
590
|
+
expect(db.todos.where({ status: 'completed' }).delete().asSql()).toEqual({
|
|
591
|
+
bindValues: ['completed'],
|
|
592
|
+
query: 'DELETE FROM \'todos\' WHERE "status" = ?',
|
|
593
|
+
usedTables: new Set(['todos']),
|
|
594
|
+
})
|
|
806
595
|
|
|
807
596
|
// Multiple where clauses
|
|
808
|
-
expect(
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
"query": "DELETE FROM 'todos' WHERE "status" = ? AND "deletedAt" IS NULL",
|
|
814
|
-
"schema": "number",
|
|
815
|
-
}
|
|
816
|
-
`)
|
|
597
|
+
expect(db.todos.where({ status: 'completed' }).where({ deletedAt: null }).delete().asSql()).toEqual({
|
|
598
|
+
bindValues: ['completed'],
|
|
599
|
+
query: 'DELETE FROM \'todos\' WHERE "status" = ? AND "deletedAt" IS NULL',
|
|
600
|
+
usedTables: new Set(['todos']),
|
|
601
|
+
})
|
|
817
602
|
})
|
|
818
603
|
|
|
819
604
|
it('should handle where().update() - preserving where clauses', () => {
|
|
820
|
-
expect(
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
],
|
|
826
|
-
"query": "UPDATE 'todos' SET "status" = ? WHERE "id" = ?",
|
|
827
|
-
"schema": "number",
|
|
828
|
-
}
|
|
829
|
-
`)
|
|
605
|
+
expect(db.todos.where({ id: '123' }).update({ status: 'completed' }).asSql()).toEqual({
|
|
606
|
+
bindValues: ['completed', '123'],
|
|
607
|
+
query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ?',
|
|
608
|
+
usedTables: new Set(['todos']),
|
|
609
|
+
})
|
|
830
610
|
|
|
831
611
|
// Multiple where clauses
|
|
832
|
-
expect(
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
"completed",
|
|
838
|
-
"123",
|
|
839
|
-
],
|
|
840
|
-
"query": "UPDATE 'todos' SET "status" = ? WHERE "id" = ? AND "deletedAt" IS NULL",
|
|
841
|
-
"schema": "number",
|
|
842
|
-
}
|
|
843
|
-
`)
|
|
612
|
+
expect(db.todos.where({ id: '123' }).where({ deletedAt: null }).update({ status: 'completed' }).asSql()).toEqual({
|
|
613
|
+
bindValues: ['completed', '123'],
|
|
614
|
+
query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ? AND "deletedAt" IS NULL',
|
|
615
|
+
usedTables: new Set(['todos']),
|
|
616
|
+
})
|
|
844
617
|
})
|
|
845
618
|
|
|
846
619
|
it('should have equivalent behavior for both delete patterns', () => {
|
|
847
|
-
const pattern1 =
|
|
848
|
-
const pattern2 =
|
|
620
|
+
const pattern1 = db.todos.where({ status: 'completed', id: '123' }).delete().asSql()
|
|
621
|
+
const pattern2 = db.todos.delete().where({ status: 'completed', id: '123' }).asSql()
|
|
849
622
|
|
|
850
623
|
expect(pattern1).toEqual(pattern2)
|
|
851
624
|
})
|
|
852
625
|
|
|
853
626
|
it('should have equivalent behavior for both update patterns', () => {
|
|
854
|
-
const pattern1 =
|
|
855
|
-
const pattern2 =
|
|
627
|
+
const pattern1 = db.todos.where({ id: '123' }).update({ status: 'completed', text: 'Updated' }).asSql()
|
|
628
|
+
const pattern2 = db.todos.update({ status: 'completed', text: 'Updated' }).where({ id: '123' }).asSql()
|
|
856
629
|
|
|
857
630
|
expect(pattern1).toEqual(pattern2)
|
|
858
631
|
})
|
|
@@ -866,32 +639,33 @@ describe('query builder', () => {
|
|
|
866
639
|
contactEmail: Schema.String.pipe(State.SQLite.withUnique),
|
|
867
640
|
})
|
|
868
641
|
|
|
869
|
-
const Nested =
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
}),
|
|
879
|
-
{
|
|
880
|
-
decode: ({ id, contactFirstName, contactLastName, contactEmail }) => ({
|
|
881
|
-
id,
|
|
882
|
-
contact: {
|
|
883
|
-
firstName: contactFirstName,
|
|
884
|
-
lastName: contactLastName,
|
|
885
|
-
email: contactEmail,
|
|
886
|
-
},
|
|
642
|
+
const Nested = Flat.pipe(
|
|
643
|
+
Schema.decodeTo(
|
|
644
|
+
Schema.Struct({
|
|
645
|
+
id: Schema.String,
|
|
646
|
+
contact: Schema.Struct({
|
|
647
|
+
firstName: Schema.String,
|
|
648
|
+
lastName: Schema.String,
|
|
649
|
+
email: Schema.String,
|
|
650
|
+
}),
|
|
887
651
|
}),
|
|
888
|
-
|
|
889
|
-
id,
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
652
|
+
SchemaTransformation.transform({
|
|
653
|
+
decode: ({ id, contactFirstName, contactLastName, contactEmail }) => ({
|
|
654
|
+
id,
|
|
655
|
+
contact: {
|
|
656
|
+
firstName: contactFirstName,
|
|
657
|
+
lastName: contactLastName,
|
|
658
|
+
email: contactEmail,
|
|
659
|
+
},
|
|
660
|
+
}),
|
|
661
|
+
encode: ({ id, contact }) => ({
|
|
662
|
+
id,
|
|
663
|
+
contactFirstName: contact.firstName,
|
|
664
|
+
contactLastName: contact.lastName,
|
|
665
|
+
contactEmail: contact.email,
|
|
666
|
+
}),
|
|
893
667
|
}),
|
|
894
|
-
|
|
668
|
+
),
|
|
895
669
|
)
|
|
896
670
|
|
|
897
671
|
const makeContactsTable = () =>
|
|
@@ -905,7 +679,7 @@ describe('query builder', () => {
|
|
|
905
679
|
const contactsTable = makeContactsTable()
|
|
906
680
|
|
|
907
681
|
type InsertInput = Parameters<(typeof contactsTable)['insert']>[0]
|
|
908
|
-
type NestedType =
|
|
682
|
+
type NestedType = (typeof Nested)['Type']
|
|
909
683
|
|
|
910
684
|
type Assert<T extends true> = T
|
|
911
685
|
|
|
@@ -929,7 +703,7 @@ describe('query builder', () => {
|
|
|
929
703
|
void contactsTable
|
|
930
704
|
})
|
|
931
705
|
|
|
932
|
-
it('
|
|
706
|
+
it('encodes flat insert values for transformed schemas', () => {
|
|
933
707
|
const contactsTable = makeContactsTable()
|
|
934
708
|
|
|
935
709
|
expect(
|
|
@@ -942,23 +716,15 @@ describe('query builder', () => {
|
|
|
942
716
|
contactEmail: 'ada@example.com',
|
|
943
717
|
})
|
|
944
718
|
.asSql(),
|
|
945
|
-
).
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
"usedTables": Set {
|
|
955
|
-
"contacts",
|
|
956
|
-
},
|
|
957
|
-
}
|
|
958
|
-
`)
|
|
959
|
-
})
|
|
960
|
-
|
|
961
|
-
it('fails to encode nested inserts because flat columns are required', () => {
|
|
719
|
+
).toEqual({
|
|
720
|
+
bindValues: ['person-1', 'Ada', 'Lovelace', 'ada@example.com'],
|
|
721
|
+
query:
|
|
722
|
+
'INSERT INTO \'contacts\' ("id", "contactFirstName", "contactLastName", "contactEmail") VALUES (?, ?, ?, ?)',
|
|
723
|
+
usedTables: new Set(['contacts']),
|
|
724
|
+
})
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
it('rejects nested insert values because flat columns are required', () => {
|
|
962
728
|
const contactsTable = makeContactsTable()
|
|
963
729
|
|
|
964
730
|
expect(() =>
|
|
@@ -973,15 +739,13 @@ describe('query builder', () => {
|
|
|
973
739
|
},
|
|
974
740
|
})
|
|
975
741
|
.asSql(),
|
|
976
|
-
).
|
|
977
|
-
[ParseError: contacts\n└─ ["contactFirstName"]\n └─ is missing]
|
|
978
|
-
`)
|
|
742
|
+
).toThrowError(/contactFirstName/)
|
|
979
743
|
})
|
|
980
744
|
})
|
|
981
745
|
})
|
|
982
746
|
|
|
983
747
|
// TODO nested queries
|
|
984
|
-
// const rawSql = <A, I>(sql: string, params: { [key: string]: any }, schema: Schema.
|
|
748
|
+
// const rawSql = <A, I>(sql: string, params: { [key: string]: any }, schema: Schema.Codec<A, I>) =>
|
|
985
749
|
// ({
|
|
986
750
|
// sql,
|
|
987
751
|
// params,
|