@livestore/common 0.4.0-dev.21 → 0.4.0-dev.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/ClientSessionLeaderThreadProxy.d.ts +16 -9
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/ClientSessionLeaderThreadProxy.js.map +1 -1
- package/dist/WorkerTransportError.d.ts +11 -0
- package/dist/WorkerTransportError.d.ts.map +1 -0
- package/dist/WorkerTransportError.js +11 -0
- package/dist/WorkerTransportError.js.map +1 -0
- package/dist/adapter-types.d.ts +26 -3
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +27 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/bounded-collections.d.ts.map +1 -1
- package/dist/bounded-collections.js +6 -4
- package/dist/bounded-collections.js.map +1 -1
- package/dist/debug-info.js +4 -4
- package/dist/debug-info.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +42 -22
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.js +12 -1
- package/dist/devtools/devtools-messages-client-session.js.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +12 -6
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +8 -3
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +45 -25
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +12 -1
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/devtools/mod.js +1 -1
- package/dist/devtools/mod.js.map +1 -1
- package/dist/errors.d.ts +15 -15
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -11
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +20 -6
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +283 -253
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/RejectedPushError.d.ts +107 -0
- package/dist/leader-thread/RejectedPushError.d.ts.map +1 -0
- package/dist/leader-thread/RejectedPushError.js +78 -0
- package/dist/leader-thread/RejectedPushError.js.map +1 -0
- package/dist/leader-thread/connection.js +1 -1
- package/dist/leader-thread/connection.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +12 -11
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -2
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +34 -14
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +12 -5
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +12 -11
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -1
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +7 -4
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/recreate-db.js +1 -1
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -2
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -2
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/stream-events.d.ts.map +1 -1
- package/dist/leader-thread/stream-events.js +4 -3
- package/dist/leader-thread/stream-events.js.map +1 -1
- package/dist/leader-thread/types.d.ts +7 -6
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/logging.js +4 -4
- package/dist/logging.js.map +1 -1
- package/dist/make-client-session.js +2 -2
- package/dist/make-client-session.js.map +1 -1
- package/dist/materializer-helper.js +6 -6
- package/dist/materializer-helper.js.map +1 -1
- package/dist/otel.d.ts +1 -1
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +2 -2
- package/dist/otel.js.map +1 -1
- package/dist/rematerialize-from-eventlog.d.ts +1 -1
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +11 -9
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef/define.d.ts +16 -2
- package/dist/schema/EventDef/define.d.ts.map +1 -1
- package/dist/schema/EventDef/define.js +5 -4
- package/dist/schema/EventDef/define.js.map +1 -1
- package/dist/schema/EventDef/deprecated.d.ts +99 -0
- package/dist/schema/EventDef/deprecated.d.ts.map +1 -0
- package/dist/schema/EventDef/deprecated.js +144 -0
- package/dist/schema/EventDef/deprecated.js.map +1 -0
- package/dist/schema/EventDef/deprecated.test.d.ts +2 -0
- package/dist/schema/EventDef/deprecated.test.d.ts.map +1 -0
- package/dist/schema/EventDef/deprecated.test.js +95 -0
- package/dist/schema/EventDef/deprecated.test.js.map +1 -0
- package/dist/schema/EventDef/event-def.d.ts +4 -0
- package/dist/schema/EventDef/event-def.d.ts.map +1 -1
- package/dist/schema/EventDef/mod.d.ts +1 -0
- package/dist/schema/EventDef/mod.d.ts.map +1 -1
- package/dist/schema/EventDef/mod.js +1 -0
- package/dist/schema/EventDef/mod.js.map +1 -1
- package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/client.js +11 -11
- package/dist/schema/EventSequenceNumber/client.js.map +1 -1
- package/dist/schema/EventSequenceNumber.test.js +1 -1
- package/dist/schema/EventSequenceNumber.test.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.d.ts +6 -6
- package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/client.js +6 -3
- package/dist/schema/LiveStoreEvent/client.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.test.d.ts +2 -0
- package/dist/schema/LiveStoreEvent/client.test.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent/client.test.js +83 -0
- package/dist/schema/LiveStoreEvent/client.test.js.map +1 -0
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +7 -4
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts +1 -0
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +34 -13
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +121 -2
- package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.js +1 -1
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-def.js +36 -34
- package/dist/schema/state/sqlite/column-def.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.test.js +7 -6
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-spec.js +8 -8
- package/dist/schema/state/sqlite/column-spec.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +2 -2
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +2 -2
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +11 -2
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +3 -5
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.d.ts +37 -13
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/astToSql.js +77 -7
- package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +28 -14
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +112 -3
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js +2 -2
- package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +5 -3
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +1 -1
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +57 -4
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/schema/unknown-events.d.ts +1 -1
- package/dist/schema/unknown-events.d.ts.map +1 -1
- package/dist/schema/unknown-events.js +1 -1
- package/dist/schema/unknown-events.js.map +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -1
- package/dist/schema-management/common.js +2 -2
- package/dist/schema-management/common.js.map +1 -1
- package/dist/schema-management/migrations.js +1 -1
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sql-queries/sql-queries.js +8 -6
- package/dist/sql-queries/sql-queries.js.map +1 -1
- package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
- package/dist/sql-queries/sql-query-builder.js.map +1 -1
- package/dist/sqlite-db-helper.js +3 -3
- package/dist/sqlite-db-helper.js.map +1 -1
- package/dist/sqlite-types.d.ts +2 -2
- package/dist/sqlite-types.d.ts.map +1 -1
- package/dist/sqlite-types.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +8 -9
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +93 -107
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/errors.d.ts +0 -38
- package/dist/sync/errors.d.ts.map +1 -1
- package/dist/sync/errors.js +3 -20
- package/dist/sync/errors.js.map +1 -1
- package/dist/sync/mock-sync-backend.d.ts +5 -3
- package/dist/sync/mock-sync-backend.d.ts.map +1 -1
- package/dist/sync/mock-sync-backend.js +70 -68
- package/dist/sync/mock-sync-backend.js.map +1 -1
- package/dist/sync/next/compact-events.js +6 -6
- package/dist/sync/next/compact-events.js.map +1 -1
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +6 -6
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +6 -6
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.js +3 -3
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.js +1 -1
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +2 -2
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.d.ts.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +2 -2
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/event-fixtures.js +2 -2
- package/dist/sync/next/test/event-fixtures.js.map +1 -1
- package/dist/sync/sync-backend-kv.d.ts.map +1 -1
- package/dist/sync/sync-backend-kv.js.map +1 -1
- package/dist/sync/sync-backend.d.ts +3 -3
- package/dist/sync/sync-backend.d.ts.map +1 -1
- package/dist/sync/sync-backend.js +1 -1
- package/dist/sync/sync-backend.js.map +1 -1
- package/dist/sync/sync.d.ts +20 -0
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/syncstate.d.ts +4 -17
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +51 -74
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +112 -96
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/transport-chunking.js +3 -3
- package/dist/sync/transport-chunking.js.map +1 -1
- package/dist/sync/validate-push-payload.d.ts +2 -2
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +4 -6
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/util.js +2 -2
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +7 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +8 -4
- package/dist/version.js.map +1 -1
- package/package.json +66 -12
- package/src/ClientSessionLeaderThreadProxy.ts +16 -9
- package/src/WorkerTransportError.ts +12 -0
- package/src/adapter-types.ts +39 -3
- package/src/bounded-collections.ts +6 -5
- package/src/debug-info.ts +4 -4
- package/src/devtools/devtools-messages-client-session.ts +12 -0
- package/src/devtools/devtools-messages-common.ts +8 -4
- package/src/devtools/devtools-messages-leader.ts +12 -0
- package/src/devtools/mod.ts +1 -1
- package/src/errors.ts +18 -17
- package/src/index.ts +2 -0
- package/src/leader-thread/LeaderSyncProcessor.ts +417 -347
- package/src/leader-thread/RejectedPushError.ts +106 -0
- package/src/leader-thread/connection.ts +1 -1
- package/src/leader-thread/eventlog.ts +16 -14
- package/src/leader-thread/leader-worker-devtools.ts +107 -66
- package/src/leader-thread/make-leader-thread-layer.test.ts +1 -1
- package/src/leader-thread/make-leader-thread-layer.ts +41 -31
- package/src/leader-thread/materialize-event.ts +8 -4
- package/src/leader-thread/recreate-db.ts +1 -1
- package/src/leader-thread/shutdown-channel.ts +2 -6
- package/src/leader-thread/stream-events.ts +10 -5
- package/src/leader-thread/types.ts +7 -6
- package/src/logging.ts +4 -4
- package/src/make-client-session.ts +2 -2
- package/src/materializer-helper.ts +9 -9
- package/src/otel.ts +3 -2
- package/src/rematerialize-from-eventlog.ts +60 -60
- package/src/schema/EventDef/define.ts +22 -6
- package/src/schema/EventDef/deprecated.test.ts +129 -0
- package/src/schema/EventDef/deprecated.ts +175 -0
- package/src/schema/EventDef/event-def.ts +5 -0
- package/src/schema/EventDef/mod.ts +1 -0
- package/src/schema/EventSequenceNumber/client.ts +11 -11
- package/src/schema/EventSequenceNumber.test.ts +2 -1
- package/src/schema/LiveStoreEvent/client.test.ts +97 -0
- package/src/schema/LiveStoreEvent/client.ts +6 -3
- package/src/schema/schema.ts +9 -4
- package/src/schema/state/sqlite/client-document-def.test.ts +142 -3
- package/src/schema/state/sqlite/client-document-def.ts +37 -14
- package/src/schema/state/sqlite/column-annotations.test.ts +2 -1
- package/src/schema/state/sqlite/column-annotations.ts +2 -1
- package/src/schema/state/sqlite/column-def.test.ts +8 -6
- package/src/schema/state/sqlite/column-def.ts +41 -36
- package/src/schema/state/sqlite/column-spec.test.ts +3 -1
- package/src/schema/state/sqlite/column-spec.ts +9 -8
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -2
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +2 -1
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +13 -4
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +3 -3
- package/src/schema/state/sqlite/mod.ts +4 -5
- package/src/schema/state/sqlite/query-builder/api.ts +37 -8
- package/src/schema/state/sqlite/query-builder/astToSql.ts +87 -7
- package/src/schema/state/sqlite/query-builder/impl.test.ts +145 -3
- package/src/schema/state/sqlite/query-builder/impl.ts +26 -12
- package/src/schema/state/sqlite/schema-helpers.ts +2 -2
- package/src/schema/state/sqlite/table-def.test.ts +67 -4
- package/src/schema/state/sqlite/table-def.ts +8 -15
- package/src/schema/unknown-events.ts +2 -2
- package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +3 -1
- package/src/schema-management/common.ts +2 -2
- package/src/schema-management/migrations.ts +1 -1
- package/src/sql-queries/sql-queries.ts +10 -6
- package/src/sql-queries/sql-query-builder.ts +1 -0
- package/src/sqlite-db-helper.ts +3 -3
- package/src/sqlite-types.ts +3 -2
- package/src/sync/ClientSessionSyncProcessor.ts +142 -133
- package/src/sync/errors.ts +10 -22
- package/src/sync/mock-sync-backend.ts +139 -97
- package/src/sync/next/compact-events.ts +5 -5
- package/src/sync/next/facts.ts +7 -6
- package/src/sync/next/history-dag-common.ts +9 -6
- package/src/sync/next/history-dag.ts +3 -3
- package/src/sync/next/rebase-events.ts +1 -1
- package/src/sync/next/test/compact-events.calculator.test.ts +3 -2
- package/src/sync/next/test/compact-events.test.ts +4 -3
- package/src/sync/next/test/event-fixtures.ts +2 -2
- package/src/sync/sync-backend-kv.ts +1 -0
- package/src/sync/sync-backend.ts +5 -4
- package/src/sync/sync.ts +21 -0
- package/src/sync/syncstate.test.ts +513 -435
- package/src/sync/syncstate.ts +80 -86
- package/src/sync/transport-chunking.ts +3 -3
- package/src/sync/validate-push-payload.ts +4 -6
- package/src/util.ts +2 -2
- package/src/version.ts +8 -4
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deprecation Annotations for Events
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for marking event fields and entire events as deprecated.
|
|
5
|
+
* When a deprecated field is used or a deprecated event is committed, a warning is logged.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { Events } from '@livestore/livestore'
|
|
10
|
+
* import { Schema } from 'effect'
|
|
11
|
+
* import { deprecated } from '@livestore/common/schema'
|
|
12
|
+
*
|
|
13
|
+
* // Field-level deprecation
|
|
14
|
+
* const todoUpdated = Events.synced({
|
|
15
|
+
* name: 'v1.TodoUpdated',
|
|
16
|
+
* schema: Schema.Struct({
|
|
17
|
+
* id: Schema.String,
|
|
18
|
+
* title: Schema.optional(Schema.String).pipe(deprecated("Use 'text' instead")),
|
|
19
|
+
* text: Schema.optional(Schema.String),
|
|
20
|
+
* }),
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* // Event-level deprecation
|
|
24
|
+
* const todoRenamed = Events.synced({
|
|
25
|
+
* name: 'v1.TodoRenamed',
|
|
26
|
+
* schema: Schema.Struct({ id: Schema.String, name: Schema.String }),
|
|
27
|
+
* deprecated: "Use 'v1.TodoUpdated' instead",
|
|
28
|
+
* })
|
|
29
|
+
* ```
|
|
30
|
+
* @module
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type { Schema } from '@livestore/utils/effect'
|
|
34
|
+
import { Effect, Option, SchemaAST } from '@livestore/utils/effect'
|
|
35
|
+
|
|
36
|
+
import type { EventDef } from './event-def.ts'
|
|
37
|
+
|
|
38
|
+
/** Symbol used to mark schemas as deprecated. */
|
|
39
|
+
export const DeprecatedId = Symbol.for('livestore/schema/annotations/deprecated')
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Marks a schema field as deprecated with a reason message.
|
|
43
|
+
* When an event is committed with a deprecated field that has a value,
|
|
44
|
+
* a warning will be logged.
|
|
45
|
+
*
|
|
46
|
+
* Works with both Schema types and PropertySignatures (from Schema.optional).
|
|
47
|
+
*
|
|
48
|
+
* @param reason - Explanation of why this field is deprecated and what to use instead
|
|
49
|
+
* @returns A function that adds the deprecation annotation to the schema
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const schema = Schema.Struct({
|
|
54
|
+
* oldField: Schema.optional(Schema.String).pipe(deprecated("Use 'newField' instead")),
|
|
55
|
+
* newField: Schema.optional(Schema.String),
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export const deprecated =
|
|
60
|
+
(reason: string) =>
|
|
61
|
+
<T extends { annotations: (annotations: { readonly [DeprecatedId]?: string }) => T }>(schema: T): T =>
|
|
62
|
+
schema.annotations({ [DeprecatedId]: reason })
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Checks if a schema has a deprecation annotation.
|
|
66
|
+
*
|
|
67
|
+
* @param schema - The schema to check
|
|
68
|
+
* @returns The deprecation reason if deprecated, None otherwise
|
|
69
|
+
*/
|
|
70
|
+
export const getDeprecatedReason = <A, I, R>(schema: Schema.Schema<A, I, R>): Option.Option<string> =>
|
|
71
|
+
SchemaAST.getAnnotation<string>(DeprecatedId)(schema.ast)
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Checks if a schema is deprecated.
|
|
75
|
+
*
|
|
76
|
+
* @param schema - The schema to check
|
|
77
|
+
* @returns true if the schema is deprecated
|
|
78
|
+
*/
|
|
79
|
+
export const isDeprecated = <A, I, R>(schema: Schema.Schema<A, I, R>): boolean =>
|
|
80
|
+
Option.isSome(getDeprecatedReason(schema))
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Finds deprecated fields with values in the given event arguments.
|
|
84
|
+
* This walks through a Struct schema and checks each property for deprecation.
|
|
85
|
+
*
|
|
86
|
+
* @param schema - The event schema (expected to be a Struct)
|
|
87
|
+
* @param args - The event arguments
|
|
88
|
+
* @returns Array of objects containing field name and deprecation reason
|
|
89
|
+
*/
|
|
90
|
+
export const findDeprecatedFieldsWithValues = (
|
|
91
|
+
schema: Schema.Schema.All,
|
|
92
|
+
args: Record<string, unknown>,
|
|
93
|
+
): Array<{ field: string; reason: string }> => {
|
|
94
|
+
const result: Array<{ field: string; reason: string }> = []
|
|
95
|
+
const ast = schema.ast
|
|
96
|
+
|
|
97
|
+
// Handle TypeLiteral (Struct) schemas
|
|
98
|
+
if (ast._tag === 'TypeLiteral') {
|
|
99
|
+
for (const prop of ast.propertySignatures) {
|
|
100
|
+
const fieldName = String(prop.name)
|
|
101
|
+
const fieldValue = args[fieldName]
|
|
102
|
+
|
|
103
|
+
// Only check fields that have a value (not undefined)
|
|
104
|
+
if (fieldValue !== undefined) {
|
|
105
|
+
// Check deprecation on the property signature itself (for Schema.optional(...).pipe(deprecated(...)))
|
|
106
|
+
const propAnnotations = prop.annotations as Record<symbol, unknown> | undefined
|
|
107
|
+
const deprecationReason = propAnnotations?.[DeprecatedId] as string | undefined
|
|
108
|
+
|
|
109
|
+
// Also check deprecation on the type (for direct field deprecation)
|
|
110
|
+
const typeDeprecation = SchemaAST.getAnnotation<string>(DeprecatedId)(prop.type)
|
|
111
|
+
|
|
112
|
+
const reason = deprecationReason ?? (Option.isSome(typeDeprecation) === true ? typeDeprecation.value : undefined)
|
|
113
|
+
if (reason !== undefined) {
|
|
114
|
+
result.push({ field: fieldName, reason })
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Set of event names that have already logged deprecation warnings. */
|
|
124
|
+
const warnedDeprecatedEvents = new Set<string>()
|
|
125
|
+
|
|
126
|
+
/** Map of event+field combinations that have already logged deprecation warnings. */
|
|
127
|
+
const warnedDeprecatedFields = new Set<string>()
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Logs deprecation warnings for an event using Effect.logWarning.
|
|
131
|
+
* Checks both event-level and field-level deprecation, with deduplication.
|
|
132
|
+
*
|
|
133
|
+
* @param eventDef - The event definition to check
|
|
134
|
+
* @param args - The event arguments
|
|
135
|
+
* @returns An Effect that logs warnings for any deprecations found
|
|
136
|
+
*/
|
|
137
|
+
export const logDeprecationWarnings = (
|
|
138
|
+
eventDef: EventDef.AnyWithoutFn,
|
|
139
|
+
args: Record<string, unknown>,
|
|
140
|
+
): Effect.Effect<void> =>
|
|
141
|
+
Effect.gen(function* () {
|
|
142
|
+
const eventName = eventDef.name
|
|
143
|
+
|
|
144
|
+
// Check for event-level deprecation
|
|
145
|
+
const eventDeprecation = eventDef.options.deprecated
|
|
146
|
+
if (eventDeprecation !== undefined && warnedDeprecatedEvents.has(eventName) === false) {
|
|
147
|
+
warnedDeprecatedEvents.add(eventName)
|
|
148
|
+
yield* Effect.logWarning('@livestore/schema:deprecated-event', {
|
|
149
|
+
event: eventName,
|
|
150
|
+
reason: eventDeprecation,
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check for deprecated fields with values
|
|
155
|
+
const deprecatedFields = findDeprecatedFieldsWithValues(eventDef.schema, args)
|
|
156
|
+
for (const { field, reason } of deprecatedFields) {
|
|
157
|
+
const key = `${eventName}:${field}`
|
|
158
|
+
if (warnedDeprecatedFields.has(key) === false) {
|
|
159
|
+
warnedDeprecatedFields.add(key)
|
|
160
|
+
yield* Effect.logWarning('@livestore/schema:deprecated-field', {
|
|
161
|
+
event: eventName,
|
|
162
|
+
field,
|
|
163
|
+
reason,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Resets the deprecation warning state. Useful for testing.
|
|
171
|
+
*/
|
|
172
|
+
export const resetDeprecationWarnings = (): void => {
|
|
173
|
+
warnedDeprecatedEvents.clear()
|
|
174
|
+
warnedDeprecatedFields.clear()
|
|
175
|
+
}
|
|
@@ -51,6 +51,11 @@ export type EventDef<TName extends string, TType, TEncoded = TType, TDerived ext
|
|
|
51
51
|
|
|
52
52
|
/** Whether this is a derived event. Derived events cannot have materializers. */
|
|
53
53
|
derived: TDerived
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Deprecation reason for this event. When set, a warning is logged at commit time.
|
|
57
|
+
*/
|
|
58
|
+
deprecated: string | undefined
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
/**
|
|
@@ -28,7 +28,7 @@ export const make = ClientBrand
|
|
|
28
28
|
* const defaultSeq = EventSequenceNumber.Client.DEFAULT // 0
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
export const DEFAULT = 0
|
|
31
|
+
export const DEFAULT = make(0)
|
|
32
32
|
|
|
33
33
|
/** Default rebase generation (0). Increments each time the client rebases unconfirmed events. */
|
|
34
34
|
export const REBASE_GENERATION_DEFAULT = 0
|
|
@@ -91,7 +91,7 @@ export const toString = (seqNum: Composite) => {
|
|
|
91
91
|
* For full notation documentation, see: contributor-docs/events-notation.md
|
|
92
92
|
*/
|
|
93
93
|
export const fromString = (str: string): Composite => {
|
|
94
|
-
if (
|
|
94
|
+
if (str.startsWith('e') === false) {
|
|
95
95
|
throw new Error('Invalid event sequence number string: must start with "e"')
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -111,24 +111,24 @@ export const fromString = (str: string): Composite => {
|
|
|
111
111
|
const parts = withoutRebase.split('.')
|
|
112
112
|
|
|
113
113
|
// Validate that parts contain only digits (and possibly empty for client)
|
|
114
|
-
if (parts[0] === '' ||
|
|
114
|
+
if (parts[0] === '' || /^\d+$/.test(parts[0]!) === false) {
|
|
115
115
|
throw new Error('Invalid event sequence number string: invalid number format')
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
if (parts.length > 1 && parts[1] !== undefined && (parts[1] === '' ||
|
|
118
|
+
if (parts.length > 1 && parts[1] !== undefined && (parts[1] === '' || /^\d+$/.test(parts[1]) === false)) {
|
|
119
119
|
throw new Error('Invalid event sequence number string: invalid number format')
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
const global = Number.parseInt(parts[0]!, 10)
|
|
123
123
|
const client = parts.length > 1 && parts[1] !== undefined ? Number.parseInt(parts[1], 10) : 0
|
|
124
124
|
|
|
125
|
-
if (Number.isNaN(global) || Number.isNaN(client) || Number.isNaN(rebaseGeneration)) {
|
|
125
|
+
if (Number.isNaN(global) === true || Number.isNaN(client) === true || Number.isNaN(rebaseGeneration) === true) {
|
|
126
126
|
throw new TypeError('Invalid event sequence number string: invalid number format')
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
return {
|
|
130
|
-
global: global
|
|
131
|
-
client: client
|
|
130
|
+
global: makeGlobal(global),
|
|
131
|
+
client: make(client),
|
|
132
132
|
rebaseGeneration,
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -187,7 +187,7 @@ const CompositeSchema = S.Struct({
|
|
|
187
187
|
* If rebaseGeneration is omitted, defaults to REBASE_GENERATION_DEFAULT (0).
|
|
188
188
|
*/
|
|
189
189
|
const makeComposite = (seqNum: CompositeInput): Composite => {
|
|
190
|
-
return S.is(CompositeSchema)(seqNum)
|
|
190
|
+
return S.is(CompositeSchema)(seqNum) === true
|
|
191
191
|
? seqNum
|
|
192
192
|
: S.decodeSync(CompositeSchema)({
|
|
193
193
|
...seqNum,
|
|
@@ -234,11 +234,11 @@ export const nextPair = ({
|
|
|
234
234
|
isClient: boolean
|
|
235
235
|
rebaseGeneration?: number
|
|
236
236
|
}): CompositePair => {
|
|
237
|
-
if (isClient) {
|
|
237
|
+
if (isClient === true) {
|
|
238
238
|
return {
|
|
239
239
|
seqNum: {
|
|
240
240
|
global: seqNum.global,
|
|
241
|
-
client: (seqNum.client + 1)
|
|
241
|
+
client: make(seqNum.client + 1),
|
|
242
242
|
rebaseGeneration: rebaseGeneration ?? seqNum.rebaseGeneration,
|
|
243
243
|
},
|
|
244
244
|
parentSeqNum: seqNum,
|
|
@@ -247,7 +247,7 @@ export const nextPair = ({
|
|
|
247
247
|
|
|
248
248
|
return {
|
|
249
249
|
seqNum: {
|
|
250
|
-
global: (seqNum.global + 1)
|
|
250
|
+
global: makeGlobal(seqNum.global + 1),
|
|
251
251
|
client: DEFAULT,
|
|
252
252
|
rebaseGeneration: rebaseGeneration ?? seqNum.rebaseGeneration,
|
|
253
253
|
},
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { Option } from '@livestore/utils/effect'
|
|
4
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
5
|
+
|
|
6
|
+
import * as EventSequenceNumber from '../EventSequenceNumber/mod.ts'
|
|
7
|
+
import { EncodedWithMeta, isEqualEncoded, type Encoded } from './client.ts'
|
|
8
|
+
|
|
9
|
+
Vitest.describe('EncodedWithMeta', () => {
|
|
10
|
+
Vitest.test('toGlobal() produces numeric seqNums through JSON.stringify', () => {
|
|
11
|
+
const event = new EncodedWithMeta({
|
|
12
|
+
name: 'test-v1',
|
|
13
|
+
args: { id: '1' },
|
|
14
|
+
seqNum: EventSequenceNumber.Client.Composite.make({ global: 5, client: 0 }),
|
|
15
|
+
parentSeqNum: EventSequenceNumber.Client.Composite.make({ global: 4, client: 0 }),
|
|
16
|
+
clientId: 'client-1',
|
|
17
|
+
sessionId: 'session-1',
|
|
18
|
+
meta: {
|
|
19
|
+
sessionChangeset: { _tag: 'unset' },
|
|
20
|
+
syncMetadata: Option.none(),
|
|
21
|
+
materializerHashLeader: Option.none(),
|
|
22
|
+
materializerHashSession: Option.none(),
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const global = event.toGlobal()
|
|
27
|
+
const parsed = JSON.parse(JSON.stringify(global))
|
|
28
|
+
|
|
29
|
+
expect(parsed.seqNum).toBe(5)
|
|
30
|
+
expect(parsed.parentSeqNum).toBe(4)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
Vitest.describe('isEqualEncoded', () => {
|
|
35
|
+
const makeEncodedEvent = (args: unknown): Encoded => ({
|
|
36
|
+
name: 'testEvent-v1',
|
|
37
|
+
args,
|
|
38
|
+
seqNum: EventSequenceNumber.Client.Composite.make({ global: 1, client: 0 }),
|
|
39
|
+
parentSeqNum: EventSequenceNumber.Client.Composite.make(EventSequenceNumber.Client.ROOT),
|
|
40
|
+
clientId: 'client-1',
|
|
41
|
+
sessionId: 'session-1',
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
Vitest.it('should consider events with identical args as equal', () => {
|
|
45
|
+
const a = makeEncodedEvent({ id: 'abc', text: 'hello' })
|
|
46
|
+
const b = makeEncodedEvent({ id: 'abc', text: 'hello' })
|
|
47
|
+
expect(isEqualEncoded(a, b)).toBe(true)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
Vitest.it('should consider events with different key order in args as equal', () => {
|
|
51
|
+
const a = makeEncodedEvent({ b: 2, a: 1 })
|
|
52
|
+
const b = makeEncodedEvent({ a: 1, b: 2 })
|
|
53
|
+
expect(isEqualEncoded(a, b)).toBe(true)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
Vitest.it('should consider events with different key order in nested args as equal', () => {
|
|
57
|
+
const a = makeEncodedEvent({ outer: { b: 2, a: 1 }, x: 'y' })
|
|
58
|
+
const b = makeEncodedEvent({ x: 'y', outer: { a: 1, b: 2 } })
|
|
59
|
+
expect(isEqualEncoded(a, b)).toBe(true)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
Vitest.it('should consider events with different args values as not equal', () => {
|
|
63
|
+
const a = makeEncodedEvent({ id: 'abc' })
|
|
64
|
+
const b = makeEncodedEvent({ id: 'def' })
|
|
65
|
+
expect(isEqualEncoded(a, b)).toBe(false)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
Vitest.it('should consider events with different args keys as not equal', () => {
|
|
69
|
+
const a = makeEncodedEvent({ a: 1 })
|
|
70
|
+
const b = makeEncodedEvent({ b: 1 })
|
|
71
|
+
expect(isEqualEncoded(a, b)).toBe(false)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
Vitest.it('should handle null args', () => {
|
|
75
|
+
const a = makeEncodedEvent(null)
|
|
76
|
+
const b = makeEncodedEvent(null)
|
|
77
|
+
expect(isEqualEncoded(a, b)).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
Vitest.it('should handle array args', () => {
|
|
81
|
+
const a = makeEncodedEvent([1, 2, 3])
|
|
82
|
+
const b = makeEncodedEvent([1, 2, 3])
|
|
83
|
+
expect(isEqualEncoded(a, b)).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
Vitest.it('should handle empty object args', () => {
|
|
87
|
+
const a = makeEncodedEvent({})
|
|
88
|
+
const b = makeEncodedEvent({})
|
|
89
|
+
expect(isEqualEncoded(a, b)).toBe(true)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
Vitest.it('should consider events with different names as not equal', () => {
|
|
93
|
+
const a = { ...makeEncodedEvent({ id: 'abc' }), name: 'eventA' }
|
|
94
|
+
const b = { ...makeEncodedEvent({ id: 'abc' }), name: 'eventB' }
|
|
95
|
+
expect(isEqualEncoded(a, b)).toBe(false)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -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,9 +1,16 @@
|
|
|
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
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
ClientDocumentTableDefSymbol,
|
|
9
|
+
clientDocument,
|
|
10
|
+
createOptimisticEventSchema,
|
|
11
|
+
mergeDefaultValues,
|
|
12
|
+
} from './client-document-def.ts'
|
|
13
|
+
import { getResultSchema } from './query-builder/impl.ts'
|
|
7
14
|
|
|
8
15
|
describe('client document table', () => {
|
|
9
16
|
test('set event', () => {
|
|
@@ -248,6 +255,139 @@ describe('client document table', () => {
|
|
|
248
255
|
`)
|
|
249
256
|
})
|
|
250
257
|
})
|
|
258
|
+
|
|
259
|
+
/** Ensures optimistic decoding stays robust when persisted JSON is incompatible. */
|
|
260
|
+
describe('optimistic schema', () => {
|
|
261
|
+
/** Models persisted JSON using epoch numbers + base64 while app code expects Date + Uint8Array. */
|
|
262
|
+
const valueSchema = Schema.Struct({
|
|
263
|
+
createdAt: Schema.DateFromNumber,
|
|
264
|
+
avatar: Schema.Uint8ArrayFromBase64,
|
|
265
|
+
})
|
|
266
|
+
const defaultValue = {
|
|
267
|
+
createdAt: new Date(0),
|
|
268
|
+
avatar: new Uint8Array(),
|
|
269
|
+
}
|
|
270
|
+
const invalidPayloads: Array<{ label: string; value: unknown }> = [
|
|
271
|
+
{ label: 'decoded-shape JSON', value: { createdAt: new Date(0), avatar: new Uint8Array([1, 2]) } },
|
|
272
|
+
{ label: 'wrong types', value: { createdAt: 'not-a-number', avatar: { nested: 'bad' } } },
|
|
273
|
+
{ label: 'missing required fields', value: {} },
|
|
274
|
+
]
|
|
275
|
+
const validPayload = { createdAt: 42, avatar: 'AQI=' }
|
|
276
|
+
const extraFieldsPayload = { createdAt: 42, avatar: 'AQI=', extra: 'ignored' }
|
|
277
|
+
|
|
278
|
+
test.each(invalidPayloads)('decodes invalid persisted JSON ($label)', ({ value }) => {
|
|
279
|
+
const optimisticSchema = createOptimisticEventSchema({
|
|
280
|
+
valueSchema,
|
|
281
|
+
defaultValue,
|
|
282
|
+
partialSet: false,
|
|
283
|
+
})
|
|
284
|
+
const rowSchema = Schema.parseJson(optimisticSchema)
|
|
285
|
+
|
|
286
|
+
expect(Schema.decodeUnknownSync(rowSchema)(JSON.stringify(value))).toEqual(defaultValue)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test('decodes valid persisted JSON (encoded shape)', () => {
|
|
290
|
+
const optimisticSchema = createOptimisticEventSchema({
|
|
291
|
+
valueSchema,
|
|
292
|
+
defaultValue,
|
|
293
|
+
partialSet: false,
|
|
294
|
+
})
|
|
295
|
+
const rowSchema = Schema.parseJson(optimisticSchema)
|
|
296
|
+
|
|
297
|
+
expect(Schema.decodeUnknownSync(rowSchema)(JSON.stringify(validPayload))).toEqual({
|
|
298
|
+
createdAt: new Date(42),
|
|
299
|
+
avatar: new Uint8Array([1, 2]),
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
test('decodes valid persisted JSON with extra fields', () => {
|
|
304
|
+
const optimisticSchema = createOptimisticEventSchema({
|
|
305
|
+
valueSchema,
|
|
306
|
+
defaultValue,
|
|
307
|
+
partialSet: false,
|
|
308
|
+
})
|
|
309
|
+
const rowSchema = Schema.parseJson(optimisticSchema)
|
|
310
|
+
|
|
311
|
+
expect(Schema.decodeUnknownSync(rowSchema)(JSON.stringify(extraFieldsPayload))).toEqual({
|
|
312
|
+
createdAt: new Date(42),
|
|
313
|
+
avatar: new Uint8Array([1, 2]),
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
test.each(invalidPayloads)('decodes clientDocument rowSchema with invalid JSON ($label)', ({ value }) => {
|
|
318
|
+
const Doc = clientDocument({
|
|
319
|
+
name: 'test_numbers',
|
|
320
|
+
schema: valueSchema,
|
|
321
|
+
default: { value: defaultValue },
|
|
322
|
+
partialSet: false,
|
|
323
|
+
})
|
|
324
|
+
const row = {
|
|
325
|
+
id: 'row-1',
|
|
326
|
+
value: JSON.stringify(value),
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
expect(Schema.decodeUnknownSync(Doc.rowSchema)(row)).toEqual({ id: 'row-1', value: defaultValue })
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
test('decodes clientDocument rowSchema with valid encoded JSON', () => {
|
|
333
|
+
const Doc = clientDocument({
|
|
334
|
+
name: 'test_numbers',
|
|
335
|
+
schema: valueSchema,
|
|
336
|
+
default: { value: defaultValue },
|
|
337
|
+
partialSet: false,
|
|
338
|
+
})
|
|
339
|
+
const row = {
|
|
340
|
+
id: 'row-1',
|
|
341
|
+
value: JSON.stringify(validPayload),
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
expect(Schema.decodeUnknownSync(Doc.rowSchema)(row)).toEqual({
|
|
345
|
+
id: 'row-1',
|
|
346
|
+
value: { createdAt: new Date(42), avatar: new Uint8Array([1, 2]) },
|
|
347
|
+
})
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
test.each(invalidPayloads)('decodes RowQuery result schema with invalid JSON ($label)', ({ value }) => {
|
|
351
|
+
const Doc = clientDocument({
|
|
352
|
+
name: 'test_numbers',
|
|
353
|
+
schema: valueSchema,
|
|
354
|
+
default: { value: defaultValue },
|
|
355
|
+
partialSet: false,
|
|
356
|
+
})
|
|
357
|
+
const query = Doc.get('row-1')
|
|
358
|
+
const resultSchema = getResultSchema(query)
|
|
359
|
+
const rawDbResults = [
|
|
360
|
+
{
|
|
361
|
+
id: 'row-1',
|
|
362
|
+
value: JSON.stringify(value),
|
|
363
|
+
},
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
expect(Schema.decodeUnknownSync(resultSchema)(rawDbResults)).toEqual(defaultValue)
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
test('decodes RowQuery result schema with valid encoded JSON', () => {
|
|
370
|
+
const Doc = clientDocument({
|
|
371
|
+
name: 'test_numbers',
|
|
372
|
+
schema: valueSchema,
|
|
373
|
+
default: { value: defaultValue },
|
|
374
|
+
partialSet: false,
|
|
375
|
+
})
|
|
376
|
+
const query = Doc.get('row-1')
|
|
377
|
+
const resultSchema = getResultSchema(query)
|
|
378
|
+
const rawDbResults = [
|
|
379
|
+
{
|
|
380
|
+
id: 'row-1',
|
|
381
|
+
value: JSON.stringify(validPayload),
|
|
382
|
+
},
|
|
383
|
+
]
|
|
384
|
+
|
|
385
|
+
expect(Schema.decodeUnknownSync(resultSchema)(rawDbResults)).toEqual({
|
|
386
|
+
createdAt: new Date(42),
|
|
387
|
+
avatar: new Uint8Array([1, 2]),
|
|
388
|
+
})
|
|
389
|
+
})
|
|
390
|
+
})
|
|
251
391
|
})
|
|
252
392
|
|
|
253
393
|
const patchId = (muationEvent: LiveStoreEvent.Input.Decoded) => {
|
|
@@ -255,7 +395,6 @@ const patchId = (muationEvent: LiveStoreEvent.Input.Decoded) => {
|
|
|
255
395
|
const id = `00000000-0000-0000-0000-000000000000`
|
|
256
396
|
return { ...muationEvent, id }
|
|
257
397
|
}
|
|
258
|
-
|
|
259
398
|
describe('mergeDefaultValues', () => {
|
|
260
399
|
test('merges values from both objects', () => {
|
|
261
400
|
const defaults = { a: 1, b: 2 }
|