@livestore/common 0.4.0-dev.0 → 0.4.0-dev.10
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 +7 -2
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/ClientSessionLeaderThreadProxy.js.map +1 -1
- package/dist/adapter-types.d.ts +9 -3
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
- package/dist/devtools/devtools-messages-common.d.ts +7 -14
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +1 -6
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +27 -25
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/errors.d.ts +47 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +22 -3
- package/dist/errors.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +7 -3
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +122 -49
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts +4 -10
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +4 -6
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +6 -2
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +1 -2
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +68 -19
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.d.ts +2 -0
- package/dist/leader-thread/make-leader-thread-layer.test.d.ts.map +1 -0
- package/dist/leader-thread/make-leader-thread-layer.test.js +32 -0
- package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -0
- package/dist/leader-thread/materialize-event.d.ts +2 -2
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +23 -9
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts +2 -3
- package/dist/leader-thread/recreate-db.d.ts.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/types.d.ts +7 -5
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/materializer-helper.d.ts +1 -1
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +20 -4
- package/dist/materializer-helper.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 +25 -16
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef.d.ts +3 -0
- package/dist/schema/EventDef.d.ts.map +1 -1
- package/dist/schema/EventDef.js.map +1 -1
- package/dist/schema/LiveStoreEvent.d.ts +1 -1
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent.js +1 -2
- package/dist/schema/LiveStoreEvent.js.map +1 -1
- package/dist/schema/mod.d.ts +2 -0
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +1 -0
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/schema.d.ts +15 -0
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +26 -1
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts +35 -5
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +95 -4
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +16 -0
- 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 +14 -6
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts +19 -0
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-def.js +179 -0
- package/dist/schema/state/sqlite/column-def.js.map +1 -0
- package/dist/schema/state/sqlite/column-def.test.d.ts +2 -0
- package/dist/schema/state/sqlite/column-def.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/column-def.test.js +572 -0
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +23 -6
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +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 +2 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts +1 -1
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +1 -1
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.d.ts +5 -2
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +6 -2
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +137 -2
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables.d.ts +42 -6
- package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables.js +2 -0
- package/dist/schema/state/sqlite/system-tables.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +6 -8
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +4 -211
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +59 -453
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/schema/unknown-events.d.ts +47 -0
- package/dist/schema/unknown-events.d.ts.map +1 -0
- package/dist/schema/unknown-events.js +69 -0
- package/dist/schema/unknown-events.js.map +1 -0
- package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
- package/dist/sql-queries/sql-query-builder.js +2 -1
- package/dist/sql-queries/sql-query-builder.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +9 -11
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +35 -33
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/errors.d.ts +61 -0
- package/dist/sync/errors.d.ts.map +1 -0
- package/dist/sync/errors.js +36 -0
- package/dist/sync/errors.js.map +1 -0
- package/dist/sync/index.d.ts +3 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +3 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/mock-sync-backend.d.ts +23 -0
- package/dist/sync/mock-sync-backend.d.ts.map +1 -0
- package/dist/sync/mock-sync-backend.js +114 -0
- package/dist/sync/mock-sync-backend.js.map +1 -0
- package/dist/sync/next/compact-events.d.ts.map +1 -1
- package/dist/sync/next/compact-events.js +4 -5
- 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 +1 -2
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +50 -11
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +193 -4
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/history-dag.js +3 -1
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/sync-backend-kv.d.ts +7 -0
- package/dist/sync/sync-backend-kv.d.ts.map +1 -0
- package/dist/sync/sync-backend-kv.js +18 -0
- package/dist/sync/sync-backend-kv.js.map +1 -0
- package/dist/sync/sync-backend.d.ts +105 -0
- package/dist/sync/sync-backend.d.ts.map +1 -0
- package/dist/sync/sync-backend.js +61 -0
- package/dist/sync/sync-backend.js.map +1 -0
- package/dist/sync/sync.d.ts +6 -84
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js +2 -27
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/transport-chunking.d.ts +36 -0
- package/dist/sync/transport-chunking.d.ts.map +1 -0
- package/dist/sync/transport-chunking.js +56 -0
- package/dist/sync/transport-chunking.js.map +1 -0
- package/dist/sync/validate-push-payload.d.ts +1 -1
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +6 -6
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/testing/event-factory.d.ts +68 -0
- package/dist/testing/event-factory.d.ts.map +1 -0
- package/dist/testing/event-factory.js +80 -0
- package/dist/testing/event-factory.js.map +1 -0
- package/dist/testing/mod.d.ts +2 -0
- package/dist/testing/mod.d.ts.map +1 -0
- package/dist/testing/mod.js +2 -0
- package/dist/testing/mod.js.map +1 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +7 -8
- package/src/ClientSessionLeaderThreadProxy.ts +7 -2
- package/src/adapter-types.ts +13 -3
- package/src/devtools/devtools-messages-common.ts +1 -8
- package/src/errors.ts +33 -4
- package/src/leader-thread/LeaderSyncProcessor.ts +179 -57
- package/src/leader-thread/eventlog.ts +10 -6
- package/src/leader-thread/leader-worker-devtools.ts +6 -2
- package/src/leader-thread/make-leader-thread-layer.test.ts +44 -0
- package/src/leader-thread/make-leader-thread-layer.ts +137 -26
- package/src/leader-thread/materialize-event.ts +34 -9
- package/src/leader-thread/recreate-db.ts +11 -3
- package/src/leader-thread/shutdown-channel.ts +16 -2
- package/src/leader-thread/types.ts +7 -5
- package/src/materializer-helper.ts +22 -5
- package/src/rematerialize-from-eventlog.ts +33 -23
- package/src/schema/EventDef.ts +3 -0
- package/src/schema/LiveStoreEvent.ts +1 -2
- package/src/schema/mod.ts +2 -0
- package/src/schema/schema.ts +37 -1
- package/src/schema/state/sqlite/client-document-def.test.ts +17 -0
- package/src/schema/state/sqlite/client-document-def.ts +117 -5
- package/src/schema/state/sqlite/column-annotations.ts +16 -6
- package/src/schema/state/sqlite/column-def.test.ts +722 -0
- package/src/schema/state/sqlite/column-def.ts +215 -0
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +26 -6
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +2 -1
- package/src/schema/state/sqlite/mod.ts +1 -0
- package/src/schema/state/sqlite/query-builder/api.ts +7 -2
- package/src/schema/state/sqlite/query-builder/impl.test.ts +187 -6
- package/src/schema/state/sqlite/query-builder/impl.ts +8 -2
- package/src/schema/state/sqlite/system-tables.ts +2 -0
- package/src/schema/state/sqlite/table-def.test.ts +74 -569
- package/src/schema/state/sqlite/table-def.ts +13 -262
- package/src/schema/unknown-events.ts +131 -0
- package/src/sql-queries/sql-query-builder.ts +2 -1
- package/src/sync/ClientSessionSyncProcessor.ts +55 -49
- package/src/sync/errors.ts +38 -0
- package/src/sync/index.ts +3 -0
- package/src/sync/mock-sync-backend.ts +184 -0
- package/src/sync/next/compact-events.ts +4 -5
- package/src/sync/next/facts.ts +1 -3
- package/src/sync/next/history-dag-common.ts +272 -21
- package/src/sync/next/history-dag.ts +3 -1
- package/src/sync/sync-backend-kv.ts +22 -0
- package/src/sync/sync-backend.ts +185 -0
- package/src/sync/sync.ts +6 -89
- package/src/sync/transport-chunking.ts +90 -0
- package/src/sync/validate-push-payload.ts +6 -7
- package/src/testing/event-factory.ts +133 -0
- package/src/testing/mod.ts +1 -0
- package/src/version.ts +2 -2
- package/dist/schema-management/migrations.test.d.ts +0 -2
- package/dist/schema-management/migrations.test.d.ts.map +0 -1
- package/dist/schema-management/migrations.test.js +0 -52
- package/dist/schema-management/migrations.test.js.map +0 -1
- package/dist/sync/next/graphology.d.ts +0 -8
- package/dist/sync/next/graphology.d.ts.map +0 -1
- package/dist/sync/next/graphology.js +0 -30
- package/dist/sync/next/graphology.js.map +0 -1
- package/dist/sync/next/graphology_.d.ts +0 -3
- package/dist/sync/next/graphology_.d.ts.map +0 -1
- package/dist/sync/next/graphology_.js +0 -3
- package/dist/sync/next/graphology_.js.map +0 -1
- package/src/sync/next/ambient.d.ts +0 -3
- package/src/sync/next/graphology.ts +0 -41
- package/src/sync/next/graphology_.ts +0 -2
@@ -0,0 +1,38 @@
|
|
1
|
+
import { Schema } from '@livestore/utils/effect'
|
2
|
+
import { UnexpectedError } from '../errors.ts'
|
3
|
+
import { EventSequenceNumber } from '../schema/mod.ts'
|
4
|
+
|
5
|
+
export class IsOfflineError extends Schema.TaggedError<IsOfflineError>()('IsOfflineError', {
|
6
|
+
cause: Schema.Defect,
|
7
|
+
}) {}
|
8
|
+
|
9
|
+
/** Unique ID generated by the backend when its created. Used to check whether the backend identity has changed. */
|
10
|
+
export const BackendId = Schema.String.annotations({ title: '@livestore/sync-cf:BackendId' })
|
11
|
+
|
12
|
+
export class BackendIdMismatchError extends Schema.TaggedError<BackendIdMismatchError>()('BackendIdMismatchError', {
|
13
|
+
expected: BackendId,
|
14
|
+
received: BackendId,
|
15
|
+
}) {}
|
16
|
+
|
17
|
+
export class ServerAheadError extends Schema.TaggedError<ServerAheadError>()('ServerAheadError', {
|
18
|
+
minimumExpectedNum: EventSequenceNumber.GlobalEventSequenceNumber,
|
19
|
+
providedNum: EventSequenceNumber.GlobalEventSequenceNumber,
|
20
|
+
}) {}
|
21
|
+
|
22
|
+
export class InvalidPushError extends Schema.TaggedError<InvalidPushError>()('InvalidPushError', {
|
23
|
+
cause: Schema.Union(UnexpectedError, ServerAheadError, BackendIdMismatchError),
|
24
|
+
}) {}
|
25
|
+
|
26
|
+
export class InvalidPullError extends Schema.TaggedError<InvalidPullError>()('InvalidPullError', {
|
27
|
+
cause: Schema.Defect,
|
28
|
+
}) {}
|
29
|
+
|
30
|
+
export class LeaderAheadError extends Schema.TaggedError<LeaderAheadError>()('LeaderAheadError', {
|
31
|
+
minimumExpectedNum: EventSequenceNumber.EventSequenceNumber,
|
32
|
+
providedNum: EventSequenceNumber.EventSequenceNumber,
|
33
|
+
/** Generation number the client session should use for subsequent pushes */
|
34
|
+
// nextGeneration: Schema.Number,
|
35
|
+
}) {}
|
36
|
+
|
37
|
+
export const SyncError = Schema.Union(InvalidPushError, InvalidPullError)
|
38
|
+
export type SyncError = typeof SyncError.Type
|
package/src/sync/index.ts
CHANGED
@@ -0,0 +1,184 @@
|
|
1
|
+
import type { Schema, Scope } from '@livestore/utils/effect'
|
2
|
+
import { Effect, Mailbox, Option, Queue, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
3
|
+
import { UnexpectedError } from '../errors.ts'
|
4
|
+
import { EventSequenceNumber, type LiveStoreEvent } from '../schema/mod.ts'
|
5
|
+
import { InvalidPushError } from './errors.ts'
|
6
|
+
import * as SyncBackend from './sync-backend.ts'
|
7
|
+
import { validatePushPayload } from './validate-push-payload.ts'
|
8
|
+
|
9
|
+
export interface MockSyncBackend {
|
10
|
+
pushedEvents: Stream.Stream<LiveStoreEvent.AnyEncodedGlobal>
|
11
|
+
connect: Effect.Effect<void>
|
12
|
+
disconnect: Effect.Effect<void>
|
13
|
+
makeSyncBackend: Effect.Effect<SyncBackend.SyncBackend, UnexpectedError, Scope.Scope>
|
14
|
+
advance: (...batch: LiveStoreEvent.AnyEncodedGlobal[]) => Effect.Effect<void>
|
15
|
+
/** Fail the next N push calls with an InvalidPushError (or custom error) */
|
16
|
+
failNextPushes: (
|
17
|
+
count: number,
|
18
|
+
error?: (batch: ReadonlyArray<LiveStoreEvent.AnyEncodedGlobal>) => Effect.Effect<never, InvalidPushError>,
|
19
|
+
) => Effect.Effect<void>
|
20
|
+
}
|
21
|
+
|
22
|
+
export interface MockSyncBackendOptions {
|
23
|
+
/** Chunk size for non-live pulls; defaults to 100 */
|
24
|
+
nonLiveChunkSize?: number
|
25
|
+
/** Initial connected state; defaults to false */
|
26
|
+
startConnected?: boolean
|
27
|
+
// TODO add a "flaky" mode to simulate transient network / server failures for pull/push
|
28
|
+
}
|
29
|
+
|
30
|
+
export const makeMockSyncBackend = (
|
31
|
+
options?: MockSyncBackendOptions,
|
32
|
+
): Effect.Effect<MockSyncBackend, UnexpectedError, Scope.Scope> =>
|
33
|
+
Effect.gen(function* () {
|
34
|
+
const syncEventSequenceNumberRef = { current: EventSequenceNumber.ROOT.global }
|
35
|
+
const syncPullQueue = yield* Queue.unbounded<LiveStoreEvent.AnyEncodedGlobal>()
|
36
|
+
const pushedEventsQueue = yield* Mailbox.make<LiveStoreEvent.AnyEncodedGlobal>()
|
37
|
+
const syncIsConnectedRef = yield* SubscriptionRef.make(options?.startConnected ?? false)
|
38
|
+
const allEventsRef: { current: LiveStoreEvent.AnyEncodedGlobal[] } = { current: [] }
|
39
|
+
|
40
|
+
const span = yield* Effect.currentSpan.pipe(Effect.orDie)
|
41
|
+
|
42
|
+
const semaphore = yield* Effect.makeSemaphore(1)
|
43
|
+
|
44
|
+
// TODO improve the API and implementation of simulating errors
|
45
|
+
const failCounterRef = yield* SubscriptionRef.make(0)
|
46
|
+
const failEffectRef = yield* SubscriptionRef.make<
|
47
|
+
((batch: ReadonlyArray<LiveStoreEvent.AnyEncodedGlobal>) => Effect.Effect<never, InvalidPushError>) | undefined
|
48
|
+
>(undefined)
|
49
|
+
|
50
|
+
const makeSyncBackend = Effect.gen(function* () {
|
51
|
+
const nonLiveChunkSize = Math.max(1, options?.nonLiveChunkSize ?? 100)
|
52
|
+
|
53
|
+
// TODO consider making offline state actively error pull/push.
|
54
|
+
// Currently, offline only reflects in `isConnected`, while operations still succeed,
|
55
|
+
// mirroring how some real providers behave during transient disconnects.
|
56
|
+
return SyncBackend.of<Schema.JsonValue>({
|
57
|
+
isConnected: syncIsConnectedRef,
|
58
|
+
connect: SubscriptionRef.set(syncIsConnectedRef, true),
|
59
|
+
ping: Effect.void,
|
60
|
+
pull: (cursor, options) =>
|
61
|
+
(options?.live
|
62
|
+
? Stream.concat(
|
63
|
+
Stream.make(SyncBackend.pullResItemEmpty()),
|
64
|
+
Stream.fromQueue(syncPullQueue).pipe(
|
65
|
+
Stream.chunks,
|
66
|
+
Stream.map((chunk) => ({
|
67
|
+
batch: [...chunk].map((eventEncoded) => ({ eventEncoded, metadata: Option.none() })),
|
68
|
+
pageInfo: SyncBackend.pageInfoNoMore,
|
69
|
+
})),
|
70
|
+
),
|
71
|
+
)
|
72
|
+
: Stream.fromEffect(
|
73
|
+
Effect.sync(() => {
|
74
|
+
const lastSeen = cursor.pipe(
|
75
|
+
Option.match({
|
76
|
+
onNone: () => EventSequenceNumber.ROOT.global,
|
77
|
+
onSome: (_) => _.eventSequenceNumber,
|
78
|
+
}),
|
79
|
+
)
|
80
|
+
// All events with seqNum greater than lastSeen
|
81
|
+
const slice = allEventsRef.current.filter((e) => e.seqNum > lastSeen)
|
82
|
+
// Split into configured chunk size
|
83
|
+
const chunks: { events: LiveStoreEvent.AnyEncodedGlobal[]; remaining: number }[] = []
|
84
|
+
for (let i = 0; i < slice.length; i += nonLiveChunkSize) {
|
85
|
+
const end = Math.min(i + nonLiveChunkSize, slice.length)
|
86
|
+
const remaining = Math.max(slice.length - end, 0)
|
87
|
+
chunks.push({ events: slice.slice(i, end), remaining })
|
88
|
+
}
|
89
|
+
if (chunks.length === 0) {
|
90
|
+
chunks.push({ events: [], remaining: 0 })
|
91
|
+
}
|
92
|
+
return chunks
|
93
|
+
}),
|
94
|
+
).pipe(
|
95
|
+
Stream.flatMap((chunks) =>
|
96
|
+
Stream.fromIterable(chunks).pipe(
|
97
|
+
Stream.map(({ events, remaining }) => ({
|
98
|
+
batch: events.map((eventEncoded) => ({ eventEncoded, metadata: Option.none() })),
|
99
|
+
pageInfo: remaining > 0 ? SyncBackend.pageInfoMoreKnown(remaining) : SyncBackend.pageInfoNoMore,
|
100
|
+
})),
|
101
|
+
),
|
102
|
+
),
|
103
|
+
)
|
104
|
+
).pipe(Stream.withSpan('MockSyncBackend:pull', { parent: span })),
|
105
|
+
push: (batch) =>
|
106
|
+
Effect.gen(function* () {
|
107
|
+
yield* validatePushPayload(batch, syncEventSequenceNumberRef.current)
|
108
|
+
|
109
|
+
const remaining = yield* SubscriptionRef.get(failCounterRef)
|
110
|
+
if (remaining > 0) {
|
111
|
+
const maybeFail = yield* SubscriptionRef.get(failEffectRef)
|
112
|
+
// decrement counter first
|
113
|
+
yield* SubscriptionRef.set(failCounterRef, remaining - 1)
|
114
|
+
if (maybeFail) {
|
115
|
+
return yield* maybeFail(batch)
|
116
|
+
}
|
117
|
+
return yield* new InvalidPushError({
|
118
|
+
cause: new UnexpectedError({ cause: new Error('MockSyncBackend: simulated push failure') }),
|
119
|
+
})
|
120
|
+
}
|
121
|
+
|
122
|
+
yield* Effect.sleep(10).pipe(Effect.withSpan('MockSyncBackend:push:sleep')) // Simulate network latency
|
123
|
+
|
124
|
+
yield* pushedEventsQueue.offerAll(batch)
|
125
|
+
yield* syncPullQueue.offerAll(batch)
|
126
|
+
allEventsRef.current = allEventsRef.current.concat(batch)
|
127
|
+
|
128
|
+
syncEventSequenceNumberRef.current = batch.at(-1)!.seqNum
|
129
|
+
}).pipe(
|
130
|
+
Effect.withSpan('MockSyncBackend:push', {
|
131
|
+
parent: span,
|
132
|
+
attributes: {
|
133
|
+
nums: batch.map((_) => _.seqNum),
|
134
|
+
},
|
135
|
+
}),
|
136
|
+
semaphore.withPermits(1),
|
137
|
+
),
|
138
|
+
metadata: {
|
139
|
+
name: '@livestore/mock-sync',
|
140
|
+
description: 'Just a mock sync backend',
|
141
|
+
},
|
142
|
+
supports: {
|
143
|
+
pullPageInfoKnown: true,
|
144
|
+
pullLive: true,
|
145
|
+
},
|
146
|
+
})
|
147
|
+
})
|
148
|
+
|
149
|
+
const advance = (...batch: LiveStoreEvent.AnyEncodedGlobal[]) =>
|
150
|
+
Effect.gen(function* () {
|
151
|
+
syncEventSequenceNumberRef.current = batch.at(-1)!.seqNum
|
152
|
+
allEventsRef.current = allEventsRef.current.concat(batch)
|
153
|
+
yield* syncPullQueue.offerAll(batch)
|
154
|
+
}).pipe(
|
155
|
+
Effect.withSpan('MockSyncBackend:advance', {
|
156
|
+
parent: span,
|
157
|
+
attributes: { nums: batch.map((_) => _.seqNum) },
|
158
|
+
}),
|
159
|
+
semaphore.withPermits(1),
|
160
|
+
)
|
161
|
+
|
162
|
+
const connect = SubscriptionRef.set(syncIsConnectedRef, true)
|
163
|
+
const disconnect = SubscriptionRef.set(syncIsConnectedRef, false)
|
164
|
+
|
165
|
+
const failNextPushes = (
|
166
|
+
count: number,
|
167
|
+
error?: (batch: ReadonlyArray<LiveStoreEvent.AnyEncodedGlobal>) => Effect.Effect<never, InvalidPushError>,
|
168
|
+
) =>
|
169
|
+
Effect.gen(function* () {
|
170
|
+
yield* SubscriptionRef.set(failCounterRef, count)
|
171
|
+
yield* SubscriptionRef.set(failEffectRef, error)
|
172
|
+
})
|
173
|
+
|
174
|
+
return {
|
175
|
+
syncEventSequenceNumberRef,
|
176
|
+
syncPullQueue,
|
177
|
+
pushedEvents: Mailbox.toStream(pushedEventsQueue),
|
178
|
+
connect,
|
179
|
+
disconnect,
|
180
|
+
makeSyncBackend,
|
181
|
+
advance,
|
182
|
+
failNextPushes,
|
183
|
+
}
|
184
|
+
}).pipe(Effect.withSpanScoped('MockSyncBackend'))
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import { EventSequenceNumber } from '../../schema/mod.ts'
|
2
2
|
import { replacesFacts } from './facts.ts'
|
3
|
-
import { graphologyDag } from './graphology_.ts'
|
4
3
|
import type { HistoryDag } from './history-dag-common.ts'
|
5
4
|
import { emptyHistoryDag } from './history-dag-common.ts'
|
6
5
|
|
@@ -17,7 +16,7 @@ export const compactEvents = (inputDag: HistoryDag): { dag: HistoryDag; compacte
|
|
17
16
|
const dag = inputDag.copy()
|
18
17
|
const compactedEventCount = 0
|
19
18
|
|
20
|
-
const orderedEventSequenceNumberStrs =
|
19
|
+
const orderedEventSequenceNumberStrs = dag.topologicalNodeIds().reverse()
|
21
20
|
|
22
21
|
// drop root
|
23
22
|
orderedEventSequenceNumberStrs.pop()
|
@@ -116,7 +115,7 @@ const findSubDagsInHistory = (
|
|
116
115
|
const subDags: HistoryDag[] = []
|
117
116
|
const allOutsideDependencies: string[][] = []
|
118
117
|
|
119
|
-
for (const eventNumStr of
|
118
|
+
for (const eventNumStr of inputDag.topologicalNodeIds()) {
|
120
119
|
if (eventNumStr === upToExclEventSequenceNumberStr) {
|
121
120
|
break
|
122
121
|
}
|
@@ -189,8 +188,8 @@ const dagReplacesDag = (dagA: HistoryDag, dagB: HistoryDag): boolean => {
|
|
189
188
|
}
|
190
189
|
|
191
190
|
// TODO write tests that covers deterministic order when DAGs have branches
|
192
|
-
const nodeEntriesA =
|
193
|
-
const nodeEntriesB =
|
191
|
+
const nodeEntriesA = dagA.topologicalNodeIds().map((nodeId) => dagA.getNodeAttributes(nodeId))
|
192
|
+
const nodeEntriesB = dagB.topologicalNodeIds().map((nodeId) => dagB.getNodeAttributes(nodeId))
|
194
193
|
|
195
194
|
for (let i = 0; i < nodeEntriesA.length; i++) {
|
196
195
|
const nodeA = nodeEntriesA[i]!
|
package/src/sync/next/facts.ts
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import { notYetImplemented } from '@livestore/utils'
|
2
|
-
|
3
2
|
import type {
|
4
3
|
EventDefFactInput,
|
5
4
|
EventDefFacts,
|
@@ -8,7 +7,6 @@ import type {
|
|
8
7
|
FactsCallback,
|
9
8
|
} from '../../schema/EventDef.ts'
|
10
9
|
import type * as EventSequenceNumber from '../../schema/EventSequenceNumber.ts'
|
11
|
-
import { graphologyDag } from './graphology_.ts'
|
12
10
|
import { EMPTY_FACT_VALUE, type HistoryDag, type HistoryDagNode } from './history-dag-common.ts'
|
13
11
|
|
14
12
|
export const factsSnapshotForEvents = (
|
@@ -34,7 +32,7 @@ export const factsSnapshotForDag = (
|
|
34
32
|
): EventDefFactsSnapshot => {
|
35
33
|
const facts = new Map<string, any>()
|
36
34
|
|
37
|
-
const orderedEventSequenceNumberStrs =
|
35
|
+
const orderedEventSequenceNumberStrs = dag.topologicalNodeIds()
|
38
36
|
|
39
37
|
for (let i = 0; i < orderedEventSequenceNumberStrs.length; i++) {
|
40
38
|
const event = dag.getNodeAttributes(orderedEventSequenceNumberStrs[i]!)
|
@@ -1,30 +1,11 @@
|
|
1
|
+
import { shouldNeverHappen } from '@livestore/utils'
|
2
|
+
import { Graph } from '@livestore/utils/effect'
|
1
3
|
import type { EventDefFactsGroup } from '../../schema/EventDef.ts'
|
2
4
|
import * as EventSequenceNumber from '../../schema/EventSequenceNumber.ts'
|
3
|
-
import { graphology } from './graphology_.ts'
|
4
5
|
|
5
6
|
export const connectionTypeOptions = ['parent', 'facts'] as const
|
6
7
|
export type ConnectionType = (typeof connectionTypeOptions)[number]
|
7
8
|
|
8
|
-
/**
|
9
|
-
* Eventlog represented as a multi-DAG including edges for
|
10
|
-
* - total-order (parent) relationships
|
11
|
-
* - dependency (requires/reads facts) relationships
|
12
|
-
*/
|
13
|
-
export type HistoryDag = graphology.IGraph<HistoryDagNode, { type: ConnectionType }>
|
14
|
-
|
15
|
-
export const emptyHistoryDag = (): HistoryDag =>
|
16
|
-
new graphology.Graph({
|
17
|
-
allowSelfLoops: false,
|
18
|
-
multi: true,
|
19
|
-
type: 'directed',
|
20
|
-
})
|
21
|
-
|
22
|
-
// TODO consider making `ROOT_ID` parent to itself
|
23
|
-
export const rootParentNum = EventSequenceNumber.make({
|
24
|
-
global: EventSequenceNumber.ROOT.global - 1,
|
25
|
-
client: EventSequenceNumber.clientDefault,
|
26
|
-
})
|
27
|
-
|
28
9
|
export type HistoryDagNode = {
|
29
10
|
seqNum: EventSequenceNumber.EventSequenceNumber
|
30
11
|
parentSeqNum: EventSequenceNumber.EventSequenceNumber
|
@@ -37,6 +18,276 @@ export type HistoryDagNode = {
|
|
37
18
|
sessionId: string | undefined
|
38
19
|
}
|
39
20
|
|
21
|
+
type HistoryDagEdgeAttributes = { type: ConnectionType }
|
22
|
+
|
23
|
+
type HistoryDagEdgeEntry = {
|
24
|
+
edge: Graph.EdgeIndex
|
25
|
+
source: string
|
26
|
+
target: string
|
27
|
+
attributes: HistoryDagEdgeAttributes
|
28
|
+
}
|
29
|
+
|
30
|
+
type HistoryDagOptions = {
|
31
|
+
allowSelfLoops: boolean
|
32
|
+
}
|
33
|
+
|
34
|
+
const defaultOptions: HistoryDagOptions = {
|
35
|
+
allowSelfLoops: false,
|
36
|
+
}
|
37
|
+
|
38
|
+
const cloneFactsGroup = (factsGroup: EventDefFactsGroup): EventDefFactsGroup => ({
|
39
|
+
depRead: new Map(factsGroup.depRead),
|
40
|
+
depRequire: new Map(factsGroup.depRequire),
|
41
|
+
modifySet: new Map(factsGroup.modifySet),
|
42
|
+
modifyUnset: new Map(factsGroup.modifyUnset),
|
43
|
+
})
|
44
|
+
|
45
|
+
const cloneHistoryDagNode = (node: HistoryDagNode): HistoryDagNode => ({
|
46
|
+
...node,
|
47
|
+
// Copy the event sequence numbers to avoid accidental aliasing
|
48
|
+
parentSeqNum: { ...node.parentSeqNum },
|
49
|
+
seqNum: { ...node.seqNum },
|
50
|
+
// Facts are represented via maps which should not be shared across DAG copies
|
51
|
+
factsGroup: cloneFactsGroup(node.factsGroup),
|
52
|
+
})
|
53
|
+
|
54
|
+
/**
|
55
|
+
* Mutable DAG wrapper that retains the previous string-based node ids API
|
56
|
+
* while delegating storage and algorithms to Effect's graph module.
|
57
|
+
*/
|
58
|
+
export class HistoryDag {
|
59
|
+
private readonly options: HistoryDagOptions
|
60
|
+
private readonly idToIndex: Map<string, Graph.NodeIndex>
|
61
|
+
private readonly indexToId: Map<Graph.NodeIndex, string>
|
62
|
+
private readonly graph: Graph.MutableDirectedGraph<HistoryDagNode, HistoryDagEdgeAttributes>
|
63
|
+
|
64
|
+
private constructor({
|
65
|
+
graph,
|
66
|
+
idToIndex,
|
67
|
+
indexToId,
|
68
|
+
options,
|
69
|
+
}: {
|
70
|
+
graph: Graph.MutableDirectedGraph<HistoryDagNode, HistoryDagEdgeAttributes>
|
71
|
+
idToIndex?: Map<string, Graph.NodeIndex>
|
72
|
+
indexToId?: Map<Graph.NodeIndex, string>
|
73
|
+
options?: Partial<HistoryDagOptions>
|
74
|
+
}) {
|
75
|
+
this.graph = graph
|
76
|
+
this.options = { ...defaultOptions, ...options }
|
77
|
+
this.idToIndex = idToIndex ? new Map(idToIndex) : new Map()
|
78
|
+
this.indexToId = indexToId ? new Map(indexToId) : new Map()
|
79
|
+
}
|
80
|
+
|
81
|
+
static create(options?: Partial<HistoryDagOptions>): HistoryDag {
|
82
|
+
const graph = Graph.beginMutation(Graph.directed<HistoryDagNode, HistoryDagEdgeAttributes>())
|
83
|
+
return options ? new HistoryDag({ graph, options }) : new HistoryDag({ graph })
|
84
|
+
}
|
85
|
+
|
86
|
+
copy(): HistoryDag {
|
87
|
+
const clone = HistoryDag.create(this.options)
|
88
|
+
|
89
|
+
for (const [id, index] of this.idToIndex) {
|
90
|
+
const node = this.graph.nodes.get(index) ?? shouldNeverHappen(`HistoryDag.copy missing node for ${id}`)
|
91
|
+
clone.addNode(id, cloneHistoryDagNode(node))
|
92
|
+
}
|
93
|
+
|
94
|
+
for (const edge of this.graph.edges.values()) {
|
95
|
+
const sourceId = this.indexToId.get(edge.source) ?? shouldNeverHappen('HistoryDag.copy missing source id')
|
96
|
+
const targetId = this.indexToId.get(edge.target) ?? shouldNeverHappen('HistoryDag.copy missing target id')
|
97
|
+
clone.addEdge(sourceId, targetId, { ...edge.data })
|
98
|
+
}
|
99
|
+
|
100
|
+
return clone
|
101
|
+
}
|
102
|
+
|
103
|
+
topologicalNodeIds(): Array<string> {
|
104
|
+
const walker = Graph.topo(this.graph)
|
105
|
+
const indices = Array.from(Graph.indices(walker))
|
106
|
+
return indices.map((index) => this.indexToId.get(index) ?? shouldNeverHappen(`Missing node id for index ${index}`))
|
107
|
+
}
|
108
|
+
|
109
|
+
addNode(id: string, attributes: HistoryDagNode): void {
|
110
|
+
if (this.idToIndex.has(id)) {
|
111
|
+
shouldNeverHappen(`HistoryDag node ${id} already exists`)
|
112
|
+
}
|
113
|
+
|
114
|
+
const nodeIndex = Graph.addNode(this.graph, attributes)
|
115
|
+
this.idToIndex.set(id, nodeIndex)
|
116
|
+
this.indexToId.set(nodeIndex, id)
|
117
|
+
}
|
118
|
+
|
119
|
+
hasNode(id: string): boolean {
|
120
|
+
return this.idToIndex.has(id)
|
121
|
+
}
|
122
|
+
|
123
|
+
getNodeAttributes(id: string): HistoryDagNode {
|
124
|
+
const index = this.idToIndex.get(id)
|
125
|
+
if (index === undefined) {
|
126
|
+
return shouldNeverHappen(`HistoryDag node ${id} not found`)
|
127
|
+
}
|
128
|
+
|
129
|
+
const node = this.graph.nodes.get(index)
|
130
|
+
return node ?? shouldNeverHappen(`HistoryDag node data missing for ${id}`)
|
131
|
+
}
|
132
|
+
|
133
|
+
nodes(): IterableIterator<string> {
|
134
|
+
return this.idToIndex.keys()
|
135
|
+
}
|
136
|
+
|
137
|
+
nodeEntries(): IterableIterator<{ key: string; attributes: HistoryDagNode }> {
|
138
|
+
return function* (this: HistoryDag) {
|
139
|
+
for (const [id, index] of this.idToIndex) {
|
140
|
+
const attributes = this.graph.nodes.get(index) ?? shouldNeverHappen(`HistoryDag node data missing for ${id}`)
|
141
|
+
yield { key: id, attributes }
|
142
|
+
}
|
143
|
+
}.call(this)
|
144
|
+
}
|
145
|
+
|
146
|
+
addEdge(sourceId: string, targetId: string, attributes: HistoryDagEdgeAttributes): Graph.EdgeIndex {
|
147
|
+
if (this.options.allowSelfLoops === false && sourceId === targetId) {
|
148
|
+
return shouldNeverHappen('HistoryDag self-loops are disabled')
|
149
|
+
}
|
150
|
+
|
151
|
+
const sourceIndex = this.idToIndex.get(sourceId)
|
152
|
+
const targetIndex = this.idToIndex.get(targetId)
|
153
|
+
|
154
|
+
if (sourceIndex === undefined || targetIndex === undefined) {
|
155
|
+
return shouldNeverHappen(`HistoryDag edge references unknown nodes: ${sourceId} -> ${targetId}`)
|
156
|
+
}
|
157
|
+
|
158
|
+
return Graph.addEdge(this.graph, sourceIndex, targetIndex, attributes)
|
159
|
+
}
|
160
|
+
|
161
|
+
edges(sourceId: string, targetId: string): Array<Graph.EdgeIndex> {
|
162
|
+
const sourceIndex = this.idToIndex.get(sourceId)
|
163
|
+
const targetIndex = this.idToIndex.get(targetId)
|
164
|
+
|
165
|
+
if (sourceIndex === undefined || targetIndex === undefined) {
|
166
|
+
return []
|
167
|
+
}
|
168
|
+
|
169
|
+
const adjacency = this.graph.adjacency.get(sourceIndex)
|
170
|
+
if (adjacency === undefined) {
|
171
|
+
return []
|
172
|
+
}
|
173
|
+
|
174
|
+
return adjacency.filter((edgeIndex) => {
|
175
|
+
const edge = this.graph.edges.get(edgeIndex)
|
176
|
+
return edge !== undefined && edge.target === targetIndex
|
177
|
+
})
|
178
|
+
}
|
179
|
+
|
180
|
+
inEdges(id: string): Array<Graph.EdgeIndex> {
|
181
|
+
const index = this.idToIndex.get(id)
|
182
|
+
if (index === undefined) {
|
183
|
+
return []
|
184
|
+
}
|
185
|
+
const incoming = this.graph.reverseAdjacency.get(index)
|
186
|
+
return incoming ? [...incoming] : []
|
187
|
+
}
|
188
|
+
|
189
|
+
outboundEdgeEntries(id: string): Array<HistoryDagEdgeEntry> {
|
190
|
+
const index = this.idToIndex.get(id)
|
191
|
+
if (index === undefined) {
|
192
|
+
return []
|
193
|
+
}
|
194
|
+
|
195
|
+
const adjacency = this.graph.adjacency.get(index)
|
196
|
+
if (adjacency === undefined) {
|
197
|
+
return []
|
198
|
+
}
|
199
|
+
|
200
|
+
return adjacency
|
201
|
+
.map((edgeIndex) => this.edgeEntry(edgeIndex))
|
202
|
+
.filter((entry): entry is HistoryDagEdgeEntry => entry !== undefined)
|
203
|
+
}
|
204
|
+
|
205
|
+
inboundEdgeEntries(id: string): Array<HistoryDagEdgeEntry> {
|
206
|
+
const index = this.idToIndex.get(id)
|
207
|
+
if (index === undefined) {
|
208
|
+
return []
|
209
|
+
}
|
210
|
+
|
211
|
+
const adjacency = this.graph.reverseAdjacency.get(index)
|
212
|
+
if (adjacency === undefined) {
|
213
|
+
return []
|
214
|
+
}
|
215
|
+
|
216
|
+
return adjacency
|
217
|
+
.map((edgeIndex) => this.edgeEntry(edgeIndex))
|
218
|
+
.filter((entry): entry is HistoryDagEdgeEntry => entry !== undefined)
|
219
|
+
}
|
220
|
+
|
221
|
+
getEdgeAttributes(edgeIndex: Graph.EdgeIndex): HistoryDagEdgeAttributes {
|
222
|
+
const edge = this.graph.edges.get(edgeIndex)
|
223
|
+
return edge?.data ?? shouldNeverHappen(`HistoryDag edge ${edgeIndex} not found`)
|
224
|
+
}
|
225
|
+
|
226
|
+
getEdgeAttribute<TKey extends keyof HistoryDagEdgeAttributes>(
|
227
|
+
edgeIndex: Graph.EdgeIndex,
|
228
|
+
key: TKey,
|
229
|
+
): HistoryDagEdgeAttributes[TKey] {
|
230
|
+
const attributes = this.getEdgeAttributes(edgeIndex)
|
231
|
+
return attributes[key]
|
232
|
+
}
|
233
|
+
|
234
|
+
source(edgeIndex: Graph.EdgeIndex): string {
|
235
|
+
const edge = this.graph.edges.get(edgeIndex)
|
236
|
+
const sourceId = edge !== undefined ? this.indexToId.get(edge.source) : undefined
|
237
|
+
return sourceId ?? shouldNeverHappen(`HistoryDag edge ${edgeIndex} missing source`)
|
238
|
+
}
|
239
|
+
|
240
|
+
target(edgeIndex: Graph.EdgeIndex): string {
|
241
|
+
const edge = this.graph.edges.get(edgeIndex)
|
242
|
+
const targetId = edge !== undefined ? this.indexToId.get(edge.target) : undefined
|
243
|
+
return targetId ?? shouldNeverHappen(`HistoryDag edge ${edgeIndex} missing target`)
|
244
|
+
}
|
245
|
+
|
246
|
+
dropNode(id: string): void {
|
247
|
+
const index = this.idToIndex.get(id)
|
248
|
+
if (index === undefined) {
|
249
|
+
return
|
250
|
+
}
|
251
|
+
|
252
|
+
Graph.removeNode(this.graph, index)
|
253
|
+
this.idToIndex.delete(id)
|
254
|
+
this.indexToId.delete(index)
|
255
|
+
}
|
256
|
+
|
257
|
+
get size(): number {
|
258
|
+
return this.idToIndex.size
|
259
|
+
}
|
260
|
+
|
261
|
+
private edgeEntry(edgeIndex: Graph.EdgeIndex): HistoryDagEdgeEntry | undefined {
|
262
|
+
const edge = this.graph.edges.get(edgeIndex)
|
263
|
+
if (edge === undefined) {
|
264
|
+
return undefined
|
265
|
+
}
|
266
|
+
|
267
|
+
const source = this.indexToId.get(edge.source)
|
268
|
+
const target = this.indexToId.get(edge.target)
|
269
|
+
|
270
|
+
if (source === undefined || target === undefined) {
|
271
|
+
return undefined
|
272
|
+
}
|
273
|
+
|
274
|
+
return {
|
275
|
+
edge: edgeIndex,
|
276
|
+
source,
|
277
|
+
target,
|
278
|
+
attributes: edge.data,
|
279
|
+
}
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
export const emptyHistoryDag = (): HistoryDag => HistoryDag.create({ allowSelfLoops: false })
|
284
|
+
|
285
|
+
// TODO consider making `ROOT_ID` parent to itself
|
286
|
+
export const rootParentNum = EventSequenceNumber.make({
|
287
|
+
global: EventSequenceNumber.ROOT.global - 1,
|
288
|
+
client: EventSequenceNumber.clientDefault,
|
289
|
+
})
|
290
|
+
|
40
291
|
export const rootEventNode: HistoryDagNode = {
|
41
292
|
seqNum: EventSequenceNumber.ROOT,
|
42
293
|
parentSeqNum: rootParentNum,
|
@@ -18,7 +18,9 @@ export const historyDagFromNodes = (dagNodes: HistoryDagNode[], options?: { skip
|
|
18
18
|
|
19
19
|
const dag = emptyHistoryDag()
|
20
20
|
|
21
|
-
dagNodes.forEach((node) =>
|
21
|
+
dagNodes.forEach((node) => {
|
22
|
+
dag.addNode(EventSequenceNumber.toString(node.seqNum), node)
|
23
|
+
})
|
22
24
|
|
23
25
|
dagNodes.forEach((node) => {
|
24
26
|
if (EventSequenceNumber.toString(node.parentSeqNum) !== EventSequenceNumber.toString(rootParentNum)) {
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { Effect, KeyValueStore, Option } from '@livestore/utils/effect'
|
2
|
+
import { UnexpectedError } from '../errors.ts'
|
3
|
+
|
4
|
+
export const makeBackendIdHelper = Effect.gen(function* () {
|
5
|
+
const kv = yield* KeyValueStore.KeyValueStore
|
6
|
+
|
7
|
+
const backendIdKey = `backendId`
|
8
|
+
const backendIdRef = { current: yield* kv.get(backendIdKey).pipe(UnexpectedError.mapToUnexpectedError) }
|
9
|
+
|
10
|
+
const setBackendId = (backendId: string) =>
|
11
|
+
Effect.gen(function* () {
|
12
|
+
if (backendIdRef.current._tag === 'None' || backendIdRef.current.value !== backendId) {
|
13
|
+
backendIdRef.current = Option.some(backendId)
|
14
|
+
yield* kv.set(backendIdKey, backendId)
|
15
|
+
}
|
16
|
+
}).pipe(UnexpectedError.mapToUnexpectedError)
|
17
|
+
|
18
|
+
return {
|
19
|
+
lazySet: setBackendId,
|
20
|
+
get: () => backendIdRef.current,
|
21
|
+
}
|
22
|
+
})
|