@livestore/sync-cf 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 (113) hide show
  1. package/README.md +60 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cf-worker/do/durable-object.d.ts +45 -0
  4. package/dist/cf-worker/do/durable-object.d.ts.map +1 -0
  5. package/dist/cf-worker/do/durable-object.js +154 -0
  6. package/dist/cf-worker/do/durable-object.js.map +1 -0
  7. package/dist/cf-worker/do/layer.d.ts +34 -0
  8. package/dist/cf-worker/do/layer.d.ts.map +1 -0
  9. package/dist/cf-worker/do/layer.js +68 -0
  10. package/dist/cf-worker/do/layer.js.map +1 -0
  11. package/dist/cf-worker/do/pull.d.ts +6 -0
  12. package/dist/cf-worker/do/pull.d.ts.map +1 -0
  13. package/dist/cf-worker/do/pull.js +39 -0
  14. package/dist/cf-worker/do/pull.js.map +1 -0
  15. package/dist/cf-worker/do/push.d.ts +14 -0
  16. package/dist/cf-worker/do/push.d.ts.map +1 -0
  17. package/dist/cf-worker/do/push.js +99 -0
  18. package/dist/cf-worker/do/push.js.map +1 -0
  19. package/dist/cf-worker/do/sqlite.d.ts +196 -0
  20. package/dist/cf-worker/do/sqlite.d.ts.map +1 -0
  21. package/dist/cf-worker/do/sqlite.js +27 -0
  22. package/dist/cf-worker/do/sqlite.js.map +1 -0
  23. package/dist/cf-worker/do/sync-storage.d.ts +17 -0
  24. package/dist/cf-worker/do/sync-storage.d.ts.map +1 -0
  25. package/dist/cf-worker/do/sync-storage.js +73 -0
  26. package/dist/cf-worker/do/sync-storage.js.map +1 -0
  27. package/dist/cf-worker/do/transport/do-rpc-server.d.ts +8 -0
  28. package/dist/cf-worker/do/transport/do-rpc-server.d.ts.map +1 -0
  29. package/dist/cf-worker/do/transport/do-rpc-server.js +45 -0
  30. package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -0
  31. package/dist/cf-worker/do/transport/http-rpc-server.d.ts +7 -0
  32. package/dist/cf-worker/do/transport/http-rpc-server.d.ts.map +1 -0
  33. package/dist/cf-worker/do/transport/http-rpc-server.js +24 -0
  34. package/dist/cf-worker/do/transport/http-rpc-server.js.map +1 -0
  35. package/dist/cf-worker/do/transport/ws-rpc-server.d.ts +4 -0
  36. package/dist/cf-worker/do/transport/ws-rpc-server.d.ts.map +1 -0
  37. package/dist/cf-worker/do/transport/ws-rpc-server.js +21 -0
  38. package/dist/cf-worker/do/transport/ws-rpc-server.js.map +1 -0
  39. package/dist/cf-worker/mod.d.ts +4 -2
  40. package/dist/cf-worker/mod.d.ts.map +1 -1
  41. package/dist/cf-worker/mod.js +3 -2
  42. package/dist/cf-worker/mod.js.map +1 -1
  43. package/dist/cf-worker/shared.d.ts +127 -0
  44. package/dist/cf-worker/shared.d.ts.map +1 -0
  45. package/dist/cf-worker/shared.js +26 -0
  46. package/dist/cf-worker/shared.js.map +1 -0
  47. package/dist/cf-worker/worker.d.ts +36 -21
  48. package/dist/cf-worker/worker.d.ts.map +1 -1
  49. package/dist/cf-worker/worker.js +39 -32
  50. package/dist/cf-worker/worker.js.map +1 -1
  51. package/dist/client/mod.d.ts +4 -0
  52. package/dist/client/mod.d.ts.map +1 -0
  53. package/dist/client/mod.js +4 -0
  54. package/dist/client/mod.js.map +1 -0
  55. package/dist/client/transport/do-rpc-client.d.ts +40 -0
  56. package/dist/client/transport/do-rpc-client.d.ts.map +1 -0
  57. package/dist/client/transport/do-rpc-client.js +102 -0
  58. package/dist/client/transport/do-rpc-client.js.map +1 -0
  59. package/dist/client/transport/http-rpc-client.d.ts +43 -0
  60. package/dist/client/transport/http-rpc-client.d.ts.map +1 -0
  61. package/dist/client/transport/http-rpc-client.js +87 -0
  62. package/dist/client/transport/http-rpc-client.js.map +1 -0
  63. package/dist/client/transport/ws-rpc-client.d.ts +45 -0
  64. package/dist/client/transport/ws-rpc-client.d.ts.map +1 -0
  65. package/dist/client/transport/ws-rpc-client.js +94 -0
  66. package/dist/client/transport/ws-rpc-client.js.map +1 -0
  67. package/dist/common/do-rpc-schema.d.ts +76 -0
  68. package/dist/common/do-rpc-schema.d.ts.map +1 -0
  69. package/dist/common/do-rpc-schema.js +48 -0
  70. package/dist/common/do-rpc-schema.js.map +1 -0
  71. package/dist/common/http-rpc-schema.d.ts +58 -0
  72. package/dist/common/http-rpc-schema.d.ts.map +1 -0
  73. package/dist/common/http-rpc-schema.js +37 -0
  74. package/dist/common/http-rpc-schema.js.map +1 -0
  75. package/dist/common/mod.d.ts +5 -1
  76. package/dist/common/mod.d.ts.map +1 -1
  77. package/dist/common/mod.js +4 -1
  78. package/dist/common/mod.js.map +1 -1
  79. package/dist/common/sync-message-types.d.ts +236 -0
  80. package/dist/common/sync-message-types.d.ts.map +1 -0
  81. package/dist/common/sync-message-types.js +60 -0
  82. package/dist/common/sync-message-types.js.map +1 -0
  83. package/dist/common/ws-rpc-schema.d.ts +55 -0
  84. package/dist/common/ws-rpc-schema.d.ts.map +1 -0
  85. package/dist/common/ws-rpc-schema.js +32 -0
  86. package/dist/common/ws-rpc-schema.js.map +1 -0
  87. package/package.json +7 -8
  88. package/src/cf-worker/do/durable-object.ts +241 -0
  89. package/src/cf-worker/do/layer.ts +107 -0
  90. package/src/cf-worker/do/pull.ts +64 -0
  91. package/src/cf-worker/do/push.ts +162 -0
  92. package/src/cf-worker/do/sqlite.ts +28 -0
  93. package/src/cf-worker/do/sync-storage.ts +126 -0
  94. package/src/cf-worker/do/transport/do-rpc-server.ts +82 -0
  95. package/src/cf-worker/do/transport/http-rpc-server.ts +37 -0
  96. package/src/cf-worker/do/transport/ws-rpc-server.ts +34 -0
  97. package/src/cf-worker/mod.ts +4 -2
  98. package/src/cf-worker/shared.ts +95 -0
  99. package/src/cf-worker/worker.ts +72 -63
  100. package/src/client/mod.ts +3 -0
  101. package/src/client/transport/do-rpc-client.ts +171 -0
  102. package/src/client/transport/http-rpc-client.ts +205 -0
  103. package/src/client/transport/ws-rpc-client.ts +182 -0
  104. package/src/common/do-rpc-schema.ts +54 -0
  105. package/src/common/http-rpc-schema.ts +40 -0
  106. package/src/common/mod.ts +8 -1
  107. package/src/common/sync-message-types.ts +117 -0
  108. package/src/common/ws-rpc-schema.ts +36 -0
  109. package/src/cf-worker/cf-types.ts +0 -12
  110. package/src/cf-worker/durable-object.ts +0 -478
  111. package/src/common/ws-message-types.ts +0 -114
  112. package/src/sync-impl/mod.ts +0 -1
  113. package/src/sync-impl/ws-impl.ts +0 -274
@@ -0,0 +1,45 @@
1
+ import { type CfTypes } from '@livestore/common-cf';
2
+ import { type Env, type MakeDurableObjectClassOptions, type SyncBackendRpcInterface } from '../shared.ts';
3
+ export type DoState = CfTypes.DurableObjectState;
4
+ export type DoObject<T> = CfTypes.DurableObject & T;
5
+ export type MakeDurableObjectClass = (options?: MakeDurableObjectClassOptions) => {
6
+ new (ctx: DoState, env: Env): DoObject<SyncBackendRpcInterface>;
7
+ };
8
+ /**
9
+ * Creates a Durable Object class for handling WebSocket-based sync.
10
+ * A sync durable object is uniquely scoped to a specific `storeId`.
11
+ *
12
+ * The sync DO supports 3 transport modes:
13
+ * - HTTP JSON-RPC
14
+ * - WebSocket
15
+ * - Durable Object RPC calls (only works in combination with `@livestore/adapter-cf`)
16
+ *
17
+ * Example:
18
+ *
19
+ * ```ts
20
+ * // In your Cloudflare Worker file
21
+ * import { makeDurableObject } from '@livestore/sync-cf/cf-worker'
22
+ *
23
+ * export class SyncBackendDO extends makeDurableObject({
24
+ * onPush: async (message) => {
25
+ * console.log('onPush', message.batch)
26
+ * },
27
+ * onPull: async (message) => {
28
+ * console.log('onPull', message)
29
+ * },
30
+ * }) {}
31
+ * ```
32
+ *
33
+ * `wrangler.toml`
34
+ * ```toml
35
+ * [[durable_objects.bindings]]
36
+ * name = "SYNC_BACKEND_DO"
37
+ * class_name = "SyncBackendDO"
38
+
39
+ * [[migrations]]
40
+ * tag = "v1"
41
+ * new_sqlite_classes = ["SyncBackendDO"]
42
+ * ```
43
+ */
44
+ export declare const makeDurableObject: MakeDurableObjectClass;
45
+ //# sourceMappingURL=durable-object.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-object.d.ts","sourceRoot":"","sources":["../../../src/cf-worker/do/durable-object.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,OAAO,EAAkC,MAAM,sBAAsB,CAAA;AAanF,OAAO,EACL,KAAK,GAAG,EAER,KAAK,6BAA6B,EAClC,KAAK,uBAAuB,EAE7B,MAAM,cAAc,CAAA;AAmBrB,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAA;AAChD,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,OAAO,CAAC,aAAa,GAAG,CAAC,CAAA;AAEnD,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,CAAC,EAAE,6BAA6B,KAAK;IAChF,KAAK,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CAAA;CAChE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,iBAAiB,EAAE,sBA4J/B,CAAA"}
@@ -0,0 +1,154 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ import { DurableObject } from 'cloudflare:workers';
3
+ import { setupDurableObjectWebSocketRpc } from '@livestore/common-cf';
4
+ import { CfDeclare } from '@livestore/common-cf/declare';
5
+ import { Effect, FetchHttpClient, Layer, Logger, LogLevel, Otlp, RpcMessage, Schema, } from '@livestore/utils/effect';
6
+ import { getSyncRequestSearchParams, WebSocketAttachmentSchema, } from "../shared.js";
7
+ import { DoCtx } from "./layer.js";
8
+ import { createDoRpcHandler } from "./transport/do-rpc-server.js";
9
+ import { createHttpRpcHandler } from "./transport/http-rpc-server.js";
10
+ import { makeRpcServer } from "./transport/ws-rpc-server.js";
11
+ const DurableObjectBase = DurableObject;
12
+ /**
13
+ * Creates a Durable Object class for handling WebSocket-based sync.
14
+ * A sync durable object is uniquely scoped to a specific `storeId`.
15
+ *
16
+ * The sync DO supports 3 transport modes:
17
+ * - HTTP JSON-RPC
18
+ * - WebSocket
19
+ * - Durable Object RPC calls (only works in combination with `@livestore/adapter-cf`)
20
+ *
21
+ * Example:
22
+ *
23
+ * ```ts
24
+ * // In your Cloudflare Worker file
25
+ * import { makeDurableObject } from '@livestore/sync-cf/cf-worker'
26
+ *
27
+ * export class SyncBackendDO extends makeDurableObject({
28
+ * onPush: async (message) => {
29
+ * console.log('onPush', message.batch)
30
+ * },
31
+ * onPull: async (message) => {
32
+ * console.log('onPull', message)
33
+ * },
34
+ * }) {}
35
+ * ```
36
+ *
37
+ * `wrangler.toml`
38
+ * ```toml
39
+ * [[durable_objects.bindings]]
40
+ * name = "SYNC_BACKEND_DO"
41
+ * class_name = "SyncBackendDO"
42
+
43
+ * [[migrations]]
44
+ * tag = "v1"
45
+ * new_sqlite_classes = ["SyncBackendDO"]
46
+ * ```
47
+ */
48
+ export const makeDurableObject = (options) => {
49
+ const enabledTransports = options?.enabledTransports ?? new Set(['http', 'ws', 'do-rpc']);
50
+ const Logging = Logger.consoleWithThread('SyncDo');
51
+ const Observability = options?.otel?.baseUrl
52
+ ? Otlp.layer({
53
+ baseUrl: options.otel.baseUrl,
54
+ tracerExportInterval: 50,
55
+ resource: {
56
+ serviceName: options.otel.serviceName ?? 'sync-cf-do',
57
+ },
58
+ }).pipe(Layer.provide(FetchHttpClient.layer))
59
+ : Layer.empty;
60
+ return class SyncBackendDOBase extends DurableObjectBase {
61
+ __DURABLE_OBJECT_BRAND = 'SyncBackendDOBase';
62
+ ctx;
63
+ env;
64
+ constructor(ctx, env) {
65
+ super(ctx, env);
66
+ this.ctx = ctx;
67
+ this.env = env;
68
+ const WebSocketRpcServerLive = makeRpcServer({ doSelf: this, doOptions: options });
69
+ // This registers the `webSocketMessage` and `webSocketClose` handlers
70
+ if (enabledTransports.has('ws')) {
71
+ setupDurableObjectWebSocketRpc({
72
+ doSelf: this,
73
+ rpcLayer: WebSocketRpcServerLive,
74
+ webSocketMode: 'hibernate',
75
+ // See `pull.ts` for more details how `pull` Effect RPC requests streams are handled
76
+ // in combination with DO hibernation
77
+ onMessage: (request, ws) => {
78
+ if (request._tag === 'Request' && request.tag === 'SyncWsRpc.Pull') {
79
+ // Is Pull request: add requestId to pullRequestIds
80
+ const attachment = ws.deserializeAttachment();
81
+ const { pullRequestIds, ...rest } = Schema.decodeSync(WebSocketAttachmentSchema)(attachment);
82
+ ws.serializeAttachment(Schema.encodeSync(WebSocketAttachmentSchema)({
83
+ ...rest,
84
+ pullRequestIds: [...pullRequestIds, request.id],
85
+ }));
86
+ }
87
+ else if (request._tag === 'Interrupt') {
88
+ // Is Interrupt request: remove requestId from pullRequestIds
89
+ const attachment = ws.deserializeAttachment();
90
+ const { pullRequestIds, ...rest } = Schema.decodeSync(WebSocketAttachmentSchema)(attachment);
91
+ ws.serializeAttachment(Schema.encodeSync(WebSocketAttachmentSchema)({
92
+ ...rest,
93
+ pullRequestIds: pullRequestIds.filter((id) => id !== request.requestId),
94
+ }));
95
+ // TODO also emit `Exit` stream RPC message
96
+ }
97
+ },
98
+ mainLayer: Observability,
99
+ });
100
+ }
101
+ }
102
+ fetch = async (request) => Effect.gen(this, function* () {
103
+ const requestParamsResult = getSyncRequestSearchParams(request);
104
+ if (requestParamsResult._tag === 'None') {
105
+ throw new Error('No search params found in request URL');
106
+ }
107
+ const { storeId, payload, transport } = requestParamsResult.value;
108
+ if (enabledTransports.has(transport) === false) {
109
+ throw new Error(`Transport ${transport} is not enabled (based on \`options.enabledTransports\`)`);
110
+ }
111
+ if (transport === 'http') {
112
+ return yield* this.handleHttp(request);
113
+ }
114
+ if (transport === 'ws') {
115
+ const { 0: client, 1: server } = new WebSocketPair();
116
+ // Since we're using websocket hibernation, we need to remember the storeId for subsequent `webSocketMessage` calls
117
+ server.serializeAttachment(Schema.encodeSync(WebSocketAttachmentSchema)({ storeId, payload, pullRequestIds: [] }));
118
+ // See https://developers.cloudflare.com/durable-objects/examples/websocket-hibernation-server
119
+ this.ctx.acceptWebSocket(server);
120
+ // Ping requests are sent by Effect RPC internally
121
+ this.ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair(JSON.stringify(RpcMessage.constPing), JSON.stringify(RpcMessage.constPong)));
122
+ return new Response(null, {
123
+ status: 101,
124
+ webSocket: client,
125
+ });
126
+ }
127
+ console.error('Invalid path', request.url);
128
+ return new Response('Invalid path', {
129
+ status: 400,
130
+ statusText: 'Bad Request',
131
+ });
132
+ }).pipe(Effect.tapCauseLogPretty, // Also log errors to console before catching them
133
+ Effect.catchAllCause((cause) => Effect.succeed(new Response('Error', { status: 500, statusText: cause.toString() }))), Effect.withSpan('@livestore/sync-cf:durable-object:fetch'), Effect.provide(DoCtx.Default({ doSelf: this, doOptions: options, from: request })), this.runEffectAsPromise);
134
+ /**
135
+ * Handles DO <-> DO RPC calls
136
+ */
137
+ async rpc(payload) {
138
+ if (enabledTransports.has('do-rpc') === false) {
139
+ throw new Error('Do RPC transport is not enabled (based on `options.enabledTransports`)');
140
+ }
141
+ return createDoRpcHandler({ payload, input: { doSelf: this, doOptions: options } }).pipe(Effect.withSpan('@livestore/sync-cf:durable-object:rpc'), this.runEffectAsPromise);
142
+ }
143
+ /**
144
+ * Handles HTTP RPC calls
145
+ *
146
+ * Requires the `enable_request_signal` compatibility flag to properly support `pull` streaming responses
147
+ */
148
+ handleHttp = (request) => createHttpRpcHandler({
149
+ request,
150
+ }).pipe(Effect.withSpan('@livestore/sync-cf:durable-object:handleHttp'));
151
+ runEffectAsPromise = (effect) => effect.pipe(Effect.tapCauseLogPretty, Logger.withMinimumLogLevel(LogLevel.Debug), Effect.provide(Layer.mergeAll(Observability, Logging)), Effect.scoped, Effect.runPromise);
152
+ };
153
+ };
154
+ //# sourceMappingURL=durable-object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-object.js","sourceRoot":"","sources":["../../../src/cf-worker/do/durable-object.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { UnexpectedError } from '@livestore/common';
2
+ import { EventSequenceNumber } from '@livestore/common/schema';
3
+ import type { CfTypes } from '@livestore/common-cf';
4
+ import { Effect } from '@livestore/utils/effect';
5
+ import type { Env, MakeDurableObjectClassOptions, RpcSubscription } from '../shared.ts';
6
+ export interface DoCtxInput {
7
+ doSelf: CfTypes.DurableObject & {
8
+ ctx: CfTypes.DurableObjectState;
9
+ env: Env;
10
+ };
11
+ doOptions: MakeDurableObjectClassOptions | undefined;
12
+ from: CfTypes.Request | {
13
+ storeId: string;
14
+ };
15
+ }
16
+ declare const DoCtx_base: Effect.Service.Class<DoCtx, "DoCtx", {
17
+ readonly effect: (args_0: DoCtxInput) => Effect.Effect.AsEffect<Effect.Effect<{
18
+ storeId: string;
19
+ backendId: string;
20
+ currentHeadRef: {
21
+ current: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
22
+ };
23
+ updateCurrentHead: (currentHead: EventSequenceNumber.GlobalEventSequenceNumber) => void;
24
+ storage: import("./sync-storage.ts").SyncStorage;
25
+ doOptions: MakeDurableObjectClassOptions | undefined;
26
+ env: Env;
27
+ ctx: CfTypes.DurableObjectState;
28
+ rpcSubscriptions: Map<string, RpcSubscription>;
29
+ }, UnexpectedError, never>>;
30
+ }>;
31
+ export declare class DoCtx extends DoCtx_base {
32
+ }
33
+ export {};
34
+ //# sourceMappingURL=layer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layer.d.ts","sourceRoot":"","sources":["../../../src/cf-worker/do/layer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,mBAAmB,EAAS,MAAM,0BAA0B,CAAA;AACrE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEnD,OAAO,EAAE,MAAM,EAAa,MAAM,yBAAyB,CAAA;AAE3D,OAAO,KAAK,EAAE,GAAG,EAAE,6BAA6B,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAMvF,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,OAAO,CAAC,aAAa,GAAG;QAC9B,GAAG,EAAE,OAAO,CAAC,kBAAkB,CAAA;QAC/B,GAAG,EAAE,GAAG,CAAA;KACT,CAAA;IACD,SAAS,EAAE,6BAA6B,GAAG,SAAS,CAAA;IACpD,IAAI,EAAE,OAAO,CAAC,OAAO,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5C;;;;;;;;yCA8C6C,mBAAmB,CAAC,yBAAyB;;;;;;;;AA5C3F,qBAAa,KAAM,SAAQ,UAqFzB;CAAG"}
@@ -0,0 +1,68 @@
1
+ import { UnexpectedError } from '@livestore/common';
2
+ import { EventSequenceNumber, State } from '@livestore/common/schema';
3
+ import { shouldNeverHappen } from '@livestore/utils';
4
+ import { Effect, Predicate } from '@livestore/utils/effect';
5
+ import { nanoid } from '@livestore/utils/nanoid';
6
+ import { contextTable, eventlogTable } from "./sqlite.js";
7
+ import { makeStorage } from "./sync-storage.js";
8
+ const CacheSymbol = Symbol('Cache');
9
+ export class DoCtx extends Effect.Service()('DoCtx', {
10
+ effect: Effect.fn(function* ({ doSelf, doOptions, from }) {
11
+ if (doSelf[CacheSymbol] !== undefined) {
12
+ return doSelf[CacheSymbol];
13
+ }
14
+ const getStoreId = (from) => {
15
+ if (Predicate.hasProperty(from, 'url')) {
16
+ const url = new URL(from.url);
17
+ return (url.searchParams.get('storeId') ?? shouldNeverHappen(`No storeId provided in request URL search params`));
18
+ }
19
+ return from.storeId;
20
+ };
21
+ const storeId = getStoreId(from);
22
+ const storage = makeStorage(doSelf.ctx, doSelf.env, storeId);
23
+ // Initialize database tables
24
+ {
25
+ const colSpec = State.SQLite.makeColumnSpec(eventlogTable.sqliteDef.ast);
26
+ // D1 database is async, so we need to use a promise
27
+ yield* Effect.promise(() => doSelf.env.DB.exec(`CREATE TABLE IF NOT EXISTS "${storage.dbName}" (${colSpec}) strict`));
28
+ }
29
+ {
30
+ const colSpec = State.SQLite.makeColumnSpec(contextTable.sqliteDef.ast);
31
+ doSelf.ctx.storage.sql.exec(`CREATE TABLE IF NOT EXISTS "${contextTable.sqliteDef.name}" (${colSpec}) strict`);
32
+ }
33
+ const storageRow = doSelf.ctx.storage.sql
34
+ .exec(`SELECT * FROM "${contextTable.sqliteDef.name}" WHERE storeId = ?`, storeId)
35
+ .toArray()[0];
36
+ const currentHeadRef = { current: storageRow?.currentHead ?? EventSequenceNumber.ROOT.global };
37
+ // TODO do concistency check with eventlog table to make sure the head is consistent
38
+ // Should be the same backendId for lifetime of the durable object
39
+ const backendId = storageRow?.backendId ?? nanoid();
40
+ const updateCurrentHead = (currentHead) => {
41
+ doSelf.ctx.storage.sql.exec(`INSERT OR REPLACE INTO "${contextTable.sqliteDef.name}" (storeId, currentHead, backendId) VALUES (?, ?, ?)`, storeId, currentHead, backendId);
42
+ currentHeadRef.current = currentHead;
43
+ // I still don't know why we need to re-assign this ref to the `doSelf` object but somehow this seems to be needed 😵‍💫
44
+ // @ts-expect-error
45
+ doSelf[CacheSymbol].currentHeadRef = { current: currentHead };
46
+ };
47
+ const rpcSubscriptions = new Map();
48
+ const storageCache = {
49
+ storeId,
50
+ backendId,
51
+ currentHeadRef,
52
+ updateCurrentHead,
53
+ storage,
54
+ doOptions,
55
+ env: doSelf.env,
56
+ ctx: doSelf.ctx,
57
+ rpcSubscriptions,
58
+ };
59
+ doSelf[CacheSymbol] = storageCache;
60
+ // Set initial current head to root
61
+ if (storageRow === undefined) {
62
+ updateCurrentHead(EventSequenceNumber.ROOT.global);
63
+ }
64
+ return storageCache;
65
+ }, UnexpectedError.mapToUnexpectedError, Effect.withSpan('@livestore/sync-cf:durable-object:makeDoCtx')),
66
+ }) {
67
+ }
68
+ //# sourceMappingURL=layer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layer.js","sourceRoot":"","sources":["../../../src/cf-worker/do/layer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { InvalidPullError } from '@livestore/common';
2
+ import { type Schema, Stream } from '@livestore/utils/effect';
3
+ import { SyncMessage } from '../../common/mod.ts';
4
+ import { DoCtx } from './layer.ts';
5
+ export declare const makeEndingPullStream: (req: SyncMessage.PullRequest, payload: Schema.JsonValue | undefined) => Stream.Stream<SyncMessage.PullResponse, InvalidPullError, DoCtx>;
6
+ //# sourceMappingURL=pull.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../../src/cf-worker/do/pull.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,gBAAgB,EAAgC,MAAM,mBAAmB,CAAA;AAC1G,OAAO,EAAuC,KAAK,MAAM,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAClG,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEjD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAWlC,eAAO,MAAM,oBAAoB,GAC/B,KAAK,WAAW,CAAC,WAAW,EAC5B,SAAS,MAAM,CAAC,SAAS,GAAG,SAAS,KACpC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,gBAAgB,EAAE,KAAK,CA6C/D,CAAA"}
@@ -0,0 +1,39 @@
1
+ import { BackendIdMismatchError, InvalidPullError, SyncBackend, UnexpectedError } from '@livestore/common';
2
+ import { Effect, Option, pipe, ReadonlyArray, Stream } from '@livestore/utils/effect';
3
+ import { SyncMessage } from "../../common/mod.js";
4
+ import { PULL_CHUNK_SIZE } from "../shared.js";
5
+ import { DoCtx } from "./layer.js";
6
+ // Notes on stream handling:
7
+ // We're intentionally closing the stream once we've read all existing events
8
+ //
9
+ // WebSocket:
10
+ // - Further chunks will be emitted manually in `push.ts`
11
+ // - If the client sends a `Interrupt` RPC message, it will be handled in the `durable-object.ts` constructor
12
+ // DO RPC:
13
+ // - Further chunks will be emitted manually in `push.ts`
14
+ // - If the client sends a `Interrupt` RPC message, TODO
15
+ export const makeEndingPullStream = (req, payload) => Effect.gen(function* () {
16
+ const { doOptions, backendId, storeId, storage } = yield* DoCtx;
17
+ if (doOptions?.onPull) {
18
+ yield* Effect.tryAll(() => doOptions.onPull(req, { storeId, payload })).pipe(UnexpectedError.mapToUnexpectedError);
19
+ }
20
+ if (req.cursor._tag === 'Some' && req.cursor.value.backendId !== backendId) {
21
+ return yield* new BackendIdMismatchError({ expected: backendId, received: req.cursor.value.backendId });
22
+ }
23
+ // TODO use streaming for db results
24
+ const remainingEvents = yield* storage.getEvents(Option.getOrUndefined(req.cursor)?.eventSequenceNumber);
25
+ const batches = pipe(remainingEvents, ReadonlyArray.chunksOf(PULL_CHUNK_SIZE), ReadonlyArray.map((batch, i) => {
26
+ const remaining = Math.max(0, remainingEvents.length - (i + 1) * PULL_CHUNK_SIZE);
27
+ return SyncMessage.PullResponse.make({
28
+ batch,
29
+ pageInfo: remaining > 0 ? SyncBackend.pageInfoMoreKnown(remaining) : SyncBackend.pageInfoNoMore,
30
+ backendId,
31
+ });
32
+ }));
33
+ return Stream.fromIterable(batches).pipe(Stream.tap(Effect.fn(function* (res) {
34
+ if (doOptions?.onPullRes) {
35
+ yield* Effect.tryAll(() => doOptions.onPullRes(res)).pipe(UnexpectedError.mapToUnexpectedError);
36
+ }
37
+ })), Stream.emitIfEmpty(SyncMessage.emptyPullResponse(backendId)));
38
+ }).pipe(Stream.unwrap, Stream.mapError((cause) => InvalidPullError.make({ cause })), Stream.withSpan('cloudflare-provider:pull'));
39
+ //# sourceMappingURL=pull.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull.js","sourceRoot":"","sources":["../../../src/cf-worker/do/pull.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { InvalidPushError } from '@livestore/common';
2
+ import { type CfTypes } from '@livestore/common-cf';
3
+ import { Effect, Schema } from '@livestore/utils/effect';
4
+ import { SyncMessage } from '../../common/mod.ts';
5
+ import { type Env, type MakeDurableObjectClassOptions, type StoreId } from '../shared.ts';
6
+ import { DoCtx } from './layer.ts';
7
+ export declare const makePush: ({ payload, options, storeId, ctx, env, }: {
8
+ payload: Schema.JsonValue | undefined;
9
+ options: MakeDurableObjectClassOptions | undefined;
10
+ storeId: StoreId;
11
+ ctx: CfTypes.DurableObjectState;
12
+ env: Env;
13
+ }) => (pushRequest: Omit<SyncMessage.PushRequest, "_tag">) => Effect.Effect<{}, InvalidPushError, DoCtx>;
14
+ //# sourceMappingURL=push.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../src/cf-worker/do/push.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,gBAAgB,EAIjB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,KAAK,OAAO,EAAsB,MAAM,sBAAsB,CAAA;AACvE,OAAO,EAAE,MAAM,EAA2B,MAAM,EAAE,MAAM,yBAAyB,CAAA;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,6BAA6B,EAAE,KAAK,OAAO,EAA6B,MAAM,cAAc,CAAA;AACpH,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAElC,eAAO,MAAM,QAAQ,GAClB,0CAME;IACD,OAAO,EAAE,MAAM,CAAC,SAAS,GAAG,SAAS,CAAA;IACrC,OAAO,EAAE,6BAA6B,GAAG,SAAS,CAAA;IAClD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,OAAO,CAAC,kBAAkB,CAAA;IAC/B,GAAG,EAAE,GAAG,CAAA;CACT,MACA,aAAa,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,CAAC,+CAuHhD,CAAA"}
@@ -0,0 +1,99 @@
1
+ import { BackendIdMismatchError, InvalidPushError, ServerAheadError, SyncBackend, UnexpectedError, } from '@livestore/common';
2
+ import { emitStreamResponse } from '@livestore/common-cf';
3
+ import { Effect, Option, Schema } from '@livestore/utils/effect';
4
+ import { SyncMessage } from "../../common/mod.js";
5
+ import { WebSocketAttachmentSchema } from "../shared.js";
6
+ import { DoCtx } from "./layer.js";
7
+ export const makePush = ({ payload, options, storeId, ctx, env, }) => (pushRequest) => Effect.gen(function* () {
8
+ // yield* Effect.log(`Pushing ${decodedMessage.batch.length} events`, decodedMessage.batch)
9
+ const { backendId, storage, currentHeadRef, updateCurrentHead, rpcSubscriptions } = yield* DoCtx;
10
+ if (pushRequest.batch.length === 0) {
11
+ return SyncMessage.PushAck.make({});
12
+ }
13
+ if (options?.onPush) {
14
+ yield* Effect.tryAll(() => options.onPush(pushRequest, { storeId, payload })).pipe(UnexpectedError.mapToUnexpectedError);
15
+ }
16
+ if (pushRequest.backendId._tag === 'Some' && pushRequest.backendId.value !== backendId) {
17
+ return yield* new BackendIdMismatchError({ expected: backendId, received: pushRequest.backendId.value });
18
+ }
19
+ // This part of the code needs to run sequentially to avoid race conditions
20
+ const { createdAt } = yield* Effect.gen(function* () {
21
+ const currentHead = currentHeadRef.current;
22
+ // TODO handle clientId unique conflict
23
+ // Validate the batch
24
+ const firstEventParent = pushRequest.batch[0].parentSeqNum;
25
+ if (firstEventParent !== currentHead) {
26
+ return yield* new ServerAheadError({ minimumExpectedNum: currentHead, providedNum: firstEventParent });
27
+ }
28
+ const createdAt = new Date().toISOString();
29
+ // TODO possibly model this as a queue in order to speed up subsequent pushes
30
+ yield* storage.appendEvents(pushRequest.batch, createdAt);
31
+ updateCurrentHead(pushRequest.batch.at(-1).seqNum);
32
+ return { createdAt };
33
+ }).pipe(blockConcurrencyWhile(ctx));
34
+ // Run in background but already return the push ack to the client
35
+ yield* Effect.gen(function* () {
36
+ const connectedClients = ctx.getWebSockets();
37
+ // Dual broadcasting: WebSocket + RPC clients
38
+ const pullRes = SyncMessage.PullResponse.make({
39
+ batch: pushRequest.batch.map((eventEncoded) => ({
40
+ eventEncoded,
41
+ metadata: Option.some(SyncMessage.SyncMetadata.make({ createdAt })),
42
+ })),
43
+ pageInfo: SyncBackend.pageInfoNoMore,
44
+ backendId,
45
+ });
46
+ const pullResEnc = Schema.encodeSync(SyncMessage.PullResponse)(pullRes);
47
+ // Broadcast to WebSocket clients
48
+ if (connectedClients.length > 0) {
49
+ // Only calling once for now.
50
+ if (options?.onPullRes) {
51
+ yield* Effect.tryAll(() => options.onPullRes(pullRes)).pipe(UnexpectedError.mapToUnexpectedError);
52
+ }
53
+ // NOTE we're also sending the pullRes to the pushing ws client as a confirmation
54
+ for (const conn of connectedClients) {
55
+ // conn.send(pullResEnc)
56
+ const attachment = Schema.decodeSync(WebSocketAttachmentSchema)(conn.deserializeAttachment());
57
+ // We're doing something a bit "advanced" here as we're directly emitting Effect RPC-compatible
58
+ // response messsages on the Effect RPC-managed websocket connection to the WS client.
59
+ // For this we need to get the RPC `requestId` from the WebSocket attachment.
60
+ for (const requestId of attachment.pullRequestIds) {
61
+ const res = {
62
+ _tag: 'Chunk',
63
+ requestId,
64
+ values: [pullResEnc],
65
+ };
66
+ conn.send(JSON.stringify(res));
67
+ }
68
+ }
69
+ yield* Effect.logDebug(`Broadcasted to ${connectedClients.length} WebSocket clients`);
70
+ }
71
+ // RPC broadcasting would require reconstructing client stubs from clientIds
72
+ if (rpcSubscriptions.size > 0) {
73
+ yield* Effect.forEach(rpcSubscriptions.values(), (subscription) => emitStreamResponse({
74
+ callerContext: subscription.callerContext,
75
+ env,
76
+ requestId: subscription.requestId,
77
+ values: [pullResEnc],
78
+ }).pipe(Effect.tapCauseLogPretty, Effect.exit), { concurrency: 'unbounded' });
79
+ yield* Effect.logDebug(`Broadcasted to ${rpcSubscriptions.size} RPC clients`);
80
+ }
81
+ }).pipe(Effect.tapCauseLogPretty, Effect.withSpan('push-rpc-broadcast'), Effect.uninterruptible, // We need to make sure Effect RPC doesn't interrupt this fiber
82
+ Effect.fork);
83
+ // We need to yield here to make sure the fork above is kicked off before we let Effect RPC finish the request
84
+ yield* Effect.yieldNow();
85
+ return SyncMessage.PushAck.make({});
86
+ }).pipe(Effect.tap(Effect.fn(function* (message) {
87
+ if (options?.onPushRes) {
88
+ yield* Effect.tryAll(() => options.onPushRes(message)).pipe(UnexpectedError.mapToUnexpectedError);
89
+ }
90
+ })), Effect.mapError((cause) => InvalidPushError.make({ cause })));
91
+ /**
92
+ * @see https://developers.cloudflare.com/durable-objects/api/state/#blockconcurrencywhile
93
+ */
94
+ const blockConcurrencyWhile = (ctx) => (eff) => Effect.gen(function* () {
95
+ const runtime = yield* Effect.runtime();
96
+ const exit = yield* Effect.promise(() => ctx.blockConcurrencyWhile(() => eff.pipe(Effect.provide(runtime), Effect.runPromiseExit)));
97
+ return yield* exit;
98
+ });
99
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../../src/cf-worker/do/push.ts"],"names":[],"mappings":"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"}