@livestore/sync-cf 0.4.0-dev.14 → 0.4.0-dev.16

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.
@@ -1,5 +1,5 @@
1
1
  import type { HelperTypes } from '@livestore/common-cf';
2
- import type { Schema } from '@livestore/utils/effect';
2
+ import { Schema } from '@livestore/utils/effect';
3
3
  import type { CfTypes, SearchParams } from '../common/mod.ts';
4
4
  import { type Env } from './shared.ts';
5
5
  export type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = {
@@ -9,7 +9,7 @@ export type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjec
9
9
  * Options accepted by {@link makeWorker}. The Durable Object binding has to be
10
10
  * supplied explicitly so we never fall back to deprecated defaults when Cloudflare config changes.
11
11
  */
12
- export type MakeWorkerOptions<TEnv extends Env = Env> = {
12
+ export type MakeWorkerOptions<TEnv extends Env = Env, TSyncPayload = Schema.JsonValue> = {
13
13
  /**
14
14
  * Binding name of the sync Durable Object declared in wrangler config.
15
15
  */
@@ -19,7 +19,16 @@ export type MakeWorkerOptions<TEnv extends Env = Env> = {
19
19
  * Note: This runs only at connection time, not for individual push events.
20
20
  * For push event validation, use the `onPush` callback in the durable object.
21
21
  */
22
- validatePayload?: (payload: Schema.JsonValue | undefined, context: {
22
+ /**
23
+ * Optionally pass a schema to decode the client-provided payload into a typed object
24
+ * before calling {@link validatePayload}. If omitted, the raw JSON value is forwarded.
25
+ */
26
+ syncPayloadSchema?: Schema.Schema<TSyncPayload>;
27
+ /**
28
+ * Validates the (optionally decoded) payload during WebSocket connection establishment.
29
+ * If {@link syncPayloadSchema} is provided, `payload` will be of the schema’s inferred type.
30
+ */
31
+ validatePayload?: (payload: TSyncPayload, context: {
23
32
  storeId: string;
24
33
  }) => void | Promise<void>;
25
34
  /** @default false */
@@ -32,7 +41,7 @@ export type MakeWorkerOptions<TEnv extends Env = Env> = {
32
41
  * For more complex setups prefer implementing a custom `fetch` and call {@link handleSyncRequest}
33
42
  * from the branch that handles LiveStore sync requests.
34
43
  */
35
- export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>(options: MakeWorkerOptions<TEnv>) => CFWorker<TEnv, TDurableObjectRpc>;
44
+ 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
45
  /**
37
46
  * Handles LiveStore sync requests (e.g. with search params `?storeId=...&transport=...`).
38
47
  *
@@ -69,17 +78,16 @@ export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc exte
69
78
  *
70
79
  * @throws {UnexpectedError} 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: explicitlyProvidedEnv, 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
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?: (payload: Schema.JsonValue | undefined, context: {
82
- storeId: string;
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":"AAEA,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,uIAOC;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,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,CA2C0B,CAAA"}
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,6 +1,6 @@
1
1
  import { env as importedEnv } from 'cloudflare:workers';
2
2
  import { UnexpectedError } from '@livestore/common';
3
- import { Effect } from '@livestore/utils/effect';
3
+ import { Effect, Schema } from '@livestore/utils/effect';
4
4
  import { matchSyncRequest } from "./shared.js";
5
5
  /**
6
6
  * Produces a Cloudflare Worker `fetch` handler that delegates sync traffic to the
@@ -37,6 +37,7 @@ export const makeWorker = (options) => {
37
37
  syncBackendBinding: options.syncBackendBinding,
38
38
  headers: corsHeaders,
39
39
  validatePayload: options.validatePayload,
40
+ syncPayloadSchema: options.syncPayloadSchema,
40
41
  });
41
42
  }
42
43
  // Only show info message for GET requests to / without sync parameters
@@ -94,12 +95,29 @@ export const makeWorker = (options) => {
94
95
  *
95
96
  * @throws {UnexpectedError} If the payload is invalid
96
97
  */
97
- export const handleSyncRequest = ({ request, searchParams: { storeId, payload, transport }, env: explicitlyProvidedEnv, syncBackendBinding, headers, validatePayload, }) => Effect.gen(function* () {
98
+ export const handleSyncRequest = ({ request, searchParams: { storeId, payload, transport }, env: explicitlyProvidedEnv, syncBackendBinding, headers, validatePayload, syncPayloadSchema, }) => Effect.gen(function* () {
98
99
  if (validatePayload !== undefined) {
99
- const result = yield* Effect.promise(async () => validatePayload(payload, { storeId })).pipe(UnexpectedError.mapToUnexpectedError, Effect.either);
100
- if (result._tag === 'Left') {
101
- console.error('Invalid payload', result.left);
102
- return new Response(result.left.toString(), { status: 400, headers });
100
+ // Always decode with the supplied schema when present, even if payload is undefined.
101
+ // This ensures required payloads are enforced by the schema.
102
+ if (syncPayloadSchema !== undefined) {
103
+ const decodedEither = Schema.decodeUnknownEither(syncPayloadSchema)(payload);
104
+ if (decodedEither._tag === 'Left') {
105
+ const message = decodedEither.left.toString();
106
+ console.error('Invalid payload (decode failed)', message);
107
+ return new Response(message, { status: 400, headers });
108
+ }
109
+ const result = yield* Effect.promise(async () => validatePayload(decodedEither.right, { storeId })).pipe(UnexpectedError.mapToUnexpectedError, Effect.either);
110
+ if (result._tag === 'Left') {
111
+ console.error('Invalid payload (validation failed)', result.left);
112
+ return new Response(result.left.toString(), { status: 400, headers });
113
+ }
114
+ }
115
+ else {
116
+ const result = yield* Effect.promise(async () => validatePayload(payload, { storeId })).pipe(UnexpectedError.mapToUnexpectedError, Effect.either);
117
+ if (result._tag === 'Left') {
118
+ console.error('Invalid payload (validation failed)', result.left);
119
+ return new Response(result.left.toString(), { status: 400, headers });
120
+ }
103
121
  }
104
122
  }
105
123
  const env = explicitlyProvidedEnv ?? importedEnv;
@@ -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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livestore/sync-cf",
3
- "version": "0.4.0-dev.14",
3
+ "version": "0.4.0-dev.16",
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.20250923.0",
13
- "@livestore/common": "0.4.0-dev.14",
14
- "@livestore/utils": "0.4.0-dev.14",
15
- "@livestore/common-cf": "0.4.0-dev.14"
13
+ "@livestore/common-cf": "0.4.0-dev.16",
14
+ "@livestore/utils": "0.4.0-dev.16",
15
+ "@livestore/common": "0.4.0-dev.16"
16
16
  },
17
17
  "files": [
18
18
  "dist",
@@ -1,8 +1,7 @@
1
1
  import { env as importedEnv } from 'cloudflare:workers'
2
2
  import { UnexpectedError } from '@livestore/common'
3
3
  import type { HelperTypes } from '@livestore/common-cf'
4
- import type { Schema } from '@livestore/utils/effect'
5
- import { Effect } from '@livestore/utils/effect'
4
+ import { Effect, Schema } from '@livestore/utils/effect'
6
5
  import type { CfTypes, SearchParams } from '../common/mod.ts'
7
6
  import type { CfDeclare } from './mod.ts'
8
7
  import { type Env, matchSyncRequest } from './shared.ts'
@@ -23,7 +22,7 @@ export type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjec
23
22
  * Options accepted by {@link makeWorker}. The Durable Object binding has to be
24
23
  * supplied explicitly so we never fall back to deprecated defaults when Cloudflare config changes.
25
24
  */
26
- export type MakeWorkerOptions<TEnv extends Env = Env> = {
25
+ export type MakeWorkerOptions<TEnv extends Env = Env, TSyncPayload = Schema.JsonValue> = {
27
26
  /**
28
27
  * Binding name of the sync Durable Object declared in wrangler config.
29
28
  */
@@ -33,7 +32,16 @@ export type MakeWorkerOptions<TEnv extends Env = Env> = {
33
32
  * Note: This runs only at connection time, not for individual push events.
34
33
  * For push event validation, use the `onPush` callback in the durable object.
35
34
  */
36
- validatePayload?: (payload: Schema.JsonValue | undefined, context: { storeId: string }) => void | Promise<void>
35
+ /**
36
+ * Optionally pass a schema to decode the client-provided payload into a typed object
37
+ * before calling {@link validatePayload}. If omitted, the raw JSON value is forwarded.
38
+ */
39
+ syncPayloadSchema?: Schema.Schema<TSyncPayload>
40
+ /**
41
+ * Validates the (optionally decoded) payload during WebSocket connection establishment.
42
+ * If {@link syncPayloadSchema} is provided, `payload` will be of the schema’s inferred type.
43
+ */
44
+ validatePayload?: (payload: TSyncPayload, context: { storeId: string }) => void | Promise<void>
37
45
  /** @default false */
38
46
  enableCORS?: boolean
39
47
  }
@@ -48,8 +56,9 @@ export type MakeWorkerOptions<TEnv extends Env = Env> = {
48
56
  export const makeWorker = <
49
57
  TEnv extends Env = Env,
50
58
  TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined,
59
+ TSyncPayload = Schema.JsonValue,
51
60
  >(
52
- options: MakeWorkerOptions<TEnv>,
61
+ options: MakeWorkerOptions<TEnv, TSyncPayload>,
53
62
  ): CFWorker<TEnv, TDurableObjectRpc> => {
54
63
  return {
55
64
  fetch: async (request, env, _ctx) => {
@@ -74,7 +83,7 @@ export const makeWorker = <
74
83
 
75
84
  // Check if this is a sync request first, before showing info message
76
85
  if (searchParams !== undefined) {
77
- return handleSyncRequest<TEnv, TDurableObjectRpc>({
86
+ return handleSyncRequest<TEnv, TDurableObjectRpc, unknown, TSyncPayload>({
78
87
  request,
79
88
  searchParams,
80
89
  env,
@@ -82,6 +91,7 @@ export const makeWorker = <
82
91
  syncBackendBinding: options.syncBackendBinding,
83
92
  headers: corsHeaders,
84
93
  validatePayload: options.validatePayload,
94
+ syncPayloadSchema: options.syncPayloadSchema,
85
95
  })
86
96
  }
87
97
 
@@ -147,6 +157,7 @@ export const handleSyncRequest = <
147
157
  TEnv extends Env = Env,
148
158
  TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined,
149
159
  CFHostMetada = unknown,
160
+ TSyncPayload = Schema.JsonValue,
150
161
  >({
151
162
  request,
152
163
  searchParams: { storeId, payload, transport },
@@ -154,6 +165,7 @@ export const handleSyncRequest = <
154
165
  syncBackendBinding,
155
166
  headers,
156
167
  validatePayload,
168
+ syncPayloadSchema,
157
169
  }: {
158
170
  request: CfTypes.Request<CFHostMetada>
159
171
  searchParams: SearchParams
@@ -161,20 +173,41 @@ export const handleSyncRequest = <
161
173
  /** Only there for type-level reasons */
162
174
  ctx: CfTypes.ExecutionContext
163
175
  /** Binding name of the sync backend Durable Object */
164
- syncBackendBinding: MakeWorkerOptions<TEnv>['syncBackendBinding']
176
+ syncBackendBinding: MakeWorkerOptions<TEnv, TSyncPayload>['syncBackendBinding']
165
177
  headers?: CfTypes.HeadersInit | undefined
166
- validatePayload?: (payload: Schema.JsonValue | undefined, context: { storeId: string }) => void | Promise<void>
178
+ validatePayload?: MakeWorkerOptions<TEnv, TSyncPayload>['validatePayload']
179
+ syncPayloadSchema?: MakeWorkerOptions<TEnv, TSyncPayload>['syncPayloadSchema']
167
180
  }): Promise<CfTypes.Response> =>
168
181
  Effect.gen(function* () {
169
182
  if (validatePayload !== undefined) {
170
- const result = yield* Effect.promise(async () => validatePayload!(payload, { storeId })).pipe(
171
- UnexpectedError.mapToUnexpectedError,
172
- Effect.either,
173
- )
174
-
175
- if (result._tag === 'Left') {
176
- console.error('Invalid payload', result.left)
177
- return new Response(result.left.toString(), { status: 400, headers })
183
+ // Always decode with the supplied schema when present, even if payload is undefined.
184
+ // This ensures required payloads are enforced by the schema.
185
+ if (syncPayloadSchema !== undefined) {
186
+ const decodedEither = Schema.decodeUnknownEither(syncPayloadSchema)(payload)
187
+ if (decodedEither._tag === 'Left') {
188
+ const message = decodedEither.left.toString()
189
+ console.error('Invalid payload (decode failed)', message)
190
+ return new Response(message, { status: 400, headers })
191
+ }
192
+
193
+ const result = yield* Effect.promise(async () =>
194
+ validatePayload(decodedEither.right as TSyncPayload, { storeId }),
195
+ ).pipe(UnexpectedError.mapToUnexpectedError, Effect.either)
196
+
197
+ if (result._tag === 'Left') {
198
+ console.error('Invalid payload (validation failed)', result.left)
199
+ return new Response(result.left.toString(), { status: 400, headers })
200
+ }
201
+ } else {
202
+ const result = yield* Effect.promise(async () => validatePayload(payload as TSyncPayload, { storeId })).pipe(
203
+ UnexpectedError.mapToUnexpectedError,
204
+ Effect.either,
205
+ )
206
+
207
+ if (result._tag === 'Left') {
208
+ console.error('Invalid payload (validation failed)', result.left)
209
+ return new Response(result.left.toString(), { status: 400, headers })
210
+ }
178
211
  }
179
212
  }
180
213