@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/cf-worker/do/durable-object.d.ts.map +1 -1
- package/dist/cf-worker/do/durable-object.js +5 -9
- package/dist/cf-worker/do/durable-object.js.map +1 -1
- package/dist/cf-worker/do/layer.d.ts +1 -1
- package/dist/cf-worker/do/pull.d.ts +1 -1
- package/dist/cf-worker/do/pull.d.ts.map +1 -1
- package/dist/cf-worker/do/pull.js +21 -13
- package/dist/cf-worker/do/pull.js.map +1 -1
- package/dist/cf-worker/do/push.d.ts.map +1 -1
- package/dist/cf-worker/do/push.js +66 -35
- package/dist/cf-worker/do/push.js.map +1 -1
- package/dist/cf-worker/do/sync-storage.d.ts +8 -5
- package/dist/cf-worker/do/sync-storage.d.ts.map +1 -1
- package/dist/cf-worker/do/sync-storage.js +64 -19
- package/dist/cf-worker/do/sync-storage.js.map +1 -1
- package/dist/cf-worker/do/transport/do-rpc-server.d.ts +2 -1
- package/dist/cf-worker/do/transport/do-rpc-server.d.ts.map +1 -1
- package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -1
- package/dist/cf-worker/do/transport/http-rpc-server.d.ts +1 -1
- package/dist/cf-worker/do/ws-chunking.d.ts +22 -0
- package/dist/cf-worker/do/ws-chunking.d.ts.map +1 -0
- package/dist/cf-worker/do/ws-chunking.js +49 -0
- package/dist/cf-worker/do/ws-chunking.js.map +1 -0
- package/dist/cf-worker/shared.d.ts +19 -13
- package/dist/cf-worker/shared.d.ts.map +1 -1
- package/dist/cf-worker/shared.js +15 -4
- package/dist/cf-worker/shared.js.map +1 -1
- package/dist/cf-worker/worker.d.ts +30 -45
- package/dist/cf-worker/worker.d.ts.map +1 -1
- package/dist/cf-worker/worker.js +30 -25
- package/dist/cf-worker/worker.js.map +1 -1
- package/dist/common/sync-message-types.d.ts +5 -5
- package/package.json +5 -5
- package/src/cf-worker/do/durable-object.ts +6 -10
- package/src/cf-worker/do/pull.ts +30 -17
- package/src/cf-worker/do/push.ts +85 -38
- package/src/cf-worker/do/sync-storage.ts +106 -27
- package/src/cf-worker/do/transport/do-rpc-server.ts +4 -2
- package/src/cf-worker/do/ws-chunking.ts +76 -0
- package/src/cf-worker/shared.ts +19 -6
- package/src/cf-worker/worker.ts +46 -69
- package/dist/cf-worker/cf-types.d.ts +0 -2
- package/dist/cf-worker/cf-types.d.ts.map +0 -1
- package/dist/cf-worker/cf-types.js +0 -2
- package/dist/cf-worker/cf-types.js.map +0 -1
- package/dist/cf-worker/durable-object.d.ts +0 -189
- package/dist/cf-worker/durable-object.d.ts.map +0 -1
- package/dist/cf-worker/durable-object.js +0 -317
- package/dist/cf-worker/durable-object.js.map +0 -1
- package/dist/common/ws-message-types.d.ts +0 -270
- package/dist/common/ws-message-types.d.ts.map +0 -1
- package/dist/common/ws-message-types.js +0 -57
- package/dist/common/ws-message-types.js.map +0 -1
- package/dist/sync-impl/mod.d.ts +0 -2
- package/dist/sync-impl/mod.d.ts.map +0 -1
- package/dist/sync-impl/mod.js +0 -2
- package/dist/sync-impl/mod.js.map +0 -1
- package/dist/sync-impl/ws-impl.d.ts +0 -7
- package/dist/sync-impl/ws-impl.d.ts.map +0 -1
- package/dist/sync-impl/ws-impl.js +0 -175
- 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,
|
|
4
|
-
import {
|
|
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
|
-
|
|
103
|
-
|
|
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,
|
|
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"}
|
package/dist/cf-worker/shared.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
20
|
+
if (paramsResult._tag === 'None') {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return paramsResult.value;
|
|
17
24
|
};
|
|
18
|
-
export const
|
|
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,
|
|
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
|
-
|
|
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
|
|
50
|
+
* const searchParams = matchSyncRequest(request)
|
|
67
51
|
*
|
|
68
52
|
* // Is LiveStore sync request
|
|
69
|
-
* if (
|
|
53
|
+
* if (searchParams !== undefined) {
|
|
70
54
|
* return handleSyncRequest({
|
|
71
55
|
* request,
|
|
72
|
-
* searchParams
|
|
56
|
+
* searchParams,
|
|
73
57
|
* env,
|
|
74
58
|
* ctx,
|
|
75
|
-
*
|
|
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,
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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":"AACA,OAAO,KAAK,EAAE,
|
|
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"}
|
package/dist/cf-worker/worker.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { UnexpectedError } from '@livestore/common';
|
|
2
2
|
import { Effect } from '@livestore/utils/effect';
|
|
3
|
-
import {
|
|
4
|
-
|
|
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
|
|
28
|
+
const searchParams = matchSyncRequest(request);
|
|
22
29
|
// Check if this is a sync request first, before showing info message
|
|
23
|
-
if (
|
|
30
|
+
if (searchParams !== undefined) {
|
|
24
31
|
return handleSyncRequest({
|
|
25
32
|
request,
|
|
26
|
-
searchParams
|
|
33
|
+
searchParams,
|
|
27
34
|
env,
|
|
28
35
|
ctx: _ctx,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
74
|
+
* const searchParams = matchSyncRequest(request)
|
|
70
75
|
*
|
|
71
76
|
* // Is LiveStore sync request
|
|
72
|
-
* if (
|
|
77
|
+
* if (searchParams !== undefined) {
|
|
73
78
|
* return handleSyncRequest({
|
|
74
79
|
* request,
|
|
75
|
-
* searchParams
|
|
80
|
+
* searchParams,
|
|
76
81
|
* env,
|
|
77
82
|
* ctx,
|
|
78
|
-
*
|
|
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,
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
101
|
+
return new Response(result.left.toString(), { status: 400, headers });
|
|
96
102
|
}
|
|
97
103
|
}
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
107
|
+
headers,
|
|
103
108
|
});
|
|
104
109
|
}
|
|
105
|
-
const durableObjectNamespace = env[
|
|
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
|
|
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":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -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.
|
|
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.
|
|
13
|
-
"@livestore/common": "0.4.0-dev.
|
|
14
|
-
"@livestore/common-cf": "0.4.0-dev.
|
|
15
|
-
"@livestore/utils": "0.4.0-dev.
|
|
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
|
|
152
|
-
if (
|
|
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 } =
|
|
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\`)`)
|
package/src/cf-worker/do/pull.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { BackendIdMismatchError, InvalidPullError, SyncBackend, UnexpectedError } from '@livestore/common'
|
|
2
|
-
import { Effect, Option,
|
|
2
|
+
import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
|
|
3
3
|
import { SyncMessage } from '../../common/mod.ts'
|
|
4
|
-
import {
|
|
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
|
-
|
|
34
|
-
|
|
36
|
+
const { stream: storedEvents, total } = yield* storage.getEvents(
|
|
37
|
+
Option.getOrUndefined(req.cursor)?.eventSequenceNumber,
|
|
38
|
+
)
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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) {
|