@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,6 +1,6 @@
|
|
|
1
|
+
import { TestSchema } from 'effect/testing';
|
|
1
2
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { Schema } from '@livestore/utils/effect';
|
|
3
|
-
import { objectToString } from '@livestore/utils';
|
|
3
|
+
import { Schema, SchemaTransformation } from '@livestore/utils/effect';
|
|
4
4
|
import { State } from "../../../mod.js";
|
|
5
5
|
import { getResultSchema } from "./impl.js";
|
|
6
6
|
const todos = State.SQLite.table({
|
|
@@ -9,7 +9,7 @@ const todos = State.SQLite.table({
|
|
|
9
9
|
id: State.SQLite.text({ primaryKey: true }),
|
|
10
10
|
text: State.SQLite.text({ default: '', nullable: false }),
|
|
11
11
|
completed: State.SQLite.boolean({ default: false, nullable: false }),
|
|
12
|
-
status: State.SQLite.text({ schema: Schema.
|
|
12
|
+
status: State.SQLite.text({ schema: Schema.Literals(['active', 'completed']) }),
|
|
13
13
|
deletedAt: State.SQLite.datetime({ nullable: true }),
|
|
14
14
|
// TODO consider leaning more into Effect schema
|
|
15
15
|
// other: Schema.Number.pipe(State.SQLite.asInteger),
|
|
@@ -20,7 +20,7 @@ const todosWithIntId = State.SQLite.table({
|
|
|
20
20
|
columns: {
|
|
21
21
|
id: State.SQLite.integer({ primaryKey: true }),
|
|
22
22
|
text: State.SQLite.text({ default: '', nullable: false }),
|
|
23
|
-
status: State.SQLite.text({ schema: Schema.
|
|
23
|
+
status: State.SQLite.text({ schema: Schema.Literals(['active', 'completed']) }),
|
|
24
24
|
},
|
|
25
25
|
});
|
|
26
26
|
const comments = State.SQLite.table({
|
|
@@ -34,14 +34,14 @@ const comments = State.SQLite.table({
|
|
|
34
34
|
const UiState = State.SQLite.clientDocument({
|
|
35
35
|
name: 'UiState',
|
|
36
36
|
schema: Schema.Struct({
|
|
37
|
-
filter: Schema.
|
|
37
|
+
filter: Schema.Literals(['all', 'active', 'completed']),
|
|
38
38
|
}),
|
|
39
39
|
default: { value: { filter: 'all' } },
|
|
40
40
|
});
|
|
41
41
|
const UiStateWithDefaultId = State.SQLite.clientDocument({
|
|
42
42
|
name: 'UiState',
|
|
43
43
|
schema: Schema.Struct({
|
|
44
|
-
filter: Schema.
|
|
44
|
+
filter: Schema.Literals(['all', 'active', 'completed']),
|
|
45
45
|
}),
|
|
46
46
|
default: {
|
|
47
47
|
id: 'static',
|
|
@@ -54,10 +54,10 @@ const issue = State.SQLite.table({
|
|
|
54
54
|
id: State.SQLite.integer({ primaryKey: true }),
|
|
55
55
|
title: State.SQLite.text({ default: '' }),
|
|
56
56
|
creator: State.SQLite.text({ default: '' }),
|
|
57
|
-
priority: State.SQLite.integer({ schema: Schema.
|
|
58
|
-
created: State.SQLite.integer({ schema: Schema.
|
|
59
|
-
deleted: State.SQLite.integer({ nullable: true, schema: Schema.
|
|
60
|
-
modified: State.SQLite.integer({ schema: Schema.
|
|
57
|
+
priority: State.SQLite.integer({ schema: Schema.Literals([0, 1, 2, 3, 4]), default: 0 }),
|
|
58
|
+
created: State.SQLite.integer({ schema: Schema.DateFromMillis }),
|
|
59
|
+
deleted: State.SQLite.integer({ nullable: true, schema: Schema.DateFromMillis }),
|
|
60
|
+
modified: State.SQLite.integer({ schema: Schema.DateFromMillis }),
|
|
61
61
|
kanbanorder: State.SQLite.text({ nullable: false, default: '' }),
|
|
62
62
|
},
|
|
63
63
|
indexes: [
|
|
@@ -72,7 +72,7 @@ const selections = State.SQLite.table({
|
|
|
72
72
|
group: State.SQLite.text({}),
|
|
73
73
|
},
|
|
74
74
|
});
|
|
75
|
-
const Source = Schema.
|
|
75
|
+
const Source = Schema.Literals(['google', 'linkedin', 'facebook']);
|
|
76
76
|
const ProfileAttribute = Schema.Struct({ key: Schema.String, value: Schema.String });
|
|
77
77
|
const personProfiles = State.SQLite.table({
|
|
78
78
|
name: 'person_profiles',
|
|
@@ -86,417 +86,286 @@ const personProfiles = State.SQLite.table({
|
|
|
86
86
|
},
|
|
87
87
|
});
|
|
88
88
|
const db = { todos, todosWithIntId, comments, issue, selections, UiState, UiStateWithDefaultId, personProfiles };
|
|
89
|
-
const dump = (qb) => ({
|
|
90
|
-
bindValues: qb.asSql().bindValues,
|
|
91
|
-
query: qb.asSql().query,
|
|
92
|
-
schema: objectToString(getResultSchema(qb)),
|
|
93
|
-
});
|
|
94
89
|
describe('query builder', () => {
|
|
95
90
|
describe('basic queries', () => {
|
|
96
91
|
it('should handle simple SELECT queries', () => {
|
|
97
|
-
expect(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
92
|
+
expect(db.todos.asSql()).toEqual({
|
|
93
|
+
bindValues: [],
|
|
94
|
+
query: "SELECT * FROM 'todos'",
|
|
95
|
+
usedTables: new Set(['todos']),
|
|
96
|
+
});
|
|
97
|
+
expect(db.todos.select('id').asSql()).toEqual({
|
|
98
|
+
bindValues: [],
|
|
99
|
+
query: 'SELECT "id" FROM \'todos\'',
|
|
100
|
+
usedTables: new Set(['todos']),
|
|
101
|
+
});
|
|
102
|
+
expect(db.todos.select('id', 'text').asSql()).toEqual({
|
|
103
|
+
bindValues: [],
|
|
104
|
+
query: 'SELECT "id", "text" FROM \'todos\'',
|
|
105
|
+
usedTables: new Set(['todos']),
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
it('derives result schemas for SELECT queries', async () => {
|
|
109
|
+
const selectedRows = new TestSchema.Asserts(getResultSchema(db.todos.select('id', 'text')));
|
|
110
|
+
await selectedRows.decoding().succeed([{ id: '123', text: 'Buy milk' }]);
|
|
111
|
+
const selectedColumn = new TestSchema.Asserts(getResultSchema(db.todos.select('id')));
|
|
112
|
+
await selectedColumn.decoding().succeed([{ id: '123' }], ['123']);
|
|
118
113
|
});
|
|
119
114
|
it('should handle .first()', () => {
|
|
120
|
-
expect(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
{
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
115
|
+
expect(db.todos.select('id', 'text').first().asSql()).toEqual({
|
|
116
|
+
bindValues: [1],
|
|
117
|
+
query: 'SELECT "id", "text" FROM \'todos\' LIMIT ?',
|
|
118
|
+
usedTables: new Set(['todos']),
|
|
119
|
+
});
|
|
120
|
+
expect(db.todos.select('id', 'text').first({ behaviour: 'error' }).asSql()).toEqual({
|
|
121
|
+
bindValues: [1],
|
|
122
|
+
query: 'SELECT "id", "text" FROM \'todos\' LIMIT ?',
|
|
123
|
+
usedTables: new Set(['todos']),
|
|
124
|
+
});
|
|
125
|
+
expect(db.todos
|
|
126
|
+
.select('id', 'text')
|
|
127
|
+
.first({ behaviour: 'fallback', fallback: () => undefined })
|
|
128
|
+
.asSql()).toEqual({
|
|
129
|
+
bindValues: [1],
|
|
130
|
+
query: 'SELECT "id", "text" FROM \'todos\' LIMIT ?',
|
|
131
|
+
usedTables: new Set(['todos']),
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
it('derives result schemas for .first()', async () => {
|
|
135
|
+
const firstOrUndefined = new TestSchema.Asserts(getResultSchema(db.todos.select('id', 'text').first()));
|
|
136
|
+
await firstOrUndefined.decoding().succeed([], undefined);
|
|
137
|
+
await firstOrUndefined.decoding().succeed([{ id: '123', text: 'Buy milk' }], { id: '123', text: 'Buy milk' });
|
|
138
|
+
const firstOrError = new TestSchema.Asserts(getResultSchema(db.todos.select('id', 'text').first({ behaviour: 'error' })));
|
|
139
|
+
await firstOrError.decoding().succeed([{ id: '123', text: 'Buy milk' }], { id: '123', text: 'Buy milk' });
|
|
140
|
+
await firstOrError.decoding().fail([], 'Unable to retrieve the first element of an empty array');
|
|
141
|
+
const firstOrFallback = new TestSchema.Asserts(getResultSchema(db.todos.select('id', 'text').first({ behaviour: 'fallback', fallback: () => undefined })));
|
|
142
|
+
await firstOrFallback.decoding().succeed([], undefined);
|
|
147
143
|
});
|
|
148
144
|
it('should handle WHERE clauses', () => {
|
|
149
|
-
expect(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
"bindValues": [
|
|
195
|
-
"active",
|
|
196
|
-
],
|
|
197
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "status" IN (?)",
|
|
198
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
199
|
-
}
|
|
200
|
-
`);
|
|
201
|
-
expect(dump(db.todos.select('id', 'text').where({ status: { op: 'NOT IN', value: ['active', 'completed'] } }))).toMatchInlineSnapshot(`
|
|
202
|
-
{
|
|
203
|
-
"bindValues": [
|
|
204
|
-
"active",
|
|
205
|
-
"completed",
|
|
206
|
-
],
|
|
207
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "status" NOT IN (?, ?)",
|
|
208
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
209
|
-
}
|
|
210
|
-
`);
|
|
211
|
-
expect(dump(db.todos
|
|
145
|
+
expect(db.todos.select('id', 'text').where('completed', true).asSql()).toEqual({
|
|
146
|
+
bindValues: [1],
|
|
147
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ?',
|
|
148
|
+
usedTables: new Set(['todos']),
|
|
149
|
+
});
|
|
150
|
+
expect(db.todos.select('id', 'text').where('completed', '!=', true).asSql()).toEqual({
|
|
151
|
+
bindValues: [1],
|
|
152
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" != ?',
|
|
153
|
+
usedTables: new Set(['todos']),
|
|
154
|
+
});
|
|
155
|
+
expect(db.todos.select('id', 'text').where({ completed: true }).asSql()).toEqual({
|
|
156
|
+
bindValues: [1],
|
|
157
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ?',
|
|
158
|
+
usedTables: new Set(['todos']),
|
|
159
|
+
});
|
|
160
|
+
expect(db.todos.select('id', 'text').where({ completed: undefined }).asSql()).toEqual({
|
|
161
|
+
bindValues: [],
|
|
162
|
+
query: 'SELECT "id", "text" FROM \'todos\'',
|
|
163
|
+
usedTables: new Set(['todos']),
|
|
164
|
+
});
|
|
165
|
+
expect(db.todos
|
|
166
|
+
.select('id', 'text')
|
|
167
|
+
.where({ deletedAt: { op: '<=', value: new Date('2024-01-01') } })
|
|
168
|
+
.asSql()).toEqual({
|
|
169
|
+
bindValues: ['2024-01-01T00:00:00.000Z'],
|
|
170
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "deletedAt" <= ?',
|
|
171
|
+
usedTables: new Set(['todos']),
|
|
172
|
+
});
|
|
173
|
+
expect(db.todos
|
|
174
|
+
.select('id', 'text')
|
|
175
|
+
.where({ status: { op: 'IN', value: ['active'] } })
|
|
176
|
+
.asSql()).toEqual({
|
|
177
|
+
bindValues: ['active'],
|
|
178
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "status" IN (?)',
|
|
179
|
+
usedTables: new Set(['todos']),
|
|
180
|
+
});
|
|
181
|
+
expect(db.todos
|
|
182
|
+
.select('id', 'text')
|
|
183
|
+
.where({ status: { op: 'NOT IN', value: ['active', 'completed'] } })
|
|
184
|
+
.asSql()).toEqual({
|
|
185
|
+
bindValues: ['active', 'completed'],
|
|
186
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "status" NOT IN (?, ?)',
|
|
187
|
+
usedTables: new Set(['todos']),
|
|
188
|
+
});
|
|
189
|
+
expect(db.todos
|
|
212
190
|
.select('id', 'text')
|
|
213
191
|
.where({ completed: false })
|
|
214
192
|
.where({ status: { op: 'IN', value: ['active'] } })
|
|
215
|
-
.where({ deletedAt: undefined })
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ? AND "status" IN (?)",
|
|
222
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
223
|
-
}
|
|
224
|
-
`);
|
|
193
|
+
.where({ deletedAt: undefined })
|
|
194
|
+
.asSql()).toEqual({
|
|
195
|
+
bindValues: [0, 'active'],
|
|
196
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? AND "status" IN (?)',
|
|
197
|
+
usedTables: new Set(['todos']),
|
|
198
|
+
});
|
|
225
199
|
});
|
|
226
200
|
it('should handle OFFSET and LIMIT clauses', () => {
|
|
227
|
-
expect(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
10,
|
|
233
|
-
],
|
|
234
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ? LIMIT ? OFFSET ?",
|
|
235
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
236
|
-
}
|
|
237
|
-
`);
|
|
201
|
+
expect(db.todos.select('id', 'text').where('completed', true).offset(10).limit(10).asSql()).toEqual({
|
|
202
|
+
bindValues: [1, 10, 10],
|
|
203
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? LIMIT ? OFFSET ?',
|
|
204
|
+
usedTables: new Set(['todos']),
|
|
205
|
+
});
|
|
238
206
|
});
|
|
239
207
|
it('should handle OFFSET and LIMIT clauses correctly', () => {
|
|
240
208
|
// Test with both offset and limit
|
|
241
|
-
expect(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
5,
|
|
247
|
-
],
|
|
248
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ? LIMIT ? OFFSET ?",
|
|
249
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
250
|
-
}
|
|
251
|
-
`);
|
|
209
|
+
expect(db.todos.select('id', 'text').where('completed', true).offset(5).limit(10).asSql()).toEqual({
|
|
210
|
+
bindValues: [1, 10, 5],
|
|
211
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? LIMIT ? OFFSET ?',
|
|
212
|
+
usedTables: new Set(['todos']),
|
|
213
|
+
});
|
|
252
214
|
// Test with only offset
|
|
253
|
-
expect(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
],
|
|
259
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ? OFFSET ?",
|
|
260
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
261
|
-
}
|
|
262
|
-
`);
|
|
215
|
+
expect(db.todos.select('id', 'text').where('completed', true).offset(5).asSql()).toEqual({
|
|
216
|
+
bindValues: [1, 5],
|
|
217
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? OFFSET ?',
|
|
218
|
+
usedTables: new Set(['todos']),
|
|
219
|
+
});
|
|
263
220
|
// Test with only limit
|
|
264
|
-
expect(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
],
|
|
270
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "completed" = ? LIMIT ?",
|
|
271
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
272
|
-
}
|
|
273
|
-
`);
|
|
221
|
+
expect(db.todos.select('id', 'text').where('completed', true).limit(10).asSql()).toEqual({
|
|
222
|
+
bindValues: [1, 10],
|
|
223
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "completed" = ? LIMIT ?',
|
|
224
|
+
usedTables: new Set(['todos']),
|
|
225
|
+
});
|
|
274
226
|
});
|
|
275
227
|
it('should handle COUNT queries', () => {
|
|
276
|
-
expect(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
1,
|
|
296
|
-
],
|
|
297
|
-
"query": "SELECT COUNT(*) as count FROM 'todos' WHERE "completed" = ?",
|
|
298
|
-
"schema": "(ReadonlyArray<({ readonly count: number } <-> number)> <-> number)",
|
|
299
|
-
}
|
|
300
|
-
`);
|
|
228
|
+
expect(db.todos.count().asSql()).toEqual({
|
|
229
|
+
bindValues: [],
|
|
230
|
+
query: "SELECT COUNT(*) as count FROM 'todos'",
|
|
231
|
+
usedTables: new Set(['todos']),
|
|
232
|
+
});
|
|
233
|
+
expect(db.todos.count().where('completed', true).asSql()).toEqual({
|
|
234
|
+
bindValues: [1],
|
|
235
|
+
query: 'SELECT COUNT(*) as count FROM \'todos\' WHERE "completed" = ?',
|
|
236
|
+
usedTables: new Set(['todos']),
|
|
237
|
+
});
|
|
238
|
+
expect(db.todos.where('completed', true).count().asSql()).toEqual({
|
|
239
|
+
bindValues: [1],
|
|
240
|
+
query: 'SELECT COUNT(*) as count FROM \'todos\' WHERE "completed" = ?',
|
|
241
|
+
usedTables: new Set(['todos']),
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
it('derives result schemas for COUNT queries', async () => {
|
|
245
|
+
const countResult = new TestSchema.Asserts(getResultSchema(db.todos.count()));
|
|
246
|
+
await countResult.decoding().succeed([{ count: 3 }], 3);
|
|
301
247
|
});
|
|
302
248
|
it('should handle NULL comparisons', () => {
|
|
303
|
-
expect(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
"query": "SELECT "id", "text" FROM 'todos' WHERE "deletedAt" IS NOT NULL",
|
|
314
|
-
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
315
|
-
}
|
|
316
|
-
`);
|
|
249
|
+
expect(db.todos.select('id', 'text').where('deletedAt', '=', null).asSql()).toEqual({
|
|
250
|
+
bindValues: [],
|
|
251
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "deletedAt" IS NULL',
|
|
252
|
+
usedTables: new Set(['todos']),
|
|
253
|
+
});
|
|
254
|
+
expect(db.todos.select('id', 'text').where('deletedAt', '!=', null).asSql()).toEqual({
|
|
255
|
+
bindValues: [],
|
|
256
|
+
query: 'SELECT "id", "text" FROM \'todos\' WHERE "deletedAt" IS NOT NULL',
|
|
257
|
+
usedTables: new Set(['todos']),
|
|
258
|
+
});
|
|
317
259
|
});
|
|
318
260
|
it('should handle orderBy', () => {
|
|
319
|
-
expect(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
{
|
|
335
|
-
"bindValues": [],
|
|
336
|
-
"query": "SELECT * FROM 'todos'",
|
|
337
|
-
"schema": "ReadonlyArray<todos>",
|
|
338
|
-
}
|
|
339
|
-
`);
|
|
261
|
+
expect(db.todos.orderBy('completed', 'desc').asSql()).toEqual({
|
|
262
|
+
bindValues: [],
|
|
263
|
+
query: 'SELECT * FROM \'todos\' ORDER BY "completed" desc',
|
|
264
|
+
usedTables: new Set(['todos']),
|
|
265
|
+
});
|
|
266
|
+
expect(db.todos.orderBy([{ col: 'completed', direction: 'desc' }]).asSql()).toEqual({
|
|
267
|
+
bindValues: [],
|
|
268
|
+
query: 'SELECT * FROM \'todos\' ORDER BY "completed" desc',
|
|
269
|
+
usedTables: new Set(['todos']),
|
|
270
|
+
});
|
|
271
|
+
expect(db.todos.orderBy([]).asSql()).toEqual({
|
|
272
|
+
bindValues: [],
|
|
273
|
+
query: "SELECT * FROM 'todos'",
|
|
274
|
+
usedTables: new Set(['todos']),
|
|
275
|
+
});
|
|
340
276
|
});
|
|
341
277
|
it('should handle JSON_CONTAINS operator for JSON array columns', () => {
|
|
342
|
-
expect(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
"query": "SELECT * FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)",
|
|
348
|
-
"schema": "ReadonlyArray<person_profiles>",
|
|
349
|
-
}
|
|
350
|
-
`);
|
|
278
|
+
expect(db.personProfiles.where({ sources: { op: 'JSON_CONTAINS', value: 'google' } }).asSql()).toEqual({
|
|
279
|
+
bindValues: ['google'],
|
|
280
|
+
query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)',
|
|
281
|
+
usedTables: new Set(['person_profiles']),
|
|
282
|
+
});
|
|
351
283
|
// With select
|
|
352
|
-
expect(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
`);
|
|
284
|
+
expect(db.personProfiles
|
|
285
|
+
.select('personId')
|
|
286
|
+
.where({ sources: { op: 'JSON_CONTAINS', value: 'linkedin' } })
|
|
287
|
+
.asSql()).toEqual({
|
|
288
|
+
bindValues: ['linkedin'],
|
|
289
|
+
query: 'SELECT "personId" FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)',
|
|
290
|
+
usedTables: new Set(['person_profiles']),
|
|
291
|
+
});
|
|
361
292
|
// With plain string array column
|
|
362
|
-
expect(
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
"query": "SELECT * FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("tags") WHERE value = ?)",
|
|
368
|
-
"schema": "ReadonlyArray<person_profiles>",
|
|
369
|
-
}
|
|
370
|
-
`);
|
|
293
|
+
expect(db.personProfiles.where({ tags: { op: 'JSON_CONTAINS', value: 'important' } }).asSql()).toEqual({
|
|
294
|
+
bindValues: ['important'],
|
|
295
|
+
query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("tags") WHERE value = ?)',
|
|
296
|
+
usedTables: new Set(['person_profiles']),
|
|
297
|
+
});
|
|
371
298
|
});
|
|
372
299
|
it('should handle JSON_NOT_CONTAINS operator for JSON array columns', () => {
|
|
373
|
-
expect(
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
"query": "SELECT * FROM 'person_profiles' WHERE NOT EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)",
|
|
379
|
-
"schema": "ReadonlyArray<person_profiles>",
|
|
380
|
-
}
|
|
381
|
-
`);
|
|
300
|
+
expect(db.personProfiles.where({ sources: { op: 'JSON_NOT_CONTAINS', value: 'google' } }).asSql()).toEqual({
|
|
301
|
+
bindValues: ['google'],
|
|
302
|
+
query: 'SELECT * FROM \'person_profiles\' WHERE NOT EXISTS (SELECT 1 FROM json_each("sources") WHERE value = ?)',
|
|
303
|
+
usedTables: new Set(['person_profiles']),
|
|
304
|
+
});
|
|
382
305
|
});
|
|
383
306
|
it('should JSON-stringify object elements for JSON_CONTAINS', () => {
|
|
384
|
-
expect(
|
|
307
|
+
expect(db.personProfiles
|
|
308
|
+
.where({
|
|
385
309
|
attributes: { op: 'JSON_CONTAINS', value: { key: 'language', value: 'typescript' } },
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
"schema": "ReadonlyArray<person_profiles>",
|
|
393
|
-
}
|
|
394
|
-
`);
|
|
310
|
+
})
|
|
311
|
+
.asSql()).toEqual({
|
|
312
|
+
bindValues: ['{"key":"language","value":"typescript"}'],
|
|
313
|
+
query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("attributes") WHERE value = ?)',
|
|
314
|
+
usedTables: new Set(['person_profiles']),
|
|
315
|
+
});
|
|
395
316
|
});
|
|
396
317
|
it('should handle combining JSON_CONTAINS with other WHERE clauses', () => {
|
|
397
|
-
expect(
|
|
318
|
+
expect(db.personProfiles
|
|
398
319
|
.where({ sources: { op: 'JSON_CONTAINS', value: 'google' } })
|
|
399
|
-
.where({ sources: { op: 'JSON_NOT_CONTAINS', value: 'facebook' } })
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
"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 = ?)",
|
|
406
|
-
"schema": "ReadonlyArray<person_profiles>",
|
|
407
|
-
}
|
|
408
|
-
`);
|
|
320
|
+
.where({ sources: { op: 'JSON_NOT_CONTAINS', value: 'facebook' } })
|
|
321
|
+
.asSql()).toEqual({
|
|
322
|
+
bindValues: ['google', 'facebook'],
|
|
323
|
+
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 = ?)',
|
|
324
|
+
usedTables: new Set(['person_profiles']),
|
|
325
|
+
});
|
|
409
326
|
});
|
|
410
327
|
it('should handle JSON_CONTAINS on nullable JSON array columns', () => {
|
|
411
|
-
expect(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
"query": "SELECT * FROM 'person_profiles' WHERE EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)",
|
|
417
|
-
"schema": "ReadonlyArray<person_profiles>",
|
|
418
|
-
}
|
|
419
|
-
`);
|
|
328
|
+
expect(db.personProfiles.where({ optionalTags: { op: 'JSON_CONTAINS', value: 'important' } }).asSql()).toEqual({
|
|
329
|
+
bindValues: ['important'],
|
|
330
|
+
query: 'SELECT * FROM \'person_profiles\' WHERE EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)',
|
|
331
|
+
usedTables: new Set(['person_profiles']),
|
|
332
|
+
});
|
|
420
333
|
// With JSON_NOT_CONTAINS
|
|
421
|
-
expect(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
"query": "SELECT * FROM 'person_profiles' WHERE NOT EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)",
|
|
427
|
-
"schema": "ReadonlyArray<person_profiles>",
|
|
428
|
-
}
|
|
429
|
-
`);
|
|
334
|
+
expect(db.personProfiles.where({ optionalTags: { op: 'JSON_NOT_CONTAINS', value: 'archived' } }).asSql()).toEqual({
|
|
335
|
+
bindValues: ['archived'],
|
|
336
|
+
query: 'SELECT * FROM \'person_profiles\' WHERE NOT EXISTS (SELECT 1 FROM json_each("optionalTags") WHERE value = ?)',
|
|
337
|
+
usedTables: new Set(['person_profiles']),
|
|
338
|
+
});
|
|
430
339
|
});
|
|
431
340
|
it('should throw error when using JSON_CONTAINS on non-JSON array column', () => {
|
|
432
341
|
expect(() =>
|
|
433
342
|
// Type system prevents this at compile time for non-array columns, but test runtime check
|
|
434
|
-
|
|
343
|
+
db.todos.where({ status: { op: 'JSON_CONTAINS', value: 'active' } }).asSql()).toThrow('JSON_CONTAINS operator can only be used on JSON array columns');
|
|
435
344
|
});
|
|
436
345
|
});
|
|
437
|
-
// describe('getOrCreate queries', () => {
|
|
438
|
-
// it('should handle getOrCreate queries', () => {
|
|
439
|
-
// expect(dump(db.UiState.getOrCreate('sessionid-1'))).toMatchInlineSnapshot(`
|
|
440
|
-
// {
|
|
441
|
-
// "bindValues": [
|
|
442
|
-
// "sessionid-1",
|
|
443
|
-
// ],
|
|
444
|
-
// "query": "SELECT * FROM 'UiState' WHERE id = ?",
|
|
445
|
-
// "schema": "...", // TODO determine schema
|
|
446
|
-
// }
|
|
447
|
-
// `)
|
|
448
|
-
// })
|
|
449
|
-
// it('should handle getOrCreate queries with default id', () => {
|
|
450
|
-
// expect(dump(db.UiStateWithDefaultId.getOrCreate())).toMatchInlineSnapshot(`
|
|
451
|
-
// {
|
|
452
|
-
// "bindValues": [],
|
|
453
|
-
// "query": "SELECT * FROM 'UiState' WHERE id = ?",
|
|
454
|
-
// "schema": "...", // TODO determine schema
|
|
455
|
-
// }
|
|
456
|
-
// `)
|
|
457
|
-
// })
|
|
458
|
-
// // it('should handle row queries with numbers', () => {
|
|
459
|
-
// // expect(dump(db.todosWithIntId.getOrCreate(123, { insertValues: { status: 'active' } }))).toMatchInlineSnapshot(`
|
|
460
|
-
// // {
|
|
461
|
-
// // "bindValues": [
|
|
462
|
-
// // 123,
|
|
463
|
-
// // ],
|
|
464
|
-
// // "query": "SELECT * FROM 'todos_with_int_id' WHERE id = ?",
|
|
465
|
-
// // "schema": "...", // TODO determine schema
|
|
466
|
-
// // }
|
|
467
|
-
// // `)
|
|
468
|
-
// // })
|
|
469
|
-
// })
|
|
470
346
|
describe('write operations', () => {
|
|
471
347
|
it('should handle INSERT queries', () => {
|
|
472
|
-
expect(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
`);
|
|
348
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).asSql()).toEqual({
|
|
349
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
350
|
+
query: `INSERT INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?)`,
|
|
351
|
+
usedTables: new Set(['todos']),
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
it('derives result schemas for write queries', async () => {
|
|
355
|
+
const insertResult = new TestSchema.Asserts(getResultSchema(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' })));
|
|
356
|
+
await insertResult.decoding().succeed(1);
|
|
483
357
|
});
|
|
484
358
|
it('should handle INSERT queries with undefined values', () => {
|
|
485
|
-
expect(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
"active",
|
|
491
|
-
],
|
|
492
|
-
"query": "INSERT INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?)",
|
|
493
|
-
"schema": "number",
|
|
494
|
-
}
|
|
495
|
-
`);
|
|
359
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).asSql()).toEqual({
|
|
360
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
361
|
+
query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?)',
|
|
362
|
+
usedTables: new Set(['todos']),
|
|
363
|
+
});
|
|
496
364
|
});
|
|
497
365
|
// Test helped to catch a bindValues ordering bug
|
|
498
366
|
it('should handle INSERT queries (issue)', () => {
|
|
499
|
-
expect(
|
|
367
|
+
expect(db.issue
|
|
368
|
+
.insert({
|
|
500
369
|
id: 1,
|
|
501
370
|
title: 'Revert the user profile page',
|
|
502
371
|
priority: 2,
|
|
@@ -504,236 +373,144 @@ describe('query builder', () => {
|
|
|
504
373
|
modified: new Date('2024-12-29T17:15:20.507Z'),
|
|
505
374
|
kanbanorder: 'a2',
|
|
506
375
|
creator: 'John Doe',
|
|
507
|
-
})
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
1722532520507,
|
|
514
|
-
1735492520507,
|
|
515
|
-
"a2",
|
|
516
|
-
"John Doe",
|
|
517
|
-
],
|
|
518
|
-
"query": "INSERT INTO 'issue' ("id", "title", "priority", "created", "modified", "kanbanorder", "creator") VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
519
|
-
"schema": "number",
|
|
520
|
-
}
|
|
521
|
-
`);
|
|
376
|
+
})
|
|
377
|
+
.asSql()).toEqual({
|
|
378
|
+
bindValues: [1, 'Revert the user profile page', 2, 1722532520507, 1735492520507, 'a2', 'John Doe'],
|
|
379
|
+
query: 'INSERT INTO \'issue\' ("id", "title", "priority", "created", "modified", "kanbanorder", "creator") VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
380
|
+
usedTables: new Set(['issue']),
|
|
381
|
+
});
|
|
522
382
|
});
|
|
523
383
|
it('should handle UPDATE queries', () => {
|
|
524
|
-
expect(
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
],
|
|
530
|
-
"query": "UPDATE 'todos' SET "status" = ? WHERE "id" = ?",
|
|
531
|
-
"schema": "number",
|
|
532
|
-
}
|
|
533
|
-
`);
|
|
384
|
+
expect(db.todos.update({ status: 'completed' }).where({ id: '123' }).asSql()).toEqual({
|
|
385
|
+
bindValues: ['completed', '123'],
|
|
386
|
+
query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ?',
|
|
387
|
+
usedTables: new Set(['todos']),
|
|
388
|
+
});
|
|
534
389
|
// empty update set
|
|
535
|
-
expect(
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
`);
|
|
390
|
+
expect(db.todos.update({}).where({ id: '123' }).asSql()).toEqual({
|
|
391
|
+
bindValues: [],
|
|
392
|
+
query: 'SELECT 1',
|
|
393
|
+
usedTables: new Set(['todos']),
|
|
394
|
+
});
|
|
542
395
|
});
|
|
543
396
|
it('should handle UPDATE queries with undefined values', () => {
|
|
544
|
-
expect(
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
],
|
|
550
|
-
"query": "UPDATE 'todos' SET "text" = ? WHERE "id" = ?",
|
|
551
|
-
"schema": "number",
|
|
552
|
-
}
|
|
553
|
-
`);
|
|
397
|
+
expect(db.todos.update({ text: 'some text' }).where({ id: '123' }).asSql()).toEqual({
|
|
398
|
+
bindValues: ['some text', '123'],
|
|
399
|
+
query: 'UPDATE \'todos\' SET "text" = ? WHERE "id" = ?',
|
|
400
|
+
usedTables: new Set(['todos']),
|
|
401
|
+
});
|
|
554
402
|
});
|
|
555
403
|
it('should handle UPDATE queries with undefined values (issue)', () => {
|
|
556
|
-
expect(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
1,
|
|
562
|
-
],
|
|
563
|
-
"query": "UPDATE 'issue' SET "priority" = ?, "creator" = ? WHERE "id" = ?",
|
|
564
|
-
"schema": "number",
|
|
565
|
-
}
|
|
566
|
-
`);
|
|
404
|
+
expect(db.issue.update({ priority: 2, creator: 'John Doe' }).where({ id: 1 }).asSql()).toEqual({
|
|
405
|
+
bindValues: [2, 'John Doe', 1],
|
|
406
|
+
query: 'UPDATE \'issue\' SET "priority" = ?, "creator" = ? WHERE "id" = ?',
|
|
407
|
+
usedTables: new Set(['issue']),
|
|
408
|
+
});
|
|
567
409
|
});
|
|
568
410
|
it('should handle DELETE queries', () => {
|
|
569
|
-
expect(
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
"query": "DELETE FROM 'todos' WHERE "status" = ?",
|
|
575
|
-
"schema": "number",
|
|
576
|
-
}
|
|
577
|
-
`);
|
|
411
|
+
expect(db.todos.delete().where({ status: 'completed' }).asSql()).toEqual({
|
|
412
|
+
bindValues: ['completed'],
|
|
413
|
+
query: 'DELETE FROM \'todos\' WHERE "status" = ?',
|
|
414
|
+
usedTables: new Set(['todos']),
|
|
415
|
+
});
|
|
578
416
|
});
|
|
579
417
|
it('should handle INSERT with ON CONFLICT', () => {
|
|
580
|
-
expect(
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
],
|
|
587
|
-
"query": "INSERT INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id") DO NOTHING",
|
|
588
|
-
"schema": "number",
|
|
589
|
-
}
|
|
590
|
-
`);
|
|
591
|
-
expect(dump(db.todos
|
|
418
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'ignore').asSql()).toEqual({
|
|
419
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
420
|
+
query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id") DO NOTHING',
|
|
421
|
+
usedTables: new Set(['todos']),
|
|
422
|
+
});
|
|
423
|
+
expect(db.todos
|
|
592
424
|
.insert({ id: '123', text: 'Buy milk', status: 'active' })
|
|
593
|
-
.onConflict('id', 'update', { text: 'Buy soy milk', status: 'active' })
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
605
|
-
`);
|
|
606
|
-
expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'replace'))).toMatchInlineSnapshot(`
|
|
607
|
-
{
|
|
608
|
-
"bindValues": [
|
|
609
|
-
"123",
|
|
610
|
-
"Buy milk",
|
|
611
|
-
"active",
|
|
612
|
-
],
|
|
613
|
-
"query": "INSERT OR REPLACE INTO 'todos' ("id", "text", "status") VALUES (?, ?, ?)",
|
|
614
|
-
"schema": "number",
|
|
615
|
-
}
|
|
616
|
-
`);
|
|
425
|
+
.onConflict('id', 'update', { text: 'Buy soy milk', status: 'active' })
|
|
426
|
+
.asSql()).toEqual({
|
|
427
|
+
bindValues: ['123', 'Buy milk', 'active', 'Buy soy milk', 'active'],
|
|
428
|
+
query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id") DO UPDATE SET "text" = ?, "status" = ?',
|
|
429
|
+
usedTables: new Set(['todos']),
|
|
430
|
+
});
|
|
431
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'replace').asSql()).toEqual({
|
|
432
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
433
|
+
query: 'INSERT OR REPLACE INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?)',
|
|
434
|
+
usedTables: new Set(['todos']),
|
|
435
|
+
});
|
|
617
436
|
});
|
|
618
437
|
it('should quote reserved column names', () => {
|
|
619
|
-
expect(
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
expect(dump(db.selections.update({ group: 'beta' }).where({ id: 1 }))).toMatchInlineSnapshot(`
|
|
630
|
-
{
|
|
631
|
-
"bindValues": [
|
|
632
|
-
"beta",
|
|
633
|
-
1,
|
|
634
|
-
],
|
|
635
|
-
"query": "UPDATE 'selections' SET "group" = ? WHERE "id" = ?",
|
|
636
|
-
"schema": "number",
|
|
637
|
-
}
|
|
638
|
-
`);
|
|
438
|
+
expect(db.selections.insert({ id: 1, group: 'alpha' }).onConflict('id', 'ignore').asSql()).toEqual({
|
|
439
|
+
bindValues: [1, 'alpha'],
|
|
440
|
+
query: 'INSERT INTO \'selections\' ("id", "group") VALUES (?, ?) ON CONFLICT ("id") DO NOTHING',
|
|
441
|
+
usedTables: new Set(['selections']),
|
|
442
|
+
});
|
|
443
|
+
expect(db.selections.update({ group: 'beta' }).where({ id: 1 }).asSql()).toEqual({
|
|
444
|
+
bindValues: ['beta', 1],
|
|
445
|
+
query: 'UPDATE \'selections\' SET "group" = ? WHERE "id" = ?',
|
|
446
|
+
usedTables: new Set(['selections']),
|
|
447
|
+
});
|
|
639
448
|
});
|
|
640
449
|
it('should handle ON CONFLICT with multiple columns', () => {
|
|
641
|
-
expect(
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
"schema": "number",
|
|
650
|
-
}
|
|
651
|
-
`);
|
|
450
|
+
expect(db.todos
|
|
451
|
+
.insert({ id: '123', text: 'Buy milk', status: 'active' })
|
|
452
|
+
.onConflict(['id', 'status'], 'ignore')
|
|
453
|
+
.asSql()).toEqual({
|
|
454
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
455
|
+
query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) ON CONFLICT ("id", "status") DO NOTHING',
|
|
456
|
+
usedTables: new Set(['todos']),
|
|
457
|
+
});
|
|
652
458
|
});
|
|
653
459
|
it('should handle RETURNING clause', () => {
|
|
654
|
-
expect(
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
`);
|
|
675
|
-
expect(dump(db.todos.delete().where({ status: 'completed' }).returning('id'))).toMatchInlineSnapshot(`
|
|
676
|
-
{
|
|
677
|
-
"bindValues": [
|
|
678
|
-
"completed",
|
|
679
|
-
],
|
|
680
|
-
"query": "DELETE FROM 'todos' WHERE "status" = ? RETURNING "id"",
|
|
681
|
-
"schema": "ReadonlyArray<{ readonly id: string }>",
|
|
682
|
-
}
|
|
683
|
-
`);
|
|
460
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).returning('id').asSql()).toEqual({
|
|
461
|
+
bindValues: ['123', 'Buy milk', 'active'],
|
|
462
|
+
query: 'INSERT INTO \'todos\' ("id", "text", "status") VALUES (?, ?, ?) RETURNING "id"',
|
|
463
|
+
usedTables: new Set(['todos']),
|
|
464
|
+
});
|
|
465
|
+
expect(db.todos.update({ status: 'completed' }).where({ id: '123' }).returning('id').asSql()).toEqual({
|
|
466
|
+
bindValues: ['completed', '123'],
|
|
467
|
+
query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ? RETURNING "id"',
|
|
468
|
+
usedTables: new Set(['todos']),
|
|
469
|
+
});
|
|
470
|
+
expect(db.todos.delete().where({ status: 'completed' }).returning('id').asSql()).toEqual({
|
|
471
|
+
bindValues: ['completed'],
|
|
472
|
+
query: 'DELETE FROM \'todos\' WHERE "status" = ? RETURNING "id"',
|
|
473
|
+
usedTables: new Set(['todos']),
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
it('derives result schemas for RETURNING clauses', async () => {
|
|
477
|
+
const returningId = new TestSchema.Asserts(getResultSchema(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).returning('id')));
|
|
478
|
+
await returningId.decoding().succeed([{ id: '123' }]);
|
|
684
479
|
});
|
|
685
480
|
it('should handle where().delete() - preserving where clauses', () => {
|
|
686
|
-
expect(
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
"query": "DELETE FROM 'todos' WHERE "status" = ?",
|
|
692
|
-
"schema": "number",
|
|
693
|
-
}
|
|
694
|
-
`);
|
|
481
|
+
expect(db.todos.where({ status: 'completed' }).delete().asSql()).toEqual({
|
|
482
|
+
bindValues: ['completed'],
|
|
483
|
+
query: 'DELETE FROM \'todos\' WHERE "status" = ?',
|
|
484
|
+
usedTables: new Set(['todos']),
|
|
485
|
+
});
|
|
695
486
|
// Multiple where clauses
|
|
696
|
-
expect(
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
"query": "DELETE FROM 'todos' WHERE "status" = ? AND "deletedAt" IS NULL",
|
|
702
|
-
"schema": "number",
|
|
703
|
-
}
|
|
704
|
-
`);
|
|
487
|
+
expect(db.todos.where({ status: 'completed' }).where({ deletedAt: null }).delete().asSql()).toEqual({
|
|
488
|
+
bindValues: ['completed'],
|
|
489
|
+
query: 'DELETE FROM \'todos\' WHERE "status" = ? AND "deletedAt" IS NULL',
|
|
490
|
+
usedTables: new Set(['todos']),
|
|
491
|
+
});
|
|
705
492
|
});
|
|
706
493
|
it('should handle where().update() - preserving where clauses', () => {
|
|
707
|
-
expect(
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
],
|
|
713
|
-
"query": "UPDATE 'todos' SET "status" = ? WHERE "id" = ?",
|
|
714
|
-
"schema": "number",
|
|
715
|
-
}
|
|
716
|
-
`);
|
|
494
|
+
expect(db.todos.where({ id: '123' }).update({ status: 'completed' }).asSql()).toEqual({
|
|
495
|
+
bindValues: ['completed', '123'],
|
|
496
|
+
query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ?',
|
|
497
|
+
usedTables: new Set(['todos']),
|
|
498
|
+
});
|
|
717
499
|
// Multiple where clauses
|
|
718
|
-
expect(
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
],
|
|
724
|
-
"query": "UPDATE 'todos' SET "status" = ? WHERE "id" = ? AND "deletedAt" IS NULL",
|
|
725
|
-
"schema": "number",
|
|
726
|
-
}
|
|
727
|
-
`);
|
|
500
|
+
expect(db.todos.where({ id: '123' }).where({ deletedAt: null }).update({ status: 'completed' }).asSql()).toEqual({
|
|
501
|
+
bindValues: ['completed', '123'],
|
|
502
|
+
query: 'UPDATE \'todos\' SET "status" = ? WHERE "id" = ? AND "deletedAt" IS NULL',
|
|
503
|
+
usedTables: new Set(['todos']),
|
|
504
|
+
});
|
|
728
505
|
});
|
|
729
506
|
it('should have equivalent behavior for both delete patterns', () => {
|
|
730
|
-
const pattern1 =
|
|
731
|
-
const pattern2 =
|
|
507
|
+
const pattern1 = db.todos.where({ status: 'completed', id: '123' }).delete().asSql();
|
|
508
|
+
const pattern2 = db.todos.delete().where({ status: 'completed', id: '123' }).asSql();
|
|
732
509
|
expect(pattern1).toEqual(pattern2);
|
|
733
510
|
});
|
|
734
511
|
it('should have equivalent behavior for both update patterns', () => {
|
|
735
|
-
const pattern1 =
|
|
736
|
-
const pattern2 =
|
|
512
|
+
const pattern1 = db.todos.where({ id: '123' }).update({ status: 'completed', text: 'Updated' }).asSql();
|
|
513
|
+
const pattern2 = db.todos.update({ status: 'completed', text: 'Updated' }).where({ id: '123' }).asSql();
|
|
737
514
|
expect(pattern1).toEqual(pattern2);
|
|
738
515
|
});
|
|
739
516
|
});
|
|
@@ -744,14 +521,14 @@ describe('query builder', () => {
|
|
|
744
521
|
contactLastName: Schema.String,
|
|
745
522
|
contactEmail: Schema.String.pipe(State.SQLite.withUnique),
|
|
746
523
|
});
|
|
747
|
-
const Nested = Schema.
|
|
524
|
+
const Nested = Flat.pipe(Schema.decodeTo(Schema.Struct({
|
|
748
525
|
id: Schema.String,
|
|
749
526
|
contact: Schema.Struct({
|
|
750
527
|
firstName: Schema.String,
|
|
751
528
|
lastName: Schema.String,
|
|
752
529
|
email: Schema.String,
|
|
753
530
|
}),
|
|
754
|
-
}), {
|
|
531
|
+
}), SchemaTransformation.transform({
|
|
755
532
|
decode: ({ id, contactFirstName, contactLastName, contactEmail }) => ({
|
|
756
533
|
id,
|
|
757
534
|
contact: {
|
|
@@ -766,7 +543,7 @@ describe('query builder', () => {
|
|
|
766
543
|
contactLastName: contact.lastName,
|
|
767
544
|
contactEmail: contact.email,
|
|
768
545
|
}),
|
|
769
|
-
});
|
|
546
|
+
})));
|
|
770
547
|
const makeContactsTable = () => State.SQLite.table({
|
|
771
548
|
name: 'contacts',
|
|
772
549
|
schema: Nested,
|
|
@@ -776,7 +553,7 @@ describe('query builder', () => {
|
|
|
776
553
|
const contactsTable = makeContactsTable();
|
|
777
554
|
void contactsTable;
|
|
778
555
|
});
|
|
779
|
-
it('
|
|
556
|
+
it('encodes flat insert values for transformed schemas', () => {
|
|
780
557
|
const contactsTable = makeContactsTable();
|
|
781
558
|
expect(contactsTable
|
|
782
559
|
// TODO in the future we should use decoded types here instead of encoded
|
|
@@ -786,22 +563,13 @@ describe('query builder', () => {
|
|
|
786
563
|
contactLastName: 'Lovelace',
|
|
787
564
|
contactEmail: 'ada@example.com',
|
|
788
565
|
})
|
|
789
|
-
.asSql()).
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
],
|
|
797
|
-
"query": "INSERT INTO 'contacts' ("id", "contactFirstName", "contactLastName", "contactEmail") VALUES (?, ?, ?, ?)",
|
|
798
|
-
"usedTables": Set {
|
|
799
|
-
"contacts",
|
|
800
|
-
},
|
|
801
|
-
}
|
|
802
|
-
`);
|
|
803
|
-
});
|
|
804
|
-
it('fails to encode nested inserts because flat columns are required', () => {
|
|
566
|
+
.asSql()).toEqual({
|
|
567
|
+
bindValues: ['person-1', 'Ada', 'Lovelace', 'ada@example.com'],
|
|
568
|
+
query: 'INSERT INTO \'contacts\' ("id", "contactFirstName", "contactLastName", "contactEmail") VALUES (?, ?, ?, ?)',
|
|
569
|
+
usedTables: new Set(['contacts']),
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
it('rejects nested insert values because flat columns are required', () => {
|
|
805
573
|
const contactsTable = makeContactsTable();
|
|
806
574
|
expect(() => contactsTable
|
|
807
575
|
.insert({
|
|
@@ -813,14 +581,12 @@ describe('query builder', () => {
|
|
|
813
581
|
email: 'ada@example.com',
|
|
814
582
|
},
|
|
815
583
|
})
|
|
816
|
-
.asSql()).
|
|
817
|
-
[ParseError: contacts\n└─ ["contactFirstName"]\n └─ is missing]
|
|
818
|
-
`);
|
|
584
|
+
.asSql()).toThrowError(/contactFirstName/);
|
|
819
585
|
});
|
|
820
586
|
});
|
|
821
587
|
});
|
|
822
588
|
// TODO nested queries
|
|
823
|
-
// const rawSql = <A, I>(sql: string, params: { [key: string]: any }, schema: Schema.
|
|
589
|
+
// const rawSql = <A, I>(sql: string, params: { [key: string]: any }, schema: Schema.Codec<A, I>) =>
|
|
824
590
|
// ({
|
|
825
591
|
// sql,
|
|
826
592
|
// params,
|