@livestore/sync-cf 0.4.0-dev.20 → 0.4.0-dev.22

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 (51) hide show
  1. package/README.md +1 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cf-worker/do/durable-object.d.ts +1 -1
  4. package/dist/cf-worker/do/durable-object.d.ts.map +1 -1
  5. package/dist/cf-worker/do/durable-object.js +9 -5
  6. package/dist/cf-worker/do/durable-object.js.map +1 -1
  7. package/dist/cf-worker/do/layer.d.ts +2 -2
  8. package/dist/cf-worker/do/layer.js +1 -1
  9. package/dist/cf-worker/do/pull.d.ts +6 -1
  10. package/dist/cf-worker/do/pull.d.ts.map +1 -1
  11. package/dist/cf-worker/do/pull.js +2 -2
  12. package/dist/cf-worker/do/pull.js.map +1 -1
  13. package/dist/cf-worker/do/push.d.ts +3 -2
  14. package/dist/cf-worker/do/push.d.ts.map +1 -1
  15. package/dist/cf-worker/do/push.js +3 -3
  16. package/dist/cf-worker/do/push.js.map +1 -1
  17. package/dist/cf-worker/do/sqlite.d.ts +1 -1
  18. package/dist/cf-worker/do/sqlite.js +1 -1
  19. package/dist/cf-worker/do/transport/do-rpc-server.d.ts.map +1 -1
  20. package/dist/cf-worker/do/transport/do-rpc-server.js +4 -2
  21. package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -1
  22. package/dist/cf-worker/do/transport/http-rpc-server.d.ts +2 -1
  23. package/dist/cf-worker/do/transport/http-rpc-server.d.ts.map +1 -1
  24. package/dist/cf-worker/do/transport/http-rpc-server.js +16 -13
  25. package/dist/cf-worker/do/transport/http-rpc-server.js.map +1 -1
  26. package/dist/cf-worker/do/transport/ws-rpc-server.d.ts +2 -1
  27. package/dist/cf-worker/do/transport/ws-rpc-server.d.ts.map +1 -1
  28. package/dist/cf-worker/do/transport/ws-rpc-server.js +24 -6
  29. package/dist/cf-worker/do/transport/ws-rpc-server.js.map +1 -1
  30. package/dist/cf-worker/shared.d.ts +53 -8
  31. package/dist/cf-worker/shared.d.ts.map +1 -1
  32. package/dist/cf-worker/shared.js +27 -0
  33. package/dist/cf-worker/shared.js.map +1 -1
  34. package/dist/cf-worker/worker.d.ts +31 -31
  35. package/dist/cf-worker/worker.d.ts.map +1 -1
  36. package/dist/cf-worker/worker.js +20 -23
  37. package/dist/cf-worker/worker.js.map +1 -1
  38. package/dist/common/do-rpc-schema.d.ts +3 -3
  39. package/dist/common/do-rpc-schema.js +1 -1
  40. package/package.json +4 -4
  41. package/src/cf-worker/do/durable-object.ts +10 -4
  42. package/src/cf-worker/do/layer.ts +1 -1
  43. package/src/cf-worker/do/pull.ts +13 -5
  44. package/src/cf-worker/do/push.ts +10 -2
  45. package/src/cf-worker/do/sqlite.ts +1 -1
  46. package/src/cf-worker/do/transport/do-rpc-server.ts +4 -2
  47. package/src/cf-worker/do/transport/http-rpc-server.ts +11 -5
  48. package/src/cf-worker/do/transport/ws-rpc-server.ts +29 -10
  49. package/src/cf-worker/shared.ts +86 -8
  50. package/src/cf-worker/worker.ts +48 -34
  51. package/src/common/do-rpc-schema.ts +1 -1
@@ -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;AAGnE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAoElE;;;;;;;;;;;;;;;GAeG;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;AAuBD,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":"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"}
@@ -1,10 +1,16 @@
1
1
  import type { HelperTypes } from '@livestore/common-cf';
2
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.
@@ -14,11 +20,6 @@ export type MakeWorkerOptions<TEnv extends Env = Env, TSyncPayload = Schema.Json
14
20
  * Binding name of the sync Durable Object declared in wrangler config.
15
21
  */
16
22
  syncBackendBinding: HelperTypes.ExtractDurableObjectKeys<TEnv>;
17
- /**
18
- * Validates the payload during WebSocket connection establishment.
19
- * Note: This runs only at connection time, not for individual push events.
20
- * For push event validation, use the `onPush` callback in the durable object.
21
- */
22
23
  /**
23
24
  * Optionally pass a schema to decode the client-provided payload into a typed object
24
25
  * before calling {@link validatePayload}. If omitted, the raw JSON value is forwarded.
@@ -26,11 +27,23 @@ export type MakeWorkerOptions<TEnv extends Env = Env, TSyncPayload = Schema.Json
26
27
  syncPayloadSchema?: Schema.Schema<TSyncPayload>;
27
28
  /**
28
29
  * Validates the (optionally decoded) payload during WebSocket connection establishment.
29
- * If {@link syncPayloadSchema} is provided, `payload` will be of the schemas inferred type.
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
+ *
43
+ * Note: This runs only at connection time, not for individual push events.
44
+ * For push event validation, use the `onPush` callback in the Durable Object.
30
45
  */
31
- validatePayload?: (payload: TSyncPayload, context: {
32
- storeId: string;
33
- }) => void | Promise<void>;
46
+ validatePayload?: (payload: TSyncPayload, context: ValidatePayloadContext) => void | Promise<void>;
34
47
  /** @default false */
35
48
  enableCORS?: boolean;
36
49
  };
@@ -45,34 +58,21 @@ export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc exte
45
58
  /**
46
59
  * Handles LiveStore sync requests (e.g. with search params `?storeId=...&transport=...`).
47
60
  *
48
- * @example
61
+ * @example Token-based authentication
49
62
  * ```ts
50
63
  * const validatePayload = (payload: Schema.JsonValue | undefined, context: { storeId: string }) => {
51
- * console.log(`Validating connection for store: ${context.storeId}`)
52
64
  * if (payload?.authToken !== 'insecure-token-change-me') {
53
65
  * throw new Error('Invalid auth token')
54
66
  * }
55
67
  * }
68
+ * ```
56
69
  *
57
- * export default {
58
- * fetch: async (request, env, ctx) => {
59
- * const searchParams = matchSyncRequest(request)
60
- *
61
- * // Is LiveStore sync request
62
- * if (searchParams !== undefined) {
63
- * return handleSyncRequest({
64
- * request,
65
- * searchParams,
66
- * env,
67
- * ctx,
68
- * syncBackendBinding: 'SYNC_BACKEND_DO',
69
- * headers: {},
70
- * validatePayload,
71
- * })
72
- * }
73
- *
74
- * return new Response('Invalid path', { status: 400 })
75
- * }
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')
76
76
  * }
77
77
  * ```
78
78
  *
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAU,MAAM,EAAE,MAAM,yBAAyB,CAAA;AACxD,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,EAAE,YAAY,GAAG,MAAM,CAAC,SAAS,IAAI;IACvF;;OAEG;IACH,kBAAkB,EAAE,WAAW,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAA;IAC9D;;;;OAIG;IACH;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IAC/C;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/F,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,EAClF,YAAY,GAAG,MAAM,CAAC,SAAS,EAE/B,SAAS,iBAAiB,CAAC,IAAI,EAAE,YAAY,CAAC,KAC7C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAwDlC,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,YAAY,GAAG,MAAM,CAAC,SAAS,EAC/B,0JAQC;IACD,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;IACtC,YAAY,EAAE,YAAY,CAAA;IAC1B,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;IACtB,wCAAwC;IACxC,GAAG,EAAE,OAAO,CAAC,gBAAgB,CAAA;IAC7B,sDAAsD;IACtD,kBAAkB,EAAE,iBAAiB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,oBAAoB,CAAC,CAAA;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,GAAG,SAAS,CAAA;IACzC,eAAe,CAAC,EAAE,iBAAiB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,iBAAiB,CAAC,CAAA;IAC1E,iBAAiB,CAAC,EAAE,iBAAiB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,mBAAmB,CAAC,CAAA;CAC/E,KAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CA+D0B,CAAA"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"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"}
@@ -59,37 +59,32 @@ export const makeWorker = (options) => {
59
59
  },
60
60
  };
61
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
+ };
62
70
  /**
63
71
  * Handles LiveStore sync requests (e.g. with search params `?storeId=...&transport=...`).
64
72
  *
65
- * @example
73
+ * @example Token-based authentication
66
74
  * ```ts
67
75
  * const validatePayload = (payload: Schema.JsonValue | undefined, context: { storeId: string }) => {
68
- * console.log(`Validating connection for store: ${context.storeId}`)
69
76
  * if (payload?.authToken !== 'insecure-token-change-me') {
70
77
  * throw new Error('Invalid auth token')
71
78
  * }
72
79
  * }
80
+ * ```
73
81
  *
74
- * export default {
75
- * fetch: async (request, env, ctx) => {
76
- * const searchParams = matchSyncRequest(request)
77
- *
78
- * // Is LiveStore sync request
79
- * if (searchParams !== undefined) {
80
- * return handleSyncRequest({
81
- * request,
82
- * searchParams,
83
- * env,
84
- * ctx,
85
- * syncBackendBinding: 'SYNC_BACKEND_DO',
86
- * headers: {},
87
- * validatePayload,
88
- * })
89
- * }
90
- *
91
- * return new Response('Invalid path', { status: 400 })
92
- * }
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')
93
88
  * }
94
89
  * ```
95
90
  *
@@ -97,6 +92,8 @@ export const makeWorker = (options) => {
97
92
  */
98
93
  export const handleSyncRequest = ({ request, searchParams: { storeId, payload, transport }, env: explicitlyProvidedEnv, syncBackendBinding, headers, validatePayload, syncPayloadSchema, }) => Effect.gen(function* () {
99
94
  if (validatePayload !== undefined) {
95
+ // Convert request headers to a Map for the validation context
96
+ const requestHeaders = requestHeadersToMap(request);
100
97
  // Always decode with the supplied schema when present, even if payload is undefined.
101
98
  // This ensures required payloads are enforced by the schema.
102
99
  if (syncPayloadSchema !== undefined) {
@@ -106,14 +103,14 @@ export const handleSyncRequest = ({ request, searchParams: { storeId, payload, t
106
103
  console.error('Invalid payload (decode failed)', message);
107
104
  return new Response(message, { status: 400, headers });
108
105
  }
109
- const result = yield* Effect.promise(async () => validatePayload(decodedEither.right, { storeId })).pipe(UnknownError.mapToUnknownError, Effect.either);
106
+ const result = yield* Effect.promise(async () => validatePayload(decodedEither.right, { storeId, headers: requestHeaders })).pipe(UnknownError.mapToUnknownError, Effect.either);
110
107
  if (result._tag === 'Left') {
111
108
  console.error('Invalid payload (validation failed)', result.left);
112
109
  return new Response(result.left.toString(), { status: 400, headers });
113
110
  }
114
111
  }
115
112
  else {
116
- const result = yield* Effect.promise(async () => validatePayload(payload, { storeId })).pipe(UnknownError.mapToUnknownError, Effect.either);
113
+ const result = yield* Effect.promise(async () => validatePayload(payload, { storeId, headers: requestHeaders })).pipe(UnknownError.mapToUnknownError, Effect.either);
117
114
  if (result._tag === 'Left') {
118
115
  console.error('Invalid payload (validation failed)', result.left);
119
116
  return new Response(result.left.toString(), { status: 400, headers });
@@ -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":"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"}
@@ -2,7 +2,7 @@ import { InvalidPullError, InvalidPushError } from '@livestore/common';
2
2
  import { Rpc, RpcGroup, Schema } from '@livestore/utils/effect';
3
3
  declare const SyncDoRpc_base: RpcGroup.RpcGroup<Rpc.Rpc<"SyncDoRpc.Pull", Schema.Struct<{
4
4
  /**
5
- * While the storeId is already implied by the durable object, we still need the explicit storeId
5
+ * While the storeId is already implied by the Durable Object, we still need the explicit storeId
6
6
  * since a DO doesn't know its own id.name value. 🫠
7
7
  * https://community.cloudflare.com/t/how-can-i-get-the-name-of-a-durable-object-from-itself/505961/8
8
8
  */
@@ -44,7 +44,7 @@ declare const SyncDoRpc_base: RpcGroup.RpcGroup<Rpc.Rpc<"SyncDoRpc.Pull", Schema
44
44
  rpcRequestId: typeof Schema.String;
45
45
  }>, typeof InvalidPullError>, typeof Schema.Never, never> | Rpc.Rpc<"SyncDoRpc.Push", Schema.Struct<{
46
46
  /**
47
- * While the storeId is already implied by the durable object, we still need the explicit storeId
47
+ * While the storeId is already implied by the Durable Object, we still need the explicit storeId
48
48
  * since a DO doesn't know its own id.name value. 🫠
49
49
  * https://community.cloudflare.com/t/how-can-i-get-the-name-of-a-durable-object-from-itself/505961/8
50
50
  */
@@ -62,7 +62,7 @@ declare const SyncDoRpc_base: RpcGroup.RpcGroup<Rpc.Rpc<"SyncDoRpc.Pull", Schema
62
62
  backendId: Schema.Option<Schema.SchemaClass<string, string, never>>;
63
63
  }>, Schema.Struct<{}>, typeof InvalidPushError, never> | Rpc.Rpc<"SyncDoRpc.Ping", Schema.Struct<{
64
64
  /**
65
- * While the storeId is already implied by the durable object, we still need the explicit storeId
65
+ * While the storeId is already implied by the Durable Object, we still need the explicit storeId
66
66
  * since a DO doesn't know its own id.name value. 🫠
67
67
  * https://community.cloudflare.com/t/how-can-i-get-the-name-of-a-durable-object-from-itself/505961/8
68
68
  */
@@ -3,7 +3,7 @@ import { Rpc, RpcGroup, Schema } from '@livestore/utils/effect';
3
3
  import * as SyncMessage from "./sync-message-types.js";
4
4
  const commonPayloadFields = {
5
5
  /**
6
- * While the storeId is already implied by the durable object, we still need the explicit storeId
6
+ * While the storeId is already implied by the Durable Object, we still need the explicit storeId
7
7
  * since a DO doesn't know its own id.name value. 🫠
8
8
  * https://community.cloudflare.com/t/how-can-i-get-the-name-of-a-durable-object-from-itself/505961/8
9
9
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livestore/sync-cf",
3
- "version": "0.4.0-dev.20",
3
+ "version": "0.4.0-dev.22",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -10,9 +10,9 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@cloudflare/workers-types": "4.20251118.0",
13
- "@livestore/common": "0.4.0-dev.20",
14
- "@livestore/utils": "0.4.0-dev.20",
15
- "@livestore/common-cf": "0.4.0-dev.20"
13
+ "@livestore/common-cf": "0.4.0-dev.22",
14
+ "@livestore/utils": "0.4.0-dev.22",
15
+ "@livestore/common": "0.4.0-dev.22"
16
16
  },
17
17
  "files": [
18
18
  "dist",
@@ -16,6 +16,7 @@ import {
16
16
  } from '@livestore/utils/effect'
17
17
  import {
18
18
  type Env,
19
+ extractForwardedHeaders,
19
20
  type MakeDurableObjectClassOptions,
20
21
  matchSyncRequest,
21
22
  type SyncBackendRpcInterface,
@@ -48,7 +49,7 @@ export type MakeDurableObjectClass = (options?: MakeDurableObjectClassOptions) =
48
49
 
49
50
  /**
50
51
  * Creates a Durable Object class for handling WebSocket-based sync.
51
- * A sync durable object is uniquely scoped to a specific `storeId`.
52
+ * A sync Durable Object is uniquely scoped to a specific `storeId`.
52
53
  *
53
54
  * The sync DO supports 3 transport modes:
54
55
  * - HTTP JSON-RPC
@@ -155,16 +156,20 @@ export const makeDurableObject: MakeDurableObjectClass = (options) => {
155
156
  throw new Error(`Transport ${transport} is not enabled (based on \`options.enabledTransports\`)`)
156
157
  }
157
158
 
159
+ // Extract headers to forward based on configuration (available for all transports)
160
+ const headers = extractForwardedHeaders(request, options?.forwardHeaders)
161
+
158
162
  if (transport === 'http') {
159
- return yield* this.handleHttp(request)
163
+ return yield* this.handleHttp(request, headers)
160
164
  }
161
165
 
162
166
  if (transport === 'ws') {
163
167
  const { 0: client, 1: server } = new WebSocketPair()
164
168
 
165
169
  // Since we're using websocket hibernation, we need to remember the storeId for subsequent `webSocketMessage` calls
170
+ // Also store forwarded headers so they're available after hibernation resume
166
171
  server.serializeAttachment(
167
- Schema.encodeSync(WebSocketAttachmentSchema)({ storeId, payload, pullRequestIds: [] }),
172
+ Schema.encodeSync(WebSocketAttachmentSchema)({ storeId, payload, pullRequestIds: [], headers }),
168
173
  )
169
174
 
170
175
  // See https://developers.cloudflare.com/durable-objects/examples/websocket-hibernation-server
@@ -220,10 +225,11 @@ export const makeDurableObject: MakeDurableObjectClass = (options) => {
220
225
  *
221
226
  * Requires the `enable_request_signal` compatibility flag to properly support `pull` streaming responses
222
227
  */
223
- private handleHttp = (request: CfTypes.Request) =>
228
+ private handleHttp = (request: CfTypes.Request, forwardedHeaders: Record<string, string> | undefined) =>
224
229
  createHttpRpcHandler({
225
230
  request,
226
231
  responseHeaders: options?.http?.responseHeaders,
232
+ forwardedHeaders,
227
233
  }).pipe(Effect.withSpan('@livestore/sync-cf:durable-object:handleHttp'))
228
234
 
229
235
  private runEffectAsPromise = <T, E = never>(effect: Effect.Effect<T, E, Scope.Scope>): Promise<T> =>
@@ -81,7 +81,7 @@ export class DoCtx extends Effect.Service<DoCtx>()('DoCtx', {
81
81
 
82
82
  // TODO do concistency check with eventlog table to make sure the head is consistent
83
83
 
84
- // Should be the same backendId for lifetime of the durable object
84
+ // Should be the same backendId for lifetime of the Durable Object
85
85
  const backendId = storageRow?.backendId ?? nanoid()
86
86
 
87
87
  const updateCurrentHead = (currentHead: EventSequenceNumber.Global.Type) => {
@@ -3,6 +3,7 @@ import { splitChunkBySize } from '@livestore/common/sync'
3
3
  import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
4
4
  import { MAX_PULL_EVENTS_PER_MESSAGE, MAX_WS_MESSAGE_BYTES } from '../../common/constants.ts'
5
5
  import { SyncMessage } from '../../common/mod.ts'
6
+ import type { ForwardedHeaders } from '../shared.ts'
6
7
  import { DoCtx } from './layer.ts'
7
8
 
8
9
  const encodePullResponse = Schema.encodeSync(SyncMessage.PullResponse)
@@ -16,15 +17,22 @@ const encodePullResponse = Schema.encodeSync(SyncMessage.PullResponse)
16
17
  // DO RPC:
17
18
  // - Further chunks will be emitted manually in `push.ts`
18
19
  // - If the client sends a `Interrupt` RPC message, TODO
19
- export const makeEndingPullStream = (
20
- req: SyncMessage.PullRequest,
21
- payload: Schema.JsonValue | undefined,
22
- ): Stream.Stream<SyncMessage.PullResponse, InvalidPullError, DoCtx> =>
20
+ export const makeEndingPullStream = ({
21
+ req,
22
+ payload,
23
+ headers,
24
+ }: {
25
+ req: SyncMessage.PullRequest
26
+ payload: Schema.JsonValue | undefined
27
+ headers: ForwardedHeaders | undefined
28
+ }): Stream.Stream<SyncMessage.PullResponse, InvalidPullError, DoCtx> =>
23
29
  Effect.gen(function* () {
24
30
  const { doOptions, backendId, storeId, storage } = yield* DoCtx
25
31
 
26
32
  if (doOptions?.onPull) {
27
- yield* Effect.tryAll(() => doOptions!.onPull!(req, { storeId, payload })).pipe(UnknownError.mapToUnknownError)
33
+ yield* Effect.tryAll(() => doOptions!.onPull!(req, { storeId, payload, headers })).pipe(
34
+ UnknownError.mapToUnknownError,
35
+ )
28
36
  }
29
37
 
30
38
  if (req.cursor._tag === 'Some' && req.cursor.value.backendId !== backendId) {
@@ -10,7 +10,13 @@ import { type CfTypes, emitStreamResponse } from '@livestore/common-cf'
10
10
  import { Chunk, Effect, Option, type RpcMessage, Schema } from '@livestore/utils/effect'
11
11
  import { MAX_PUSH_EVENTS_PER_REQUEST, MAX_WS_MESSAGE_BYTES } from '../../common/constants.ts'
12
12
  import { SyncMessage } from '../../common/mod.ts'
13
- import { type Env, type MakeDurableObjectClassOptions, type StoreId, WebSocketAttachmentSchema } from '../shared.ts'
13
+ import {
14
+ type Env,
15
+ type ForwardedHeaders,
16
+ type MakeDurableObjectClassOptions,
17
+ type StoreId,
18
+ WebSocketAttachmentSchema,
19
+ } from '../shared.ts'
14
20
  import { DoCtx } from './layer.ts'
15
21
 
16
22
  const encodePullResponse = Schema.encodeSync(SyncMessage.PullResponse)
@@ -19,12 +25,14 @@ type PullBatchItem = SyncMessage.PullResponse['batch'][number]
19
25
  export const makePush =
20
26
  ({
21
27
  payload,
28
+ headers,
22
29
  options,
23
30
  storeId,
24
31
  ctx,
25
32
  env,
26
33
  }: {
27
34
  payload: Schema.JsonValue | undefined
35
+ headers: ForwardedHeaders | undefined
28
36
  options: MakeDurableObjectClassOptions | undefined
29
37
  storeId: StoreId
30
38
  ctx: CfTypes.DurableObjectState
@@ -40,7 +48,7 @@ export const makePush =
40
48
  }
41
49
 
42
50
  if (options?.onPush) {
43
- yield* Effect.tryAll(() => options.onPush!(pushRequest, { storeId, payload })).pipe(
51
+ yield* Effect.tryAll(() => options.onPush!(pushRequest, { storeId, payload, headers })).pipe(
44
52
  UnknownError.mapToUnknownError,
45
53
  )
46
54
  }
@@ -23,7 +23,7 @@ export const eventlogTable = State.SQLite.table({
23
23
  })
24
24
 
25
25
  /**
26
- * Context metadata table - one row per durable object.
26
+ * Context metadata table - one row per Durable Object.
27
27
  *
28
28
  * ⚠️ IMPORTANT: Any changes to this schema require bumping PERSISTENCE_FORMAT_VERSION in shared.ts
29
29
  */
@@ -49,7 +49,8 @@ export const createDoRpcHandler = (
49
49
  })
50
50
  }
51
51
 
52
- return makeEndingPullStream(req, req.payload)
52
+ // DO-RPC doesn't have HTTP headers context - headers are undefined
53
+ return makeEndingPullStream({ req, payload: req.payload, headers: undefined })
53
54
  }).pipe(
54
55
  Stream.unwrap,
55
56
  Stream.map((res) => ({
@@ -63,7 +64,8 @@ export const createDoRpcHandler = (
63
64
  'SyncDoRpc.Push': (req) =>
64
65
  Effect.gen(this, function* () {
65
66
  const { doOptions, ctx, env, storeId } = yield* DoCtx
66
- const push = makePush({ storeId, payload: req.payload, options: doOptions, ctx, env })
67
+ // DO-RPC doesn't have HTTP headers context - headers are undefined
68
+ const push = makePush({ storeId, payload: req.payload, headers: undefined, options: doOptions, ctx, env })
67
69
 
68
70
  return yield* push(req)
69
71
  }).pipe(
@@ -2,6 +2,7 @@ import type { CfTypes } from '@livestore/common-cf'
2
2
  import { Effect, HttpApp, Layer, RpcSerialization, RpcServer } from '@livestore/utils/effect'
3
3
  import { SyncHttpRpc } from '../../../common/http-rpc-schema.ts'
4
4
  import * as SyncMessage from '../../../common/sync-message-types.ts'
5
+ import { headersRecordToMap } from '../../shared.ts'
5
6
  import { DoCtx } from '../layer.ts'
6
7
  import { makeEndingPullStream } from '../pull.ts'
7
8
  import { makePush } from '../push.ts'
@@ -9,12 +10,14 @@ import { makePush } from '../push.ts'
9
10
  export const createHttpRpcHandler = ({
10
11
  request,
11
12
  responseHeaders,
13
+ forwardedHeaders,
12
14
  }: {
13
15
  request: CfTypes.Request
14
16
  responseHeaders?: Record<string, string>
17
+ forwardedHeaders?: Record<string, string>
15
18
  }) =>
16
19
  Effect.gen(function* () {
17
- const handlerLayer = createHttpRpcLayer
20
+ const handlerLayer = createHttpRpcLayer(forwardedHeaders)
18
21
  const httpApp = RpcServer.toHttpApp(SyncHttpRpc).pipe(Effect.provide(handlerLayer))
19
22
  const webHandler = yield* httpApp.pipe(Effect.map(HttpApp.toWebHandler))
20
23
 
@@ -31,15 +34,17 @@ export const createHttpRpcHandler = ({
31
34
  return response
32
35
  }).pipe(Effect.withSpan('createHttpRpcHandler'))
33
36
 
34
- const createHttpRpcLayer =
37
+ const createHttpRpcLayer = (forwardedHeaders: Record<string, string> | undefined) => {
38
+ const headers = headersRecordToMap(forwardedHeaders)
39
+
35
40
  // TODO implement admin requests
36
- SyncHttpRpc.toLayer({
37
- 'SyncHttpRpc.Pull': (req) => makeEndingPullStream(req, req.payload),
41
+ return SyncHttpRpc.toLayer({
42
+ 'SyncHttpRpc.Pull': (req) => makeEndingPullStream({ req, payload: req.payload, headers }),
38
43
 
39
44
  'SyncHttpRpc.Push': (req) =>
40
45
  Effect.gen(function* () {
41
46
  const { ctx, env, doOptions, storeId } = yield* DoCtx
42
- const push = makePush({ payload: undefined, options: doOptions, storeId, ctx, env })
47
+ const push = makePush({ payload: undefined, headers, options: doOptions, storeId, ctx, env })
43
48
 
44
49
  return yield* push(req)
45
50
  }),
@@ -49,3 +54,4 @@ const createHttpRpcLayer =
49
54
  Layer.provideMerge(RpcServer.layerProtocolHttp({ path: '/http-rpc' })),
50
55
  Layer.provideMerge(RpcSerialization.layerJson),
51
56
  )
57
+ }
@@ -1,26 +1,30 @@
1
1
  import { InvalidPullError, InvalidPushError } from '@livestore/common'
2
- import { Effect, identity, Layer, RpcServer, Stream } from '@livestore/utils/effect'
2
+ import { WsContext } from '@livestore/common-cf'
3
+ import { Effect, identity, Layer, RpcServer, Schema, Stream } from '@livestore/utils/effect'
3
4
  import { SyncWsRpc } from '../../../common/ws-rpc-schema.ts'
5
+ import { headersRecordToMap, WebSocketAttachmentSchema } from '../../shared.ts'
4
6
  import { DoCtx, type DoCtxInput } from '../layer.ts'
5
7
  import { makeEndingPullStream } from '../pull.ts'
6
8
  import { makePush } from '../push.ts'
7
9
 
8
10
  export const makeRpcServer = ({ doSelf, doOptions }: Omit<DoCtxInput, 'from'>) => {
9
- // TODO implement admin requests
10
11
  const handlersLayer = SyncWsRpc.toLayer({
11
12
  'SyncWsRpc.Pull': (req) =>
12
- makeEndingPullStream(req, req.payload).pipe(
13
- // Needed to keep the stream alive on the client side for phase 2 (i.e. not send the `Exit` stream RPC message)
14
- req.live ? Stream.concat(Stream.never) : identity,
15
- Stream.provideLayer(DoCtx.Default({ doSelf, doOptions, from: { storeId: req.storeId } })),
16
- Stream.mapError((cause) => (cause._tag === 'InvalidPullError' ? cause : InvalidPullError.make({ cause }))),
17
- // Stream.tapErrorCause(Effect.log),
18
- ),
13
+ Effect.gen(function* () {
14
+ const headers = yield* getForwardedHeaders
15
+ return makeEndingPullStream({ req, payload: req.payload, headers }).pipe(
16
+ // Needed to keep the stream alive on the client side for phase 2 (i.e. not send the `Exit` stream RPC message)
17
+ req.live ? Stream.concat(Stream.never) : identity,
18
+ Stream.provideLayer(DoCtx.Default({ doSelf, doOptions, from: { storeId: req.storeId } })),
19
+ Stream.mapError((cause) => (cause._tag === 'InvalidPullError' ? cause : InvalidPullError.make({ cause }))),
20
+ )
21
+ }).pipe(Stream.unwrap),
19
22
  'SyncWsRpc.Push': (req) =>
20
23
  Effect.gen(function* () {
21
24
  const { doOptions, storeId, ctx, env } = yield* DoCtx
25
+ const headers = yield* getForwardedHeaders
22
26
 
23
- const push = makePush({ options: doOptions, storeId, payload: req.payload, ctx, env })
27
+ const push = makePush({ options: doOptions, storeId, payload: req.payload, headers, ctx, env })
24
28
 
25
29
  return yield* push(req)
26
30
  }).pipe(
@@ -32,3 +36,18 @@ export const makeRpcServer = ({ doSelf, doOptions }: Omit<DoCtxInput, 'from'>) =
32
36
 
33
37
  return RpcServer.layer(SyncWsRpc).pipe(Layer.provide(handlersLayer))
34
38
  }
39
+
40
+ /** Extracts forwarded headers from the WebSocket attachment */
41
+ const getForwardedHeaders = Effect.gen(function* () {
42
+ const { ws } = yield* WsContext
43
+ const attachment = ws.deserializeAttachment()
44
+ const decoded = Schema.decodeUnknownEither(WebSocketAttachmentSchema)(attachment)
45
+ if (decoded._tag === 'Left') {
46
+ yield* Effect.logError('Failed to decode WebSocket attachment for forwarded headers', { error: decoded.left })
47
+ ws.close(1011, 'invalid-attachment')
48
+ return yield* Effect.die('Invalid WebSocket attachment (headers decode failed)')
49
+ }
50
+
51
+ const headers = headersRecordToMap(decoded.right.headers)
52
+ return headers
53
+ })