orez-sync-cf-host 0.11.1 → 0.11.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
@@ -1,6 +1,6 @@
1
1
  import { DurableObject } from 'cloudflare:workers';
2
- import { createSocketHost } from 'orez-lite/realtime';
3
2
  import { createSyncExecutor } from 'orez-sync-executor/core';
3
+ import { createSocketHost } from 'orez-sync-executor/realtime';
4
4
  import { validatePullCaps, validateSyncHostConfig } from './config.js';
5
5
  import { createQueryCompiler } from './query-compiler.js';
6
6
  import { resolveQueryPatch } from './query-patch.js';
@@ -1026,6 +1026,13 @@ export function createSyncDurableObject(config) {
1026
1026
  if (queryAware && body.queries) {
1027
1027
  const queries = body.queries;
1028
1028
  if (Array.isArray(queries.patch)) {
1029
+ // matches the browser host: a query-aware namespace with no
1030
+ // resolver refuses the patch (400) instead of crashing (500).
1031
+ // #queryAwareOverride can force queryAware on, so this is
1032
+ // reachable even though validateSyncHostConfig passes.
1033
+ if (!config.resolveQueries) {
1034
+ throw requestError('query put requires a server-resolved named query');
1035
+ }
1029
1036
  // resolveQueries may be async and needs `env` (a consumer can
1030
1037
  // delegate the transform to its app's real synced-queries
1031
1038
  // endpoint over an app service binding — authenticate runs in the
@@ -1690,12 +1697,17 @@ export function createSyncDurableObject(config) {
1690
1697
  // subscriptions have to be replayed before this frame is applied.
1691
1698
  await this.#realtimeRehydrate(host);
1692
1699
  const connection = this.#realtimeConnections.get(socket);
1693
- if (!connection)
1700
+ if (!connection) {
1701
+ // a wake-only socket (no clientGroupID on the upgrade) has no realtime
1702
+ // identity, so a frame here is a subscription that can never deliver.
1703
+ // Closing names the cause; ignoring it would look like streaming that
1704
+ // silently stopped.
1705
+ socket.close(1008, 'realtime frames require clientGroupID on the wake upgrade');
1694
1706
  return;
1707
+ }
1695
1708
  connection.handleMessage(message);
1696
- // The hub may authorize asynchronously, so the topic set this frame
1697
- // changed is not final until that settles.
1698
- await Promise.resolve();
1709
+ // handleMessage moves the owned-topic set synchronously before any async
1710
+ // delivery work starts, so the set is final when it returns.
1699
1711
  this.#realtimePersist(socket, connection);
1700
1712
  }
1701
1713
  webSocketClose(socket, code, reason, _wasClean) {
@@ -6,9 +6,10 @@ export type QueryResolutionRequest = {
6
6
  /**
7
7
  * One resolved query, positionally matched to the request at the same index.
8
8
  *
9
- * An `error` fails only its own query rather than the whole patch, so a client
10
- * that registers one unknown query alongside nine good ones is told which one
11
- * is unknown.
9
+ * An `error` fails the whole patch (the pull is refused as one unit), but the
10
+ * refusal names the query it came from and carries its resolver error, so a
11
+ * client that registers one unknown query alongside nine good ones is told
12
+ * which one is unknown.
12
13
  */
13
14
  export type QueryResolution = {
14
15
  readonly ast: JsonValue;
@@ -34,6 +34,8 @@ export async function resolveQueryPatch(patch, resolve, transformVersion, fail)
34
34
  if (puts.length === 0)
35
35
  return [...patch];
36
36
  const resolved = await resolve(requests);
37
+ // broken positional arity is a host-side resolver bug, not a client error,
38
+ // so it deliberately bypasses fail() and surfaces as a 500
37
39
  if (resolved.length !== requests.length) {
38
40
  throw new Error(`query resolver returned ${resolved.length} results for ${requests.length} queries`);
39
41
  }
@@ -41,7 +43,8 @@ export async function resolveQueryPatch(patch, resolve, transformVersion, fail)
41
43
  for (const put of puts) {
42
44
  const resolution = resolved[requestByKey.get(put.key)];
43
45
  if (!resolution || 'error' in resolution) {
44
- throw fail(`unknown or unsupported named query: ${put.name}`);
46
+ const detail = resolution && 'error' in resolution ? `: ${resolution.error}` : '';
47
+ throw fail(`unknown or unsupported named query: ${put.name}${detail}`);
45
48
  }
46
49
  result[put.index] = {
47
50
  op: 'put',
package/dist/types.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { QueryResolution, QueryResolutionRequest } from './query-patch.js';
2
2
  import type { TransactionQueryBudget } from './transaction-query.js';
3
3
  import type { Schema } from '@rocicorp/zero';
4
- import type { StreamingManifest } from 'orez-lite/realtime';
5
4
  import type { ExecResult, MutatorRegistry, NormalizedClaims, SqlStatementMetadata, VisibilityConfig } from 'orez-sync-executor';
5
+ import type { StreamingManifest } from 'orez-sync-executor/realtime';
6
6
  export { visibility, type VisibilityExpression, type VisibilityFilter, type VisibilityOperand, type VisibilityValue, } from './visibility.js';
7
7
  export type { VisibilityConfig } from 'orez-sync-executor';
8
8
  export type { QueryResolution, QueryResolutionRequest } from './query-patch.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orez-sync-cf-host",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -60,7 +60,7 @@
60
60
  "typecheck": "bun run build:dist:platform && tsc --noEmit"
61
61
  },
62
62
  "dependencies": {
63
- "orez-sync-executor": "0.11.1"
63
+ "orez-sync-executor": "0.11.2"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@cloudflare/workers-types": "4.20260617.1",