orez-sync-cf-host 0.5.19 → 0.5.21

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 (52) hide show
  1. package/README.md +49 -6
  2. package/dist/bun-wasm-loader.d.ts +1 -0
  3. package/dist/bun-wasm-loader.js +20 -0
  4. package/dist/config.d.ts +3 -0
  5. package/dist/config.js +93 -0
  6. package/{src → dist}/generated/sync_wasm_bg.wasm +0 -0
  7. package/dist/host.d.ts +12 -0
  8. package/dist/host.js +1512 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.js +4 -0
  11. package/dist/mutation-error.d.ts +9 -0
  12. package/dist/mutation-error.js +23 -0
  13. package/dist/node-wasm-loader.d.ts +1 -0
  14. package/dist/node-wasm-loader.js +22 -0
  15. package/dist/post-commit.d.ts +13 -0
  16. package/dist/post-commit.js +31 -0
  17. package/dist/query-compiler.d.ts +4 -0
  18. package/dist/query-compiler.js +4 -0
  19. package/dist/sql-storage-adapter.d.ts +69 -0
  20. package/dist/sql-storage-adapter.js +194 -0
  21. package/dist/transaction-query.d.ts +72 -0
  22. package/dist/transaction-query.js +379 -0
  23. package/dist/types.d.ts +159 -0
  24. package/dist/types.js +5 -0
  25. package/dist/vite-wasm-loader.d.ts +3 -0
  26. package/dist/vite-wasm-loader.js +24 -0
  27. package/dist/wasm.d.ts +1 -0
  28. package/dist/wasm.js +7 -0
  29. package/dist/write-safeguards.d.ts +53 -0
  30. package/dist/write-safeguards.js +186 -0
  31. package/package.json +59 -19
  32. package/src/bun-wasm-loader.ts +0 -16
  33. package/src/config.ts +0 -102
  34. package/src/harness-config.ts +0 -487
  35. package/src/harness-worker.ts +0 -33
  36. package/src/host.ts +0 -1958
  37. package/src/index.ts +0 -47
  38. package/src/ingest-harness-worker.ts +0 -535
  39. package/src/mutation-error.ts +0 -28
  40. package/src/platform-probe-worker.ts +0 -418
  41. package/src/post-commit.ts +0 -40
  42. package/src/query-compiler.ts +0 -16
  43. package/src/sql-storage-adapter.ts +0 -254
  44. package/src/transaction-query.ts +0 -494
  45. package/src/types.ts +0 -195
  46. package/src/wasm-platform.ts +0 -10
  47. package/src/wasm.ts +0 -34
  48. package/src/write-safeguards.ts +0 -248
  49. /package/{src → dist}/generated/package.json +0 -0
  50. /package/{src → dist}/generated/sync_wasm.d.ts +0 -0
  51. /package/{src → dist}/generated/sync_wasm.js +0 -0
  52. /package/{src → dist}/generated/sync_wasm_bg.wasm.d.ts +0 -0
package/src/index.ts DELETED
@@ -1,47 +0,0 @@
1
- export { createSyncDurableObject, createSyncWorker } from './host.js'
2
- export { createQueryCompiler } from './query-compiler.js'
3
- export {
4
- MutationApplicationError,
5
- isMutationApplicationError,
6
- registerMutators,
7
- } from './types.js'
8
- export {
9
- DEFAULT_TRANSACTION_QUERY_BUDGET,
10
- TransactionQueryBudgetError,
11
- executeTransactionQueryPlan,
12
- executeTransactionQueryPlanAsync,
13
- } from './transaction-query.js'
14
-
15
- export type {
16
- DeferredEffect,
17
- JsonPrimitive,
18
- JsonValue,
19
- MutatorContext,
20
- MutatorRegistry,
21
- MutatorSql,
22
- NormalizedClaims,
23
- PullCaps,
24
- QueryResolver,
25
- RegisteredMutator,
26
- SyncHostConfig,
27
- SyncHostEnv,
28
- ServiceBinding,
29
- SyncSql,
30
- UpstreamConfig,
31
- VisibilityConfig,
32
- VisibilityFilter,
33
- ZeroSchemaConfig,
34
- } from './types.js'
35
- export type {
36
- CompiledTransactionQueryNode,
37
- CompiledTransactionQueryPlan,
38
- CompiledTransactionQueryRelationship,
39
- TransactionQueryBinding,
40
- TransactionQueryBudget,
41
- TransactionQueryColumn,
42
- TransactionQueryColumnType,
43
- TransactionQueryExecutionOptions,
44
- TransactionQueryFormat,
45
- TransactionQueryWireValue,
46
- } from './transaction-query.js'
47
- export type { TransactionQueryCompiler } from './query-compiler.js'
@@ -1,535 +0,0 @@
1
- import { WorkerEntrypoint } from 'cloudflare:workers'
2
-
3
- import { ZeroDO } from '../../../src/cf-do/worker.js'
4
- import { createSyncDurableObject, createSyncWorker } from './index.js'
5
-
6
- import type { SyncHostConfig, SyncHostEnv, ZeroSchemaConfig } from './index.js'
7
-
8
- const schema = {
9
- tables: {
10
- item: {
11
- columns: {
12
- id: { type: 'string' },
13
- label: { type: 'string' },
14
- rank: { type: 'number' },
15
- done: { type: 'boolean' },
16
- meta: { type: 'json' },
17
- },
18
- primaryKey: ['id'],
19
- },
20
- },
21
- } as const satisfies ZeroSchemaConfig
22
-
23
- type Fetcher = { fetch(input: string | Request, init?: RequestInit): Promise<Response> }
24
- interface Env extends SyncHostEnv {
25
- DATA: Fetcher
26
- APP: Fetcher
27
- CAPPED_SYNC_DO: DurableObjectNamespace
28
- UPSTREAM_DO: DurableObjectNamespace
29
- }
30
-
31
- const runawayNamespaces = new Set<string>()
32
- const numericTextNamespaces = new Set<string>()
33
- const jsonValueNamespaces = new Set<string>()
34
- const hydratedNamespaces = new Set<string>()
35
- const heldSnapshots = new Set<string>()
36
- const holdSnapshotsAfterCursor = new Set<string>()
37
- const activeSnapshots = new Set<string>()
38
- const snapshotLimits = new Map<string, number[]>()
39
- const heldDelegatedPushes = new Set<string>()
40
- const activeDelegatedPushes = new Set<string>()
41
- const completedDelegatedPushes = new Set<string>()
42
- const heldChangeResponses = new Set<string>()
43
- const activeHeldChangeResponses = new Set<string>()
44
- let delegatedFailuresRemaining = 0
45
- let delegatedAttempts = 0
46
- let delegatedPushFailedRemaining = 0
47
- let delegatedUrl = ''
48
-
49
- const config: SyncHostConfig<Env> = {
50
- hostVersion: 'upstream-ingest-harness',
51
- schema,
52
- mutateUrl: '/api/zero/push?schema=feed_0&appID=feed',
53
- mutateOrigin: 'https://app.internal',
54
- mutateBinding: 'APP',
55
- delegatedPushRetry: {
56
- maxAttempts: 3,
57
- initialBackoffMs: 10,
58
- maxBackoffMs: 20,
59
- timeoutMs: 1_000,
60
- },
61
- upstream: {
62
- binding: 'DATA',
63
- namespacePath: (namespace) =>
64
- namespace.startsWith('root-mount-') ? '/' : `/${namespace}`,
65
- changeLimit: 2,
66
- intervalMs: 1_000,
67
- ingestBudgetRows: 600,
68
- },
69
- initialize(sql) {
70
- sql.exec(
71
- 'CREATE TABLE IF NOT EXISTS item (id TEXT PRIMARY KEY, label TEXT NOT NULL, rank REAL NOT NULL, done INTEGER NOT NULL, meta TEXT)'
72
- )
73
- },
74
- authenticate(request) {
75
- const userID = request.headers.get('authorization')?.match(/^Bearer token-(.+)$/)?.[1]
76
- return userID ? { userID } : null
77
- },
78
- authorizeWake(request) {
79
- return new URL(request.url).searchParams.get('wakeToken') === 'ingest-harness-wake'
80
- },
81
- authorizeNotify(request, env) {
82
- return Boolean(env.ADMIN_KEY) && request.headers.get('x-admin-key') === env.ADMIN_KEY
83
- },
84
- namespace(request) {
85
- return new URL(request.url).pathname.split('/')[1] || null
86
- },
87
- }
88
-
89
- export const SyncDurableObject = createSyncDurableObject(config)
90
- const cappedConfig: SyncHostConfig<Env> = {
91
- ...config,
92
- caps: { maxChangeRows: 10_000, maxChangeBytes: 1 },
93
- }
94
- export const CappedSyncDurableObject = createSyncDurableObject(cappedConfig)
95
- export { ZeroDO }
96
-
97
- async function upstreamFetch(request: Request, env: Env): Promise<Response> {
98
- const url = new URL(request.url)
99
- const [, namespace, ...rest] = url.pathname.split('/')
100
- if (!namespace) return new Response('namespace required', { status: 400 })
101
- const stub = env.UPSTREAM_DO.get(env.UPSTREAM_DO.idFromName(namespace))
102
- url.pathname = `/${rest.join('/')}`
103
- if (url.pathname === '/_orez/write-budget') return stub.fetch(new Request(url, request))
104
- const exec = async (sql: string, params: unknown[] = []) => {
105
- const response = await stub.fetch('https://upstream.invalid/exec', {
106
- method: 'POST',
107
- headers: { 'content-type': 'application/json' },
108
- body: JSON.stringify({ sql, params }),
109
- })
110
- if (!response.ok) throw new Error(`upstream init failed: ${await response.text()}`)
111
- }
112
- await exec(
113
- 'CREATE TABLE IF NOT EXISTS item (id TEXT PRIMARY KEY, label TEXT NOT NULL, rank REAL NOT NULL, done INTEGER NOT NULL, meta TEXT)'
114
- )
115
- await exec(
116
- 'CREATE TABLE IF NOT EXISTS _zero_schema_tables (name TEXT PRIMARY KEY, schema_json TEXT NOT NULL)'
117
- )
118
- await exec(
119
- 'INSERT OR IGNORE INTO _zero_schema_tables (name, schema_json) VALUES (?, ?)',
120
- ['item', JSON.stringify(schema.tables.item)]
121
- )
122
- return stub.fetch(new Request(url, request))
123
- }
124
-
125
- /** Self service-binding target backed by the real ZeroSqlDO. */
126
- export class DataService extends WorkerEntrypoint<Env> {
127
- async fetch(request: Request): Promise<Response> {
128
- const url = new URL(request.url)
129
- const pathname = url.pathname
130
- if (
131
- !pathname.endsWith('/changes') &&
132
- !pathname.endsWith('/snapshot') &&
133
- !pathname.endsWith('/_orez/write-budget')
134
- ) {
135
- return Promise.resolve(
136
- new Response('DATA route rejected non-feed request', { status: 418 })
137
- )
138
- }
139
- if (pathname === '/changes') {
140
- const cursor = Number(url.searchParams.get('watermark') ?? 0)
141
- return Promise.resolve(
142
- Response.json({
143
- watermark: 1,
144
- changes:
145
- cursor >= 1
146
- ? []
147
- : [
148
- {
149
- watermark: 1,
150
- tableName: 'item',
151
- op: 'INSERT',
152
- rowData: {
153
- id: 'root-feed-row',
154
- label: 'root-mounted upstream feed',
155
- rank: 1,
156
- done: false,
157
- meta: null,
158
- },
159
- oldData: null,
160
- },
161
- ],
162
- })
163
- )
164
- }
165
- if (pathname === '/_orez/write-budget') {
166
- return Promise.resolve(Response.json({ enabled: true, rootMount: true }))
167
- }
168
- const namespace = pathname.split('/')[1] ?? ''
169
- if (pathname.endsWith('/changes') && jsonValueNamespaces.has(namespace)) {
170
- const cursor = Number(new URL(request.url).searchParams.get('watermark') ?? 0)
171
- const values = [
172
- { nested: { tags: ['a', 2, true] } },
173
- [1, 'two', null],
174
- '42',
175
- 'true',
176
- 'null',
177
- '{"looks":"encoded"}',
178
- 42.5,
179
- true,
180
- ]
181
- return Promise.resolve(
182
- Response.json({
183
- watermark: values.length,
184
- changes: values
185
- .map((meta, index) => ({
186
- watermark: index + 1,
187
- tableName: 'item',
188
- op: 'INSERT',
189
- rowData: {
190
- id: `json-${index}`,
191
- label: 'json round trip',
192
- rank: index,
193
- done: false,
194
- meta,
195
- },
196
- oldData: null,
197
- }))
198
- .filter((change) => change.watermark > cursor)
199
- .slice(0, 2),
200
- })
201
- )
202
- }
203
- if (pathname.endsWith('/changes') && numericTextNamespaces.has(namespace)) {
204
- const watermark = Number(new URL(request.url).searchParams.get('watermark') ?? 0)
205
- return Promise.resolve(
206
- Response.json({
207
- watermark: 2,
208
- changes:
209
- watermark >= 2
210
- ? []
211
- : [
212
- {
213
- watermark: 1,
214
- tableName: 'item',
215
- op: 'INSERT',
216
- rowData: {
217
- id: 'numeric-text',
218
- label: 'SQL timestamp text',
219
- rank: '2026-07-11 13:34:46',
220
- done: false,
221
- meta: null,
222
- },
223
- oldData: null,
224
- },
225
- {
226
- watermark: 2,
227
- tableName: 'item',
228
- op: 'INSERT',
229
- rowData: {
230
- id: 'numeric-native',
231
- label: 'native JSON number',
232
- rank: 1783776886000,
233
- done: false,
234
- meta: null,
235
- },
236
- oldData: null,
237
- },
238
- ],
239
- })
240
- )
241
- }
242
- if (pathname.endsWith('/changes') && runawayNamespaces.has(namespace)) {
243
- return Promise.resolve(
244
- Response.json({
245
- watermark: 100,
246
- changes: [
247
- {
248
- watermark: 1,
249
- tableName: 'item',
250
- op: 'INSERT',
251
- rowData: {
252
- id: 'runaway-replay',
253
- label: 'replayed without cursor progress',
254
- rank: 1,
255
- done: false,
256
- meta: null,
257
- },
258
- oldData: null,
259
- },
260
- ],
261
- })
262
- )
263
- }
264
- const response = await upstreamFetch(request, this.env)
265
- if (pathname.endsWith('/changes') && heldChangeResponses.has(namespace)) {
266
- activeHeldChangeResponses.add(namespace)
267
- try {
268
- while (heldChangeResponses.has(namespace)) await scheduler.wait(10)
269
- } finally {
270
- activeHeldChangeResponses.delete(namespace)
271
- }
272
- }
273
- if (pathname.endsWith('/snapshot')) {
274
- const limit = Number(url.searchParams.get('limit'))
275
- if (Number.isSafeInteger(limit)) {
276
- const limits = snapshotLimits.get(namespace) ?? []
277
- limits.push(limit)
278
- snapshotLimits.set(namespace, limits)
279
- }
280
- const shouldHold =
281
- heldSnapshots.has(namespace) &&
282
- (!holdSnapshotsAfterCursor.has(namespace) || url.searchParams.has('cursor'))
283
- if (shouldHold) {
284
- activeSnapshots.add(namespace)
285
- try {
286
- while (
287
- heldSnapshots.has(namespace) &&
288
- (!holdSnapshotsAfterCursor.has(namespace) || url.searchParams.has('cursor'))
289
- ) {
290
- await scheduler.wait(10)
291
- }
292
- } finally {
293
- activeSnapshots.delete(namespace)
294
- }
295
- }
296
- }
297
- if (pathname.endsWith('/changes') && response.ok) hydratedNamespaces.add(namespace)
298
- return response
299
- }
300
- }
301
-
302
- export class AppService extends WorkerEntrypoint<Env> {
303
- async fetch(request: Request): Promise<Response> {
304
- delegatedUrl = request.url
305
- if (!new URL(request.url).pathname.endsWith('/api/zero/push')) {
306
- return Promise.resolve(
307
- new Response('APP route rejected non-push request', { status: 418 })
308
- )
309
- }
310
- const namespace = new URL(request.url).pathname.split('/')[1] ?? ''
311
- if (!hydratedNamespaces.has(namespace)) {
312
- return Promise.resolve(
313
- Response.json({ error: 'schema provisioning has not completed' }, { status: 500 })
314
- )
315
- }
316
- delegatedAttempts++
317
- if (delegatedPushFailedRemaining > 0) {
318
- delegatedPushFailedRemaining--
319
- return Promise.resolve(
320
- Response.json({
321
- kind: 'PushFailed',
322
- origin: 'server',
323
- reason: 'database',
324
- mutationIDs: [{ clientID: 'writer', id: 2 }],
325
- message: 'synthetic mutation result persistence failure',
326
- })
327
- )
328
- }
329
- if (delegatedFailuresRemaining > 0) {
330
- delegatedFailuresRemaining--
331
- return Promise.resolve(
332
- Response.json({ error: 'synthetic delegated push failure' }, { status: 503 })
333
- )
334
- }
335
- if (heldDelegatedPushes.has(namespace)) {
336
- activeDelegatedPushes.add(namespace)
337
- try {
338
- while (heldDelegatedPushes.has(namespace)) await scheduler.wait(10)
339
- } finally {
340
- activeDelegatedPushes.delete(namespace)
341
- }
342
- }
343
- const push = (await request.clone().json()) as {
344
- mutations?: Array<{ clientID?: string; id?: number; name?: string }>
345
- }
346
- const cleanupIDs = new Set(
347
- (push.mutations ?? [])
348
- .filter((mutation) => mutation.name === '_zero_cleanupResults')
349
- .map((mutation) => `${mutation.clientID}:${mutation.id}`)
350
- )
351
- const response = await upstreamFetch(request, this.env)
352
- completedDelegatedPushes.add(namespace)
353
- if (response.ok && cleanupIDs.size > 0) {
354
- const body = (await response.json()) as {
355
- mutations?: Array<{ id?: { clientID?: string; id?: number } }>
356
- pushResponse?: {
357
- mutations?: Array<{ id?: { clientID?: string; id?: number } }>
358
- }
359
- }
360
- const mutations = body.pushResponse?.mutations ?? body.mutations
361
- if (Array.isArray(mutations)) {
362
- const filtered = mutations.filter(
363
- (mutation) => !cleanupIDs.has(`${mutation.id?.clientID}:${mutation.id?.id}`)
364
- )
365
- if (body.pushResponse) body.pushResponse.mutations = filtered
366
- else body.mutations = filtered
367
- }
368
- return Response.json(body, { status: response.status })
369
- }
370
- return response
371
- }
372
- }
373
-
374
- const syncWorker = createSyncWorker(config)
375
- const cappedSyncWorker = createSyncWorker(cappedConfig)
376
- export default {
377
- fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
378
- const url = new URL(request.url)
379
- if (url.pathname.startsWith('/delegated-ingest-control/')) {
380
- const namespace = url.pathname.slice('/delegated-ingest-control/'.length)
381
- if (request.method === 'GET') {
382
- return Promise.resolve(
383
- Response.json({
384
- appHeld: heldDelegatedPushes.has(namespace),
385
- appActive: activeDelegatedPushes.has(namespace),
386
- appCompleted: completedDelegatedPushes.has(namespace),
387
- changesHeld: heldChangeResponses.has(namespace),
388
- changesActive: activeHeldChangeResponses.has(namespace),
389
- })
390
- )
391
- }
392
- return request
393
- .json()
394
- .catch(() => ({}))
395
- .then((body) => {
396
- if ((body as { appHeld?: unknown }).appHeld === true) {
397
- heldDelegatedPushes.add(namespace)
398
- completedDelegatedPushes.delete(namespace)
399
- } else if ((body as { appHeld?: unknown }).appHeld === false) {
400
- heldDelegatedPushes.delete(namespace)
401
- }
402
- if ((body as { changesHeld?: unknown }).changesHeld === true) {
403
- heldChangeResponses.add(namespace)
404
- } else if ((body as { changesHeld?: unknown }).changesHeld === false) {
405
- heldChangeResponses.delete(namespace)
406
- }
407
- return Response.json({ ok: true, namespace })
408
- })
409
- }
410
- if (url.pathname.startsWith('/snapshot-control/')) {
411
- const namespace = url.pathname.slice('/snapshot-control/'.length)
412
- if (request.method === 'GET') {
413
- return Promise.resolve(
414
- Response.json({
415
- active: activeSnapshots.has(namespace),
416
- held: heldSnapshots.has(namespace),
417
- afterCursor: holdSnapshotsAfterCursor.has(namespace),
418
- limits: snapshotLimits.get(namespace) ?? [],
419
- })
420
- )
421
- }
422
- return request
423
- .json()
424
- .catch(() => ({}))
425
- .then((body) => {
426
- if ((body as { hold?: unknown }).hold === true) {
427
- heldSnapshots.add(namespace)
428
- if ((body as { afterCursor?: unknown }).afterCursor === true) {
429
- holdSnapshotsAfterCursor.add(namespace)
430
- } else {
431
- holdSnapshotsAfterCursor.delete(namespace)
432
- }
433
- } else {
434
- heldSnapshots.delete(namespace)
435
- holdSnapshotsAfterCursor.delete(namespace)
436
- }
437
- if ((body as { reset?: unknown }).reset === true)
438
- snapshotLimits.set(namespace, [])
439
- return Response.json({
440
- active: activeSnapshots.has(namespace),
441
- held: heldSnapshots.has(namespace),
442
- afterCursor: holdSnapshotsAfterCursor.has(namespace),
443
- limits: snapshotLimits.get(namespace) ?? [],
444
- })
445
- })
446
- }
447
- if (url.pathname.startsWith('/json-values-control/')) {
448
- const namespace = url.pathname.slice('/json-values-control/'.length)
449
- return request
450
- .json()
451
- .catch(() => ({}))
452
- .then((body) => {
453
- if ((body as { enabled?: unknown }).enabled === true)
454
- jsonValueNamespaces.add(namespace)
455
- else jsonValueNamespaces.delete(namespace)
456
- return Response.json({ ok: true, namespace })
457
- })
458
- }
459
- if (url.pathname.startsWith('/numeric-text-control/')) {
460
- const namespace = url.pathname.slice('/numeric-text-control/'.length)
461
- return request
462
- .json()
463
- .catch(() => ({}))
464
- .then((body) => {
465
- if ((body as { enabled?: unknown }).enabled === true)
466
- numericTextNamespaces.add(namespace)
467
- else numericTextNamespaces.delete(namespace)
468
- return Response.json({ ok: true, namespace })
469
- })
470
- }
471
- if (url.pathname.startsWith('/runaway-control/')) {
472
- const namespace = url.pathname.slice('/runaway-control/'.length)
473
- return request
474
- .json()
475
- .catch(() => ({}))
476
- .then((body) => {
477
- if ((body as { enabled?: unknown }).enabled === true)
478
- runawayNamespaces.add(namespace)
479
- else runawayNamespaces.delete(namespace)
480
- return Response.json({
481
- ok: true,
482
- namespace,
483
- enabled: runawayNamespaces.has(namespace),
484
- })
485
- })
486
- }
487
- if (url.pathname === '/delegation-control') {
488
- if (request.method === 'GET') {
489
- return Promise.resolve(
490
- Response.json({
491
- delegatedFailuresRemaining,
492
- delegatedPushFailedRemaining,
493
- delegatedAttempts,
494
- delegatedUrl,
495
- })
496
- )
497
- }
498
- return request
499
- .json()
500
- .catch(() => ({}))
501
- .then((body) => {
502
- delegatedFailuresRemaining = Math.max(
503
- 0,
504
- Number((body as { failures?: unknown }).failures) || 0
505
- )
506
- delegatedPushFailedRemaining = Math.max(
507
- 0,
508
- Number((body as { pushFailed?: unknown }).pushFailed) || 0
509
- )
510
- delegatedAttempts = 0
511
- delegatedUrl = ''
512
- return Response.json({
513
- delegatedFailuresRemaining,
514
- delegatedPushFailedRemaining,
515
- delegatedAttempts,
516
- delegatedUrl,
517
- })
518
- })
519
- }
520
- if (url.pathname.startsWith('/capped/')) {
521
- url.pathname = url.pathname.slice('/capped'.length)
522
- const cappedEnv = { ...env, SYNC_DO: env.CAPPED_SYNC_DO }
523
- return cappedSyncWorker.fetch!(
524
- new Request(url, request) as never,
525
- cappedEnv,
526
- ctx
527
- ) as Promise<Response>
528
- }
529
- if (url.pathname.startsWith('/upstream/')) {
530
- url.pathname = url.pathname.slice('/upstream'.length)
531
- return upstreamFetch(new Request(url, request), env)
532
- }
533
- return syncWorker.fetch!(request as never, env, ctx) as Promise<Response>
534
- },
535
- }
@@ -1,28 +0,0 @@
1
- // the app-rejection contract shared by every orez sync host and by
2
- // protocol-neutral authoring layers (on-zero adapters). classification is
3
- // STRUCTURAL, never instanceof: adapters must not depend on orez to reject a
4
- // mutation, and instanceof breaks across package copies (dual-package
5
- // installs, --into tarballs, workspace duplication). any error whose name is
6
- // 'MutationApplicationError' with string details/message is claiming this
7
- // contract deliberately.
8
- export class MutationApplicationError extends Error {
9
- constructor(
10
- readonly details: string,
11
- message = details
12
- ) {
13
- super(message)
14
- this.name = 'MutationApplicationError'
15
- }
16
- }
17
-
18
- export function isMutationApplicationError(
19
- error: unknown
20
- ): error is { name: string; message: string; details: string } {
21
- if (typeof error !== 'object' || error === null) return false
22
- const shaped = error as { name?: unknown; message?: unknown; details?: unknown }
23
- return (
24
- shaped.name === 'MutationApplicationError' &&
25
- typeof shaped.message === 'string' &&
26
- typeof shaped.details === 'string'
27
- )
28
- }