@livestore/common 0.4.0-dev.22 → 0.4.0-dev.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/ClientSessionLeaderThreadProxy.d.ts +9 -9
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/WorkerTransportError.d.ts +11 -0
- package/dist/WorkerTransportError.d.ts.map +1 -0
- package/dist/WorkerTransportError.js +11 -0
- package/dist/WorkerTransportError.js.map +1 -0
- package/dist/adapter-types.d.ts +3 -3
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/bounded-collections.d.ts.map +1 -1
- package/dist/bounded-collections.js +6 -4
- package/dist/bounded-collections.js.map +1 -1
- package/dist/debug-info.js +4 -4
- package/dist/debug-info.js.map +1 -1
- package/dist/devtools/devtools-messages-common.js +1 -1
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/mod.js +1 -1
- package/dist/devtools/mod.js.map +1 -1
- package/dist/errors.d.ts +15 -15
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -11
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +20 -6
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +287 -257
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/RejectedPushError.d.ts +107 -0
- package/dist/leader-thread/RejectedPushError.d.ts.map +1 -0
- package/dist/leader-thread/RejectedPushError.js +78 -0
- package/dist/leader-thread/RejectedPushError.js.map +1 -0
- package/dist/leader-thread/connection.js +1 -1
- package/dist/leader-thread/connection.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +12 -11
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -2
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +25 -14
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +8 -3
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +7 -10
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -1
- package/dist/leader-thread/materialize-event.js +4 -4
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/recreate-db.js +1 -1
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -2
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -2
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/stream-events.d.ts.map +1 -1
- package/dist/leader-thread/stream-events.js +4 -3
- package/dist/leader-thread/stream-events.js.map +1 -1
- package/dist/leader-thread/types.d.ts +7 -6
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/logging.js +4 -4
- package/dist/logging.js.map +1 -1
- package/dist/make-client-session.js +2 -2
- package/dist/make-client-session.js.map +1 -1
- package/dist/materializer-helper.js +6 -6
- package/dist/materializer-helper.js.map +1 -1
- package/dist/otel.d.ts +1 -1
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +2 -2
- package/dist/otel.js.map +1 -1
- package/dist/rematerialize-from-eventlog.d.ts +1 -1
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +11 -9
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef/define.d.ts +2 -2
- package/dist/schema/EventDef/define.d.ts.map +1 -1
- package/dist/schema/EventDef/define.js +4 -4
- package/dist/schema/EventDef/define.js.map +1 -1
- package/dist/schema/EventDef/deprecated.js +3 -3
- package/dist/schema/EventDef/deprecated.js.map +1 -1
- package/dist/schema/EventDef/deprecated.test.js +1 -1
- package/dist/schema/EventDef/deprecated.test.js.map +1 -1
- package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/client.js +11 -11
- package/dist/schema/EventSequenceNumber/client.js.map +1 -1
- package/dist/schema/EventSequenceNumber.test.js +1 -1
- package/dist/schema/EventSequenceNumber.test.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/client.js +6 -3
- package/dist/schema/LiveStoreEvent/client.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.test.d.ts +2 -0
- package/dist/schema/LiveStoreEvent/client.test.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent/client.test.js +83 -0
- package/dist/schema/LiveStoreEvent/client.test.js.map +1 -0
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +7 -4
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +18 -6
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.js +1 -1
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-def.js +36 -34
- package/dist/schema/state/sqlite/column-def.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.test.js +7 -6
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-spec.js +8 -8
- package/dist/schema/state/sqlite/column-spec.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +2 -2
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +2 -2
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +11 -2
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +3 -5
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.d.ts +10 -2
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/astToSql.js +11 -11
- package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +28 -14
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +3 -2
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js +2 -2
- package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +5 -3
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +1 -1
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +57 -4
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/schema/unknown-events.d.ts +1 -1
- package/dist/schema/unknown-events.d.ts.map +1 -1
- package/dist/schema/unknown-events.js +1 -1
- package/dist/schema/unknown-events.js.map +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -1
- package/dist/schema-management/common.js +2 -2
- package/dist/schema-management/common.js.map +1 -1
- package/dist/schema-management/migrations.js +1 -1
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sql-queries/sql-queries.js +8 -6
- package/dist/sql-queries/sql-queries.js.map +1 -1
- package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
- package/dist/sql-queries/sql-query-builder.js.map +1 -1
- package/dist/sqlite-db-helper.js +3 -3
- package/dist/sqlite-db-helper.js.map +1 -1
- package/dist/sqlite-types.d.ts +2 -2
- package/dist/sqlite-types.d.ts.map +1 -1
- package/dist/sqlite-types.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +8 -9
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +95 -113
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/errors.d.ts +0 -38
- package/dist/sync/errors.d.ts.map +1 -1
- package/dist/sync/errors.js +3 -20
- package/dist/sync/errors.js.map +1 -1
- package/dist/sync/mock-sync-backend.d.ts +5 -3
- package/dist/sync/mock-sync-backend.d.ts.map +1 -1
- package/dist/sync/mock-sync-backend.js +70 -68
- package/dist/sync/mock-sync-backend.js.map +1 -1
- package/dist/sync/next/compact-events.js +6 -6
- package/dist/sync/next/compact-events.js.map +1 -1
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +6 -6
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +6 -6
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.js +3 -3
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.js +1 -1
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +2 -2
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.d.ts.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +2 -2
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/event-fixtures.js +2 -2
- package/dist/sync/next/test/event-fixtures.js.map +1 -1
- package/dist/sync/sync-backend-kv.d.ts.map +1 -1
- package/dist/sync/sync-backend-kv.js.map +1 -1
- package/dist/sync/sync-backend.d.ts +3 -3
- package/dist/sync/sync-backend.d.ts.map +1 -1
- package/dist/sync/sync-backend.js +1 -1
- package/dist/sync/sync-backend.js.map +1 -1
- package/dist/sync/sync.d.ts +20 -0
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/syncstate.d.ts +4 -17
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +51 -74
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +112 -96
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/transport-chunking.js +3 -3
- package/dist/sync/transport-chunking.js.map +1 -1
- package/dist/sync/validate-push-payload.d.ts +2 -2
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +4 -6
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/util.js +2 -2
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -5
- package/dist/version.js.map +1 -1
- package/package.json +66 -12
- package/src/ClientSessionLeaderThreadProxy.ts +9 -9
- package/src/WorkerTransportError.ts +12 -0
- package/src/adapter-types.ts +9 -3
- package/src/bounded-collections.ts +6 -5
- package/src/debug-info.ts +4 -4
- package/src/devtools/devtools-messages-common.ts +1 -1
- package/src/devtools/mod.ts +1 -1
- package/src/errors.ts +18 -17
- package/src/index.ts +2 -0
- package/src/leader-thread/LeaderSyncProcessor.ts +421 -392
- package/src/leader-thread/RejectedPushError.ts +106 -0
- package/src/leader-thread/connection.ts +1 -1
- package/src/leader-thread/eventlog.ts +16 -14
- package/src/leader-thread/leader-worker-devtools.ts +96 -66
- package/src/leader-thread/make-leader-thread-layer.test.ts +1 -1
- package/src/leader-thread/make-leader-thread-layer.ts +33 -31
- package/src/leader-thread/materialize-event.ts +4 -4
- package/src/leader-thread/recreate-db.ts +1 -1
- package/src/leader-thread/shutdown-channel.ts +2 -6
- package/src/leader-thread/stream-events.ts +10 -5
- package/src/leader-thread/types.ts +7 -6
- package/src/logging.ts +4 -4
- package/src/make-client-session.ts +2 -2
- package/src/materializer-helper.ts +9 -9
- package/src/otel.ts +3 -2
- package/src/rematerialize-from-eventlog.ts +60 -60
- package/src/schema/EventDef/define.ts +6 -6
- package/src/schema/EventDef/deprecated.test.ts +2 -1
- package/src/schema/EventDef/deprecated.ts +3 -3
- package/src/schema/EventSequenceNumber/client.ts +11 -11
- package/src/schema/EventSequenceNumber.test.ts +2 -1
- package/src/schema/LiveStoreEvent/client.test.ts +97 -0
- package/src/schema/LiveStoreEvent/client.ts +6 -3
- package/src/schema/schema.ts +9 -4
- package/src/schema/state/sqlite/client-document-def.test.ts +2 -1
- package/src/schema/state/sqlite/client-document-def.ts +20 -6
- package/src/schema/state/sqlite/column-annotations.test.ts +2 -1
- package/src/schema/state/sqlite/column-annotations.ts +2 -1
- package/src/schema/state/sqlite/column-def.test.ts +8 -6
- package/src/schema/state/sqlite/column-def.ts +41 -36
- package/src/schema/state/sqlite/column-spec.test.ts +3 -1
- package/src/schema/state/sqlite/column-spec.ts +9 -8
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -2
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +2 -1
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +13 -4
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +3 -3
- package/src/schema/state/sqlite/mod.ts +4 -5
- package/src/schema/state/sqlite/query-builder/api.ts +12 -5
- package/src/schema/state/sqlite/query-builder/astToSql.ts +11 -11
- package/src/schema/state/sqlite/query-builder/impl.test.ts +4 -2
- package/src/schema/state/sqlite/query-builder/impl.ts +26 -12
- package/src/schema/state/sqlite/schema-helpers.ts +2 -2
- package/src/schema/state/sqlite/table-def.test.ts +67 -4
- package/src/schema/state/sqlite/table-def.ts +8 -15
- package/src/schema/unknown-events.ts +2 -2
- package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +3 -1
- package/src/schema-management/common.ts +2 -2
- package/src/schema-management/migrations.ts +1 -1
- package/src/sql-queries/sql-queries.ts +10 -6
- package/src/sql-queries/sql-query-builder.ts +1 -0
- package/src/sqlite-db-helper.ts +3 -3
- package/src/sqlite-types.ts +3 -2
- package/src/sync/ClientSessionSyncProcessor.ts +148 -152
- package/src/sync/errors.ts +10 -22
- package/src/sync/mock-sync-backend.ts +139 -97
- package/src/sync/next/compact-events.ts +5 -5
- package/src/sync/next/facts.ts +7 -6
- package/src/sync/next/history-dag-common.ts +9 -6
- package/src/sync/next/history-dag.ts +3 -3
- package/src/sync/next/rebase-events.ts +1 -1
- package/src/sync/next/test/compact-events.calculator.test.ts +3 -2
- package/src/sync/next/test/compact-events.test.ts +4 -3
- package/src/sync/next/test/event-fixtures.ts +2 -2
- package/src/sync/sync-backend-kv.ts +1 -0
- package/src/sync/sync-backend.ts +5 -4
- package/src/sync/sync.ts +21 -0
- package/src/sync/syncstate.test.ts +513 -435
- package/src/sync/syncstate.ts +80 -86
- package/src/sync/transport-chunking.ts +3 -3
- package/src/sync/validate-push-payload.ts +4 -6
- package/src/util.ts +2 -2
- package/src/version.ts +2 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { memoizeByRef } from '@livestore/utils'
|
|
1
|
+
import { deepEqual, memoizeByRef } from '@livestore/utils'
|
|
2
2
|
import { Option, Schema } from '@livestore/utils/effect'
|
|
3
3
|
|
|
4
4
|
import type { EventDef } from '../EventDef/mod.ts'
|
|
@@ -175,9 +175,12 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('LiveStoreEve
|
|
|
175
175
|
})
|
|
176
176
|
|
|
177
177
|
toGlobal = (): Global.Encoded => ({
|
|
178
|
-
|
|
178
|
+
name: this.name,
|
|
179
|
+
args: this.args,
|
|
179
180
|
seqNum: this.seqNum.global,
|
|
180
181
|
parentSeqNum: this.parentSeqNum.global,
|
|
182
|
+
clientId: this.clientId,
|
|
183
|
+
sessionId: this.sessionId,
|
|
181
184
|
})
|
|
182
185
|
}
|
|
183
186
|
|
|
@@ -191,7 +194,7 @@ export const isEqualEncoded = (a: Encoded, b: Encoded) =>
|
|
|
191
194
|
a.name === b.name &&
|
|
192
195
|
a.clientId === b.clientId &&
|
|
193
196
|
a.sessionId === b.sessionId &&
|
|
194
|
-
|
|
197
|
+
deepEqual(a.args, b.args) // TODO use schema equality here
|
|
195
198
|
|
|
196
199
|
/**
|
|
197
200
|
* Creates an Effect Schema union for all event types in a schema (with composite sequence numbers).
|
package/src/schema/schema.ts
CHANGED
|
@@ -58,7 +58,12 @@ export const isLiveStoreSchema = (value: unknown): value is LiveStoreSchema<any,
|
|
|
58
58
|
const hasStateMaterializers = v.state?.materializers instanceof Map
|
|
59
59
|
const hasDevtoolsAlias = typeof v.devtools?.alias === 'string'
|
|
60
60
|
|
|
61
|
-
return
|
|
61
|
+
return (
|
|
62
|
+
hasEventsMap === true &&
|
|
63
|
+
hasStateSqliteTables === true &&
|
|
64
|
+
hasStateMaterializers === true &&
|
|
65
|
+
hasDevtoolsAlias === true
|
|
66
|
+
)
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
// TODO abstract this further away from sqlite/tables
|
|
@@ -103,13 +108,13 @@ export const makeSchema = <TInputSchema extends InputSchema>(
|
|
|
103
108
|
|
|
104
109
|
const eventsDefsMap = new Map<string, EventDef.AnyWithoutFn>()
|
|
105
110
|
|
|
106
|
-
if (isReadonlyArray(inputSchema.events)) {
|
|
111
|
+
if (isReadonlyArray(inputSchema.events) === true) {
|
|
107
112
|
for (const eventDef of inputSchema.events) {
|
|
108
113
|
eventsDefsMap.set(eventDef.name, eventDef)
|
|
109
114
|
}
|
|
110
115
|
} else {
|
|
111
116
|
for (const eventDef of Object.values(inputSchema.events ?? {})) {
|
|
112
|
-
if (eventsDefsMap.has(eventDef.name)) {
|
|
117
|
+
if (eventsDefsMap.has(eventDef.name) === true) {
|
|
113
118
|
shouldNeverHappen(`Duplicate event name: ${eventDef.name}. Please use unique names for events.`)
|
|
114
119
|
}
|
|
115
120
|
eventsDefsMap.set(eventDef.name, eventDef)
|
|
@@ -117,7 +122,7 @@ export const makeSchema = <TInputSchema extends InputSchema>(
|
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
for (const tableDef of tables.values()) {
|
|
120
|
-
if (tableIsClientDocumentTable(tableDef) && eventsDefsMap.has(tableDef.set.name) === false) {
|
|
125
|
+
if (tableIsClientDocumentTable(tableDef) === true && eventsDefsMap.has(tableDef.set.name) === false) {
|
|
121
126
|
eventsDefsMap.set(tableDef.set.name, tableDef.set)
|
|
122
127
|
}
|
|
123
128
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Schema } from '@livestore/utils/effect'
|
|
2
1
|
import { describe, expect, test } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import { Schema } from '@livestore/utils/effect'
|
|
4
|
+
|
|
4
5
|
import { tables } from '../../../__tests__/fixture.ts'
|
|
5
6
|
import type * as LiveStoreEvent from '../../LiveStoreEvent/mod.ts'
|
|
6
7
|
import {
|
|
@@ -95,7 +95,7 @@ export const clientDocument = <
|
|
|
95
95
|
Object.defineProperty(setEventDef, 'schema', {
|
|
96
96
|
value: Schema.Struct({
|
|
97
97
|
id: Schema.String,
|
|
98
|
-
value: options.partialSet ? Schema.partial(valueSchema) : valueSchema,
|
|
98
|
+
value: options.partialSet === true ? Schema.partial(valueSchema) : valueSchema,
|
|
99
99
|
}).annotations({ title: `${name}Set:Args` }),
|
|
100
100
|
})
|
|
101
101
|
Object.defineProperty(setEventDef, 'options', {
|
|
@@ -108,20 +108,26 @@ export const clientDocument = <
|
|
|
108
108
|
TEncoded,
|
|
109
109
|
ClientDocumentTableOptions<TType>
|
|
110
110
|
> = {
|
|
111
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- dynamic table def composition; type safety ensured by ClientDocumentTableDef.Trait
|
|
111
112
|
get: makeGetQueryBuilder(() => clientDocumentTableDef) as any,
|
|
113
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- dynamic table def composition; type safety ensured by SetEventDefLike signature
|
|
112
114
|
set: setEventDef as any,
|
|
115
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- phantom type field for generic inference only
|
|
113
116
|
Value: 'only-for-type-inference' as any,
|
|
114
117
|
default: options.default,
|
|
115
118
|
valueSchema,
|
|
116
119
|
[ClientDocumentTableDefSymbol]: {
|
|
117
120
|
derived: {
|
|
121
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- dynamic event def composition; type checked at usage sites
|
|
118
122
|
setEventDef: derivedSetEventDef as any,
|
|
123
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- dynamic materializer composition; type checked at usage sites
|
|
119
124
|
setMaterializer: derivedSetMaterializer as any,
|
|
120
125
|
},
|
|
121
126
|
options,
|
|
122
127
|
},
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- composing tableDef + clientDocumentTableDefTrait into final type; validated by satisfies above
|
|
125
131
|
const clientDocumentTableDef = {
|
|
126
132
|
...tableDef,
|
|
127
133
|
...clientDocumentTableDefTrait,
|
|
@@ -141,11 +147,14 @@ export const mergeDefaultValues = <T>(defaultValues: T, explicitDefaultValues: T
|
|
|
141
147
|
}
|
|
142
148
|
|
|
143
149
|
// Get all unique keys from both objects
|
|
150
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Object.keys requires indexable type; T is constrained to object by preceding typeof checks
|
|
144
151
|
const allKeys = new Set([...Object.keys(defaultValues as any), ...Object.keys(explicitDefaultValues as any)])
|
|
145
152
|
|
|
146
153
|
return Array.from(allKeys).reduce((acc, key) => {
|
|
154
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- dynamic key access on generic object; keys validated from Object.keys above
|
|
147
155
|
acc[key] = (explicitDefaultValues as any)[key] ?? (defaultValues as any)[key]
|
|
148
156
|
return acc
|
|
157
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- reduce accumulator type; result type is T
|
|
149
158
|
}, {} as any)
|
|
150
159
|
}
|
|
151
160
|
|
|
@@ -177,7 +186,7 @@ export const createOptimisticEventSchema = ({
|
|
|
177
186
|
defaultValue: any
|
|
178
187
|
partialSet: boolean
|
|
179
188
|
}) => {
|
|
180
|
-
const targetSchema = partialSet ? Schema.partial(valueSchema) : valueSchema
|
|
189
|
+
const targetSchema = partialSet === true ? Schema.partial(valueSchema) : valueSchema
|
|
181
190
|
// The transform decode must yield values in the target schema's ENCODED shape.
|
|
182
191
|
// This keeps JSON columns consistent when Encoded != Type (e.g. Option).
|
|
183
192
|
const encodeTarget = Schema.encodeSync(targetSchema)
|
|
@@ -197,11 +206,13 @@ export const createOptimisticEventSchema = ({
|
|
|
197
206
|
|
|
198
207
|
// Handle null/undefined/non-object cases
|
|
199
208
|
if (typeof eventValue !== 'object' || eventValue === null) {
|
|
200
|
-
console.warn(
|
|
201
|
-
|
|
209
|
+
console.warn(
|
|
210
|
+
`Client document: Non-object event value, using ${partialSet === true ? 'empty partial' : 'defaults'}`,
|
|
211
|
+
)
|
|
212
|
+
return encodeTarget(partialSet === true ? {} : defaultValue)
|
|
202
213
|
}
|
|
203
214
|
|
|
204
|
-
if (partialSet) {
|
|
215
|
+
if (partialSet === true) {
|
|
205
216
|
// For partial sets: only preserve fields that exist in new schema
|
|
206
217
|
const partialResult: Record<string, unknown> = {}
|
|
207
218
|
let hasValidFields = false
|
|
@@ -214,7 +225,7 @@ export const createOptimisticEventSchema = ({
|
|
|
214
225
|
// Drop fields that don't exist in new schema
|
|
215
226
|
}
|
|
216
227
|
|
|
217
|
-
if (hasValidFields) {
|
|
228
|
+
if (hasValidFields === true) {
|
|
218
229
|
try {
|
|
219
230
|
const decoded = Schema.decodeUnknownSync(targetSchema)(partialResult)
|
|
220
231
|
return encodeTarget(decoded)
|
|
@@ -366,11 +377,14 @@ const makeGetQueryBuilder = <TTableDef extends ClientDocumentTableDef<any, any,
|
|
|
366
377
|
return {
|
|
367
378
|
[QueryBuilderTypeId]: QueryBuilderTypeId,
|
|
368
379
|
[QueryBuilderAstSymbol]: ast,
|
|
380
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- phantom type field for generic inference only
|
|
369
381
|
ResultType: 'only-for-type-inference' as any,
|
|
370
382
|
asSql: () => ({ query, bindValues: [id] }),
|
|
371
383
|
toString: () => query.toString(),
|
|
384
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- spreading empty object to satisfy QueryBuilder interface requirements
|
|
372
385
|
...({} as any), // Needed for type cast
|
|
373
386
|
}
|
|
387
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- makeGetQueryBuilder return type uses complex conditional generics
|
|
374
388
|
}) as any
|
|
375
389
|
}
|
|
376
390
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Schema, SchemaAST } from '@livestore/utils/effect'
|
|
2
1
|
import { describe, expect, test } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import { Schema, SchemaAST } from '@livestore/utils/effect'
|
|
4
|
+
|
|
4
5
|
import { withColumnType, withPrimaryKey } from './column-annotations.ts'
|
|
5
6
|
|
|
6
7
|
describe.concurrent('annotations', () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Schema } from '@livestore/utils/effect'
|
|
2
2
|
import { dual, Option, SchemaAST } from '@livestore/utils/effect'
|
|
3
|
+
|
|
3
4
|
import type { SqliteDsl } from './db-schema/mod.ts'
|
|
4
5
|
|
|
5
6
|
export const PrimaryKeyId = Symbol.for('livestore/state/sqlite/annotations/primary-key')
|
|
@@ -79,7 +80,7 @@ const validateSchemaColumnTypeCompatibility = (
|
|
|
79
80
|
const applyAnnotations = <T extends Schema.Schema.All>(schema: T, overrides: Record<PropertyKey, unknown>): T => {
|
|
80
81
|
const identifier = SchemaAST.getIdentifierAnnotation(schema.ast)
|
|
81
82
|
const shouldPreserveIdentifier = Option.isSome(identifier) && !(SchemaAST.IdentifierAnnotationId in overrides)
|
|
82
|
-
const annotations: Record<PropertyKey, unknown> = shouldPreserveIdentifier
|
|
83
|
+
const annotations: Record<PropertyKey, unknown> = shouldPreserveIdentifier === true
|
|
83
84
|
? { ...overrides, [SchemaAST.IdentifierAnnotationId]: identifier.value }
|
|
84
85
|
: overrides
|
|
85
86
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { Schema } from '@livestore/utils/effect'
|
|
2
1
|
import { assert, describe, expect, it } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import { Schema } from '@livestore/utils/effect'
|
|
4
|
+
import { objectToString } from '@livestore/utils'
|
|
5
|
+
|
|
4
6
|
import * as State from '../mod.ts'
|
|
5
7
|
import { withAutoIncrement, withColumnType, withDefault, withPrimaryKey, withUnique } from './column-annotations.ts'
|
|
6
8
|
|
|
@@ -299,7 +301,7 @@ describe('getColumnDefForSchema', () => {
|
|
|
299
301
|
it('should handle Uint8Array as blob column', () => {
|
|
300
302
|
const columnDef = State.SQLite.getColumnDefForSchema(Schema.Uint8Array)
|
|
301
303
|
expect(columnDef.columnType).toBe('blob')
|
|
302
|
-
expect(columnDef.schema
|
|
304
|
+
expect(objectToString(columnDef.schema)).toBe('Uint8ArrayFromSelf')
|
|
303
305
|
})
|
|
304
306
|
})
|
|
305
307
|
|
|
@@ -356,8 +358,8 @@ describe('getColumnDefForSchema', () => {
|
|
|
356
358
|
|
|
357
359
|
expect(table.sqliteDef.columns.active.nullable).toBe(true)
|
|
358
360
|
expect(table.sqliteDef.columns.active.columnType).toBe('integer')
|
|
359
|
-
expect(table.sqliteDef.columns.active.schema
|
|
360
|
-
expect((table.rowSchema as any).fields.active
|
|
361
|
+
expect(objectToString(table.sqliteDef.columns.active.schema)).toBe('(number <-> boolean) | null')
|
|
362
|
+
expect(String((table.rowSchema as any).fields.active)).toBe('(number <-> boolean) | null')
|
|
361
363
|
})
|
|
362
364
|
|
|
363
365
|
it('should handle optional complex types with JSON encoding', () => {
|
|
@@ -406,8 +408,8 @@ describe('getColumnDefForSchema', () => {
|
|
|
406
408
|
const table = State.SQLite.table({ name: 'timers', schema })
|
|
407
409
|
|
|
408
410
|
expect(table.sqliteDef.columns.status.columnType).toBe('text')
|
|
409
|
-
expect(table.sqliteDef.columns.status.schema
|
|
410
|
-
expect((table.rowSchema as any).fields.status
|
|
411
|
+
expect(objectToString(table.sqliteDef.columns.status.schema)).toBe('"idle" | "running" | "stopped"')
|
|
412
|
+
expect(String((table.rowSchema as any).fields.status)).toBe('"idle" | "running" | "stopped"')
|
|
411
413
|
})
|
|
412
414
|
|
|
413
415
|
it('should handle Schema.NullOr with complex types', () => {
|
|
@@ -20,19 +20,20 @@ export const getColumnDefForSchema = (
|
|
|
20
20
|
|
|
21
21
|
// Extract annotations
|
|
22
22
|
const getAnnotation = <T>(annotationId: symbol): Option.Option<T> =>
|
|
23
|
-
propertySignature
|
|
23
|
+
propertySignature !== undefined
|
|
24
24
|
? hasPropertyAnnotation<T>(propertySignature, annotationId)
|
|
25
25
|
: SchemaAST.getAnnotation<T>(annotationId)(ast)
|
|
26
26
|
|
|
27
27
|
const columnType = SchemaAST.getAnnotation<SqliteDsl.FieldColumnType>(ColumnType)(ast)
|
|
28
28
|
|
|
29
29
|
// Check if schema has null (e.g., Schema.NullOr) or undefined or if it's forced nullable (optional field)
|
|
30
|
-
const isNullable = forceNullable || hasNull(ast) || hasUndefined(ast)
|
|
30
|
+
const isNullable = forceNullable === true || hasNull(ast) === true || hasUndefined(ast) === true
|
|
31
31
|
|
|
32
32
|
// Get base column definition with nullable flag
|
|
33
|
-
const baseColumn =
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const baseColumn =
|
|
34
|
+
Option.isSome(columnType) === true
|
|
35
|
+
? getColumnForType(columnType.value, isNullable)
|
|
36
|
+
: getColumnForSchema(schema, isNullable)
|
|
36
37
|
|
|
37
38
|
// Apply annotations
|
|
38
39
|
const primaryKey = getAnnotation<boolean>(PrimaryKeyId).pipe(Option.getOrElse(() => false))
|
|
@@ -41,9 +42,9 @@ export const getColumnDefForSchema = (
|
|
|
41
42
|
|
|
42
43
|
return {
|
|
43
44
|
...baseColumn,
|
|
44
|
-
...(primaryKey
|
|
45
|
-
...(autoIncrement
|
|
46
|
-
...(Option.isSome(defaultValue)
|
|
45
|
+
...(primaryKey === true ? { primaryKey: true } : {}),
|
|
46
|
+
...(autoIncrement === true ? { autoIncrement: true } : {}),
|
|
47
|
+
...(Option.isSome(defaultValue) === true ? { default: Option.some(defaultValue.value) } : {}),
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -51,9 +52,9 @@ const hasPropertyAnnotation = <T>(
|
|
|
51
52
|
propertySignature: SchemaAST.PropertySignature,
|
|
52
53
|
annotationId: symbol,
|
|
53
54
|
): Option.Option<T> => {
|
|
54
|
-
if ('annotations' in propertySignature && propertySignature.annotations) {
|
|
55
|
+
if ('annotations' in propertySignature && propertySignature.annotations !== undefined) {
|
|
55
56
|
const annotation = SchemaAST.getAnnotation<T>(annotationId)(propertySignature as any)
|
|
56
|
-
if (Option.isSome(annotation)) return annotation
|
|
57
|
+
if (Option.isSome(annotation) === true) return annotation
|
|
57
58
|
}
|
|
58
59
|
return SchemaAST.getAnnotation<T>(annotationId)(propertySignature.type)
|
|
59
60
|
}
|
|
@@ -74,15 +75,15 @@ export const schemaFieldsToColumns = (
|
|
|
74
75
|
const fieldSchema = Schema.make(prop.type)
|
|
75
76
|
|
|
76
77
|
// Warn about lossy conversion for fields with both null and undefined
|
|
77
|
-
if (prop.isOptional) {
|
|
78
|
+
if (prop.isOptional === true) {
|
|
78
79
|
const { hasNull, hasUndefined } = checkNullUndefined(fieldSchema.ast)
|
|
79
|
-
if (hasNull && hasUndefined) {
|
|
80
|
+
if (hasNull === true && hasUndefined === true) {
|
|
80
81
|
console.warn(`Field '${prop.name}' has both null and undefined - treating | undefined as | null`)
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
// Get column definition - pass nullable flag for optional fields
|
|
85
|
-
const columnDef = getColumnDefForSchema(fieldSchema, prop, prop.isOptional)
|
|
86
|
+
const columnDef = getColumnDefForSchema(fieldSchema, prop, prop.isOptional === true)
|
|
86
87
|
|
|
87
88
|
// Check for primary key and unique annotations
|
|
88
89
|
const hasPrimaryKey = hasPropertyAnnotation<boolean>(prop, PrimaryKeyId).pipe(Option.getOrElse(() => false))
|
|
@@ -91,16 +92,16 @@ export const schemaFieldsToColumns = (
|
|
|
91
92
|
// Build final column
|
|
92
93
|
columns[prop.name] = {
|
|
93
94
|
...columnDef,
|
|
94
|
-
...(hasPrimaryKey
|
|
95
|
+
...(hasPrimaryKey === true ? { primaryKey: true } : {}),
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// Validate primary key + nullable
|
|
98
99
|
const column = columns[prop.name]
|
|
99
|
-
if (column?.primaryKey && column.nullable) {
|
|
100
|
+
if (column?.primaryKey === true && column.nullable === true) {
|
|
100
101
|
throw new Error('Primary key columns cannot be nullable')
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
if (hasUnique) uniqueColumns.push(prop.name)
|
|
104
|
+
if (hasUnique === true) uniqueColumns.push(prop.name)
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
return { columns, uniqueColumns }
|
|
@@ -111,9 +112,9 @@ const checkNullUndefined = (ast: SchemaAST.AST): { hasNull: boolean; hasUndefine
|
|
|
111
112
|
let hasUndefined = false
|
|
112
113
|
|
|
113
114
|
const visit = (type: SchemaAST.AST): void => {
|
|
114
|
-
if (SchemaAST.isUndefinedKeyword(type)) hasUndefined = true
|
|
115
|
-
else if (SchemaAST.isLiteral(type) && type.literal === null) hasNull = true
|
|
116
|
-
else if (SchemaAST.isUnion(type)) type.types.forEach(visit)
|
|
115
|
+
if (SchemaAST.isUndefinedKeyword(type) === true) hasUndefined = true
|
|
116
|
+
else if (SchemaAST.isLiteral(type) === true && type.literal === null) hasNull = true
|
|
117
|
+
else if (SchemaAST.isUnion(type) === true) type.types.forEach(visit)
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
visit(ast)
|
|
@@ -121,16 +122,16 @@ const checkNullUndefined = (ast: SchemaAST.AST): { hasNull: boolean; hasUndefine
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
const hasNull = (ast: SchemaAST.AST): boolean => {
|
|
124
|
-
if (SchemaAST.isLiteral(ast) && ast.literal === null) return true
|
|
125
|
-
if (SchemaAST.isUnion(ast)) {
|
|
125
|
+
if (SchemaAST.isLiteral(ast) === true && ast.literal === null) return true
|
|
126
|
+
if (SchemaAST.isUnion(ast) === true) {
|
|
126
127
|
return ast.types.some((type) => hasNull(type))
|
|
127
128
|
}
|
|
128
129
|
return false
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
const hasUndefined = (ast: SchemaAST.AST): boolean => {
|
|
132
|
-
if (SchemaAST.isUndefinedKeyword(ast)) return true
|
|
133
|
-
if (SchemaAST.isUnion(ast)) {
|
|
133
|
+
if (SchemaAST.isUndefinedKeyword(ast) === true) return true
|
|
134
|
+
if (SchemaAST.isUnion(ast) === true) {
|
|
134
135
|
return ast.types.some((type) => hasUndefined(type))
|
|
135
136
|
}
|
|
136
137
|
return false
|
|
@@ -158,7 +159,7 @@ const getColumnForSchema = (schema: Schema.Schema.AnyNoContext, nullable = false
|
|
|
158
159
|
const coreSchema = stripNullable(ast) === ast ? schema : Schema.make(coreAst)
|
|
159
160
|
|
|
160
161
|
// Special case: Boolean is transformed to integer in SQLite
|
|
161
|
-
if (SchemaAST.isBooleanKeyword(coreAst)) {
|
|
162
|
+
if (SchemaAST.isBooleanKeyword(coreAst) === true) {
|
|
162
163
|
return SqliteDsl.boolean({ nullable })
|
|
163
164
|
}
|
|
164
165
|
|
|
@@ -166,11 +167,11 @@ const getColumnForSchema = (schema: Schema.Schema.AnyNoContext, nullable = false
|
|
|
166
167
|
const encodedAst = Schema.encodedSchema(coreSchema).ast
|
|
167
168
|
|
|
168
169
|
// Check if the encoded type matches SQLite native types
|
|
169
|
-
if (SchemaAST.isStringKeyword(encodedAst)) {
|
|
170
|
+
if (SchemaAST.isStringKeyword(encodedAst) === true) {
|
|
170
171
|
return SqliteDsl.text({ schema: coreSchema, nullable })
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
if (SchemaAST.isNumberKeyword(encodedAst)) {
|
|
174
|
+
if (SchemaAST.isNumberKeyword(encodedAst) === true) {
|
|
174
175
|
// Special cases for integer columns
|
|
175
176
|
const id = SchemaAST.getIdentifierAnnotation(coreAst).pipe(Option.getOrElse(() => ''))
|
|
176
177
|
if (id === 'Int' || id === 'DateFromNumber') {
|
|
@@ -179,23 +180,23 @@ const getColumnForSchema = (schema: Schema.Schema.AnyNoContext, nullable = false
|
|
|
179
180
|
return SqliteDsl.real({ schema: coreSchema, nullable })
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
if (isUint8ArraySchema(coreAst) || isUint8ArraySchema(encodedAst)) {
|
|
183
|
+
if (isUint8ArraySchema(coreAst) === true || isUint8ArraySchema(encodedAst) === true) {
|
|
183
184
|
return SqliteDsl.blob({ schema: Schema.Uint8ArrayFromSelf as Schema.Schema<Uint8Array<ArrayBuffer>>, nullable })
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
const literalColumn = getLiteralColumnDefinition(encodedAst, coreSchema, nullable, coreAst)
|
|
187
|
-
if (literalColumn) return literalColumn
|
|
188
|
+
if (literalColumn !== null) return literalColumn
|
|
188
189
|
|
|
189
190
|
// Fallback to checking the original AST in case the encoded schema differs
|
|
190
191
|
const coreLiteralColumn = getLiteralColumnDefinition(coreAst, coreSchema, nullable, coreAst)
|
|
191
|
-
if (coreLiteralColumn) return coreLiteralColumn
|
|
192
|
+
if (coreLiteralColumn !== null) return coreLiteralColumn
|
|
192
193
|
|
|
193
194
|
// Everything else needs JSON encoding
|
|
194
195
|
return SqliteDsl.json({ schema: coreSchema, nullable })
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
const stripNullable = (ast: SchemaAST.AST): SchemaAST.AST => {
|
|
198
|
-
if (
|
|
199
|
+
if (SchemaAST.isUnion(ast) === false) return ast
|
|
199
200
|
|
|
200
201
|
// Filter out null/undefined members while preserving any annotations on the union
|
|
201
202
|
const coreTypes = ast.types.filter(
|
|
@@ -220,7 +221,7 @@ const getLiteralColumnDefinition = (
|
|
|
220
221
|
sourceAst: SchemaAST.AST,
|
|
221
222
|
): SqliteDsl.ColumnDefinition.Any | null => {
|
|
222
223
|
const literalValues = extractLiteralValues(ast)
|
|
223
|
-
if (
|
|
224
|
+
if (literalValues == null) return null
|
|
224
225
|
|
|
225
226
|
const literalType = getLiteralValueType(literalValues)
|
|
226
227
|
switch (literalType) {
|
|
@@ -235,7 +236,7 @@ const getLiteralColumnDefinition = (
|
|
|
235
236
|
const useIntegerColumn =
|
|
236
237
|
literalValues.length > 1 && literalValues.every((value) => typeof value === 'number' && Number.isInteger(value))
|
|
237
238
|
|
|
238
|
-
return useIntegerColumn ? SqliteDsl.integer({ schema, nullable }) : SqliteDsl.real({ schema, nullable })
|
|
239
|
+
return useIntegerColumn === true ? SqliteDsl.integer({ schema, nullable }) : SqliteDsl.real({ schema, nullable })
|
|
239
240
|
}
|
|
240
241
|
case 'boolean':
|
|
241
242
|
return SqliteDsl.boolean({ nullable })
|
|
@@ -247,9 +248,13 @@ const getLiteralColumnDefinition = (
|
|
|
247
248
|
}
|
|
248
249
|
|
|
249
250
|
const extractLiteralValues = (ast: SchemaAST.AST): ReadonlyArray<SchemaAST.LiteralValue> | null => {
|
|
250
|
-
if (SchemaAST.isLiteral(ast)) return [ast.literal]
|
|
251
|
+
if (SchemaAST.isLiteral(ast) === true) return [ast.literal]
|
|
251
252
|
|
|
252
|
-
if (
|
|
253
|
+
if (
|
|
254
|
+
SchemaAST.isUnion(ast) === true &&
|
|
255
|
+
ast.types.length > 0 &&
|
|
256
|
+
ast.types.every((type) => SchemaAST.isLiteral(type)) === true
|
|
257
|
+
) {
|
|
253
258
|
return ast.types.map((type) => (type as SchemaAST.Literal).literal)
|
|
254
259
|
}
|
|
255
260
|
|
|
@@ -270,11 +275,11 @@ const getLiteralValueType = (
|
|
|
270
275
|
|
|
271
276
|
const isUint8ArraySchema = (ast: SchemaAST.AST): boolean => {
|
|
272
277
|
const identifier = SchemaAST.getIdentifierAnnotation(ast)
|
|
273
|
-
if (Option.isSome(identifier) && identifier.value.includes('Uint8Array')) {
|
|
278
|
+
if (Option.isSome(identifier) === true && identifier.value.includes('Uint8Array') === true) {
|
|
274
279
|
return true
|
|
275
280
|
}
|
|
276
281
|
|
|
277
|
-
if (SchemaAST.isTupleType(ast)) {
|
|
282
|
+
if (SchemaAST.isTupleType(ast) === true) {
|
|
278
283
|
return ast.elements.length === 0 && ast.rest.length === 1 && SchemaAST.isNumberKeyword(ast.rest[0]!.type)
|
|
279
284
|
}
|
|
280
285
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Option, Schema } from '@livestore/utils/effect'
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { Option, Schema } from '@livestore/utils/effect'
|
|
4
|
+
|
|
3
5
|
import { makeColumnSpec } from './column-spec.ts'
|
|
4
6
|
import { SqliteAst } from './db-schema/mod.ts'
|
|
5
7
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Schema } from '@livestore/utils/effect'
|
|
2
|
+
|
|
2
3
|
import { type SqliteAst, SqliteDsl } from './db-schema/mod.ts'
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -12,13 +13,13 @@ import { type SqliteAst, SqliteDsl } from './db-schema/mod.ts'
|
|
|
12
13
|
export const makeColumnSpec = (tableAst: SqliteAst.Table) => {
|
|
13
14
|
const pkColumns = tableAst.columns.filter((_) => _.primaryKey)
|
|
14
15
|
const hasSinglePk = pkColumns.length === 1
|
|
15
|
-
const pkColumn = hasSinglePk ? pkColumns[0] : undefined
|
|
16
|
+
const pkColumn = hasSinglePk === true ? pkColumns[0] : undefined
|
|
16
17
|
|
|
17
18
|
// Build column definitions, handling the special SQLite rule that AUTOINCREMENT
|
|
18
19
|
// is only valid on a single column declared as INTEGER PRIMARY KEY (column-level).
|
|
19
20
|
const columnDefStrs = tableAst.columns.map((column) =>
|
|
20
21
|
toSqliteColumnSpec(column, {
|
|
21
|
-
inlinePrimaryKey: hasSinglePk && column === pkColumn &&
|
|
22
|
+
inlinePrimaryKey: hasSinglePk && column === pkColumn && column.primaryKey,
|
|
22
23
|
}),
|
|
23
24
|
)
|
|
24
25
|
|
|
@@ -36,24 +37,24 @@ const toSqliteColumnSpec = (column: SqliteAst.Column, opts: { inlinePrimaryKey:
|
|
|
36
37
|
const columnTypeStr = column.type._tag
|
|
37
38
|
// When PRIMARY KEY is declared inline, NOT NULL is implied and should not be emitted,
|
|
38
39
|
// and AUTOINCREMENT must immediately follow PRIMARY KEY within the same constraint.
|
|
39
|
-
const nullableStr = opts.inlinePrimaryKey ? '' : column.nullable === false ? 'not null' : ''
|
|
40
|
+
const nullableStr = opts.inlinePrimaryKey === true ? '' : column.nullable === false ? 'not null' : ''
|
|
40
41
|
|
|
41
42
|
// Only include AUTOINCREMENT when it's valid: single-column INTEGER PRIMARY KEY
|
|
42
|
-
const includeAutoIncrement = opts.inlinePrimaryKey && column.type._tag === 'integer' && column.autoIncrement === true
|
|
43
|
+
const includeAutoIncrement = opts.inlinePrimaryKey === true && column.type._tag === 'integer' && column.autoIncrement === true
|
|
43
44
|
|
|
44
|
-
const pkStr = opts.inlinePrimaryKey ? 'primary key' : ''
|
|
45
|
-
const autoIncrementStr = includeAutoIncrement ? 'autoincrement' : ''
|
|
45
|
+
const pkStr = opts.inlinePrimaryKey === true ? 'primary key' : ''
|
|
46
|
+
const autoIncrementStr = includeAutoIncrement === true ? 'autoincrement' : ''
|
|
46
47
|
|
|
47
48
|
const defaultValueStr = (() => {
|
|
48
49
|
if (column.default._tag === 'None') return ''
|
|
49
50
|
|
|
50
51
|
const defaultValue = column.default.value
|
|
51
|
-
if (SqliteDsl.isDefaultThunk(defaultValue)) return ''
|
|
52
|
+
if (SqliteDsl.isDefaultThunk(defaultValue) === true) return ''
|
|
52
53
|
|
|
53
54
|
const resolvedDefault = SqliteDsl.resolveColumnDefault(defaultValue)
|
|
54
55
|
|
|
55
56
|
if (resolvedDefault === null) return 'default null'
|
|
56
|
-
if (SqliteDsl.isSqlDefaultValue(resolvedDefault)) return `default ${resolvedDefault.sql}`
|
|
57
|
+
if (SqliteDsl.isSqlDefaultValue(resolvedDefault) === true) return `default ${resolvedDefault.sql}`
|
|
57
58
|
|
|
58
59
|
const encodeValue = Schema.encodeSync(column.schema)
|
|
59
60
|
const encodedDefaultValue = encodeValue(resolvedDefault)
|
|
@@ -124,7 +124,7 @@ const trimInfoForHasing = (obj: Table | Column | Index | ForeignKey | DbSchema):
|
|
|
124
124
|
|
|
125
125
|
// NEW: Include schema hash for JSON columns
|
|
126
126
|
// This ensures that changes to the JSON schema are detected
|
|
127
|
-
if (isJsonColumn(obj) && obj.schema) {
|
|
127
|
+
if (isJsonColumn(obj) === true && obj.schema !== undefined) {
|
|
128
128
|
// Use Effect's Schema.hash for consistent hashing
|
|
129
129
|
baseInfo.jsonSchemaHash = Schema.hash(obj.schema)
|
|
130
130
|
}
|
|
@@ -155,7 +155,7 @@ const trimInfoForHasing = (obj: Table | Column | Index | ForeignKey | DbSchema):
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
default: {
|
|
158
|
-
throw new Error(`Unreachable: ${obj}`)
|
|
158
|
+
throw new Error(`Unreachable: ${String(obj)}`)
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
}
|
|
@@ -6,6 +6,7 @@ export type SqlDefaultValue = {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export const isSqlDefaultValue = (value: unknown): value is SqlDefaultValue => {
|
|
9
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- type guard property access after structural check
|
|
9
10
|
return typeof value === 'object' && value !== null && 'sql' in value && typeof (value as any).sql === 'string'
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -16,14 +17,14 @@ export const isDefaultThunk = (value: unknown): value is ColumnDefaultThunk<unkn
|
|
|
16
17
|
export type ColumnDefaultValue<T> = T | null | ColumnDefaultThunk<T | null> | SqlDefaultValue
|
|
17
18
|
|
|
18
19
|
export const resolveColumnDefault = <T>(value: ColumnDefaultValue<T>): T | null | SqlDefaultValue =>
|
|
19
|
-
isDefaultThunk(value) ?
|
|
20
|
+
isDefaultThunk(value) === true ? value() : value
|
|
20
21
|
|
|
21
|
-
export type ColumnDefinition<TEncoded, TDecoded> = {
|
|
22
|
+
export type ColumnDefinition<TEncoded, TDecoded, TNullable extends boolean = boolean> = {
|
|
22
23
|
readonly columnType: FieldColumnType
|
|
23
24
|
readonly schema: Schema.Schema<TDecoded, TEncoded>
|
|
24
25
|
readonly default: Option.Option<ColumnDefaultValue<TDecoded>>
|
|
25
26
|
/** @default false */
|
|
26
|
-
readonly nullable:
|
|
27
|
+
readonly nullable: TNullable
|
|
27
28
|
/** @default false */
|
|
28
29
|
readonly primaryKey: boolean
|
|
29
30
|
/** @default false */
|
|
@@ -40,6 +41,7 @@ export const isColumnDefinition = (value: unknown): value is ColumnDefinition.An
|
|
|
40
41
|
typeof value === 'object' &&
|
|
41
42
|
value !== null &&
|
|
42
43
|
'columnType' in value &&
|
|
44
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- type guard narrowing; columnType checked to be in valid set
|
|
43
45
|
validColumnTypes.includes(value.columnType as any)
|
|
44
46
|
)
|
|
45
47
|
}
|
|
@@ -105,6 +107,7 @@ const makeColDef =
|
|
|
105
107
|
const schema = nullable === true ? Schema.NullOr(schemaWithoutNull) : schemaWithoutNull
|
|
106
108
|
const default_ = def?.default === undefined || def.default === NoDefault ? Option.none() : Option.some(def.default)
|
|
107
109
|
|
|
110
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- column factory return type uses complex conditional generics; consumer type safety enforced by ColDefFn signature
|
|
108
111
|
return {
|
|
109
112
|
columnType,
|
|
110
113
|
schema,
|
|
@@ -200,10 +203,12 @@ type MakeSpecializedColDefFn = {
|
|
|
200
203
|
|
|
201
204
|
const makeSpecializedColDef: MakeSpecializedColDefFn = (columnType, opts) => (def?: ColumnDefinitionInput) => {
|
|
202
205
|
const nullable = def?.nullable ?? false
|
|
206
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- schema type variance; custom schema compatibility checked at call site
|
|
203
207
|
const schemaWithoutNull = opts._tag === 'baseSchemaFn' ? opts.baseSchemaFn(def?.schema as any) : opts.baseSchema
|
|
204
208
|
const schema = nullable === true ? Schema.NullOr(schemaWithoutNull) : schemaWithoutNull
|
|
205
209
|
const default_ = def?.default === undefined || def.default === NoDefault ? Option.none() : Option.some(def.default)
|
|
206
210
|
|
|
211
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- specialized column factory return type uses complex conditional generics; consumer type safety enforced by SpecializedColDefFn signature
|
|
207
212
|
return {
|
|
208
213
|
columnType,
|
|
209
214
|
schema,
|
|
@@ -236,7 +241,7 @@ export const boolean: SpecializedColDefFn<'integer', false, boolean> = makeSpeci
|
|
|
236
241
|
_tag: 'baseSchema',
|
|
237
242
|
baseSchema: Schema.transform(Schema.Number, Schema.Boolean, {
|
|
238
243
|
decode: (_) => _ === 1,
|
|
239
|
-
encode: (_) => (_ ? 1 : 0),
|
|
244
|
+
encode: (_) => (_ === true ? 1 : 0),
|
|
240
245
|
}),
|
|
241
246
|
})
|
|
242
247
|
|
|
@@ -259,15 +264,19 @@ export const defaultSchemaForColumnType = <TColumnType extends FieldColumnType>(
|
|
|
259
264
|
|
|
260
265
|
switch (columnType) {
|
|
261
266
|
case 'text': {
|
|
267
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- switch-based type narrowing for column type to schema mapping; each case is correct for its branch
|
|
262
268
|
return Schema.String as any as Schema.Schema<T>
|
|
263
269
|
}
|
|
264
270
|
case 'integer': {
|
|
271
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- switch-based type narrowing for column type to schema mapping; each case is correct for its branch
|
|
265
272
|
return Schema.Number as any as Schema.Schema<T>
|
|
266
273
|
}
|
|
267
274
|
case 'real': {
|
|
275
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- switch-based type narrowing for column type to schema mapping; each case is correct for its branch
|
|
268
276
|
return Schema.Number as any as Schema.Schema<T>
|
|
269
277
|
}
|
|
270
278
|
case 'blob': {
|
|
279
|
+
// oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- switch-based type narrowing for column type to schema mapping; each case is correct for its branch
|
|
271
280
|
return Schema.Uint8ArrayFromSelf as any as Schema.Schema<T>
|
|
272
281
|
}
|
|
273
282
|
default: {
|