@livestore/sync-cf 0.4.0-dev.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/cf-worker/do/durable-object.d.ts +1 -1
- package/dist/cf-worker/do/durable-object.d.ts.map +1 -1
- package/dist/cf-worker/do/durable-object.js +13 -8
- package/dist/cf-worker/do/durable-object.js.map +1 -1
- package/dist/cf-worker/do/layer.d.ts +5 -5
- package/dist/cf-worker/do/layer.d.ts.map +1 -1
- package/dist/cf-worker/do/layer.js +32 -9
- package/dist/cf-worker/do/layer.js.map +1 -1
- package/dist/cf-worker/do/pull.d.ts +7 -2
- package/dist/cf-worker/do/pull.d.ts.map +1 -1
- package/dist/cf-worker/do/pull.js +16 -10
- package/dist/cf-worker/do/pull.js.map +1 -1
- package/dist/cf-worker/do/push.d.ts +5 -4
- package/dist/cf-worker/do/push.d.ts.map +1 -1
- package/dist/cf-worker/do/push.js +25 -17
- package/dist/cf-worker/do/push.js.map +1 -1
- package/dist/cf-worker/do/sqlite.d.ts +10 -1
- package/dist/cf-worker/do/sqlite.d.ts.map +1 -1
- package/dist/cf-worker/do/sqlite.js +13 -4
- package/dist/cf-worker/do/sqlite.js.map +1 -1
- package/dist/cf-worker/do/sync-storage.d.ts +14 -9
- package/dist/cf-worker/do/sync-storage.d.ts.map +1 -1
- package/dist/cf-worker/do/sync-storage.js +92 -18
- package/dist/cf-worker/do/sync-storage.js.map +1 -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 +13 -7
- package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -1
- package/dist/cf-worker/do/transport/http-rpc-server.d.ts +4 -2
- package/dist/cf-worker/do/transport/http-rpc-server.d.ts.map +1 -1
- package/dist/cf-worker/do/transport/http-rpc-server.js +24 -15
- package/dist/cf-worker/do/transport/http-rpc-server.js.map +1 -1
- package/dist/cf-worker/do/transport/ws-rpc-server.d.ts +2 -1
- package/dist/cf-worker/do/transport/ws-rpc-server.d.ts.map +1 -1
- package/dist/cf-worker/do/transport/ws-rpc-server.js +30 -8
- package/dist/cf-worker/do/transport/ws-rpc-server.js.map +1 -1
- package/dist/cf-worker/shared.d.ts +118 -31
- package/dist/cf-worker/shared.d.ts.map +1 -1
- package/dist/cf-worker/shared.js +40 -7
- package/dist/cf-worker/shared.js.map +1 -1
- package/dist/cf-worker/worker.d.ts +46 -38
- package/dist/cf-worker/worker.d.ts.map +1 -1
- package/dist/cf-worker/worker.js +51 -34
- package/dist/cf-worker/worker.js.map +1 -1
- package/dist/client/transport/do-rpc-client.d.ts.map +1 -1
- package/dist/client/transport/do-rpc-client.js +27 -10
- package/dist/client/transport/do-rpc-client.js.map +1 -1
- package/dist/client/transport/http-rpc-client.d.ts.map +1 -1
- package/dist/client/transport/http-rpc-client.js +29 -9
- package/dist/client/transport/http-rpc-client.js.map +1 -1
- package/dist/client/transport/ws-rpc-client.d.ts +2 -1
- package/dist/client/transport/ws-rpc-client.d.ts.map +1 -1
- package/dist/client/transport/ws-rpc-client.js +31 -17
- package/dist/client/transport/ws-rpc-client.js.map +1 -1
- package/dist/common/constants.d.ts +7 -0
- package/dist/common/constants.d.ts.map +1 -0
- package/dist/common/constants.js +17 -0
- package/dist/common/constants.js.map +1 -0
- package/dist/common/do-rpc-schema.d.ts +6 -6
- package/dist/common/do-rpc-schema.d.ts.map +1 -1
- package/dist/common/do-rpc-schema.js +4 -4
- package/dist/common/do-rpc-schema.js.map +1 -1
- package/dist/common/http-rpc-schema.d.ts +4 -4
- package/dist/common/http-rpc-schema.d.ts.map +1 -1
- package/dist/common/http-rpc-schema.js +4 -4
- package/dist/common/http-rpc-schema.js.map +1 -1
- package/dist/common/mod.d.ts +4 -1
- package/dist/common/mod.d.ts.map +1 -1
- package/dist/common/mod.js +4 -1
- package/dist/common/mod.js.map +1 -1
- package/dist/common/sync-message-types.d.ts +7 -7
- package/dist/common/sync-message-types.js +3 -3
- package/dist/common/sync-message-types.js.map +1 -1
- package/dist/common/ws-rpc-schema.d.ts +3 -3
- package/dist/common/ws-rpc-schema.d.ts.map +1 -1
- package/dist/common/ws-rpc-schema.js +3 -3
- package/dist/common/ws-rpc-schema.js.map +1 -1
- package/package.json +72 -14
- package/src/cf-worker/do/durable-object.ts +19 -10
- package/src/cf-worker/do/layer.ts +35 -13
- package/src/cf-worker/do/pull.ts +31 -14
- package/src/cf-worker/do/push.ts +49 -34
- package/src/cf-worker/do/sqlite.ts +14 -4
- package/src/cf-worker/do/sync-storage.ts +151 -31
- package/src/cf-worker/do/transport/do-rpc-server.ts +18 -7
- package/src/cf-worker/do/transport/http-rpc-server.ts +33 -13
- package/src/cf-worker/do/transport/ws-rpc-server.ts +40 -12
- package/src/cf-worker/shared.ts +136 -25
- package/src/cf-worker/worker.ts +107 -54
- package/src/client/transport/do-rpc-client.ts +41 -17
- package/src/client/transport/http-rpc-client.ts +43 -17
- package/src/client/transport/ws-rpc-client.ts +42 -19
- package/src/common/constants.ts +18 -0
- package/src/common/do-rpc-schema.ts +5 -4
- package/src/common/http-rpc-schema.ts +5 -4
- package/src/common/mod.ts +4 -2
- package/src/common/sync-message-types.ts +3 -3
- package/src/common/ws-rpc-schema.ts +4 -3
- package/dist/cf-worker/do/ws-chunking.d.ts +0 -22
- package/dist/cf-worker/do/ws-chunking.d.ts.map +0 -1
- package/dist/cf-worker/do/ws-chunking.js +0 -49
- package/dist/cf-worker/do/ws-chunking.js.map +0 -1
- package/src/cf-worker/do/ws-chunking.ts +0 -76
|
@@ -1,24 +1,78 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { UnknownError } from '@livestore/common';
|
|
2
2
|
import type { CfTypes } from '@livestore/common-cf';
|
|
3
3
|
import { Effect, Schema } from '@livestore/utils/effect';
|
|
4
4
|
import type { SearchParams } from '../common/mod.ts';
|
|
5
5
|
import { SyncMessage } from '../common/mod.ts';
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
export type Env = {};
|
|
7
|
+
/** Headers forwarded from the request to callbacks */
|
|
8
|
+
export type ForwardedHeaders = ReadonlyMap<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* Configuration for forwarding request headers to DO callbacks.
|
|
11
|
+
* - `string[]`: List of header names to forward (case-insensitive)
|
|
12
|
+
* - `(request) => Record<string, string>`: Custom extraction function (sync)
|
|
13
|
+
*/
|
|
14
|
+
export type ForwardHeadersOption = readonly string[] | ((request: CfTypes.Request) => Record<string, string>);
|
|
15
|
+
/** Context passed to onPush/onPull callbacks */
|
|
16
|
+
export type CallbackContext = {
|
|
17
|
+
storeId: StoreId;
|
|
18
|
+
payload?: Schema.JsonValue;
|
|
19
|
+
/** Headers forwarded from the request (only present if `forwardHeaders` is configured) */
|
|
20
|
+
headers?: ForwardedHeaders;
|
|
21
|
+
};
|
|
11
22
|
export type MakeDurableObjectClassOptions = {
|
|
12
|
-
onPush?: (message: SyncMessage.PushRequest, context:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
onPush?: (message: SyncMessage.PushRequest, context: CallbackContext) => Effect.SyncOrPromiseOrEffect<void>;
|
|
24
|
+
onPushRes?: (message: SyncMessage.PushAck | UnknownError) => Effect.SyncOrPromiseOrEffect<void>;
|
|
25
|
+
onPull?: (message: SyncMessage.PullRequest, context: CallbackContext) => Effect.SyncOrPromiseOrEffect<void>;
|
|
26
|
+
onPullRes?: (message: SyncMessage.PullResponse | UnknownError) => Effect.SyncOrPromiseOrEffect<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Forward request headers to `onPush`/`onPull` callbacks for authentication.
|
|
29
|
+
*
|
|
30
|
+
* This enables cookie-based or header-based authentication patterns where
|
|
31
|
+
* you need access to request headers inside the Durable Object.
|
|
32
|
+
*
|
|
33
|
+
* @example Forward specific headers by name (case-insensitive)
|
|
34
|
+
* ```ts
|
|
35
|
+
* makeDurableObject({
|
|
36
|
+
* forwardHeaders: ['cookie', 'authorization'],
|
|
37
|
+
* onPush: async (message, { headers }) => {
|
|
38
|
+
* const cookie = headers?.get('cookie')
|
|
39
|
+
* const session = await validateSession(cookie)
|
|
40
|
+
* },
|
|
41
|
+
* })
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example Custom extraction function for derived values
|
|
45
|
+
* ```ts
|
|
46
|
+
* makeDurableObject({
|
|
47
|
+
* forwardHeaders: (request) => ({
|
|
48
|
+
* 'x-user-id': request.headers.get('x-user-id') ?? '',
|
|
49
|
+
* 'x-session': request.headers.get('cookie')?.split('session=')[1]?.split(';')[0] ?? '',
|
|
50
|
+
* }),
|
|
51
|
+
* onPush: async (message, { headers }) => {
|
|
52
|
+
* const userId = headers?.get('x-user-id')
|
|
53
|
+
* },
|
|
54
|
+
* })
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
forwardHeaders?: ForwardHeadersOption;
|
|
58
|
+
/**
|
|
59
|
+
* Storage engine for event persistence.
|
|
60
|
+
* - Default: `{ _tag: 'do-sqlite' }` (Durable Object SQLite)
|
|
61
|
+
* - D1: `{ _tag: 'd1', binding: string }` where `binding` is the D1 binding name in wrangler.toml.
|
|
62
|
+
*
|
|
63
|
+
* If omitted, the runtime defaults to DO SQLite. For backwards-compatibility, if an env binding named
|
|
64
|
+
* `DB` exists and looks like a D1Database, D1 will be used.
|
|
65
|
+
*
|
|
66
|
+
* Trade-offs:
|
|
67
|
+
* - DO SQLite: simpler deploy, data co-located with DO, not externally queryable
|
|
68
|
+
* - D1: centralized DB, inspectable with DB tools, extra network hop and JSON size limits
|
|
69
|
+
*/
|
|
70
|
+
storage?: {
|
|
71
|
+
_tag: 'do-sqlite';
|
|
72
|
+
} | {
|
|
73
|
+
_tag: 'd1';
|
|
74
|
+
binding: string;
|
|
75
|
+
};
|
|
22
76
|
/**
|
|
23
77
|
* Enabled transports for sync backend
|
|
24
78
|
* - `http`: HTTP JSON-RPC
|
|
@@ -28,6 +82,25 @@ export type MakeDurableObjectClassOptions = {
|
|
|
28
82
|
* @default Set(['http', 'ws', 'do-rpc'])
|
|
29
83
|
*/
|
|
30
84
|
enabledTransports?: Set<'http' | 'ws' | 'do-rpc'>;
|
|
85
|
+
/**
|
|
86
|
+
* Custom HTTP response headers for HTTP transport
|
|
87
|
+
* These headers will be added to all HTTP RPC responses (Pull, Push, Ping)
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* {
|
|
92
|
+
* http: {
|
|
93
|
+
* responseHeaders: {
|
|
94
|
+
* 'Access-Control-Allow-Origin': '*',
|
|
95
|
+
* 'Cache-Control': 'no-cache'
|
|
96
|
+
* }
|
|
97
|
+
* }
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
http?: {
|
|
102
|
+
responseHeaders?: Record<string, string>;
|
|
103
|
+
};
|
|
31
104
|
otel?: {
|
|
32
105
|
baseUrl?: string;
|
|
33
106
|
serviceName?: string;
|
|
@@ -36,25 +109,37 @@ export type MakeDurableObjectClassOptions = {
|
|
|
36
109
|
export type StoreId = string;
|
|
37
110
|
export type DurableObjectId = string;
|
|
38
111
|
/**
|
|
39
|
-
*
|
|
112
|
+
* CRITICAL: Increment this version whenever you modify the database schema structure.
|
|
113
|
+
*
|
|
114
|
+
* Bump required when:
|
|
115
|
+
* - Adding/removing/renaming columns in eventlogTable or contextTable (see sqlite.ts)
|
|
116
|
+
* - Changing column types or constraints
|
|
117
|
+
* - Modifying primary keys or indexes
|
|
40
118
|
*
|
|
41
|
-
*
|
|
119
|
+
* Bump NOT required when:
|
|
120
|
+
* - Changing query patterns, pagination logic, or streaming behavior
|
|
121
|
+
* - Adding new tables (as long as existing table schemas remain unchanged)
|
|
122
|
+
* - Updating implementation details in sync-storage.ts
|
|
123
|
+
*
|
|
124
|
+
* Impact: Changing this version triggers a "soft reset" - new table names are created
|
|
125
|
+
* and old data becomes inaccessible (but remains in storage).
|
|
42
126
|
*/
|
|
43
127
|
export declare const PERSISTENCE_FORMAT_VERSION = 7;
|
|
44
128
|
export declare const encodeOutgoingMessage: (a: {
|
|
129
|
+
readonly backendId: string;
|
|
45
130
|
readonly batch: readonly {
|
|
131
|
+
readonly metadata: import("effect/Option").Option<{
|
|
132
|
+
readonly createdAt: string;
|
|
133
|
+
readonly _tag: "SyncMessage.SyncMetadata";
|
|
134
|
+
}>;
|
|
46
135
|
readonly eventEncoded: {
|
|
47
136
|
readonly name: string;
|
|
48
137
|
readonly args: any;
|
|
49
|
-
readonly seqNum:
|
|
50
|
-
readonly parentSeqNum:
|
|
138
|
+
readonly seqNum: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
|
|
139
|
+
readonly parentSeqNum: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
|
|
51
140
|
readonly clientId: string;
|
|
52
141
|
readonly sessionId: string;
|
|
53
142
|
};
|
|
54
|
-
readonly metadata: import("effect/Option").Option<{
|
|
55
|
-
readonly _tag: "SyncMessage.SyncMetadata";
|
|
56
|
-
readonly createdAt: string;
|
|
57
|
-
}>;
|
|
58
143
|
}[];
|
|
59
144
|
readonly pageInfo: {
|
|
60
145
|
readonly _tag: "MoreUnknown";
|
|
@@ -64,16 +149,15 @@ export declare const encodeOutgoingMessage: (a: {
|
|
|
64
149
|
} | {
|
|
65
150
|
readonly _tag: "NoMore";
|
|
66
151
|
};
|
|
67
|
-
readonly backendId: string;
|
|
68
152
|
} | {} | {
|
|
69
153
|
readonly _tag: "SyncMessage.Pong";
|
|
70
154
|
} | {
|
|
71
155
|
readonly _tag: "SyncMessage.AdminResetRoomResponse";
|
|
72
156
|
} | {
|
|
73
|
-
readonly _tag: "SyncMessage.AdminInfoResponse";
|
|
74
157
|
readonly info: {
|
|
75
158
|
readonly durableObjectId: string;
|
|
76
159
|
};
|
|
160
|
+
readonly _tag: "SyncMessage.AdminInfoResponse";
|
|
77
161
|
}, overrideOptions?: import("effect/SchemaAST").ParseOptions) => string;
|
|
78
162
|
export declare const encodeIncomingMessage: (a: {
|
|
79
163
|
readonly cursor: import("effect/Option").Option<{
|
|
@@ -81,15 +165,15 @@ export declare const encodeIncomingMessage: (a: {
|
|
|
81
165
|
readonly eventSequenceNumber: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
|
|
82
166
|
}>;
|
|
83
167
|
} | {
|
|
168
|
+
readonly backendId: import("effect/Option").Option<string>;
|
|
84
169
|
readonly batch: readonly {
|
|
85
170
|
readonly name: string;
|
|
86
171
|
readonly args: any;
|
|
87
|
-
readonly seqNum:
|
|
88
|
-
readonly parentSeqNum:
|
|
172
|
+
readonly seqNum: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
|
|
173
|
+
readonly parentSeqNum: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
|
|
89
174
|
readonly clientId: string;
|
|
90
175
|
readonly sessionId: string;
|
|
91
176
|
}[];
|
|
92
|
-
readonly backendId: import("effect/Option").Option<string>;
|
|
93
177
|
} | {
|
|
94
178
|
readonly _tag: "SyncMessage.Ping";
|
|
95
179
|
} | {
|
|
@@ -105,8 +189,6 @@ export declare const encodeIncomingMessage: (a: {
|
|
|
105
189
|
* can fall back to custom routing.
|
|
106
190
|
*/
|
|
107
191
|
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;
|
|
110
192
|
export type RpcSubscription = {
|
|
111
193
|
storeId: StoreId;
|
|
112
194
|
payload?: Schema.JsonValue;
|
|
@@ -129,5 +211,10 @@ export declare const WebSocketAttachmentSchema: Schema.transform<Schema.SchemaCl
|
|
|
129
211
|
storeId: typeof Schema.String;
|
|
130
212
|
payload: Schema.optional<Schema.Schema<Schema.JsonValue, Schema.JsonValue, never>>;
|
|
131
213
|
pullRequestIds: Schema.Array$<typeof Schema.String>;
|
|
214
|
+
headers: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
|
|
132
215
|
}>>;
|
|
216
|
+
/** Helper to extract headers from a request based on the forwardHeaders option */
|
|
217
|
+
export declare const extractForwardedHeaders: (request: CfTypes.Request, forwardHeaders: ForwardHeadersOption | undefined) => Record<string, string> | undefined;
|
|
218
|
+
/** Convert a headers record to a ReadonlyMap */
|
|
219
|
+
export declare const headersRecordToMap: (headers: Record<string, string> | undefined) => ForwardedHeaders | undefined;
|
|
133
220
|
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/cf-worker/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/cf-worker/shared.ts"],"names":[],"mappings":"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"}
|
package/dist/cf-worker/shared.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { Effect, Schema, UrlParams } from '@livestore/utils/effect';
|
|
2
2
|
import { SearchParamsSchema, SyncMessage } from "../common/mod.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* CRITICAL: Increment this version whenever you modify the database schema structure.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Bump required when:
|
|
7
|
+
* - Adding/removing/renaming columns in eventlogTable or contextTable (see sqlite.ts)
|
|
8
|
+
* - Changing column types or constraints
|
|
9
|
+
* - Modifying primary keys or indexes
|
|
10
|
+
*
|
|
11
|
+
* Bump NOT required when:
|
|
12
|
+
* - Changing query patterns, pagination logic, or streaming behavior
|
|
13
|
+
* - Adding new tables (as long as existing table schemas remain unchanged)
|
|
14
|
+
* - Updating implementation details in sync-storage.ts
|
|
15
|
+
*
|
|
16
|
+
* Impact: Changing this version triggers a "soft reset" - new table names are created
|
|
17
|
+
* and old data becomes inaccessible (but remains in storage).
|
|
7
18
|
*/
|
|
8
19
|
export const PERSISTENCE_FORMAT_VERSION = 7;
|
|
9
20
|
export const encodeOutgoingMessage = Schema.encodeSync(Schema.parseJson(SyncMessage.BackendToClientMessage));
|
|
@@ -22,16 +33,38 @@ export const matchSyncRequest = (request) => {
|
|
|
22
33
|
}
|
|
23
34
|
return paramsResult.value;
|
|
24
35
|
};
|
|
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;
|
|
30
36
|
export const WebSocketAttachmentSchema = Schema.parseJson(Schema.Struct({
|
|
31
37
|
// Same across all websocket connections
|
|
32
38
|
storeId: Schema.String,
|
|
33
39
|
// Different for each websocket connection
|
|
34
40
|
payload: Schema.optional(Schema.JsonValue),
|
|
35
41
|
pullRequestIds: Schema.Array(Schema.String),
|
|
42
|
+
// Headers forwarded from the initial request (via forwardHeaders option)
|
|
43
|
+
headers: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.String })),
|
|
36
44
|
}));
|
|
45
|
+
/** Helper to extract headers from a request based on the forwardHeaders option */
|
|
46
|
+
export const extractForwardedHeaders = (request, forwardHeaders) => {
|
|
47
|
+
if (forwardHeaders === undefined) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
if (typeof forwardHeaders === 'function') {
|
|
51
|
+
return forwardHeaders(request);
|
|
52
|
+
}
|
|
53
|
+
// Array of header names - extract them case-insensitively
|
|
54
|
+
const result = {};
|
|
55
|
+
for (const name of forwardHeaders) {
|
|
56
|
+
const value = request.headers.get(name);
|
|
57
|
+
if (value !== null) {
|
|
58
|
+
result[name.toLowerCase()] = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
62
|
+
};
|
|
63
|
+
/** Convert a headers record to a ReadonlyMap */
|
|
64
|
+
export const headersRecordToMap = (headers) => {
|
|
65
|
+
if (headers === undefined) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
return new Map(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
|
|
69
|
+
};
|
|
37
70
|
//# sourceMappingURL=shared.js.map
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/cf-worker/shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,27 +1,49 @@
|
|
|
1
1
|
import type { HelperTypes } from '@livestore/common-cf';
|
|
2
|
-
import
|
|
2
|
+
import { Schema } from '@livestore/utils/effect';
|
|
3
3
|
import type { CfTypes, SearchParams } from '../common/mod.ts';
|
|
4
|
-
import { type Env } from './shared.ts';
|
|
4
|
+
import { type Env, type ForwardedHeaders } from './shared.ts';
|
|
5
5
|
export type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = {
|
|
6
6
|
fetch: <CFHostMetada = unknown>(request: CfTypes.Request<CFHostMetada>, env: TEnv, ctx: CfTypes.ExecutionContext) => Promise<CfTypes.Response>;
|
|
7
7
|
};
|
|
8
|
+
/** Context passed to validatePayload callback */
|
|
9
|
+
export type ValidatePayloadContext = {
|
|
10
|
+
storeId: string;
|
|
11
|
+
/** Request headers (raw, not filtered by forwardHeaders) */
|
|
12
|
+
headers: ForwardedHeaders;
|
|
13
|
+
};
|
|
8
14
|
/**
|
|
9
15
|
* Options accepted by {@link makeWorker}. The Durable Object binding has to be
|
|
10
16
|
* supplied explicitly so we never fall back to deprecated defaults when Cloudflare config changes.
|
|
11
17
|
*/
|
|
12
|
-
export type MakeWorkerOptions<TEnv extends Env = Env> = {
|
|
18
|
+
export type MakeWorkerOptions<TEnv extends Env = Env, TSyncPayload = Schema.JsonValue> = {
|
|
13
19
|
/**
|
|
14
20
|
* Binding name of the sync Durable Object declared in wrangler config.
|
|
15
21
|
*/
|
|
16
22
|
syncBackendBinding: HelperTypes.ExtractDurableObjectKeys<TEnv>;
|
|
17
23
|
/**
|
|
18
|
-
*
|
|
24
|
+
* Optionally pass a schema to decode the client-provided payload into a typed object
|
|
25
|
+
* before calling {@link validatePayload}. If omitted, the raw JSON value is forwarded.
|
|
26
|
+
*/
|
|
27
|
+
syncPayloadSchema?: Schema.Schema<TSyncPayload>;
|
|
28
|
+
/**
|
|
29
|
+
* Validates the (optionally decoded) payload during WebSocket connection establishment.
|
|
30
|
+
* If {@link syncPayloadSchema} is provided, `payload` will be of the schema's inferred type.
|
|
31
|
+
*
|
|
32
|
+
* The context includes request headers for cookie-based or header-based authentication.
|
|
33
|
+
*
|
|
34
|
+
* @example Cookie-based authentication
|
|
35
|
+
* ```ts
|
|
36
|
+
* validatePayload: async (payload, { storeId, headers }) => {
|
|
37
|
+
* const cookie = headers.get('cookie')
|
|
38
|
+
* const session = await validateSessionFromCookie(cookie)
|
|
39
|
+
* if (!session) throw new Error('Unauthorized')
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
19
43
|
* Note: This runs only at connection time, not for individual push events.
|
|
20
|
-
* For push event validation, use the `onPush` callback in the
|
|
44
|
+
* For push event validation, use the `onPush` callback in the Durable Object.
|
|
21
45
|
*/
|
|
22
|
-
validatePayload?: (payload:
|
|
23
|
-
storeId: string;
|
|
24
|
-
}) => void | Promise<void>;
|
|
46
|
+
validatePayload?: (payload: TSyncPayload, context: ValidatePayloadContext) => void | Promise<void>;
|
|
25
47
|
/** @default false */
|
|
26
48
|
enableCORS?: boolean;
|
|
27
49
|
};
|
|
@@ -32,54 +54,40 @@ export type MakeWorkerOptions<TEnv extends Env = Env> = {
|
|
|
32
54
|
* For more complex setups prefer implementing a custom `fetch` and call {@link handleSyncRequest}
|
|
33
55
|
* from the branch that handles LiveStore sync requests.
|
|
34
56
|
*/
|
|
35
|
-
export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>(options: MakeWorkerOptions<TEnv>) => CFWorker<TEnv, TDurableObjectRpc>;
|
|
57
|
+
export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined, TSyncPayload = Schema.JsonValue>(options: MakeWorkerOptions<TEnv, TSyncPayload>) => CFWorker<TEnv, TDurableObjectRpc>;
|
|
36
58
|
/**
|
|
37
|
-
* Handles
|
|
59
|
+
* Handles LiveStore sync requests (e.g. with search params `?storeId=...&transport=...`).
|
|
38
60
|
*
|
|
39
|
-
* @example
|
|
61
|
+
* @example Token-based authentication
|
|
40
62
|
* ```ts
|
|
41
63
|
* const validatePayload = (payload: Schema.JsonValue | undefined, context: { storeId: string }) => {
|
|
42
|
-
* console.log(`Validating connection for store: ${context.storeId}`)
|
|
43
64
|
* if (payload?.authToken !== 'insecure-token-change-me') {
|
|
44
65
|
* throw new Error('Invalid auth token')
|
|
45
66
|
* }
|
|
46
67
|
* }
|
|
68
|
+
* ```
|
|
47
69
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* return handleSyncRequest({
|
|
55
|
-
* request,
|
|
56
|
-
* searchParams,
|
|
57
|
-
* env,
|
|
58
|
-
* ctx,
|
|
59
|
-
* syncBackendBinding: 'SYNC_BACKEND_DO',
|
|
60
|
-
* headers: {},
|
|
61
|
-
* validatePayload,
|
|
62
|
-
* })
|
|
63
|
-
* }
|
|
64
|
-
*
|
|
65
|
-
* return new Response('Invalid path', { status: 400 })
|
|
66
|
-
* }
|
|
70
|
+
* @example Cookie-based authentication
|
|
71
|
+
* ```ts
|
|
72
|
+
* const validatePayload = async (payload: Schema.JsonValue | undefined, { storeId, headers }) => {
|
|
73
|
+
* const cookie = headers.get('cookie')
|
|
74
|
+
* const session = await validateSessionFromCookie(cookie)
|
|
75
|
+
* if (!session) throw new Error('Unauthorized')
|
|
67
76
|
* }
|
|
68
77
|
* ```
|
|
69
78
|
*
|
|
70
|
-
* @throws {
|
|
79
|
+
* @throws {UnknownError} If the payload is invalid
|
|
71
80
|
*/
|
|
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, }: {
|
|
81
|
+
export declare const handleSyncRequest: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined, CFHostMetada = unknown, TSyncPayload = Schema.JsonValue>({ request, searchParams: { storeId, payload, transport }, env: explicitlyProvidedEnv, syncBackendBinding, headers, validatePayload, syncPayloadSchema, }: {
|
|
73
82
|
request: CfTypes.Request<CFHostMetada>;
|
|
74
83
|
searchParams: SearchParams;
|
|
75
|
-
env
|
|
84
|
+
env?: TEnv | undefined;
|
|
76
85
|
/** Only there for type-level reasons */
|
|
77
86
|
ctx: CfTypes.ExecutionContext;
|
|
78
87
|
/** Binding name of the sync backend Durable Object */
|
|
79
|
-
syncBackendBinding: MakeWorkerOptions<TEnv>["syncBackendBinding"];
|
|
88
|
+
syncBackendBinding: MakeWorkerOptions<TEnv, TSyncPayload>["syncBackendBinding"];
|
|
80
89
|
headers?: CfTypes.HeadersInit | undefined;
|
|
81
|
-
validatePayload?:
|
|
82
|
-
|
|
83
|
-
}) => void | Promise<void>;
|
|
90
|
+
validatePayload?: MakeWorkerOptions<TEnv, TSyncPayload>["validatePayload"];
|
|
91
|
+
syncPayloadSchema?: MakeWorkerOptions<TEnv, TSyncPayload>["syncPayloadSchema"];
|
|
84
92
|
}) => Promise<CfTypes.Response>;
|
|
85
93
|
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"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"}
|
package/dist/cf-worker/worker.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { env as importedEnv } from 'cloudflare:workers';
|
|
2
|
+
import { UnknownError } from '@livestore/common';
|
|
3
|
+
import { Effect, Schema } from '@livestore/utils/effect';
|
|
3
4
|
import { matchSyncRequest } from "./shared.js";
|
|
4
5
|
/**
|
|
5
6
|
* Produces a Cloudflare Worker `fetch` handler that delegates sync traffic to the
|
|
@@ -12,14 +13,14 @@ export const makeWorker = (options) => {
|
|
|
12
13
|
return {
|
|
13
14
|
fetch: async (request, env, _ctx) => {
|
|
14
15
|
const url = new URL(request.url);
|
|
15
|
-
const corsHeaders = options.enableCORS
|
|
16
|
+
const corsHeaders = options.enableCORS === true
|
|
16
17
|
? {
|
|
17
18
|
'Access-Control-Allow-Origin': '*',
|
|
18
19
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
19
20
|
'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers') ?? '*',
|
|
20
21
|
}
|
|
21
22
|
: {};
|
|
22
|
-
if (request.method === 'OPTIONS' && options.enableCORS) {
|
|
23
|
+
if (request.method === 'OPTIONS' && options.enableCORS === true) {
|
|
23
24
|
return new Response(null, {
|
|
24
25
|
status: 204,
|
|
25
26
|
headers: corsHeaders,
|
|
@@ -36,6 +37,7 @@ export const makeWorker = (options) => {
|
|
|
36
37
|
syncBackendBinding: options.syncBackendBinding,
|
|
37
38
|
headers: corsHeaders,
|
|
38
39
|
validatePayload: options.validatePayload,
|
|
40
|
+
syncPayloadSchema: options.syncPayloadSchema,
|
|
39
41
|
});
|
|
40
42
|
}
|
|
41
43
|
// Only show info message for GET requests to / without sync parameters
|
|
@@ -57,54 +59,69 @@ export const makeWorker = (options) => {
|
|
|
57
59
|
},
|
|
58
60
|
};
|
|
59
61
|
};
|
|
62
|
+
/** Convert CF Request headers to a ForwardedHeaders map */
|
|
63
|
+
const requestHeadersToMap = (request) => {
|
|
64
|
+
const result = new Map();
|
|
65
|
+
request.headers.forEach((value, key) => {
|
|
66
|
+
result.set(key.toLowerCase(), value);
|
|
67
|
+
});
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
60
70
|
/**
|
|
61
|
-
* Handles
|
|
71
|
+
* Handles LiveStore sync requests (e.g. with search params `?storeId=...&transport=...`).
|
|
62
72
|
*
|
|
63
|
-
* @example
|
|
73
|
+
* @example Token-based authentication
|
|
64
74
|
* ```ts
|
|
65
75
|
* const validatePayload = (payload: Schema.JsonValue | undefined, context: { storeId: string }) => {
|
|
66
|
-
* console.log(`Validating connection for store: ${context.storeId}`)
|
|
67
76
|
* if (payload?.authToken !== 'insecure-token-change-me') {
|
|
68
77
|
* throw new Error('Invalid auth token')
|
|
69
78
|
* }
|
|
70
79
|
* }
|
|
80
|
+
* ```
|
|
71
81
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* return handleSyncRequest({
|
|
79
|
-
* request,
|
|
80
|
-
* searchParams,
|
|
81
|
-
* env,
|
|
82
|
-
* ctx,
|
|
83
|
-
* syncBackendBinding: 'SYNC_BACKEND_DO',
|
|
84
|
-
* headers: {},
|
|
85
|
-
* validatePayload,
|
|
86
|
-
* })
|
|
87
|
-
* }
|
|
88
|
-
*
|
|
89
|
-
* return new Response('Invalid path', { status: 400 })
|
|
90
|
-
* }
|
|
82
|
+
* @example Cookie-based authentication
|
|
83
|
+
* ```ts
|
|
84
|
+
* const validatePayload = async (payload: Schema.JsonValue | undefined, { storeId, headers }) => {
|
|
85
|
+
* const cookie = headers.get('cookie')
|
|
86
|
+
* const session = await validateSessionFromCookie(cookie)
|
|
87
|
+
* if (!session) throw new Error('Unauthorized')
|
|
91
88
|
* }
|
|
92
89
|
* ```
|
|
93
90
|
*
|
|
94
|
-
* @throws {
|
|
91
|
+
* @throws {UnknownError} If the payload is invalid
|
|
95
92
|
*/
|
|
96
|
-
export const handleSyncRequest = ({ request, searchParams: { storeId, payload, transport }, env, syncBackendBinding, headers, validatePayload, }) => Effect.gen(function* () {
|
|
93
|
+
export const handleSyncRequest = ({ request, searchParams: { storeId, payload, transport }, env: explicitlyProvidedEnv, syncBackendBinding, headers, validatePayload, syncPayloadSchema, }) => Effect.gen(function* () {
|
|
97
94
|
if (validatePayload !== undefined) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
// Convert request headers to a Map for the validation context
|
|
96
|
+
const requestHeaders = requestHeadersToMap(request);
|
|
97
|
+
// Always decode with the supplied schema when present, even if payload is undefined.
|
|
98
|
+
// This ensures required payloads are enforced by the schema.
|
|
99
|
+
if (syncPayloadSchema !== undefined) {
|
|
100
|
+
const decodedEither = Schema.decodeUnknownEither(syncPayloadSchema)(payload);
|
|
101
|
+
if (decodedEither._tag === 'Left') {
|
|
102
|
+
const message = decodedEither.left.toString();
|
|
103
|
+
console.error('Invalid payload (decode failed)', message);
|
|
104
|
+
return new Response(message, { status: 400, ...(headers !== undefined ? { headers } : {}) });
|
|
105
|
+
}
|
|
106
|
+
const result = yield* Effect.promise(async () => validatePayload(decodedEither.right, { storeId, headers: requestHeaders })).pipe(UnknownError.mapToUnknownError, Effect.either);
|
|
107
|
+
if (result._tag === 'Left') {
|
|
108
|
+
console.error('Invalid payload (validation failed)', result.left);
|
|
109
|
+
return new Response(result.left.toString(), { status: 400, ...(headers !== undefined ? { headers } : {}) });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const result = yield* Effect.promise(async () => validatePayload(payload, { storeId, headers: requestHeaders })).pipe(UnknownError.mapToUnknownError, Effect.either);
|
|
114
|
+
if (result._tag === 'Left') {
|
|
115
|
+
console.error('Invalid payload (validation failed)', result.left);
|
|
116
|
+
return new Response(result.left.toString(), { status: 400, ...(headers !== undefined ? { headers } : {}) });
|
|
117
|
+
}
|
|
102
118
|
}
|
|
103
119
|
}
|
|
120
|
+
const env = explicitlyProvidedEnv ?? importedEnv;
|
|
104
121
|
if (!(syncBackendBinding in env)) {
|
|
105
122
|
return new Response(`Failed dependency: Required Durable Object binding '${syncBackendBinding}' not available`, {
|
|
106
123
|
status: 424,
|
|
107
|
-
headers,
|
|
124
|
+
...(headers !== undefined ? { headers } : {}),
|
|
108
125
|
});
|
|
109
126
|
}
|
|
110
127
|
const durableObjectNamespace = env[syncBackendBinding];
|
|
@@ -115,7 +132,7 @@ export const handleSyncRequest = ({ request, searchParams: { storeId, payload, t
|
|
|
115
132
|
if (transport === 'ws' && (upgradeHeader === null || upgradeHeader !== 'websocket')) {
|
|
116
133
|
return new Response('Durable Object expected Upgrade: websocket', {
|
|
117
134
|
status: 426,
|
|
118
|
-
headers,
|
|
135
|
+
...(headers !== undefined ? { headers } : {}),
|
|
119
136
|
});
|
|
120
137
|
}
|
|
121
138
|
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,
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"do-rpc-client.d.ts","sourceRoot":"","sources":["../../../src/client/transport/do-rpc-client.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"do-rpc-client.d.ts","sourceRoot":"","sources":["../../../src/client/transport/do-rpc-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAgB,MAAM,mBAAmB,CAAA;AAE7D,OAAO,EAAE,KAAK,OAAO,EAA8B,MAAM,sBAAsB,CAAA;AAgB/E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AAIxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE,MAAM,WAAW,kBAAmB,SAAQ,OAAO,CAAC,iBAAiB,EAAE,uBAAuB;CAAG;AAMjG,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,eAAe,EAAE,kBAAkB,CAAA;IACnC,2GAA2G;IAC3G,oBAAoB,EAAE;QACpB,+CAA+C;QAC/C,WAAW,EAAE,MAAM,CAAA;QACnB,sCAAsC;QACtC,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACvB,2CAA2C,gBAAgB,KAAG,WAAW,CAAC,sBAAsB,CAAC,YAAY,CA8GnD,CAAA;AAE7D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,OAAO,kBAckD,CAAA"}
|