orez-sync-cf-host 0.16.1 → 0.16.2

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/dist/host.js CHANGED
@@ -1293,7 +1293,7 @@ export function createSyncDurableObject(config) {
1293
1293
  // Zero registry, synchronously: patch application order is
1294
1294
  // arrival order by construction, with no cross-request await
1295
1295
  // between resolution and the engine transaction.
1296
- const patch = resolveQueryPatch(queries.patch, config.queries, claims, transformVersion, requestError);
1296
+ const patch = resolveQueryPatch(queries.patch, config.queries, claims, transformVersion, requestError, config.tolerateUnknownQueries === true);
1297
1297
  body = { ...body, queries: { ...queries, patch } };
1298
1298
  }
1299
1299
  body = { ...body, _serverQueryTransformVersion: transformVersion };
@@ -14,8 +14,21 @@ import type { AnyQueryRegistry } from '@rocicorp/zero';
14
14
  * `asQueryInternals`, whose instance-tag check fails whenever the app's
15
15
  * registry and this host resolve different copies of the zero module.
16
16
  *
17
- * A resolution error fails the whole patch (the pull is refused as one unit),
18
- * but the refusal names the query it came from, so a client that registers
19
- * one unknown query alongside nine good ones is told which one is unknown.
17
+ * By default a name the registry does not contain fails the whole patch (the
18
+ * pull is refused as one unit) and the refusal names the query it came from, so
19
+ * a client that registers one unknown query alongside nine good ones is told
20
+ * which one is unknown. Where client and server ship together that is the right
21
+ * answer: an unknown name means the registry was not regenerated, and a loud
22
+ * failure is how the developer finds out.
23
+ *
24
+ * A host that serves independently shipped clients sets
25
+ * `tolerateUnknownQueries`. A native binary is always older than the server it
26
+ * talks to, so it can ask for a query since renamed or removed; that is version
27
+ * skew rather than a fault. Under the flag the unknown put is dropped and the
28
+ * rest of the patch resolves, leaving the client without rows for that one
29
+ * query instead of unable to sync anything at all.
30
+ *
31
+ * A query that IS registered but fails to build is a real fault in the app's
32
+ * own code either way, so it always fails the whole patch and names the query.
20
33
  */
21
- export declare function resolveQueryPatch(patch: readonly unknown[], queries: AnyQueryRegistry, context: unknown, transformVersion: number, fail: (message: string) => Error): unknown[];
34
+ export declare function resolveQueryPatch(patch: readonly unknown[], queries: AnyQueryRegistry, context: unknown, transformVersion: number, fail: (message: string) => Error, tolerateUnknownQueries?: boolean): unknown[];
@@ -15,18 +15,35 @@ const NAME_SEPARATOR = /[.|]/;
15
15
  * `asQueryInternals`, whose instance-tag check fails whenever the app's
16
16
  * registry and this host resolve different copies of the zero module.
17
17
  *
18
- * A resolution error fails the whole patch (the pull is refused as one unit),
19
- * but the refusal names the query it came from, so a client that registers
20
- * one unknown query alongside nine good ones is told which one is unknown.
18
+ * By default a name the registry does not contain fails the whole patch (the
19
+ * pull is refused as one unit) and the refusal names the query it came from, so
20
+ * a client that registers one unknown query alongside nine good ones is told
21
+ * which one is unknown. Where client and server ship together that is the right
22
+ * answer: an unknown name means the registry was not regenerated, and a loud
23
+ * failure is how the developer finds out.
24
+ *
25
+ * A host that serves independently shipped clients sets
26
+ * `tolerateUnknownQueries`. A native binary is always older than the server it
27
+ * talks to, so it can ask for a query since renamed or removed; that is version
28
+ * skew rather than a fault. Under the flag the unknown put is dropped and the
29
+ * rest of the patch resolves, leaving the client without rows for that one
30
+ * query instead of unable to sync anything at all.
31
+ *
32
+ * A query that IS registered but fails to build is a real fault in the app's
33
+ * own code either way, so it always fails the whole patch and names the query.
21
34
  */
22
- export function resolveQueryPatch(patch, queries, context, transformVersion, fail) {
23
- const result = [...patch];
24
- patch.forEach((operation, index) => {
25
- if (!operation || typeof operation !== 'object')
26
- return;
35
+ export function resolveQueryPatch(patch, queries, context, transformVersion, fail, tolerateUnknownQueries = false) {
36
+ const result = [];
37
+ for (const operation of patch) {
38
+ if (!operation || typeof operation !== 'object') {
39
+ result.push(operation);
40
+ continue;
41
+ }
27
42
  const op = operation;
28
- if (op.op !== 'put')
29
- return;
43
+ if (op.op !== 'put') {
44
+ result.push(operation);
45
+ continue;
46
+ }
30
47
  if (typeof op.name !== 'string') {
31
48
  throw fail('query put requires a server-resolved named query');
32
49
  }
@@ -41,7 +58,11 @@ export function resolveQueryPatch(patch, queries, context, transformVersion, fai
41
58
  }
42
59
  const custom = node;
43
60
  if (!custom || typeof custom.fn !== 'function') {
44
- throw fail(`unknown or unsupported named query: ${op.name}`);
61
+ if (!tolerateUnknownQueries)
62
+ throw fail(`unknown or unsupported named query: ${op.name}`);
63
+ // version skew: drop this query and keep syncing the rest.
64
+ console.warn(`orez: dropping unknown named query from pull: ${op.name}`);
65
+ continue;
45
66
  }
46
67
  let ast;
47
68
  try {
@@ -56,14 +77,14 @@ export function resolveQueryPatch(patch, queries, context, transformVersion, fai
56
77
  }
57
78
  catch (error) {
58
79
  const detail = error instanceof Error ? error.message : String(error);
59
- throw fail(`unknown or unsupported named query: ${op.name}: ${detail}`);
80
+ throw fail(`named query failed to build: ${op.name}: ${detail}`);
60
81
  }
61
- result[index] = {
82
+ result.push({
62
83
  op: 'put',
63
84
  hash: op.hash,
64
85
  ast,
65
86
  transformVersion,
66
- };
67
- });
87
+ });
88
+ }
68
89
  return result;
69
90
  }
package/dist/types.d.ts CHANGED
@@ -106,6 +106,19 @@ export type SyncHostConfig<Env extends SyncHostEnv = SyncHostEnv, S extends Sche
106
106
  * query context. There is no app endpoint to call and nothing else to wire.
107
107
  */
108
108
  queries: AnyQueryRegistry;
109
+ /**
110
+ * Drop a desired query whose name is not in `queries` instead of refusing the
111
+ * whole pull. Off by default, because where the client ships with the server
112
+ * an unknown name means the registry was not regenerated and the request
113
+ * error is how that gets noticed.
114
+ *
115
+ * Set it when the host serves clients that ship on their own schedule, such
116
+ * as a native app in an app store. Those clients are routinely older than the
117
+ * server and will ask for queries that have since been renamed or removed;
118
+ * without this the pull is refused as one unit and an app one feature out of
119
+ * date cannot sync anything at all.
120
+ */
121
+ tolerateUnknownQueries?: boolean;
109
122
  /**
110
123
  * Streaming fields for this namespace: which columns may carry a live,
111
124
  * uncommitted value, and their publish mode and rate bounds.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orez-sync-cf-host",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
4
4
  "description": "Internal orez Cloudflare sync host. Do not import directly: consumers use orez-lite/cloudflare/sync.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -48,7 +48,7 @@
48
48
  "bundle": "bun run build:dist && wrangler deploy --dry-run --outdir .wrangler/bundle",
49
49
  "measure": "bun run build:dist && bun measure.mjs",
50
50
  "test": "bun run test:config && bun run test:query-compiler && bun run test:package && bun run test:platform && bun run test:integration && bun run test:large-data && bun run test:ingest && bun run test:restart",
51
- "test:config": "bun test config.test.mjs write-safeguards.test.mjs transaction-query.test.mjs upstream-response.test.mjs serving-lag.test.mjs",
51
+ "test:config": "bun test config.test.mjs query-patch.test.mjs write-safeguards.test.mjs transaction-query.test.mjs upstream-response.test.mjs serving-lag.test.mjs",
52
52
  "test:integration": "bun run build:dist && bun integration-test.mjs",
53
53
  "test:ingest": "bun run build:dist && bun ingest-test.mjs",
54
54
  "test:package": "bun run build:dist && node package-runtime-test.mjs",
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "@opentelemetry/api": "^1.9.0",
63
- "orez-sync-executor": "0.16.1"
63
+ "orez-sync-executor": "0.16.2"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@cloudflare/workers-types": "4.20260617.1",