@livestore/sync-cf 0.4.0-dev.7 → 0.4.0-dev.9

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 (62) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/cf-worker/do/durable-object.d.ts.map +1 -1
  3. package/dist/cf-worker/do/durable-object.js +5 -9
  4. package/dist/cf-worker/do/durable-object.js.map +1 -1
  5. package/dist/cf-worker/do/layer.d.ts +1 -1
  6. package/dist/cf-worker/do/pull.d.ts +1 -1
  7. package/dist/cf-worker/do/pull.d.ts.map +1 -1
  8. package/dist/cf-worker/do/pull.js +21 -13
  9. package/dist/cf-worker/do/pull.js.map +1 -1
  10. package/dist/cf-worker/do/push.d.ts.map +1 -1
  11. package/dist/cf-worker/do/push.js +66 -35
  12. package/dist/cf-worker/do/push.js.map +1 -1
  13. package/dist/cf-worker/do/sync-storage.d.ts +8 -5
  14. package/dist/cf-worker/do/sync-storage.d.ts.map +1 -1
  15. package/dist/cf-worker/do/sync-storage.js +64 -19
  16. package/dist/cf-worker/do/sync-storage.js.map +1 -1
  17. package/dist/cf-worker/do/transport/do-rpc-server.d.ts +2 -1
  18. package/dist/cf-worker/do/transport/do-rpc-server.d.ts.map +1 -1
  19. package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -1
  20. package/dist/cf-worker/do/transport/http-rpc-server.d.ts +1 -1
  21. package/dist/cf-worker/do/ws-chunking.d.ts +22 -0
  22. package/dist/cf-worker/do/ws-chunking.d.ts.map +1 -0
  23. package/dist/cf-worker/do/ws-chunking.js +49 -0
  24. package/dist/cf-worker/do/ws-chunking.js.map +1 -0
  25. package/dist/cf-worker/shared.d.ts +19 -13
  26. package/dist/cf-worker/shared.d.ts.map +1 -1
  27. package/dist/cf-worker/shared.js +15 -4
  28. package/dist/cf-worker/shared.js.map +1 -1
  29. package/dist/cf-worker/worker.d.ts +30 -45
  30. package/dist/cf-worker/worker.d.ts.map +1 -1
  31. package/dist/cf-worker/worker.js +30 -25
  32. package/dist/cf-worker/worker.js.map +1 -1
  33. package/dist/common/sync-message-types.d.ts +5 -5
  34. package/package.json +5 -5
  35. package/src/cf-worker/do/durable-object.ts +6 -10
  36. package/src/cf-worker/do/pull.ts +30 -17
  37. package/src/cf-worker/do/push.ts +85 -38
  38. package/src/cf-worker/do/sync-storage.ts +106 -27
  39. package/src/cf-worker/do/transport/do-rpc-server.ts +4 -2
  40. package/src/cf-worker/do/ws-chunking.ts +76 -0
  41. package/src/cf-worker/shared.ts +19 -6
  42. package/src/cf-worker/worker.ts +46 -69
  43. package/dist/cf-worker/cf-types.d.ts +0 -2
  44. package/dist/cf-worker/cf-types.d.ts.map +0 -1
  45. package/dist/cf-worker/cf-types.js +0 -2
  46. package/dist/cf-worker/cf-types.js.map +0 -1
  47. package/dist/cf-worker/durable-object.d.ts +0 -189
  48. package/dist/cf-worker/durable-object.d.ts.map +0 -1
  49. package/dist/cf-worker/durable-object.js +0 -317
  50. package/dist/cf-worker/durable-object.js.map +0 -1
  51. package/dist/common/ws-message-types.d.ts +0 -270
  52. package/dist/common/ws-message-types.d.ts.map +0 -1
  53. package/dist/common/ws-message-types.js +0 -57
  54. package/dist/common/ws-message-types.js.map +0 -1
  55. package/dist/sync-impl/mod.d.ts +0 -2
  56. package/dist/sync-impl/mod.d.ts.map +0 -1
  57. package/dist/sync-impl/mod.js +0 -2
  58. package/dist/sync-impl/mod.js.map +0 -1
  59. package/dist/sync-impl/ws-impl.d.ts +0 -7
  60. package/dist/sync-impl/ws-impl.d.ts.map +0 -1
  61. package/dist/sync-impl/ws-impl.js +0 -175
  62. package/dist/sync-impl/ws-impl.js.map +0 -1
@@ -0,0 +1,49 @@
1
+ import { Chunk } from '@livestore/utils/effect';
2
+ const textEncoder = new TextEncoder();
3
+ /**
4
+ * Derives a function that splits an input chunk into sub-chunks confined by
5
+ * both item count and encoded byte size limits. Designed for transports with
6
+ * strict frame caps (e.g. Cloudflare hibernated WebSockets).
7
+ */
8
+ export const splitChunkBySize = (options) => (chunk) => {
9
+ const maxItems = Math.max(1, options.maxItems);
10
+ const maxBytes = Math.max(1, options.maxBytes);
11
+ const encode = options.encode;
12
+ const measure = (items) => {
13
+ const encoded = encode(items);
14
+ return textEncoder.encode(JSON.stringify(encoded)).byteLength;
15
+ };
16
+ const items = Chunk.toReadonlyArray(chunk);
17
+ if (items.length === 0) {
18
+ return Chunk.fromIterable([]);
19
+ }
20
+ const result = [];
21
+ let current = [];
22
+ const flushCurrent = () => {
23
+ if (current.length > 0) {
24
+ result.push(Chunk.fromIterable(current));
25
+ current = [];
26
+ }
27
+ };
28
+ for (const item of items) {
29
+ current.push(item);
30
+ const exceedsLimit = current.length > maxItems || measure(current) > maxBytes;
31
+ if (exceedsLimit) {
32
+ // remove the item we just added and emit the previous chunk if it exists
33
+ const last = current.pop();
34
+ flushCurrent();
35
+ if (last !== undefined) {
36
+ current = [last];
37
+ const singleItemTooLarge = measure(current) > maxBytes;
38
+ if (singleItemTooLarge || current.length > maxItems) {
39
+ // Emit the oversized item on its own; downstream can decide how to handle it.
40
+ result.push(Chunk.of(last));
41
+ current = [];
42
+ }
43
+ }
44
+ }
45
+ }
46
+ flushCurrent();
47
+ return Chunk.fromIterable(result);
48
+ };
49
+ //# sourceMappingURL=ws-chunking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-chunking.js","sourceRoot":"","sources":["../../../src/cf-worker/do/ws-chunking.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAE/C,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAA;AAiBrC;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,CAAI,OAA2B,EAAE,EAAE,CACnC,CAAC,KAAqB,EAA+B,EAAE;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IAE7B,MAAM,OAAO,GAAG,CAAC,KAAuB,EAAE,EAAE;QAC1C,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;QAC7B,OAAO,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAA;IAC/D,CAAC,CAAA;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAA;IAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC,YAAY,CAAiB,EAAE,CAAC,CAAA;IAC/C,CAAC;IAED,MAAM,MAAM,GAA0B,EAAE,CAAA;IACxC,IAAI,OAAO,GAAa,EAAE,CAAA;IAE1B,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;YACxC,OAAO,GAAG,EAAE,CAAA;QACd,CAAC;IACH,CAAC,CAAA;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClB,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,GAAG,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAA;QAE7E,IAAI,YAAY,EAAE,CAAC;YACjB,yEAAyE;YACzE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAG,CAAA;YAC3B,YAAY,EAAE,CAAA;YAEd,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,OAAO,GAAG,CAAC,IAAI,CAAC,CAAA;gBAChB,MAAM,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAA;gBACtD,IAAI,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;oBACpD,8EAA8E;oBAC9E,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;oBAC3B,OAAO,GAAG,EAAE,CAAA;gBACd,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,YAAY,EAAE,CAAA;IAEd,OAAO,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;AACnC,CAAC,CAAA"}
@@ -1,7 +1,8 @@
1
1
  import type { InvalidPullError, InvalidPushError } from '@livestore/common';
2
2
  import type { CfTypes } from '@livestore/common-cf';
3
- import { Effect, type Option, Schema } from '@livestore/utils/effect';
4
- import { SearchParamsSchema, SyncMessage } from '../common/mod.ts';
3
+ import { Effect, Schema } from '@livestore/utils/effect';
4
+ import type { SearchParams } from '../common/mod.ts';
5
+ import { SyncMessage } from '../common/mod.ts';
5
6
  export interface Env {
6
7
  /** Eventlog database */
7
8
  DB: CfTypes.D1Database;
@@ -40,14 +41,8 @@ export type DurableObjectId = string;
40
41
  * Changing this version number will lead to a "soft reset".
41
42
  */
42
43
  export declare const PERSISTENCE_FORMAT_VERSION = 7;
43
- export declare const DEFAULT_SYNC_DURABLE_OBJECT_NAME = "SYNC_BACKEND_DO";
44
44
  export declare const encodeOutgoingMessage: (a: {
45
- readonly backendId: string;
46
45
  readonly batch: readonly {
47
- readonly metadata: Option.Option<{
48
- readonly createdAt: string;
49
- readonly _tag: "SyncMessage.SyncMetadata";
50
- }>;
51
46
  readonly eventEncoded: {
52
47
  readonly name: string;
53
48
  readonly args: any;
@@ -56,6 +51,10 @@ export declare const encodeOutgoingMessage: (a: {
56
51
  readonly clientId: string;
57
52
  readonly sessionId: string;
58
53
  };
54
+ readonly metadata: import("effect/Option").Option<{
55
+ readonly _tag: "SyncMessage.SyncMetadata";
56
+ readonly createdAt: string;
57
+ }>;
59
58
  }[];
60
59
  readonly pageInfo: {
61
60
  readonly _tag: "MoreUnknown";
@@ -65,23 +64,23 @@ export declare const encodeOutgoingMessage: (a: {
65
64
  } | {
66
65
  readonly _tag: "NoMore";
67
66
  };
67
+ readonly backendId: string;
68
68
  } | {} | {
69
69
  readonly _tag: "SyncMessage.Pong";
70
70
  } | {
71
71
  readonly _tag: "SyncMessage.AdminResetRoomResponse";
72
72
  } | {
73
+ readonly _tag: "SyncMessage.AdminInfoResponse";
73
74
  readonly info: {
74
75
  readonly durableObjectId: string;
75
76
  };
76
- readonly _tag: "SyncMessage.AdminInfoResponse";
77
77
  }, overrideOptions?: import("effect/SchemaAST").ParseOptions) => string;
78
78
  export declare const encodeIncomingMessage: (a: {
79
- readonly cursor: Option.Option<{
79
+ readonly cursor: import("effect/Option").Option<{
80
80
  readonly backendId: string;
81
81
  readonly eventSequenceNumber: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
82
82
  }>;
83
83
  } | {
84
- readonly backendId: Option.Option<string>;
85
84
  readonly batch: readonly {
86
85
  readonly name: string;
87
86
  readonly args: any;
@@ -90,6 +89,7 @@ export declare const encodeIncomingMessage: (a: {
90
89
  readonly clientId: string;
91
90
  readonly sessionId: string;
92
91
  }[];
92
+ readonly backendId: import("effect/Option").Option<string>;
93
93
  } | {
94
94
  readonly _tag: "SyncMessage.Ping";
95
95
  } | {
@@ -99,8 +99,14 @@ export declare const encodeIncomingMessage: (a: {
99
99
  readonly _tag: "SyncMessage.AdminInfoRequest";
100
100
  readonly adminSecret: string;
101
101
  }, overrideOptions?: import("effect/SchemaAST").ParseOptions) => string;
102
- export declare const getSyncRequestSearchParams: (request: CfTypes.Request) => Option.Option<typeof SearchParamsSchema.Type>;
103
- export declare const PULL_CHUNK_SIZE = 100;
102
+ /**
103
+ * Extracts the LiveStore sync search parameters from a request. Returns
104
+ * `undefined` when the request does not carry valid sync metadata so callers
105
+ * can fall back to custom routing.
106
+ */
107
+ export declare const matchSyncRequest: (request: CfTypes.Request) => SearchParams | undefined;
108
+ export declare const MAX_PULL_EVENTS_PER_MESSAGE = 100;
109
+ export declare const MAX_WS_MESSAGE_BYTES = 900000;
104
110
  export type RpcSubscription = {
105
111
  storeId: StoreId;
106
112
  payload?: Schema.JsonValue;
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/cf-worker/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,EAAa,MAAM,yBAAyB,CAAA;AAChF,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAElE,MAAM,WAAW,GAAG;IAClB,wBAAwB;IACxB,EAAE,EAAE,OAAO,CAAC,UAAU,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,WAAW,CAAC,WAAW,EAChC,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;KAAE,KACtD,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACvC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,gBAAgB,KAAK,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACnG,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,WAAW,CAAC,WAAW,EAChC,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;KAAE,KACtD,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACvC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,YAAY,GAAG,gBAAgB,KAAK,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IAGxG;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAA;IAEjD,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;CACF,CAAA;AAED,MAAM,MAAM,OAAO,GAAG,MAAM,CAAA;AAC5B,MAAM,MAAM,eAAe,GAAG,MAAM,CAAA;AAEpC;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAE3C,eAAO,MAAM,gCAAgC,oBAAoB,CAAA;AAEjE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uEAA0E,CAAA;AAC5G,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;uEAA0E,CAAA;AAE5G,eAAO,MAAM,0BAA0B,GAAI,SAAS,OAAO,CAAC,OAAO,KAAG,MAAM,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,IAAI,CAMjH,CAAA;AAED,eAAO,MAAM,eAAe,MAAM,CAAA;AAGlC,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE;QACb,WAAW,EAAE,MAAM,CAAA;QACnB,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF,CAAA;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,sBAAsB,EAAE,KAAK,CAAA;IAC7B,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;CACvE;AAED,eAAO,MAAM,yBAAyB;;;;GAQrC,CAAA"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/cf-worker/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAa,MAAM,yBAAyB,CAAA;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAsB,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAElE,MAAM,WAAW,GAAG;IAClB,wBAAwB;IACxB,EAAE,EAAE,OAAO,CAAC,UAAU,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,WAAW,CAAC,WAAW,EAChC,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;KAAE,KACtD,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACvC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,gBAAgB,KAAK,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACnG,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,WAAW,CAAC,WAAW,EAChC,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;KAAE,KACtD,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACvC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,YAAY,GAAG,gBAAgB,KAAK,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IAGxG;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAA;IAEjD,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;CACF,CAAA;AAED,MAAM,MAAM,OAAO,GAAG,MAAM,CAAA;AAC5B,MAAM,MAAM,eAAe,GAAG,MAAM,CAAA;AAEpC;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAE3C,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uEAA0E,CAAA;AAC5G,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;uEAA0E,CAAA;AAE5G;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,OAAO,CAAC,OAAO,KAAG,YAAY,GAAG,SAU1E,CAAA;AAED,eAAO,MAAM,2BAA2B,MAAM,CAAA;AAK9C,eAAO,MAAM,oBAAoB,SAAU,CAAA;AAG3C,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE;QACb,WAAW,EAAE,MAAM,CAAA;QACnB,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF,CAAA;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,sBAAsB,EAAE,KAAK,CAAA;IAC7B,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;CACvE;AAED,eAAO,MAAM,yBAAyB;;;;GAQrC,CAAA"}
@@ -6,16 +6,27 @@ import { SearchParamsSchema, SyncMessage } from "../common/mod.js";
6
6
  * Changing this version number will lead to a "soft reset".
7
7
  */
8
8
  export const PERSISTENCE_FORMAT_VERSION = 7;
9
- export const DEFAULT_SYNC_DURABLE_OBJECT_NAME = 'SYNC_BACKEND_DO';
10
9
  export const encodeOutgoingMessage = Schema.encodeSync(Schema.parseJson(SyncMessage.BackendToClientMessage));
11
10
  export const encodeIncomingMessage = Schema.encodeSync(Schema.parseJson(SyncMessage.ClientToBackendMessage));
12
- export const getSyncRequestSearchParams = (request) => {
11
+ /**
12
+ * Extracts the LiveStore sync search parameters from a request. Returns
13
+ * `undefined` when the request does not carry valid sync metadata so callers
14
+ * can fall back to custom routing.
15
+ */
16
+ export const matchSyncRequest = (request) => {
13
17
  const url = new URL(request.url);
14
18
  const urlParams = UrlParams.fromInput(url.searchParams);
15
19
  const paramsResult = UrlParams.schemaStruct(SearchParamsSchema)(urlParams).pipe(Effect.option, Effect.runSync);
16
- return paramsResult;
20
+ if (paramsResult._tag === 'None') {
21
+ return undefined;
22
+ }
23
+ return paramsResult.value;
17
24
  };
18
- export const PULL_CHUNK_SIZE = 100;
25
+ export const MAX_PULL_EVENTS_PER_MESSAGE = 100;
26
+ // Cloudflare hibernated WebSocket frames begin failing just below 1MB. Keep our
27
+ // payloads comfortably beneath that ceiling so we don't rely on implementation
28
+ // quirks of local dev servers.
29
+ export const MAX_WS_MESSAGE_BYTES = 900_000;
19
30
  export const WebSocketAttachmentSchema = Schema.parseJson(Schema.Struct({
20
31
  // Same across all websocket connections
21
32
  storeId: Schema.String,
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/cf-worker/shared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAe,MAAM,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAChF,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAwClE;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAA;AAE3C,MAAM,CAAC,MAAM,gCAAgC,GAAG,iBAAiB,CAAA;AAEjE,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAA;AAC5G,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAA;AAE5G,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,OAAwB,EAAiD,EAAE;IACpH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAChC,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IACvD,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;IAE9G,OAAO,YAAY,CAAA;AACrB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAuBlC,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC,SAAS,CACvD,MAAM,CAAC,MAAM,CAAC;IACZ,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;IAC1C,cAAc,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;CAC5C,CAAC,CACH,CAAA"}
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/cf-worker/shared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAEnE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAwClE;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAA;AAE3C,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAA;AAC5G,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAA;AAE5G;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAwB,EAA4B,EAAE;IACrF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAChC,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IACvD,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;IAE9G,IAAI,YAAY,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACjC,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,OAAO,YAAY,CAAC,KAAK,CAAA;AAC3B,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAA;AAE9C,gFAAgF;AAChF,+EAA+E;AAC/E,+BAA+B;AAC/B,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAA;AAuB3C,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC,SAAS,CACvD,MAAM,CAAC,MAAM,CAAC;IACZ,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;IAC1C,cAAc,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;CAC5C,CAAC,CACH,CAAA"}
@@ -1,34 +1,19 @@
1
+ import type { HelperTypes } from '@livestore/common-cf';
1
2
  import type { Schema } from '@livestore/utils/effect';
2
3
  import type { CfTypes, SearchParams } from '../common/mod.ts';
3
4
  import { type Env } from './shared.ts';
4
- export declare namespace HelperTypes {
5
- type AnyDON = CfTypes.DurableObjectNamespace<undefined>;
6
- type DOKeys<T> = {
7
- [K in keyof T]-?: T[K] extends AnyDON ? K : never;
8
- }[keyof T];
9
- type NonBuiltins<T> = Omit<T, keyof Env>;
10
- /**
11
- * Helper type to extract DurableObject keys from Env to give consumer type safety.
12
- *
13
- * @example
14
- * ```ts
15
- * type PlatformEnv = {
16
- * DB: D1Database
17
- * ADMIN_TOKEN: string
18
- * SYNC_BACKEND_DO: DurableObjectNamespace<SyncBackendDO>
19
- * }
20
- * export default makeWorker<PlatformEnv>({
21
- * durableObject: { name: "SYNC_BACKEND_DO" },
22
- * // ^ (property) name?: "SYNC_BACKEND_DO" | undefined
23
- * });
24
- */
25
- export type ExtractDurableObjectKeys<TEnv = Env> = DOKeys<NonBuiltins<TEnv>> extends never ? string : DOKeys<NonBuiltins<TEnv>>;
26
- export {};
27
- }
28
5
  export type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = {
29
6
  fetch: <CFHostMetada = unknown>(request: CfTypes.Request<CFHostMetada>, env: TEnv, ctx: CfTypes.ExecutionContext) => Promise<CfTypes.Response>;
30
7
  };
8
+ /**
9
+ * Options accepted by {@link makeWorker}. The Durable Object binding has to be
10
+ * supplied explicitly so we never fall back to deprecated defaults when Cloudflare config changes.
11
+ */
31
12
  export type MakeWorkerOptions<TEnv extends Env = Env> = {
13
+ /**
14
+ * Binding name of the sync Durable Object declared in wrangler config.
15
+ */
16
+ syncBackendBinding: HelperTypes.ExtractDurableObjectKeys<TEnv>;
32
17
  /**
33
18
  * Validates the payload during WebSocket connection establishment.
34
19
  * Note: This runs only at connection time, not for individual push events.
@@ -39,16 +24,15 @@ export type MakeWorkerOptions<TEnv extends Env = Env> = {
39
24
  }) => void | Promise<void>;
40
25
  /** @default false */
41
26
  enableCORS?: boolean;
42
- durableObject?: {
43
- /**
44
- * Needs to match the binding name from the wrangler config
45
- *
46
- * @default 'SYNC_BACKEND_DO'
47
- */
48
- name?: HelperTypes.ExtractDurableObjectKeys<TEnv>;
49
- };
50
27
  };
51
- export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>(options?: MakeWorkerOptions<TEnv>) => CFWorker<TEnv, TDurableObjectRpc>;
28
+ /**
29
+ * Produces a Cloudflare Worker `fetch` handler that delegates sync traffic to the
30
+ * Durable Object identified by `syncBackendBinding`.
31
+ *
32
+ * For more complex setups prefer implementing a custom `fetch` and call {@link handleSyncRequest}
33
+ * from the branch that handles LiveStore sync requests.
34
+ */
35
+ export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>(options: MakeWorkerOptions<TEnv>) => CFWorker<TEnv, TDurableObjectRpc>;
52
36
  /**
53
37
  * Handles `/sync` endpoint.
54
38
  *
@@ -63,16 +47,18 @@ export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc exte
63
47
  *
64
48
  * export default {
65
49
  * fetch: async (request, env, ctx) => {
66
- * const requestParamsResult = getSyncRequestSearchParams(request)
50
+ * const searchParams = matchSyncRequest(request)
67
51
  *
68
52
  * // Is LiveStore sync request
69
- * if (requestParamsResult._tag === 'Some') {
53
+ * if (searchParams !== undefined) {
70
54
  * return handleSyncRequest({
71
55
  * request,
72
- * searchParams: requestParamsResult.value,
56
+ * searchParams,
73
57
  * env,
74
58
  * ctx,
75
- * options: { headers: {}, validatePayload }
59
+ * syncBackendBinding: 'SYNC_BACKEND_DO',
60
+ * headers: {},
61
+ * validatePayload,
76
62
  * })
77
63
  * }
78
64
  *
@@ -83,18 +69,17 @@ export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc exte
83
69
  *
84
70
  * @throws {UnexpectedError} If the payload is invalid
85
71
  */
86
- export declare const handleSyncRequest: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined, CFHostMetada = unknown>({ request, searchParams, env, options, }: {
72
+ export declare const handleSyncRequest: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined, CFHostMetada = unknown>({ request, searchParams: { storeId, payload, transport }, env, syncBackendBinding, headers, validatePayload, }: {
87
73
  request: CfTypes.Request<CFHostMetada>;
88
74
  searchParams: SearchParams;
89
75
  env: TEnv;
90
76
  /** Only there for type-level reasons */
91
77
  ctx: CfTypes.ExecutionContext;
92
- options?: {
93
- headers?: CfTypes.HeadersInit;
94
- durableObject?: MakeWorkerOptions<TEnv>["durableObject"];
95
- validatePayload?: (payload: Schema.JsonValue | undefined, context: {
96
- storeId: string;
97
- }) => void | Promise<void>;
98
- };
78
+ /** Binding name of the sync backend Durable Object */
79
+ syncBackendBinding: MakeWorkerOptions<TEnv>["syncBackendBinding"];
80
+ headers?: CfTypes.HeadersInit | undefined;
81
+ validatePayload?: (payload: Schema.JsonValue | undefined, context: {
82
+ storeId: string;
83
+ }) => void | Promise<void>;
99
84
  }) => Promise<CfTypes.Response>;
100
85
  //# sourceMappingURL=worker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE7D,OAAO,EAAE,KAAK,GAAG,EAAoB,MAAM,aAAa,CAAA;AAMxD,MAAM,MAAM,QAAQ,CAAC,IAAI,SAAS,GAAG,GAAG,GAAG,EAAE,EAAE,SAAS,OAAO,CAAC,GAAG,CAAC,oBAAoB,GAAG,SAAS,GAAG,SAAS,IAAI;IAClH,KAAK,EAAE,CAAC,YAAY,GAAG,OAAO,EAC5B,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,EACtC,GAAG,EAAE,IAAI,EACT,GAAG,EAAE,OAAO,CAAC,gBAAgB,KAC1B,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC/B,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,IAAI,SAAS,GAAG,GAAG,GAAG,IAAI;IACtD;;OAEG;IACH,kBAAkB,EAAE,WAAW,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAA;IAC9D;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,GAAG,SAAS,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/G,qBAAqB;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GACrB,IAAI,SAAS,GAAG,GAAG,GAAG,EACtB,iBAAiB,SAAS,OAAO,CAAC,GAAG,CAAC,oBAAoB,GAAG,SAAS,GAAG,SAAS,EAElF,SAAS,iBAAiB,CAAC,IAAI,CAAC,KAC/B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAuDlC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,iBAAiB,GAC5B,IAAI,SAAS,GAAG,GAAG,GAAG,EACtB,iBAAiB,SAAS,OAAO,CAAC,GAAG,CAAC,oBAAoB,GAAG,SAAS,GAAG,SAAS,EAClF,YAAY,GAAG,OAAO,EACtB,gHAOC;IACD,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;IACtC,YAAY,EAAE,YAAY,CAAA;IAC1B,GAAG,EAAE,IAAI,CAAA;IACT,wCAAwC;IACxC,GAAG,EAAE,OAAO,CAAC,gBAAgB,CAAA;IAC7B,sDAAsD;IACtD,kBAAkB,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,CAAA;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,GAAG,SAAS,CAAA;IACzC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,GAAG,SAAS,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChH,KAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAyC0B,CAAA"}
@@ -1,7 +1,14 @@
1
1
  import { UnexpectedError } from '@livestore/common';
2
2
  import { Effect } from '@livestore/utils/effect';
3
- import { DEFAULT_SYNC_DURABLE_OBJECT_NAME, getSyncRequestSearchParams } from "./shared.js";
4
- export const makeWorker = (options = {}) => {
3
+ import { matchSyncRequest } from "./shared.js";
4
+ /**
5
+ * Produces a Cloudflare Worker `fetch` handler that delegates sync traffic to the
6
+ * Durable Object identified by `syncBackendBinding`.
7
+ *
8
+ * For more complex setups prefer implementing a custom `fetch` and call {@link handleSyncRequest}
9
+ * from the branch that handles LiveStore sync requests.
10
+ */
11
+ export const makeWorker = (options) => {
5
12
  return {
6
13
  fetch: async (request, env, _ctx) => {
7
14
  const url = new URL(request.url);
@@ -18,19 +25,17 @@ export const makeWorker = (options = {}) => {
18
25
  headers: corsHeaders,
19
26
  });
20
27
  }
21
- const requestParamsResult = getSyncRequestSearchParams(request);
28
+ const searchParams = matchSyncRequest(request);
22
29
  // Check if this is a sync request first, before showing info message
23
- if (requestParamsResult._tag === 'Some') {
30
+ if (searchParams !== undefined) {
24
31
  return handleSyncRequest({
25
32
  request,
26
- searchParams: requestParamsResult.value,
33
+ searchParams,
27
34
  env,
28
35
  ctx: _ctx,
29
- options: {
30
- headers: corsHeaders,
31
- validatePayload: options.validatePayload,
32
- durableObject: options.durableObject,
33
- },
36
+ syncBackendBinding: options.syncBackendBinding,
37
+ headers: corsHeaders,
38
+ validatePayload: options.validatePayload,
34
39
  });
35
40
  }
36
41
  // Only show info message for GET requests to / without sync parameters
@@ -66,16 +71,18 @@ export const makeWorker = (options = {}) => {
66
71
  *
67
72
  * export default {
68
73
  * fetch: async (request, env, ctx) => {
69
- * const requestParamsResult = getSyncRequestSearchParams(request)
74
+ * const searchParams = matchSyncRequest(request)
70
75
  *
71
76
  * // Is LiveStore sync request
72
- * if (requestParamsResult._tag === 'Some') {
77
+ * if (searchParams !== undefined) {
73
78
  * return handleSyncRequest({
74
79
  * request,
75
- * searchParams: requestParamsResult.value,
80
+ * searchParams,
76
81
  * env,
77
82
  * ctx,
78
- * options: { headers: {}, validatePayload }
83
+ * syncBackendBinding: 'SYNC_BACKEND_DO',
84
+ * headers: {},
85
+ * validatePayload,
79
86
  * })
80
87
  * }
81
88
  *
@@ -86,23 +93,21 @@ export const makeWorker = (options = {}) => {
86
93
  *
87
94
  * @throws {UnexpectedError} If the payload is invalid
88
95
  */
89
- export const handleSyncRequest = ({ request, searchParams, env, options = {}, }) => Effect.gen(function* () {
90
- const { storeId, payload, transport } = searchParams;
91
- if (options.validatePayload !== undefined) {
92
- const result = yield* Effect.promise(async () => options.validatePayload(payload, { storeId })).pipe(UnexpectedError.mapToUnexpectedError, Effect.either);
96
+ export const handleSyncRequest = ({ request, searchParams: { storeId, payload, transport }, env, syncBackendBinding, headers, validatePayload, }) => Effect.gen(function* () {
97
+ if (validatePayload !== undefined) {
98
+ const result = yield* Effect.promise(async () => validatePayload(payload, { storeId })).pipe(UnexpectedError.mapToUnexpectedError, Effect.either);
93
99
  if (result._tag === 'Left') {
94
100
  console.error('Invalid payload', result.left);
95
- return new Response(result.left.toString(), { status: 400, headers: options.headers });
101
+ return new Response(result.left.toString(), { status: 400, headers });
96
102
  }
97
103
  }
98
- const durableObjectName = options.durableObject?.name ?? DEFAULT_SYNC_DURABLE_OBJECT_NAME;
99
- if (!(durableObjectName in env)) {
100
- return new Response(`Failed dependency: Required Durable Object binding '${durableObjectName}' not available`, {
104
+ if (!(syncBackendBinding in env)) {
105
+ return new Response(`Failed dependency: Required Durable Object binding '${syncBackendBinding}' not available`, {
101
106
  status: 424,
102
- headers: options.headers,
107
+ headers,
103
108
  });
104
109
  }
105
- const durableObjectNamespace = env[durableObjectName];
110
+ const durableObjectNamespace = env[syncBackendBinding];
106
111
  const id = durableObjectNamespace.idFromName(storeId);
107
112
  const durableObject = durableObjectNamespace.get(id);
108
113
  // Handle WebSocket upgrade request
@@ -110,7 +115,7 @@ export const handleSyncRequest = ({ request, searchParams, env, options = {}, })
110
115
  if (transport === 'ws' && (upgradeHeader === null || upgradeHeader !== 'websocket')) {
111
116
  return new Response('Durable Object expected Upgrade: websocket', {
112
117
  status: 426,
113
- headers: options?.headers,
118
+ headers,
114
119
  });
115
120
  }
116
121
  return yield* Effect.promise(() => durableObject.fetch(request));
@@ -1 +1 @@
1
- {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAGnD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAGhD,OAAO,EAAY,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAiCxD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAIxB,OAAgC,EACG,EAAE;IACrC,OAAO;QACL,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YAClC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAEhC,MAAM,WAAW,GAAwB,OAAO,CAAC,UAAU;gBACzD,CAAC,CAAC;oBACE,6BAA6B,EAAE,GAAG;oBAClC,8BAA8B,EAAE,oBAAoB;oBACpD,8BAA8B,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,IAAI,GAAG;iBAC7F;gBACH,CAAC,CAAC,EAAE,CAAA;YAEN,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;gBACvD,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;oBACxB,MAAM,EAAE,GAAG;oBACX,OAAO,EAAE,WAAW;iBACrB,CAAC,CAAA;YACJ,CAAC;YAED,MAAM,YAAY,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;YAE9C,qEAAqE;YACrE,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC/B,OAAO,iBAAiB,CAA0B;oBAChD,OAAO;oBACP,YAAY;oBACZ,GAAG;oBACH,GAAG,EAAE,IAAI;oBACT,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;oBAC9C,OAAO,EAAE,WAAW;oBACpB,eAAe,EAAE,OAAO,CAAC,eAAe;iBACzC,CAAC,CAAA;YACJ,CAAC;YAED,uEAAuE;YACvE,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAC;gBACrD,OAAO,IAAI,QAAQ,CAAC,qDAAqD,EAAE;oBACzE,MAAM,EAAE,GAAG;oBACX,OAAO,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE;iBAC1C,CAAC,CAAA;YACJ,CAAC;YAED,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAA;YAE3C,OAAO,IAAI,QAAQ,CAAC,cAAc,EAAE;gBAClC,MAAM,EAAE,GAAG;gBACX,UAAU,EAAE,aAAa;gBACzB,OAAO,EAAE;oBACP,GAAG,WAAW;oBACd,cAAc,EAAE,YAAY;iBAC7B;aACF,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAI/B,EACA,OAAO,EACP,YAAY,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAC7C,GAAG,EACH,kBAAkB,EAClB,OAAO,EACP,eAAe,GAWhB,EAA6B,EAAE,CAC9B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,eAAgB,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAC3F,eAAe,CAAC,oBAAoB,EACpC,MAAM,CAAC,MAAM,CACd,CAAA;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;YAC7C,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAA;QACvE,CAAC;IACH,CAAC;IAED,IAAI,CAAC,CAAC,kBAAkB,IAAI,GAAG,CAAC,EAAE,CAAC;QACjC,OAAO,IAAI,QAAQ,CACjB,uDAAuD,kBAA4B,iBAAiB,EACpG;YACE,MAAM,EAAE,GAAG;YACX,OAAO;SACR,CACF,CAAA;IACH,CAAC;IAED,MAAM,sBAAsB,GAAG,GAAG,CAChC,kBAAgC,CACoB,CAAA;IAEtD,MAAM,EAAE,GAAG,sBAAsB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;IACrD,MAAM,aAAa,GAAG,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IAEpD,mCAAmC;IACnC,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACpD,IAAI,SAAS,KAAK,IAAI,IAAI,CAAC,aAAa,KAAK,IAAI,IAAI,aAAa,KAAK,WAAW,CAAC,EAAE,CAAC;QACpF,OAAO,IAAI,QAAQ,CAAC,4CAA4C,EAAE;YAChE,MAAM,EAAE,GAAG;YACX,OAAO;SACR,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;AAClE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA"}
@@ -42,12 +42,7 @@ export declare const PullResponse: Schema.Struct<{
42
42
  backendId: Schema.SchemaClass<string, string, never>;
43
43
  }>;
44
44
  export declare const emptyPullResponse: (backendId: string) => {
45
- readonly backendId: string;
46
45
  readonly batch: readonly {
47
- readonly metadata: import("effect/Option").Option<{
48
- readonly createdAt: string;
49
- readonly _tag: "SyncMessage.SyncMetadata";
50
- }>;
51
46
  readonly eventEncoded: {
52
47
  readonly name: string;
53
48
  readonly args: any;
@@ -56,6 +51,10 @@ export declare const emptyPullResponse: (backendId: string) => {
56
51
  readonly clientId: string;
57
52
  readonly sessionId: string;
58
53
  };
54
+ readonly metadata: import("effect/Option").Option<{
55
+ readonly _tag: "SyncMessage.SyncMetadata";
56
+ readonly createdAt: string;
57
+ }>;
59
58
  }[];
60
59
  readonly pageInfo: {
61
60
  readonly _tag: "MoreUnknown";
@@ -65,6 +64,7 @@ export declare const emptyPullResponse: (backendId: string) => {
65
64
  } | {
66
65
  readonly _tag: "NoMore";
67
66
  };
67
+ readonly backendId: string;
68
68
  };
69
69
  export type PullResponse = typeof PullResponse.Type;
70
70
  export declare const PushRequest: Schema.Struct<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livestore/sync-cf",
3
- "version": "0.4.0-dev.7",
3
+ "version": "0.4.0-dev.9",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -9,10 +9,10 @@
9
9
  "./cf-worker": "./dist/cf-worker/mod.js"
10
10
  },
11
11
  "dependencies": {
12
- "@cloudflare/workers-types": "4.20250823.0",
13
- "@livestore/common": "0.4.0-dev.7",
14
- "@livestore/common-cf": "0.4.0-dev.7",
15
- "@livestore/utils": "0.4.0-dev.7"
12
+ "@cloudflare/workers-types": "4.20250923.0",
13
+ "@livestore/common": "0.4.0-dev.9",
14
+ "@livestore/common-cf": "0.4.0-dev.9",
15
+ "@livestore/utils": "0.4.0-dev.9"
16
16
  },
17
17
  "files": [
18
18
  "dist",
@@ -16,8 +16,8 @@ import {
16
16
  } from '@livestore/utils/effect'
17
17
  import {
18
18
  type Env,
19
- getSyncRequestSearchParams,
20
19
  type MakeDurableObjectClassOptions,
20
+ matchSyncRequest,
21
21
  type SyncBackendRpcInterface,
22
22
  WebSocketAttachmentSchema,
23
23
  } from '../shared.ts'
@@ -33,10 +33,10 @@ declare class Response extends CfDeclare.Response {}
33
33
  declare class WebSocketPair extends CfDeclare.WebSocketPair {}
34
34
  declare class WebSocketRequestResponsePair extends CfDeclare.WebSocketRequestResponsePair {}
35
35
 
36
- const DurableObjectBase = DurableObject as any as new (
36
+ const DurableObjectBase = DurableObject<Env> as any as new (
37
37
  state: CfTypes.DurableObjectState,
38
38
  env: Env,
39
- ) => CfTypes.DurableObject
39
+ ) => CfTypes.DurableObject & { ctx: CfTypes.DurableObjectState; env: Env }
40
40
 
41
41
  // Type aliases needed to avoid TS bug https://github.com/microsoft/TypeScript/issues/55021
42
42
  export type DoState = CfTypes.DurableObjectState
@@ -99,13 +99,9 @@ export const makeDurableObject: MakeDurableObjectClass = (options) => {
99
99
 
100
100
  return class SyncBackendDOBase extends DurableObjectBase implements SyncBackendRpcInterface {
101
101
  __DURABLE_OBJECT_BRAND = 'SyncBackendDOBase' as never
102
- ctx: CfTypes.DurableObjectState
103
- env: Env
104
102
 
105
103
  constructor(ctx: CfTypes.DurableObjectState, env: Env) {
106
104
  super(ctx, env)
107
- this.ctx = ctx
108
- this.env = env
109
105
 
110
106
  const WebSocketRpcServerLive = makeRpcServer({ doSelf: this, doOptions: options })
111
107
 
@@ -148,12 +144,12 @@ export const makeDurableObject: MakeDurableObjectClass = (options) => {
148
144
 
149
145
  fetch = async (request: Request): Promise<Response> =>
150
146
  Effect.gen(this, function* () {
151
- const requestParamsResult = getSyncRequestSearchParams(request)
152
- if (requestParamsResult._tag === 'None') {
147
+ const searchParams = matchSyncRequest(request)
148
+ if (searchParams === undefined) {
153
149
  throw new Error('No search params found in request URL')
154
150
  }
155
151
 
156
- const { storeId, payload, transport } = requestParamsResult.value
152
+ const { storeId, payload, transport } = searchParams
157
153
 
158
154
  if (enabledTransports.has(transport) === false) {
159
155
  throw new Error(`Transport ${transport} is not enabled (based on \`options.enabledTransports\`)`)
@@ -1,8 +1,11 @@
1
1
  import { BackendIdMismatchError, InvalidPullError, SyncBackend, UnexpectedError } from '@livestore/common'
2
- import { Effect, Option, pipe, ReadonlyArray, type Schema, Stream } from '@livestore/utils/effect'
2
+ import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
3
3
  import { SyncMessage } from '../../common/mod.ts'
4
- import { PULL_CHUNK_SIZE } from '../shared.ts'
4
+ import { MAX_PULL_EVENTS_PER_MESSAGE, MAX_WS_MESSAGE_BYTES } from '../shared.ts'
5
5
  import { DoCtx } from './layer.ts'
6
+ import { splitChunkBySize } from './ws-chunking.ts'
7
+
8
+ const encodePullResponse = Schema.encodeSync(SyncMessage.PullResponse)
6
9
 
7
10
  // Notes on stream handling:
8
11
  // We're intentionally closing the stream once we've read all existing events
@@ -30,24 +33,34 @@ export const makeEndingPullStream = (
30
33
  return yield* new BackendIdMismatchError({ expected: backendId, received: req.cursor.value.backendId })
31
34
  }
32
35
 
33
- // TODO use streaming for db results
34
- const remainingEvents = yield* storage.getEvents(Option.getOrUndefined(req.cursor)?.eventSequenceNumber)
36
+ const { stream: storedEvents, total } = yield* storage.getEvents(
37
+ Option.getOrUndefined(req.cursor)?.eventSequenceNumber,
38
+ )
35
39
 
36
- const batches = pipe(
37
- remainingEvents,
38
- ReadonlyArray.chunksOf(PULL_CHUNK_SIZE),
39
- ReadonlyArray.map((batch, i) => {
40
- const remaining = Math.max(0, remainingEvents.length - (i + 1) * PULL_CHUNK_SIZE)
40
+ return storedEvents.pipe(
41
+ Stream.mapChunks(
42
+ splitChunkBySize({
43
+ maxItems: MAX_PULL_EVENTS_PER_MESSAGE,
44
+ maxBytes: MAX_WS_MESSAGE_BYTES,
45
+ encode: (batch) =>
46
+ encodePullResponse(
47
+ SyncMessage.PullResponse.make({ batch, pageInfo: SyncBackend.pageInfoNoMore, backendId }),
48
+ ),
49
+ }),
50
+ ),
51
+ Stream.mapAccum(total, (remaining, chunk) => {
52
+ const asArray = Chunk.toReadonlyArray(chunk)
53
+ const nextRemaining = Math.max(0, remaining - asArray.length)
41
54
 
42
- return SyncMessage.PullResponse.make({
43
- batch,
44
- pageInfo: remaining > 0 ? SyncBackend.pageInfoMoreKnown(remaining) : SyncBackend.pageInfoNoMore,
45
- backendId,
46
- })
55
+ return [
56
+ nextRemaining,
57
+ SyncMessage.PullResponse.make({
58
+ batch: asArray,
59
+ pageInfo: nextRemaining > 0 ? SyncBackend.pageInfoMoreKnown(nextRemaining) : SyncBackend.pageInfoNoMore,
60
+ backendId,
61
+ }),
62
+ ] as const
47
63
  }),
48
- )
49
-
50
- return Stream.fromIterable(batches).pipe(
51
64
  Stream.tap(
52
65
  Effect.fn(function* (res) {
53
66
  if (doOptions?.onPullRes) {