orez-sync-cf-host 0.4.64 → 0.4.66

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orez-sync-cf-host",
3
- "version": "0.4.64",
3
+ "version": "0.4.66",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
Binary file
package/src/host.ts CHANGED
@@ -73,6 +73,13 @@ type MutationResult = {
73
73
  result: Record<string, unknown>
74
74
  }
75
75
 
76
+ type DelegatedMutationResult = { id?: { clientID?: unknown; id?: unknown } }
77
+ type DelegatedPushBody = {
78
+ mutations?: DelegatedMutationResult[]
79
+ pushResponse?: unknown
80
+ [key: string]: unknown
81
+ }
82
+
76
83
  type EngineState = { watermark: string; floor: string; upstreamWatermark: string }
77
84
  type UpstreamBatch = {
78
85
  watermark: number
@@ -135,6 +142,18 @@ function json(value: unknown, status = 200): Response {
135
142
  })
136
143
  }
137
144
 
145
+ function isStructuredPushFailed(value: unknown): value is Record<string, unknown> {
146
+ if (typeof value !== 'object' || value === null) return false
147
+ const body = value as Record<string, unknown>
148
+ return (
149
+ body.kind === 'PushFailed' &&
150
+ typeof body.origin === 'string' &&
151
+ typeof body.reason === 'string' &&
152
+ typeof body.message === 'string' &&
153
+ Array.isArray(body.mutationIDs)
154
+ )
155
+ }
156
+
138
157
  function statusOf(error: unknown): number {
139
158
  if (typeof error === 'object' && error !== null && 'status' in error) {
140
159
  const status = Number(error.status)
@@ -1057,14 +1076,21 @@ export function createSyncDurableObject<Env extends SyncHostEnv>(
1057
1076
  if (!upstreamResponse.ok) {
1058
1077
  return new Response(upstreamResponse.body, upstreamResponse)
1059
1078
  }
1060
- const upstreamBody = (await upstreamResponse.json()) as {
1061
- mutations?: Array<{ id?: { clientID?: unknown; id?: unknown } }>
1062
- pushResponse?: {
1063
- mutations?: Array<{ id?: { clientID?: unknown; id?: unknown } }>
1064
- }
1079
+ const upstreamBody = (await upstreamResponse.json()) as DelegatedPushBody
1080
+ const delegatedResponse = upstreamBody.pushResponse ?? upstreamBody
1081
+ if (isStructuredPushFailed(delegatedResponse)) {
1082
+ // PushFailed is a successful protocol response describing an
1083
+ // application-level failure. There are intentionally no mutation
1084
+ // acknowledgements to finalize in the host; preserve the body so
1085
+ // the Zero client can apply its retry/error policy.
1086
+ return json({ pushResponse: delegatedResponse })
1065
1087
  }
1066
1088
  const acknowledged =
1067
- upstreamBody.pushResponse?.mutations ?? upstreamBody.mutations
1089
+ typeof upstreamBody.pushResponse === 'object' &&
1090
+ upstreamBody.pushResponse !== null &&
1091
+ 'mutations' in upstreamBody.pushResponse
1092
+ ? upstreamBody.pushResponse.mutations
1093
+ : upstreamBody.mutations
1068
1094
  if (!Array.isArray(acknowledged)) {
1069
1095
  throw new Error('delegated push returned no mutation results')
1070
1096
  }
@@ -1104,7 +1130,7 @@ export function createSyncDurableObject<Env extends SyncHostEnv>(
1104
1130
  )
1105
1131
  }
1106
1132
  await this.#ingest(upstreamPath)
1107
- return json({ pushResponse: upstreamBody.pushResponse ?? upstreamBody })
1133
+ return json({ pushResponse: delegatedResponse })
1108
1134
  } catch (error) {
1109
1135
  const status = statusOf(error)
1110
1136
  return json(errorBody(error), status)
@@ -32,6 +32,7 @@ const numericTextNamespaces = new Set<string>()
32
32
  const hydratedNamespaces = new Set<string>()
33
33
  let delegatedFailuresRemaining = 0
34
34
  let delegatedAttempts = 0
35
+ let delegatedPushFailedRemaining = 0
35
36
 
36
37
  const config: SyncHostConfig<Env> = {
37
38
  hostVersion: 'upstream-ingest-harness',
@@ -191,6 +192,18 @@ export class AppService extends WorkerEntrypoint<Env> {
191
192
  )
192
193
  }
193
194
  delegatedAttempts++
195
+ if (delegatedPushFailedRemaining > 0) {
196
+ delegatedPushFailedRemaining--
197
+ return Promise.resolve(
198
+ Response.json({
199
+ kind: 'PushFailed',
200
+ origin: 'server',
201
+ reason: 'database',
202
+ mutationIDs: [{ clientID: 'writer', id: 2 }],
203
+ message: 'synthetic mutation result persistence failure',
204
+ })
205
+ )
206
+ }
194
207
  if (delegatedFailuresRemaining > 0) {
195
208
  delegatedFailuresRemaining--
196
209
  return Promise.resolve(
@@ -236,7 +249,11 @@ export default {
236
249
  if (url.pathname === '/delegation-control') {
237
250
  if (request.method === 'GET') {
238
251
  return Promise.resolve(
239
- Response.json({ delegatedFailuresRemaining, delegatedAttempts })
252
+ Response.json({
253
+ delegatedFailuresRemaining,
254
+ delegatedPushFailedRemaining,
255
+ delegatedAttempts,
256
+ })
240
257
  )
241
258
  }
242
259
  return request
@@ -247,8 +264,16 @@ export default {
247
264
  0,
248
265
  Number((body as { failures?: unknown }).failures) || 0
249
266
  )
267
+ delegatedPushFailedRemaining = Math.max(
268
+ 0,
269
+ Number((body as { pushFailed?: unknown }).pushFailed) || 0
270
+ )
250
271
  delegatedAttempts = 0
251
- return Response.json({ delegatedFailuresRemaining, delegatedAttempts })
272
+ return Response.json({
273
+ delegatedFailuresRemaining,
274
+ delegatedPushFailedRemaining,
275
+ delegatedAttempts,
276
+ })
252
277
  })
253
278
  }
254
279
  if (url.pathname.startsWith('/upstream/')) {