@livestore/sync-cf 0.4.0-dev.9 → 0.5.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/cf-worker/do/durable-object.d.ts +1 -1
- package/dist/cf-worker/do/durable-object.d.ts.map +1 -1
- package/dist/cf-worker/do/durable-object.js +36 -18
- package/dist/cf-worker/do/durable-object.js.map +1 -1
- package/dist/cf-worker/do/layer.d.ts +24 -20
- package/dist/cf-worker/do/layer.d.ts.map +1 -1
- package/dist/cf-worker/do/layer.js +57 -24
- package/dist/cf-worker/do/layer.js.map +1 -1
- package/dist/cf-worker/do/pull.d.ts +8 -3
- package/dist/cf-worker/do/pull.d.ts.map +1 -1
- package/dist/cf-worker/do/pull.js +26 -21
- package/dist/cf-worker/do/pull.js.map +1 -1
- package/dist/cf-worker/do/push.d.ts +20 -6
- package/dist/cf-worker/do/push.d.ts.map +1 -1
- package/dist/cf-worker/do/push.js +84 -56
- package/dist/cf-worker/do/push.js.map +1 -1
- package/dist/cf-worker/do/push.test.d.ts +2 -0
- package/dist/cf-worker/do/push.test.d.ts.map +1 -0
- package/dist/cf-worker/do/push.test.js +30 -0
- package/dist/cf-worker/do/push.test.js.map +1 -0
- package/dist/cf-worker/do/sqlite.d.ts +49 -52
- package/dist/cf-worker/do/sqlite.d.ts.map +1 -1
- package/dist/cf-worker/do/sqlite.js +14 -5
- package/dist/cf-worker/do/sqlite.js.map +1 -1
- package/dist/cf-worker/do/sync-storage.d.ts +15 -9
- package/dist/cf-worker/do/sync-storage.d.ts.map +1 -1
- package/dist/cf-worker/do/sync-storage.js +111 -25
- package/dist/cf-worker/do/sync-storage.js.map +1 -1
- package/dist/cf-worker/do/transport/do-rpc-server.d.ts +2 -2
- package/dist/cf-worker/do/transport/do-rpc-server.d.ts.map +1 -1
- package/dist/cf-worker/do/transport/do-rpc-server.js +31 -23
- package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -1
- package/dist/cf-worker/do/transport/http-rpc-server.d.ts +5 -3
- package/dist/cf-worker/do/transport/http-rpc-server.d.ts.map +1 -1
- package/dist/cf-worker/do/transport/http-rpc-server.js +28 -19
- package/dist/cf-worker/do/transport/http-rpc-server.js.map +1 -1
- package/dist/cf-worker/do/transport/ws-rpc-server.d.ts +3 -2
- package/dist/cf-worker/do/transport/ws-rpc-server.d.ts.map +1 -1
- package/dist/cf-worker/do/transport/ws-rpc-server.js +32 -10
- package/dist/cf-worker/do/transport/ws-rpc-server.js.map +1 -1
- package/dist/cf-worker/shared.d.ts +124 -34
- package/dist/cf-worker/shared.d.ts.map +1 -1
- package/dist/cf-worker/shared.js +52 -17
- package/dist/cf-worker/shared.js.map +1 -1
- package/dist/cf-worker/worker.d.ts +47 -39
- package/dist/cf-worker/worker.d.ts.map +1 -1
- package/dist/cf-worker/worker.js +54 -35
- package/dist/cf-worker/worker.js.map +1 -1
- package/dist/client/transport/do-rpc-client.d.ts +13 -4
- package/dist/client/transport/do-rpc-client.d.ts.map +1 -1
- package/dist/client/transport/do-rpc-client.js +74 -28
- package/dist/client/transport/do-rpc-client.js.map +1 -1
- package/dist/client/transport/http-rpc-client.d.ts +3 -3
- package/dist/client/transport/http-rpc-client.d.ts.map +1 -1
- package/dist/client/transport/http-rpc-client.js +51 -22
- package/dist/client/transport/http-rpc-client.js.map +1 -1
- package/dist/client/transport/ws-rpc-client.d.ts +4 -3
- package/dist/client/transport/ws-rpc-client.d.ts.map +1 -1
- package/dist/client/transport/ws-rpc-client.js +40 -22
- package/dist/client/transport/ws-rpc-client.js.map +1 -1
- package/dist/common/constants.d.ts +7 -0
- package/dist/common/constants.d.ts.map +1 -0
- package/dist/common/constants.js +17 -0
- package/dist/common/constants.js.map +1 -0
- package/dist/common/do-rpc-schema.d.ts +42 -43
- package/dist/common/do-rpc-schema.d.ts.map +1 -1
- package/dist/common/do-rpc-schema.js +5 -5
- package/dist/common/do-rpc-schema.js.map +1 -1
- package/dist/common/http-rpc-schema.d.ts +37 -38
- package/dist/common/http-rpc-schema.d.ts.map +1 -1
- package/dist/common/http-rpc-schema.js +7 -7
- package/dist/common/http-rpc-schema.js.map +1 -1
- package/dist/common/mod.d.ts +5 -3
- package/dist/common/mod.d.ts.map +1 -1
- package/dist/common/mod.js +4 -2
- package/dist/common/mod.js.map +1 -1
- package/dist/common/sync-message-types.d.ts +113 -130
- package/dist/common/sync-message-types.d.ts.map +1 -1
- package/dist/common/sync-message-types.js +29 -17
- package/dist/common/sync-message-types.js.map +1 -1
- package/dist/common/ws-rpc-schema.d.ts +34 -35
- package/dist/common/ws-rpc-schema.d.ts.map +1 -1
- package/dist/common/ws-rpc-schema.js +5 -5
- package/dist/common/ws-rpc-schema.js.map +1 -1
- package/package.json +57 -15
- package/src/cf-worker/do/durable-object.ts +59 -41
- package/src/cf-worker/do/layer.ts +153 -88
- package/src/cf-worker/do/pull.ts +48 -31
- package/src/cf-worker/do/push.test.ts +40 -0
- package/src/cf-worker/do/push.ts +163 -125
- package/src/cf-worker/do/sqlite.ts +15 -5
- package/src/cf-worker/do/sync-storage.ts +171 -38
- package/src/cf-worker/do/transport/do-rpc-server.ts +40 -40
- package/src/cf-worker/do/transport/http-rpc-server.ts +37 -20
- package/src/cf-worker/do/transport/ws-rpc-server.ts +44 -16
- package/src/cf-worker/shared.ts +152 -36
- package/src/cf-worker/worker.ts +110 -58
- package/src/client/transport/do-rpc-client.ts +106 -40
- package/src/client/transport/http-rpc-client.ts +74 -38
- package/src/client/transport/ws-rpc-client.ts +55 -29
- package/src/common/constants.ts +18 -0
- package/src/common/do-rpc-schema.ts +6 -5
- package/src/common/http-rpc-schema.ts +8 -7
- package/src/common/mod.ts +4 -3
- package/src/common/sync-message-types.ts +19 -19
- package/src/common/ws-rpc-schema.ts +6 -5
- package/dist/cf-worker/do/ws-chunking.d.ts +0 -22
- package/dist/cf-worker/do/ws-chunking.d.ts.map +0 -1
- package/dist/cf-worker/do/ws-chunking.js +0 -49
- package/dist/cf-worker/do/ws-chunking.js.map +0 -1
- package/src/cf-worker/do/ws-chunking.ts +0 -76
|
@@ -1,50 +1,57 @@
|
|
|
1
|
-
import { BackendIdMismatchError,
|
|
1
|
+
import { BackendIdMismatchError, ServerAheadError, SyncBackend, UnknownError } from '@livestore/common';
|
|
2
2
|
import { emitStreamResponse } from '@livestore/common-cf';
|
|
3
|
-
import {
|
|
3
|
+
import { splitArrayBySize } from '@livestore/common/sync';
|
|
4
|
+
import { Effect, Option, ReadonlyArray as EffectArray, Schema } from '@livestore/utils/effect';
|
|
5
|
+
import { MAX_PUSH_EVENTS_PER_REQUEST, MAX_WS_MESSAGE_BYTES } from "../../common/constants.js";
|
|
4
6
|
import { SyncMessage } from "../../common/mod.js";
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
const encodePullResponse = Schema.encodeSync(
|
|
9
|
-
|
|
7
|
+
import { rpcSubscriptionKeyPrefix, WebSocketAttachmentSchema, } from "../shared.js";
|
|
8
|
+
import * as DoCtx from "./layer.js";
|
|
9
|
+
const pullResponseJsonSchema = Schema.toCodecJson(SyncMessage.PullResponse);
|
|
10
|
+
const encodePullResponse = Schema.encodeSync(pullResponseJsonSchema);
|
|
11
|
+
const jsonStringify = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));
|
|
12
|
+
export const makePush = ({ payload, headers, options, storeId, ctx, env, }) => (pushRequest) => Effect.gen(function* () {
|
|
10
13
|
// yield* Effect.log(`Pushing ${decodedMessage.batch.length} events`, decodedMessage.batch)
|
|
11
|
-
const { backendId, storage, currentHeadRef, updateCurrentHead,
|
|
14
|
+
const { backendId, storage, currentHeadRef, updateCurrentHead, pushSemaphore } = yield* DoCtx.DoCtx;
|
|
12
15
|
if (pushRequest.batch.length === 0) {
|
|
13
16
|
return SyncMessage.PushAck.make({});
|
|
14
17
|
}
|
|
15
|
-
if (options?.onPush) {
|
|
16
|
-
yield* Effect.
|
|
18
|
+
if (options?.onPush !== undefined) {
|
|
19
|
+
yield* Effect.trySyncOrPromiseOrEffect(() => options.onPush(pushRequest, {
|
|
20
|
+
storeId,
|
|
21
|
+
...(payload !== undefined ? { payload } : {}),
|
|
22
|
+
...(headers !== undefined ? { headers } : {}),
|
|
23
|
+
})).pipe(UnknownError.mapToUnknownError);
|
|
17
24
|
}
|
|
18
25
|
if (pushRequest.backendId._tag === 'Some' && pushRequest.backendId.value !== backendId) {
|
|
19
26
|
return yield* new BackendIdMismatchError({ expected: backendId, received: pushRequest.backendId.value });
|
|
20
27
|
}
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}).pipe(blockConcurrencyWhile(ctx));
|
|
42
|
-
// Run in background but already return the push ack to the client
|
|
43
|
-
yield* Effect.gen(function* () {
|
|
28
|
+
// A push is one ordered admit → persist → publish operation. `blockConcurrencyWhile` below protects
|
|
29
|
+
// the Durable Object state transition, while this semaphore keeps later pushes from overtaking this
|
|
30
|
+
// push during broadcasting without holding Cloudflare's concurrency gate across network I/O.
|
|
31
|
+
yield* runSerializedPushAdmission(pushSemaphore, Effect.gen(function* () {
|
|
32
|
+
// Keep the head check, durable append, and in-memory head update atomic with respect to other
|
|
33
|
+
// Durable Object requests. Broadcasting intentionally happens after this gate is released.
|
|
34
|
+
const { createdAt } = yield* Effect.gen(function* () {
|
|
35
|
+
const currentHead = currentHeadRef.current;
|
|
36
|
+
// TODO handle clientId unique conflict
|
|
37
|
+
// Validate the batch
|
|
38
|
+
const firstEventParent = pushRequest.batch[0].parentSeqNum;
|
|
39
|
+
if (firstEventParent !== currentHead) {
|
|
40
|
+
return yield* new ServerAheadError({ minimumExpectedNum: currentHead, providedNum: firstEventParent });
|
|
41
|
+
}
|
|
42
|
+
const createdAt = new Date().toISOString();
|
|
43
|
+
// TODO possibly model this as a queue in order to speed up subsequent pushes
|
|
44
|
+
yield* storage.appendEvents(pushRequest.batch, createdAt);
|
|
45
|
+
updateCurrentHead(pushRequest.batch.at(-1).seqNum);
|
|
46
|
+
return { createdAt };
|
|
47
|
+
}).pipe(blockConcurrencyWhile(ctx));
|
|
44
48
|
const connectedClients = ctx.getWebSockets();
|
|
45
49
|
// Preparing chunks of responses to make sure we don't exceed the WS message size limit.
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
if (EffectArray.isReadonlyArrayNonEmpty(pushRequest.batch) === false) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const responses = yield* splitArrayBySize({
|
|
54
|
+
maxItems: MAX_PUSH_EVENTS_PER_REQUEST,
|
|
48
55
|
maxBytes: MAX_WS_MESSAGE_BYTES,
|
|
49
56
|
encode: (items) => encodePullResponse(SyncMessage.PullResponse.make({
|
|
50
57
|
batch: items.map((eventEncoded) => ({
|
|
@@ -54,8 +61,8 @@ export const makePush = ({ payload, options, storeId, ctx, env, }) => (pushReque
|
|
|
54
61
|
pageInfo: SyncBackend.pageInfoNoMore,
|
|
55
62
|
backendId,
|
|
56
63
|
})),
|
|
57
|
-
})
|
|
58
|
-
const batchWithMetadata =
|
|
64
|
+
})(pushRequest.batch).pipe(Effect.map((eventBatch) => eventBatch.map((events) => {
|
|
65
|
+
const batchWithMetadata = events.map((eventEncoded) => ({
|
|
59
66
|
eventEncoded,
|
|
60
67
|
metadata: Option.some(SyncMessage.SyncMetadata.make({ createdAt })),
|
|
61
68
|
}));
|
|
@@ -66,20 +73,20 @@ export const makePush = ({ payload, options, storeId, ctx, env, }) => (pushReque
|
|
|
66
73
|
});
|
|
67
74
|
return {
|
|
68
75
|
response,
|
|
69
|
-
encoded:
|
|
76
|
+
encoded: encodePullResponse(response),
|
|
70
77
|
};
|
|
71
|
-
}));
|
|
78
|
+
})));
|
|
72
79
|
// Dual broadcasting: WebSocket + RPC clients
|
|
73
80
|
// Broadcast to WebSocket clients
|
|
74
81
|
if (connectedClients.length > 0) {
|
|
75
82
|
for (const { response, encoded } of responses) {
|
|
76
83
|
// Only calling once for now.
|
|
77
|
-
if (options?.onPullRes) {
|
|
78
|
-
yield* Effect.
|
|
84
|
+
if (options?.onPullRes !== undefined) {
|
|
85
|
+
yield* Effect.trySyncOrPromiseOrEffect(() => options.onPullRes(response)).pipe(UnknownError.mapToUnknownError);
|
|
79
86
|
}
|
|
80
87
|
// NOTE we're also sending the pullRes chunk to the pushing ws client as confirmation
|
|
81
88
|
for (const conn of connectedClients) {
|
|
82
|
-
const attachment = Schema.
|
|
89
|
+
const attachment = yield* Schema.decodeEffect(WebSocketAttachmentSchema)(conn.deserializeAttachment());
|
|
83
90
|
// We're doing something a bit "advanced" here as we're directly emitting Effect RPC-compatible
|
|
84
91
|
// response messsages on the Effect RPC-managed websocket connection to the WS client.
|
|
85
92
|
// For this we need to get the RPC `requestId` from the WebSocket attachment.
|
|
@@ -89,42 +96,63 @@ export const makePush = ({ payload, options, storeId, ctx, env, }) => (pushReque
|
|
|
89
96
|
requestId,
|
|
90
97
|
values: [encoded],
|
|
91
98
|
};
|
|
92
|
-
conn.send(
|
|
99
|
+
conn.send(jsonStringify(res));
|
|
93
100
|
}
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
103
|
yield* Effect.logDebug(`Broadcasted to ${connectedClients.length} WebSocket clients`);
|
|
97
104
|
}
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
// Subscribers live in the DO's KV storage (single source of truth), so fan-out survives reconstruction.
|
|
106
|
+
// emitStreamResponse reconstructs each client stub from its callerContext.
|
|
107
|
+
const rpcSubscriptions = Array.from(ctx.storage.kv.list({ prefix: rpcSubscriptionKeyPrefix }));
|
|
108
|
+
if (rpcSubscriptions.length > 0) {
|
|
109
|
+
for (const [, subscription] of rpcSubscriptions) {
|
|
101
110
|
for (const { encoded } of responses) {
|
|
102
111
|
yield* emitStreamResponse({
|
|
103
112
|
callerContext: subscription.callerContext,
|
|
104
113
|
env,
|
|
105
114
|
requestId: subscription.requestId,
|
|
115
|
+
storeId: subscription.storeId,
|
|
106
116
|
values: [encoded],
|
|
107
117
|
}).pipe(Effect.tapCauseLogPretty, Effect.exit);
|
|
108
118
|
}
|
|
109
119
|
}
|
|
110
|
-
yield* Effect.logDebug(`Broadcasted to ${rpcSubscriptions.
|
|
120
|
+
yield* Effect.logDebug(`Broadcasted to ${rpcSubscriptions.length} RPC clients`);
|
|
111
121
|
}
|
|
112
|
-
}).pipe(Effect.tapCauseLogPretty, Effect.withSpan('push-rpc-broadcast')
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
yield* Effect.yieldNow();
|
|
122
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.withSpan('push-rpc-broadcast')));
|
|
123
|
+
// Acknowledge only after the committed batch has been published, so a client cannot observe an
|
|
124
|
+
// advanced server head while the corresponding pull response is still waiting in a detached fiber.
|
|
116
125
|
return SyncMessage.PushAck.make({});
|
|
117
126
|
}).pipe(Effect.tap(Effect.fn(function* (message) {
|
|
118
|
-
if (options?.onPushRes) {
|
|
119
|
-
yield* Effect.
|
|
127
|
+
if (options?.onPushRes !== undefined) {
|
|
128
|
+
yield* Effect.trySyncOrPromiseOrEffect(() => options.onPushRes(message)).pipe(UnknownError.mapToUnknownError);
|
|
120
129
|
}
|
|
121
|
-
})), Effect.mapError((cause) =>
|
|
130
|
+
})), Effect.mapError((cause) => cause._tag === 'BackendIdMismatchError' || cause._tag === 'ServerAheadError' || cause._tag === 'UnknownError'
|
|
131
|
+
? cause
|
|
132
|
+
: new UnknownError({ cause })), Effect.withSpan('sync-cf:do:push', { attributes: { storeId, batchSize: pushRequest.batch.length } }));
|
|
133
|
+
/**
|
|
134
|
+
* Serializes the complete push admission lifecycle, including publication to pull subscribers.
|
|
135
|
+
*
|
|
136
|
+
* This complements `blockConcurrencyWhile`: that Cloudflare primitive makes the durable state transition
|
|
137
|
+
* atomic with respect to other Durable Object requests, whereas this semaphore preserves push order after
|
|
138
|
+
* the storage gate is released and broadcasting begins. Keeping broadcast outside `blockConcurrencyWhile`
|
|
139
|
+
* avoids holding Cloudflare's concurrency gate across network I/O.
|
|
140
|
+
*
|
|
141
|
+
* Once admitted, the effect is uninterruptible so cancellation cannot leave a committed head without its
|
|
142
|
+
* matching pull response. Such a gap would make later pushers receive `ServerAhead` while waiting for a
|
|
143
|
+
* response that was never published.
|
|
144
|
+
*/
|
|
145
|
+
export const runSerializedPushAdmission = (semaphore, admission) => semaphore.withPermits(1)(admission.pipe(Effect.uninterruptible));
|
|
122
146
|
/**
|
|
147
|
+
* Runs the storage-backed state transition behind Cloudflare's Durable Object concurrency gate.
|
|
148
|
+
* This scope should remain limited to local persistence and head mutation; ordered publication is handled
|
|
149
|
+
* separately by `runSerializedPushAdmission` so outbound work does not block unrelated Durable Object events.
|
|
150
|
+
*
|
|
123
151
|
* @see https://developers.cloudflare.com/durable-objects/api/state/#blockconcurrencywhile
|
|
124
152
|
*/
|
|
125
153
|
const blockConcurrencyWhile = (ctx) => (eff) => Effect.gen(function* () {
|
|
126
|
-
const
|
|
127
|
-
const exit = yield* Effect.promise(() => ctx.blockConcurrencyWhile(() => eff.pipe(Effect.
|
|
154
|
+
const services = yield* Effect.context();
|
|
155
|
+
const exit = yield* Effect.promise(() => ctx.blockConcurrencyWhile(() => eff.pipe(Effect.runPromiseExitWith(services))));
|
|
128
156
|
return yield* exit;
|
|
129
157
|
});
|
|
130
158
|
//# sourceMappingURL=push.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../../src/cf-worker/do/push.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../../src/cf-worker/do/push.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.test.d.ts","sourceRoot":"","sources":["../../../src/cf-worker/do/push.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest';
|
|
2
|
+
import { Deferred, Duration, Effect, Fiber, Semaphore } from '@livestore/utils/effect';
|
|
3
|
+
import { runSerializedPushAdmission } from "./push.js";
|
|
4
|
+
Vitest.describe('sync-cf push admission', () => {
|
|
5
|
+
Vitest.live('publishes an admitted push before honoring interruption', () => Effect.gen(function* () {
|
|
6
|
+
const semaphore = yield* Semaphore.make(1);
|
|
7
|
+
const persisted = yield* Deferred.make();
|
|
8
|
+
const allowBroadcast = yield* Deferred.make();
|
|
9
|
+
const broadcasted = yield* Deferred.make();
|
|
10
|
+
const admissionFiber = yield* runSerializedPushAdmission(semaphore, Effect.gen(function* () {
|
|
11
|
+
yield* Deferred.succeed(persisted, undefined);
|
|
12
|
+
yield* Deferred.await(allowBroadcast);
|
|
13
|
+
yield* Deferred.succeed(broadcasted, undefined);
|
|
14
|
+
})).pipe(Effect.forkChild);
|
|
15
|
+
// Reproduce cancellation after persistence but before the pull broadcast handoff.
|
|
16
|
+
yield* Deferred.await(persisted);
|
|
17
|
+
const interruptStarted = yield* Deferred.make();
|
|
18
|
+
const interruptFiber = yield* Effect.gen(function* () {
|
|
19
|
+
yield* Deferred.succeed(interruptStarted, undefined);
|
|
20
|
+
yield* Fiber.interrupt(admissionFiber);
|
|
21
|
+
}).pipe(Effect.forkChild);
|
|
22
|
+
yield* Deferred.await(interruptStarted);
|
|
23
|
+
yield* Effect.yieldNow;
|
|
24
|
+
yield* Deferred.succeed(allowBroadcast, undefined);
|
|
25
|
+
yield* Deferred.await(broadcasted).pipe(Effect.timeout(Duration.seconds(1)));
|
|
26
|
+
yield* Fiber.join(interruptFiber);
|
|
27
|
+
Vitest.expect(yield* Deferred.isDone(broadcasted)).toBe(true);
|
|
28
|
+
}));
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=push.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.test.js","sourceRoot":"","sources":["../../../src/cf-worker/do/push.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAEtF,OAAO,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAA;AAEtD,MAAM,CAAC,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IAC7C,MAAM,CAAC,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE,CAC1E,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAC1C,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAQ,CAAA;QAC9C,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAQ,CAAA;QACnD,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAQ,CAAA;QAEhD,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,0BAA0B,CACtD,SAAS,EACT,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;YAC7C,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;YACrC,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;QACjD,CAAC,CAAC,CACH,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAExB,kFAAkF;QAClF,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAChC,MAAM,gBAAgB,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAQ,CAAA;QACrD,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAChD,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAA;YACpD,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;QACxC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QACzB,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAA;QACvC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAA;QAEtB,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,SAAS,CAAC,CAAA;QAClD,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5E,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QAEjC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC/D,CAAC,CAAC,CACH,CAAA;AACH,CAAC,CAAC,CAAA"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { State } from '@livestore/common/schema';
|
|
2
2
|
import { Schema } from '@livestore/utils/effect';
|
|
3
|
+
/**
|
|
4
|
+
* Main event log table storing all LiveStore events.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ IMPORTANT: Any changes to this schema require bumping PERSISTENCE_FORMAT_VERSION in shared.ts
|
|
7
|
+
*/
|
|
3
8
|
export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"eventlog_7_$storeId", {
|
|
4
9
|
readonly seqNum: {
|
|
5
10
|
columnType: "integer";
|
|
6
|
-
schema: Schema.
|
|
11
|
+
schema: Schema.Codec<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never, never>;
|
|
7
12
|
default: import("effect/Option").None<never>;
|
|
8
13
|
nullable: false;
|
|
9
14
|
primaryKey: true;
|
|
@@ -11,7 +16,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
11
16
|
};
|
|
12
17
|
readonly parentSeqNum: {
|
|
13
18
|
columnType: "integer";
|
|
14
|
-
schema: Schema.
|
|
19
|
+
schema: Schema.Codec<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never, never>;
|
|
15
20
|
default: import("effect/Option").None<never>;
|
|
16
21
|
nullable: false;
|
|
17
22
|
primaryKey: false;
|
|
@@ -19,7 +24,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
19
24
|
};
|
|
20
25
|
readonly name: {
|
|
21
26
|
columnType: "text";
|
|
22
|
-
schema: Schema.
|
|
27
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
23
28
|
default: import("effect/Option").None<never>;
|
|
24
29
|
nullable: false;
|
|
25
30
|
primaryKey: false;
|
|
@@ -27,7 +32,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
27
32
|
};
|
|
28
33
|
readonly args: {
|
|
29
34
|
columnType: "text";
|
|
30
|
-
schema: Schema.
|
|
35
|
+
schema: Schema.Codec<any, string | null, never, never>;
|
|
31
36
|
default: import("effect/Option").None<never>;
|
|
32
37
|
nullable: true;
|
|
33
38
|
primaryKey: false;
|
|
@@ -36,7 +41,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
36
41
|
/** ISO date format. Currently only used for debugging purposes. */
|
|
37
42
|
readonly createdAt: {
|
|
38
43
|
columnType: "text";
|
|
39
|
-
schema: Schema.
|
|
44
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
40
45
|
default: import("effect/Option").None<never>;
|
|
41
46
|
nullable: false;
|
|
42
47
|
primaryKey: false;
|
|
@@ -44,7 +49,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
44
49
|
};
|
|
45
50
|
readonly clientId: {
|
|
46
51
|
columnType: "text";
|
|
47
|
-
schema: Schema.
|
|
52
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
48
53
|
default: import("effect/Option").None<never>;
|
|
49
54
|
nullable: false;
|
|
50
55
|
primaryKey: false;
|
|
@@ -52,7 +57,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
52
57
|
};
|
|
53
58
|
readonly sessionId: {
|
|
54
59
|
columnType: "text";
|
|
55
|
-
schema: Schema.
|
|
60
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
56
61
|
default: import("effect/Option").None<never>;
|
|
57
62
|
nullable: false;
|
|
58
63
|
primaryKey: false;
|
|
@@ -61,7 +66,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
61
66
|
}>, State.SQLite.WithDefaults<{
|
|
62
67
|
readonly seqNum: {
|
|
63
68
|
columnType: "integer";
|
|
64
|
-
schema: Schema.
|
|
69
|
+
schema: Schema.Codec<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never, never>;
|
|
65
70
|
default: import("effect/Option").None<never>;
|
|
66
71
|
nullable: false;
|
|
67
72
|
primaryKey: true;
|
|
@@ -69,7 +74,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
69
74
|
};
|
|
70
75
|
readonly parentSeqNum: {
|
|
71
76
|
columnType: "integer";
|
|
72
|
-
schema: Schema.
|
|
77
|
+
schema: Schema.Codec<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never, never>;
|
|
73
78
|
default: import("effect/Option").None<never>;
|
|
74
79
|
nullable: false;
|
|
75
80
|
primaryKey: false;
|
|
@@ -77,7 +82,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
77
82
|
};
|
|
78
83
|
readonly name: {
|
|
79
84
|
columnType: "text";
|
|
80
|
-
schema: Schema.
|
|
85
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
81
86
|
default: import("effect/Option").None<never>;
|
|
82
87
|
nullable: false;
|
|
83
88
|
primaryKey: false;
|
|
@@ -85,7 +90,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
85
90
|
};
|
|
86
91
|
readonly args: {
|
|
87
92
|
columnType: "text";
|
|
88
|
-
schema: Schema.
|
|
93
|
+
schema: Schema.Codec<any, string | null, never, never>;
|
|
89
94
|
default: import("effect/Option").None<never>;
|
|
90
95
|
nullable: true;
|
|
91
96
|
primaryKey: false;
|
|
@@ -94,7 +99,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
94
99
|
/** ISO date format. Currently only used for debugging purposes. */
|
|
95
100
|
readonly createdAt: {
|
|
96
101
|
columnType: "text";
|
|
97
|
-
schema: Schema.
|
|
102
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
98
103
|
default: import("effect/Option").None<never>;
|
|
99
104
|
nullable: false;
|
|
100
105
|
primaryKey: false;
|
|
@@ -102,7 +107,7 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
102
107
|
};
|
|
103
108
|
readonly clientId: {
|
|
104
109
|
columnType: "text";
|
|
105
|
-
schema: Schema.
|
|
110
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
106
111
|
default: import("effect/Option").None<never>;
|
|
107
112
|
nullable: false;
|
|
108
113
|
primaryKey: false;
|
|
@@ -110,34 +115,30 @@ export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTab
|
|
|
110
115
|
};
|
|
111
116
|
readonly sessionId: {
|
|
112
117
|
columnType: "text";
|
|
113
|
-
schema: Schema.
|
|
114
|
-
default: import("effect/Option").None<never>;
|
|
115
|
-
nullable: false;
|
|
116
|
-
primaryKey: false;
|
|
117
|
-
autoIncrement: false;
|
|
118
|
-
};
|
|
119
|
-
}>, Schema.
|
|
120
|
-
readonly seqNum: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
|
|
121
|
-
readonly parentSeqNum: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
|
|
122
|
-
readonly name: string
|
|
123
|
-
readonly args: any
|
|
124
|
-
readonly createdAt: string
|
|
125
|
-
readonly clientId: string
|
|
126
|
-
readonly sessionId: string
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
readonly clientId: string;
|
|
134
|
-
readonly sessionId: string;
|
|
135
|
-
}, never>>;
|
|
136
|
-
/** Will only ever have one row per durable object. */
|
|
118
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
119
|
+
default: import("effect/Option").None<never>;
|
|
120
|
+
nullable: false;
|
|
121
|
+
primaryKey: false;
|
|
122
|
+
autoIncrement: false;
|
|
123
|
+
};
|
|
124
|
+
}>, Schema.Struct<{
|
|
125
|
+
readonly seqNum: Schema.Codec<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never, never>;
|
|
126
|
+
readonly parentSeqNum: Schema.Codec<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never, never>;
|
|
127
|
+
readonly name: Schema.Codec<string, string, never, never>;
|
|
128
|
+
readonly args: Schema.Codec<any, string | null, never, never>;
|
|
129
|
+
readonly createdAt: Schema.Codec<string, string, never, never>;
|
|
130
|
+
readonly clientId: Schema.Codec<string, string, never, never>;
|
|
131
|
+
readonly sessionId: Schema.Codec<string, string, never, never>;
|
|
132
|
+
}>>;
|
|
133
|
+
/**
|
|
134
|
+
* Context metadata table - one row per Durable Object.
|
|
135
|
+
*
|
|
136
|
+
* ⚠️ IMPORTANT: Any changes to this schema require bumping PERSISTENCE_FORMAT_VERSION in shared.ts
|
|
137
|
+
*/
|
|
137
138
|
export declare const contextTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"context_7", {
|
|
138
139
|
readonly storeId: {
|
|
139
140
|
columnType: "text";
|
|
140
|
-
schema: Schema.
|
|
141
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
141
142
|
default: import("effect/Option").None<never>;
|
|
142
143
|
nullable: false;
|
|
143
144
|
primaryKey: true;
|
|
@@ -145,7 +146,7 @@ export declare const contextTable: State.SQLite.TableDef<State.SQLite.SqliteTabl
|
|
|
145
146
|
};
|
|
146
147
|
readonly currentHead: {
|
|
147
148
|
columnType: "integer";
|
|
148
|
-
schema: Schema.
|
|
149
|
+
schema: Schema.Codec<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never, never>;
|
|
149
150
|
default: import("effect/Option").None<never>;
|
|
150
151
|
nullable: false;
|
|
151
152
|
primaryKey: false;
|
|
@@ -153,7 +154,7 @@ export declare const contextTable: State.SQLite.TableDef<State.SQLite.SqliteTabl
|
|
|
153
154
|
};
|
|
154
155
|
readonly backendId: {
|
|
155
156
|
columnType: "text";
|
|
156
|
-
schema: Schema.
|
|
157
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
157
158
|
default: import("effect/Option").None<never>;
|
|
158
159
|
nullable: false;
|
|
159
160
|
primaryKey: false;
|
|
@@ -162,7 +163,7 @@ export declare const contextTable: State.SQLite.TableDef<State.SQLite.SqliteTabl
|
|
|
162
163
|
}>, State.SQLite.WithDefaults<{
|
|
163
164
|
readonly storeId: {
|
|
164
165
|
columnType: "text";
|
|
165
|
-
schema: Schema.
|
|
166
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
166
167
|
default: import("effect/Option").None<never>;
|
|
167
168
|
nullable: false;
|
|
168
169
|
primaryKey: true;
|
|
@@ -170,7 +171,7 @@ export declare const contextTable: State.SQLite.TableDef<State.SQLite.SqliteTabl
|
|
|
170
171
|
};
|
|
171
172
|
readonly currentHead: {
|
|
172
173
|
columnType: "integer";
|
|
173
|
-
schema: Schema.
|
|
174
|
+
schema: Schema.Codec<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never, never>;
|
|
174
175
|
default: import("effect/Option").None<never>;
|
|
175
176
|
nullable: false;
|
|
176
177
|
primaryKey: false;
|
|
@@ -178,19 +179,15 @@ export declare const contextTable: State.SQLite.TableDef<State.SQLite.SqliteTabl
|
|
|
178
179
|
};
|
|
179
180
|
readonly backendId: {
|
|
180
181
|
columnType: "text";
|
|
181
|
-
schema: Schema.
|
|
182
|
+
schema: Schema.Codec<string, string, never, never>;
|
|
182
183
|
default: import("effect/Option").None<never>;
|
|
183
184
|
nullable: false;
|
|
184
185
|
primaryKey: false;
|
|
185
186
|
autoIncrement: false;
|
|
186
187
|
};
|
|
187
|
-
}>, Schema.
|
|
188
|
-
readonly storeId: string
|
|
189
|
-
readonly currentHead: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
|
|
190
|
-
readonly backendId: string
|
|
191
|
-
}
|
|
192
|
-
readonly storeId: string;
|
|
193
|
-
readonly currentHead: number;
|
|
194
|
-
readonly backendId: string;
|
|
195
|
-
}, never>>;
|
|
188
|
+
}>, Schema.Struct<{
|
|
189
|
+
readonly storeId: Schema.Codec<string, string, never, never>;
|
|
190
|
+
readonly currentHead: Schema.Codec<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never, never>;
|
|
191
|
+
readonly backendId: Schema.Codec<string, string, never, never>;
|
|
192
|
+
}>>;
|
|
196
193
|
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../../src/cf-worker/do/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../../src/cf-worker/do/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAIhD;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAQtB,mEAAmE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAAnE,mEAAmE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAKrE,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAOvB,CAAA"}
|
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
import { EventSequenceNumber, State } from '@livestore/common/schema';
|
|
2
2
|
import { Schema } from '@livestore/utils/effect';
|
|
3
3
|
import { PERSISTENCE_FORMAT_VERSION } from "../shared.js";
|
|
4
|
+
/**
|
|
5
|
+
* Main event log table storing all LiveStore events.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ IMPORTANT: Any changes to this schema require bumping PERSISTENCE_FORMAT_VERSION in shared.ts
|
|
8
|
+
*/
|
|
4
9
|
export const eventlogTable = State.SQLite.table({
|
|
5
10
|
// NOTE actual table name is determined at runtime to use proper storeId
|
|
6
11
|
name: `eventlog_${PERSISTENCE_FORMAT_VERSION}_$storeId`,
|
|
7
12
|
columns: {
|
|
8
|
-
seqNum: State.SQLite.integer({ primaryKey: true, schema: EventSequenceNumber.
|
|
9
|
-
parentSeqNum: State.SQLite.integer({ schema: EventSequenceNumber.
|
|
13
|
+
seqNum: State.SQLite.integer({ primaryKey: true, schema: EventSequenceNumber.Global.Schema }),
|
|
14
|
+
parentSeqNum: State.SQLite.integer({ schema: EventSequenceNumber.Global.Schema }),
|
|
10
15
|
name: State.SQLite.text({}),
|
|
11
|
-
args: State.SQLite.text({ schema: Schema.
|
|
16
|
+
args: State.SQLite.text({ schema: Schema.fromJsonString(Schema.Any), nullable: true }),
|
|
12
17
|
/** ISO date format. Currently only used for debugging purposes. */
|
|
13
18
|
createdAt: State.SQLite.text({}),
|
|
14
19
|
clientId: State.SQLite.text({}),
|
|
15
20
|
sessionId: State.SQLite.text({}),
|
|
16
21
|
},
|
|
17
22
|
});
|
|
18
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Context metadata table - one row per Durable Object.
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ IMPORTANT: Any changes to this schema require bumping PERSISTENCE_FORMAT_VERSION in shared.ts
|
|
27
|
+
*/
|
|
19
28
|
export const contextTable = State.SQLite.table({
|
|
20
29
|
name: `context_${PERSISTENCE_FORMAT_VERSION}`,
|
|
21
30
|
columns: {
|
|
22
31
|
storeId: State.SQLite.text({ primaryKey: true }),
|
|
23
|
-
currentHead: State.SQLite.integer({ schema: EventSequenceNumber.
|
|
32
|
+
currentHead: State.SQLite.integer({ schema: EventSequenceNumber.Global.Schema }),
|
|
24
33
|
backendId: State.SQLite.text({}),
|
|
25
34
|
},
|
|
26
35
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../../src/cf-worker/do/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../../src/cf-worker/do/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC;IAC9C,wEAAwE;IACxE,IAAI,EAAE,YAAY,0BAA0B,WAAW;IACvD,OAAO,EAAE;QACP,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAC7F,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,mBAAmB,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACjF,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACtF,mEAAmE;QACnE,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;KACjC;CACF,CAAC,CAAA;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC;IAC7C,IAAI,EAAE,WAAW,0BAA0B,EAAE;IAC7C,OAAO,EAAE;QACP,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QAChD,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,mBAAmB,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAChF,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;KACjC;CACF,CAAC,CAAA"}
|
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { LiveStoreEvent } from '@livestore/common/schema';
|
|
1
|
+
import { UnknownError } from '@livestore/common';
|
|
3
2
|
import type { CfTypes } from '@livestore/common-cf';
|
|
3
|
+
import type { LiveStoreEvent } from '@livestore/common/schema';
|
|
4
4
|
import { Effect, Option, Stream } from '@livestore/utils/effect';
|
|
5
5
|
import { SyncMetadata } from '../../common/sync-message-types.ts';
|
|
6
|
-
import { type
|
|
6
|
+
import { type StoreId } from '../shared.ts';
|
|
7
|
+
export type StorageEngine = {
|
|
8
|
+
_tag: 'd1';
|
|
9
|
+
db: CfTypes.D1Database;
|
|
10
|
+
} | {
|
|
11
|
+
_tag: 'do-sqlite';
|
|
12
|
+
};
|
|
7
13
|
export type SyncStorage = {
|
|
8
14
|
dbName: string;
|
|
9
15
|
getEvents: (cursor: number | undefined) => Effect.Effect<{
|
|
10
16
|
total: number;
|
|
11
17
|
stream: Stream.Stream<{
|
|
12
|
-
eventEncoded: LiveStoreEvent.
|
|
18
|
+
eventEncoded: LiveStoreEvent.Global.Encoded;
|
|
13
19
|
metadata: Option.Option<SyncMetadata>;
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
appendEvents: (batch: ReadonlyArray<LiveStoreEvent.
|
|
17
|
-
resetStore: Effect.Effect<void,
|
|
20
|
+
}, UnknownError>;
|
|
21
|
+
}, UnknownError>;
|
|
22
|
+
appendEvents: (batch: ReadonlyArray<LiveStoreEvent.Global.Encoded>, createdAt: string) => Effect.Effect<void, UnknownError>;
|
|
23
|
+
resetStore: Effect.Effect<void, UnknownError>;
|
|
18
24
|
};
|
|
19
|
-
export declare const makeStorage: (ctx: CfTypes.DurableObjectState,
|
|
25
|
+
export declare const makeStorage: (ctx: CfTypes.DurableObjectState, storeId: StoreId, engine: StorageEngine) => SyncStorage;
|
|
20
26
|
//# sourceMappingURL=sync-storage.d.ts.map
|