@livestore/common 0.4.0-dev.3 → 0.4.0-dev.6

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.
Files changed (154) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +4 -3
  3. package/dist/adapter-types.d.ts.map +1 -1
  4. package/dist/adapter-types.js.map +1 -1
  5. package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
  6. package/dist/devtools/devtools-messages-common.d.ts +6 -6
  7. package/dist/devtools/devtools-messages-leader.d.ts +24 -24
  8. package/dist/errors.d.ts +17 -5
  9. package/dist/errors.d.ts.map +1 -1
  10. package/dist/errors.js +14 -3
  11. package/dist/errors.js.map +1 -1
  12. package/dist/leader-thread/LeaderSyncProcessor.d.ts +4 -3
  13. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  14. package/dist/leader-thread/LeaderSyncProcessor.js +43 -24
  15. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  16. package/dist/leader-thread/eventlog.d.ts +4 -10
  17. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  18. package/dist/leader-thread/eventlog.js +3 -5
  19. package/dist/leader-thread/eventlog.js.map +1 -1
  20. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
  21. package/dist/leader-thread/leader-worker-devtools.js +1 -1
  22. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  23. package/dist/leader-thread/make-leader-thread-layer.d.ts +1 -2
  24. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  25. package/dist/leader-thread/make-leader-thread-layer.js +40 -19
  26. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  27. package/dist/leader-thread/materialize-event.d.ts +2 -2
  28. package/dist/leader-thread/materialize-event.d.ts.map +1 -1
  29. package/dist/leader-thread/materialize-event.js +4 -6
  30. package/dist/leader-thread/materialize-event.js.map +1 -1
  31. package/dist/leader-thread/recreate-db.d.ts +2 -3
  32. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  33. package/dist/leader-thread/recreate-db.js +1 -1
  34. package/dist/leader-thread/recreate-db.js.map +1 -1
  35. package/dist/leader-thread/shutdown-channel.d.ts +2 -2
  36. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  37. package/dist/leader-thread/shutdown-channel.js +2 -2
  38. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  39. package/dist/leader-thread/types.d.ts +5 -5
  40. package/dist/leader-thread/types.d.ts.map +1 -1
  41. package/dist/materializer-helper.d.ts.map +1 -1
  42. package/dist/materializer-helper.js +8 -2
  43. package/dist/materializer-helper.js.map +1 -1
  44. package/dist/rematerialize-from-eventlog.d.ts +1 -1
  45. package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
  46. package/dist/schema/EventDef.d.ts +3 -0
  47. package/dist/schema/EventDef.d.ts.map +1 -1
  48. package/dist/schema/EventDef.js.map +1 -1
  49. package/dist/schema/LiveStoreEvent.d.ts +1 -1
  50. package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
  51. package/dist/schema/LiveStoreEvent.js +1 -2
  52. package/dist/schema/LiveStoreEvent.js.map +1 -1
  53. package/dist/schema/schema.js +1 -1
  54. package/dist/schema/schema.js.map +1 -1
  55. package/dist/schema/state/sqlite/client-document-def.d.ts +30 -2
  56. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
  57. package/dist/schema/state/sqlite/client-document-def.js +93 -2
  58. package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
  59. package/dist/schema/state/sqlite/client-document-def.test.js +3 -2
  60. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  61. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -1
  62. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
  63. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +23 -6
  64. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  65. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  66. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +2 -1
  67. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
  68. package/dist/schema/state/sqlite/mod.d.ts +1 -1
  69. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  70. package/dist/schema/state/sqlite/mod.js +1 -1
  71. package/dist/schema/state/sqlite/mod.js.map +1 -1
  72. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  73. package/dist/schema/state/sqlite/query-builder/impl.js +6 -2
  74. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  75. package/dist/schema/state/sqlite/query-builder/impl.test.js +56 -2
  76. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
  77. package/dist/schema/state/sqlite/system-tables.d.ts +42 -6
  78. package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
  79. package/dist/schema/state/sqlite/system-tables.js +2 -0
  80. package/dist/schema/state/sqlite/system-tables.js.map +1 -1
  81. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
  82. package/dist/sql-queries/sql-query-builder.js +2 -1
  83. package/dist/sql-queries/sql-query-builder.js.map +1 -1
  84. package/dist/sync/ClientSessionSyncProcessor.d.ts +6 -9
  85. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  86. package/dist/sync/ClientSessionSyncProcessor.js +22 -22
  87. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  88. package/dist/sync/errors.d.ts +61 -0
  89. package/dist/sync/errors.d.ts.map +1 -0
  90. package/dist/sync/errors.js +36 -0
  91. package/dist/sync/errors.js.map +1 -0
  92. package/dist/sync/index.d.ts +1 -0
  93. package/dist/sync/index.d.ts.map +1 -1
  94. package/dist/sync/index.js +1 -0
  95. package/dist/sync/index.js.map +1 -1
  96. package/dist/sync/mock-sync-backend.d.ts +14 -0
  97. package/dist/sync/mock-sync-backend.d.ts.map +1 -0
  98. package/dist/sync/mock-sync-backend.js +62 -0
  99. package/dist/sync/mock-sync-backend.js.map +1 -0
  100. package/dist/sync/next/history-dag.d.ts.map +1 -1
  101. package/dist/sync/next/history-dag.js +3 -1
  102. package/dist/sync/next/history-dag.js.map +1 -1
  103. package/dist/sync/sync-backend-kv.d.ts +7 -0
  104. package/dist/sync/sync-backend-kv.d.ts.map +1 -0
  105. package/dist/sync/sync-backend-kv.js +18 -0
  106. package/dist/sync/sync-backend-kv.js.map +1 -0
  107. package/dist/sync/sync-backend.d.ts +85 -0
  108. package/dist/sync/sync-backend.d.ts.map +1 -0
  109. package/dist/sync/sync-backend.js +24 -0
  110. package/dist/sync/sync-backend.js.map +1 -0
  111. package/dist/sync/sync.d.ts +6 -84
  112. package/dist/sync/sync.d.ts.map +1 -1
  113. package/dist/sync/sync.js +2 -27
  114. package/dist/sync/sync.js.map +1 -1
  115. package/dist/sync/validate-push-payload.d.ts +1 -1
  116. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  117. package/dist/sync/validate-push-payload.js +6 -6
  118. package/dist/sync/validate-push-payload.js.map +1 -1
  119. package/dist/version.d.ts +2 -2
  120. package/dist/version.js +2 -2
  121. package/package.json +4 -4
  122. package/src/adapter-types.ts +8 -3
  123. package/src/errors.ts +24 -4
  124. package/src/leader-thread/LeaderSyncProcessor.ts +79 -30
  125. package/src/leader-thread/eventlog.ts +9 -5
  126. package/src/leader-thread/leader-worker-devtools.ts +1 -1
  127. package/src/leader-thread/make-leader-thread-layer.ts +70 -26
  128. package/src/leader-thread/materialize-event.ts +5 -6
  129. package/src/leader-thread/recreate-db.ts +11 -3
  130. package/src/leader-thread/shutdown-channel.ts +16 -2
  131. package/src/leader-thread/types.ts +5 -5
  132. package/src/materializer-helper.ts +9 -3
  133. package/src/schema/EventDef.ts +3 -0
  134. package/src/schema/LiveStoreEvent.ts +1 -2
  135. package/src/schema/schema.ts +1 -1
  136. package/src/schema/state/sqlite/client-document-def.test.ts +3 -2
  137. package/src/schema/state/sqlite/client-document-def.ts +108 -2
  138. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +26 -6
  139. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +2 -1
  140. package/src/schema/state/sqlite/mod.ts +1 -0
  141. package/src/schema/state/sqlite/query-builder/impl.test.ts +66 -6
  142. package/src/schema/state/sqlite/query-builder/impl.ts +8 -2
  143. package/src/schema/state/sqlite/system-tables.ts +2 -0
  144. package/src/sql-queries/sql-query-builder.ts +2 -1
  145. package/src/sync/ClientSessionSyncProcessor.ts +37 -37
  146. package/src/sync/errors.ts +38 -0
  147. package/src/sync/index.ts +1 -0
  148. package/src/sync/mock-sync-backend.ts +96 -0
  149. package/src/sync/next/history-dag.ts +3 -1
  150. package/src/sync/sync-backend-kv.ts +22 -0
  151. package/src/sync/sync-backend.ts +137 -0
  152. package/src/sync/sync.ts +6 -89
  153. package/src/sync/validate-push-payload.ts +6 -7
  154. package/src/version.ts +2 -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
@@ -1,3 +1,4 @@
1
1
  export * from './ClientSessionSyncProcessor.ts'
2
+ export * from './mock-sync-backend.ts'
2
3
  export * from './sync.ts'
3
4
  export * from './validate-push-payload.ts'
@@ -0,0 +1,96 @@
1
+ import type { Schema, Scope } from '@livestore/utils/effect'
2
+ import { Effect, Mailbox, Option, Queue, Stream, SubscriptionRef } from '@livestore/utils/effect'
3
+ import type { UnexpectedError } from '../errors.ts'
4
+ import { EventSequenceNumber, type LiveStoreEvent } from '../schema/mod.ts'
5
+ import * as SyncBackend from './sync-backend.ts'
6
+ import { validatePushPayload } from './validate-push-payload.ts'
7
+
8
+ export interface MockSyncBackend {
9
+ pushedEvents: Stream.Stream<LiveStoreEvent.AnyEncodedGlobal>
10
+ connect: Effect.Effect<void>
11
+ disconnect: Effect.Effect<void>
12
+ makeSyncBackend: Effect.Effect<SyncBackend.SyncBackend, UnexpectedError, Scope.Scope>
13
+ advance: (...batch: LiveStoreEvent.AnyEncodedGlobal[]) => Effect.Effect<void>
14
+ }
15
+
16
+ export const makeMockSyncBackend: Effect.Effect<MockSyncBackend, UnexpectedError, Scope.Scope> = Effect.gen(
17
+ function* () {
18
+ const syncEventSequenceNumberRef = { current: EventSequenceNumber.ROOT.global }
19
+ const syncPullQueue = yield* Queue.unbounded<LiveStoreEvent.AnyEncodedGlobal>()
20
+ const pushedEventsQueue = yield* Mailbox.make<LiveStoreEvent.AnyEncodedGlobal>()
21
+ const syncIsConnectedRef = yield* SubscriptionRef.make(true)
22
+
23
+ const span = yield* Effect.currentSpan.pipe(Effect.orDie)
24
+
25
+ const semaphore = yield* Effect.makeSemaphore(1)
26
+
27
+ const makeSyncBackend = Effect.gen(function* () {
28
+ return SyncBackend.of<Schema.JsonValue>({
29
+ isConnected: syncIsConnectedRef,
30
+ connect: Effect.void,
31
+ ping: Effect.void,
32
+ pull: () =>
33
+ Stream.fromQueue(syncPullQueue).pipe(
34
+ Stream.chunks,
35
+ Stream.map((chunk) => ({
36
+ batch: [...chunk].map((eventEncoded) => ({ eventEncoded, metadata: Option.none() })),
37
+ pageInfo: SyncBackend.pageInfoNoMore,
38
+ })),
39
+ Stream.withSpan('MockSyncBackend:pull', { parent: span }),
40
+ ),
41
+ push: (batch) =>
42
+ Effect.gen(function* () {
43
+ yield* validatePushPayload(batch, syncEventSequenceNumberRef.current)
44
+
45
+ yield* Effect.sleep(10).pipe(Effect.withSpan('MockSyncBackend:push:sleep')) // Simulate network latency
46
+
47
+ yield* pushedEventsQueue.offerAll(batch)
48
+ yield* syncPullQueue.offerAll(batch)
49
+
50
+ syncEventSequenceNumberRef.current = batch.at(-1)!.seqNum
51
+ }).pipe(
52
+ Effect.withSpan('MockSyncBackend:push', {
53
+ parent: span,
54
+ attributes: {
55
+ nums: batch.map((_) => _.seqNum),
56
+ },
57
+ }),
58
+ semaphore.withPermits(1),
59
+ ),
60
+ metadata: {
61
+ name: '@livestore/mock-sync',
62
+ description: 'Just a mock sync backend',
63
+ },
64
+ supports: {
65
+ pullPageInfoKnown: true,
66
+ pullLive: true,
67
+ },
68
+ })
69
+ })
70
+
71
+ const advance = (...batch: LiveStoreEvent.AnyEncodedGlobal[]) =>
72
+ Effect.gen(function* () {
73
+ syncEventSequenceNumberRef.current = batch.at(-1)!.seqNum
74
+ yield* syncPullQueue.offerAll(batch)
75
+ }).pipe(
76
+ Effect.withSpan('MockSyncBackend:advance', {
77
+ parent: span,
78
+ attributes: { nums: batch.map((_) => _.seqNum) },
79
+ }),
80
+ semaphore.withPermits(1),
81
+ )
82
+
83
+ const connect = SubscriptionRef.set(syncIsConnectedRef, true)
84
+ const disconnect = SubscriptionRef.set(syncIsConnectedRef, false)
85
+
86
+ return {
87
+ syncEventSequenceNumberRef,
88
+ syncPullQueue,
89
+ pushedEvents: Mailbox.toStream(pushedEventsQueue),
90
+ connect,
91
+ disconnect,
92
+ makeSyncBackend,
93
+ advance,
94
+ }
95
+ },
96
+ ).pipe(Effect.withSpanScoped('MockSyncBackend'))
@@ -18,7 +18,9 @@ export const historyDagFromNodes = (dagNodes: HistoryDagNode[], options?: { skip
18
18
 
19
19
  const dag = emptyHistoryDag()
20
20
 
21
- dagNodes.forEach((node) => dag.addNode(EventSequenceNumber.toString(node.seqNum), 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
+ })
@@ -0,0 +1,137 @@
1
+ import {
2
+ type Cause,
3
+ type Effect,
4
+ type HttpClient,
5
+ type KeyValueStore,
6
+ Option,
7
+ Schema,
8
+ type Scope,
9
+ type Stream,
10
+ type SubscriptionRef,
11
+ } from '@livestore/utils/effect'
12
+ import type { UnexpectedError } from '../adapter-types.ts'
13
+ import type * as LiveStoreEvent from '../schema/LiveStoreEvent.ts'
14
+ import type { EventSequenceNumber } from '../schema/mod.ts'
15
+ import type { InvalidPullError, InvalidPushError, IsOfflineError } from './errors.ts'
16
+
17
+ export * from './sync-backend-kv.ts'
18
+
19
+ /**
20
+ * Those arguments can be used to implement multi-tenancy etc and are passed in from the store.
21
+ */
22
+ export type MakeBackendArgs = {
23
+ storeId: string
24
+ clientId: string
25
+ payload: Schema.JsonValue | undefined
26
+ }
27
+
28
+ // TODO rename to `SyncProviderClientConstructor`
29
+ export type SyncBackendConstructor<TSyncMetadata = Schema.JsonValue> = (
30
+ args: MakeBackendArgs,
31
+ ) => Effect.Effect<
32
+ SyncBackend<TSyncMetadata>,
33
+ UnexpectedError,
34
+ Scope.Scope | HttpClient.HttpClient | KeyValueStore.KeyValueStore
35
+ >
36
+
37
+ // TODO add more runtime sync metadata/metrics
38
+ // - latency histogram
39
+ // - number of events pushed/pulled
40
+ // - dynamic sync backend data;
41
+ // - data center location (e.g. colo on CF workers)
42
+
43
+ // TODO rename to `SyncProviderClient`
44
+ export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
45
+ /**
46
+ * Can be implemented to prepare a connection to the sync backend to speed up the first pull/push.
47
+ */
48
+ connect: Effect.Effect<void, IsOfflineError | UnexpectedError, Scope.Scope>
49
+ pull: (
50
+ cursor: Option.Option<{
51
+ eventSequenceNumber: EventSequenceNumber.GlobalEventSequenceNumber
52
+ /** Metadata is needed by some sync backends */
53
+ metadata: Option.Option<TSyncMetadata>
54
+ }>,
55
+ options?: {
56
+ /**
57
+ * If true, the sync backend will return a stream of events that have been pushed after the cursor.
58
+ *
59
+ * @default false
60
+ */
61
+ live?: boolean
62
+ },
63
+ ) => Stream.Stream<PullResItem<TSyncMetadata>, IsOfflineError | InvalidPullError>
64
+ // TODO support transactions (i.e. group of mutation events which need to be applied together)
65
+ push: (
66
+ /**
67
+ * Constraints for batch:
68
+ * - Number of events: 1-100
69
+ * - sequence numbers must be in ascending order
70
+ * */
71
+ batch: ReadonlyArray<LiveStoreEvent.AnyEncodedGlobal>,
72
+ ) => Effect.Effect<void, IsOfflineError | InvalidPushError>
73
+ ping: Effect.Effect<void, IsOfflineError | UnexpectedError | Cause.TimeoutException>
74
+ // TODO also expose latency information additionally to whether the backend is connected
75
+ isConnected: SubscriptionRef.SubscriptionRef<boolean>
76
+ /**
77
+ * Metadata describing the sync backend. (Currently only used by devtools.)
78
+ */
79
+ metadata: { name: string; description: string } & Record<string, Schema.JsonValue>
80
+ /** Information about the sync backend capabilities. */
81
+ supports: {
82
+ /**
83
+ * Whether the sync backend supports the `hasMore` field in the pull response.
84
+ */
85
+ pullPageInfoKnown: boolean
86
+ /**
87
+ * Whether the sync backend supports the `live` option for the pull method and thus
88
+ * long-lived, reactive pull streams.
89
+ */
90
+ pullLive: boolean
91
+ }
92
+ }
93
+
94
+ export const PullResPageInfo = Schema.Union(
95
+ Schema.TaggedStruct('MoreUnknown', {}),
96
+ Schema.TaggedStruct('MoreKnown', {
97
+ remaining: Schema.Number,
98
+ }),
99
+ Schema.TaggedStruct('NoMore', {}),
100
+ )
101
+
102
+ export type PullResPageInfo = typeof PullResPageInfo.Type
103
+
104
+ export const pageInfoNoMore: PullResPageInfo = { _tag: 'NoMore' } as const
105
+ export const pageInfoMoreUnknown: PullResPageInfo = { _tag: 'MoreUnknown' } as const
106
+ export const pageInfoMoreKnown = (remaining: number): PullResPageInfo => ({ _tag: 'MoreKnown', remaining })
107
+
108
+ export const pullResItemEmpty = <TSyncMetadata = Schema.JsonValue>(): PullResItem<TSyncMetadata> => ({
109
+ batch: [],
110
+ pageInfo: pageInfoNoMore,
111
+ })
112
+
113
+ export interface PullResItem<TSyncMetadata = Schema.JsonValue> {
114
+ batch: ReadonlyArray<{
115
+ eventEncoded: LiveStoreEvent.AnyEncodedGlobal
116
+ metadata: Option.Option<TSyncMetadata>
117
+ }>
118
+ pageInfo: PullResPageInfo
119
+ }
120
+
121
+ export const of = <TSyncMetadata = Schema.JsonValue>(obj: SyncBackend<TSyncMetadata>) => obj
122
+
123
+ /**
124
+ * Useful to continue pulling from the last event in the batch.
125
+ */
126
+ export const cursorFromPullResItem = <TSyncMetadata = Schema.JsonValue>(
127
+ item: PullResItem<TSyncMetadata>,
128
+ ): Option.Option<{
129
+ eventSequenceNumber: EventSequenceNumber.GlobalEventSequenceNumber
130
+ metadata: Option.Option<TSyncMetadata>
131
+ }> => {
132
+ const lastEvent = item.batch.at(-1)
133
+ if (!lastEvent) {
134
+ return Option.none()
135
+ }
136
+ return Option.some({ eventSequenceNumber: lastEvent.eventEncoded.seqNum, metadata: lastEvent.metadata })
137
+ }
package/src/sync/sync.ts CHANGED
@@ -1,19 +1,8 @@
1
- import type { Effect, HttpClient, Option, Scope, Stream, SubscriptionRef } from '@livestore/utils/effect'
2
- import { Schema } from '@livestore/utils/effect'
1
+ export * from './errors.ts'
2
+ export * as SyncBackend from './sync-backend.ts'
3
3
 
4
- import type { UnexpectedError } from '../adapter-types.ts'
5
4
  import type { InitialSyncOptions } from '../leader-thread/types.ts'
6
- import * as EventSequenceNumber from '../schema/EventSequenceNumber.ts'
7
- import type * as LiveStoreEvent from '../schema/LiveStoreEvent.ts'
8
-
9
- /**
10
- * Those arguments can be used to implement multi-tenancy etc and are passed in from the store.
11
- */
12
- export type MakeBackendArgs = {
13
- storeId: string
14
- clientId: string
15
- payload: Schema.JsonValue | undefined
16
- }
5
+ import type { SyncBackendConstructor } from './sync-backend.ts'
17
6
 
18
7
  export type SyncOptions = {
19
8
  backend?: SyncBackendConstructor<any>
@@ -29,81 +18,9 @@ export type SyncOptions = {
29
18
  * @default 'ignore'
30
19
  * */
31
20
  onSyncError?: 'shutdown' | 'ignore'
32
- }
33
-
34
- // TODO rename to `SyncProviderClientConstructor`
35
- export type SyncBackendConstructor<TSyncMetadata = Schema.JsonValue> = (
36
- args: MakeBackendArgs,
37
- ) => Effect.Effect<SyncBackend<TSyncMetadata>, UnexpectedError, Scope.Scope | HttpClient.HttpClient>
38
-
39
- // TODO add more runtime sync metadata
40
- // - latency histogram
41
- // - number of events pushed/pulled
42
- // - dynamic sync backend data;
43
- // - data center location (e.g. colo on CF workers)
44
-
45
- // TODO rename to `SyncProviderClient`
46
- export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
47
21
  /**
48
- * Can be implemented to prepare a connection to the sync backend to speed up the first pull/push.
22
+ * Whether the sync backend should reactively pull new events from the sync backend
23
+ * @default true
49
24
  */
50
- connect: Effect.Effect<void, IsOfflineError | UnexpectedError, HttpClient.HttpClient | Scope.Scope>
51
- pull: (
52
- args: Option.Option<{
53
- cursor: EventSequenceNumber.EventSequenceNumber
54
- metadata: Option.Option<TSyncMetadata>
55
- }>,
56
- ) => Stream.Stream<
57
- {
58
- batch: ReadonlyArray<{
59
- eventEncoded: LiveStoreEvent.AnyEncodedGlobal
60
- metadata: Option.Option<TSyncMetadata>
61
- }>
62
- remaining: number
63
- },
64
- IsOfflineError | InvalidPullError,
65
- HttpClient.HttpClient
66
- >
67
- // TODO support transactions (i.e. group of mutation events which need to be applied together)
68
- push: (
69
- /**
70
- * Constraints for batch:
71
- * - Number of events: 1-100
72
- * - sequence numbers must be in ascending order
73
- * */
74
- batch: ReadonlyArray<LiveStoreEvent.AnyEncodedGlobal>,
75
- ) => Effect.Effect<void, IsOfflineError | InvalidPushError, HttpClient.HttpClient>
76
- isConnected: SubscriptionRef.SubscriptionRef<boolean>
77
- /**
78
- * Metadata describing the sync backend. (Currently only used by devtools.)
79
- */
80
- metadata: { name: string; description: string } & Record<string, Schema.JsonValue>
25
+ livePull?: boolean
81
26
  }
82
-
83
- export class IsOfflineError extends Schema.TaggedError<IsOfflineError>()('IsOfflineError', {}) {}
84
-
85
- // TODO gt rid of this error in favour of SyncError
86
- export class InvalidPushError extends Schema.TaggedError<InvalidPushError>()('InvalidPushError', {
87
- reason: Schema.Union(
88
- Schema.TaggedStruct('Unexpected', {
89
- message: Schema.String,
90
- }),
91
- Schema.TaggedStruct('ServerAhead', {
92
- minimumExpectedNum: Schema.Number,
93
- providedNum: Schema.Number,
94
- }),
95
- ),
96
- }) {}
97
-
98
- // TODO gt rid of this error in favour of SyncError
99
- export class InvalidPullError extends Schema.TaggedError<InvalidPullError>()('InvalidPullError', {
100
- message: Schema.String,
101
- }) {}
102
-
103
- // TODO gt rid of this error in favour of SyncError
104
- export class LeaderAheadError extends Schema.TaggedError<LeaderAheadError>()('LeaderAheadError', {
105
- minimumExpectedNum: EventSequenceNumber.EventSequenceNumber,
106
- providedNum: EventSequenceNumber.EventSequenceNumber,
107
- /** Generation number the client session should use for subsequent pushes */
108
- // nextGeneration: Schema.Number,
109
- }) {}
@@ -1,7 +1,7 @@
1
1
  import { Effect } from '@livestore/utils/effect'
2
2
 
3
- import type { EventSequenceNumber, LiveStoreEvent } from '../schema/mod.ts'
4
- import { InvalidPushError } from './sync.ts'
3
+ import { EventSequenceNumber, type LiveStoreEvent } from '../schema/mod.ts'
4
+ import { InvalidPushError, ServerAheadError } from './sync.ts'
5
5
 
6
6
  // TODO proper batch validation
7
7
  export const validatePushPayload = (
@@ -11,11 +11,10 @@ export const validatePushPayload = (
11
11
  Effect.gen(function* () {
12
12
  if (batch[0]!.seqNum <= currentEventSequenceNumber) {
13
13
  return yield* InvalidPushError.make({
14
- reason: {
15
- _tag: 'ServerAhead',
16
- minimumExpectedNum: currentEventSequenceNumber + 1,
17
- providedNum: batch[0]!.seqNum,
18
- },
14
+ cause: new ServerAheadError({
15
+ minimumExpectedNum: EventSequenceNumber.globalEventSequenceNumber(currentEventSequenceNumber + 1),
16
+ providedNum: EventSequenceNumber.globalEventSequenceNumber(batch[0]!.seqNum),
17
+ }),
19
18
  })
20
19
  }
21
20
  })
package/src/version.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // import packageJson from '../package.json' with { type: 'json' }
3
3
  // export const liveStoreVersion = packageJson.version
4
4
 
5
- export const liveStoreVersion = '0.4.0-dev.3' as const
5
+ export const liveStoreVersion = '0.4.0-dev.6' as const
6
6
 
7
7
  /**
8
8
  * This version number is incremented whenever the internal storage format changes in a breaking way.
@@ -11,4 +11,4 @@ export const liveStoreVersion = '0.4.0-dev.3' as const
11
11
  * While LiveStore is in beta, this might happen more frequently.
12
12
  * In the future, LiveStore will provide a migration path for older database files to avoid the impression of data loss.
13
13
  */
14
- export const liveStoreStorageFormatVersion = 5
14
+ export const liveStoreStorageFormatVersion = 6