@lunora/runtime 1.0.0-alpha.23 → 1.0.0-alpha.24

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/index.d.mts CHANGED
@@ -2365,6 +2365,12 @@ interface RpcContext {
2365
2365
  shardKey: string;
2366
2366
  }
2367
2367
  /**
2368
+ * Ask the owner how many relays to spread new connections across for `shardKey`
2369
+ * (plan 075 Phase 2), cached per isolate so a promoted shard doesn't add a
2370
+ * round-trip to every WS upgrade. Fails closed to `0` (owner-served) on any error,
2371
+ * so a relay-probe hiccup can never break a connection.
2372
+ */
2373
+ /**
2368
2374
  * The composed Lunora worker. `fetch` / `scheduled` are the standard Cloudflare
2369
2375
  * module-worker entrypoints (so the object can be re-exported directly as
2370
2376
  * `export default createWorker(...)`). `serverQuery` is the in-process fast-path
package/dist/index.d.ts CHANGED
@@ -2365,6 +2365,12 @@ interface RpcContext {
2365
2365
  shardKey: string;
2366
2366
  }
2367
2367
  /**
2368
+ * Ask the owner how many relays to spread new connections across for `shardKey`
2369
+ * (plan 075 Phase 2), cached per isolate so a promoted shard doesn't add a
2370
+ * round-trip to every WS upgrade. Fails closed to `0` (owner-served) on any error,
2371
+ * so a relay-probe hiccup can never break a connection.
2372
+ */
2373
+ /**
2368
2374
  * The composed Lunora worker. `fetch` / `scheduled` are the standard Cloudflare
2369
2375
  * module-worker entrypoints (so the object can be re-exported directly as
2370
2376
  * `export default createWorker(...)`). `serverQuery` is the in-process fast-path
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  export { toAirbyteMessages, toFivetranResponse } from './packem_shared/toAirbyteMessages-DrHdplb4.mjs';
2
- export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-CB3pLcCP.mjs';
2
+ export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-CxwkPZUl.mjs';
3
3
  export { createCrossShardRelationCapabilities } from './packem_shared/createCrossShardRelationCapabilities-CbcWjkAn.mjs';
4
4
  export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry } from './packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-B3pA7aXp.mjs';
5
5
  export { LunoraError, toErrorResponse } from './packem_shared/LunoraError-Bpb9EFJ3.mjs';
@@ -7,7 +7,7 @@ export { emitLogEvent, emitRpcEvent } from './packem_shared/emitLogEvent-pEdtqAK
7
7
  export { analyticsEngineSink, combineSinks, consoleSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-DqEvrQs0.mjs';
8
8
  export { createQueryCoordinator, createStaticShardRegistry, mergeStrategyForAggregate } from './packem_shared/createQueryCoordinator-DNCJzOZE.mjs';
9
9
  export { applyJurisdiction, resolveShard } from './packem_shared/applyJurisdiction-BkZtTkct.mjs';
10
- export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity } from './packem_shared/decorateResponse-CsZc49QC.mjs';
10
+ export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity } from './packem_shared/decorateResponse-DRWQFNhF.mjs';
11
11
  export { NOOP_EXECUTION_CONTEXT } from './packem_shared/NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs';
12
12
  export { composeIdentityResolvers, routeIdentityResolvers } from './packem_shared/composeIdentityResolvers-XGjO7V1J.mjs';
13
13
 
@@ -5,7 +5,17 @@ import { wrapResolverWithContract } from './composeIdentityResolvers-XGjO7V1J.mj
5
5
  export { composeIdentityResolvers, routeIdentityResolvers } from './composeIdentityResolvers-XGjO7V1J.mjs';
6
6
  import { emitRpcEvent } from './emitLogEvent-pEdtqAK8.mjs';
7
7
  import { resolveShard, applyJurisdiction } from './applyJurisdiction-BkZtTkct.mjs';
8
- import { resolveSecurity, handleCorsPreflight, enforceOrigin, decorateResponse, enforceWebSocketOrigin } from './decorateResponse-CsZc49QC.mjs';
8
+ import { resolveSecurity, handleCorsPreflight, enforceOrigin, decorateResponse, enforceWebSocketOrigin } from './decorateResponse-DRWQFNhF.mjs';
9
+
10
+ const evictOldestEntry = (map, capacity) => {
11
+ if (map.size < capacity) {
12
+ return;
13
+ }
14
+ const oldest = map.keys().next().value;
15
+ if (oldest !== void 0) {
16
+ map.delete(oldest);
17
+ }
18
+ };
9
19
 
10
20
  const RELAY_NAME_INFIX = "::relay::";
11
21
  const relayName = (ownerKey, index) => `${ownerKey}${RELAY_NAME_INFIX}${String(index)}`;
@@ -477,6 +487,9 @@ const normalizeBatchCall = (raw, index, defaultShard) => {
477
487
  if (call.functionPath.startsWith("__lunora_relation__:") || call.functionPath.startsWith("__lunora_admin__")) {
478
488
  throw new LunoraError("reserved function path cannot be batched", { code: "FORBIDDEN", status: 403 });
479
489
  }
490
+ if (call.args !== void 0 && (typeof call.args !== "object" || call.args === null || Array.isArray(call.args))) {
491
+ throw new LunoraError("each batch call `args` must be an object", { code: "BAD_REQUEST", status: 400 });
492
+ }
480
493
  return {
481
494
  entry: {
482
495
  args: call.args === void 0 ? {} : call.args,
@@ -2085,12 +2098,16 @@ const forwardToShard = async (namespace, shardKey, request) => {
2085
2098
  };
2086
2099
  const relayProbeCache = /* @__PURE__ */ new Map();
2087
2100
  const RELAY_PROBE_TTL_MS = 5e3;
2101
+ const RELAY_PROBE_MAX_ENTRIES = 4096;
2088
2102
  const probeRelayCount = async (namespace, shardKey) => {
2089
2103
  const now = Date.now();
2090
2104
  const cached = relayProbeCache.get(shardKey);
2091
2105
  if (cached !== void 0 && cached.expiresMs > now) {
2092
2106
  return cached.relayCount;
2093
2107
  }
2108
+ if (cached !== void 0) {
2109
+ relayProbeCache.delete(shardKey);
2110
+ }
2094
2111
  let relayCount = 0;
2095
2112
  try {
2096
2113
  const response = await resolveShard(namespace, shardKey).fetch(new Request("https://shard.internal/_lunora/route"));
@@ -2104,6 +2121,7 @@ const probeRelayCount = async (namespace, shardKey) => {
2104
2121
  } catch {
2105
2122
  relayCount = 0;
2106
2123
  }
2124
+ evictOldestEntry(relayProbeCache, RELAY_PROBE_MAX_ENTRIES);
2107
2125
  relayProbeCache.set(shardKey, { expiresMs: now + RELAY_PROBE_TTL_MS, relayCount });
2108
2126
  return relayCount;
2109
2127
  };
@@ -2515,9 +2533,12 @@ const createWorker = (options) => {
2515
2533
  guardUnauthenticatedShardAccess("shard");
2516
2534
  }
2517
2535
  const upgradeHeaders = new Headers(request.headers);
2518
- upgradeHeaders.delete("x-lunora-userid");
2519
- upgradeHeaders.delete("x-lunora-identity");
2520
- upgradeHeaders.delete("x-lunora-identity-exp");
2536
+ const clientHeaderNames = [...upgradeHeaders.keys()];
2537
+ for (const name of clientHeaderNames) {
2538
+ if (name.startsWith("x-lunora-")) {
2539
+ upgradeHeaders.delete(name);
2540
+ }
2541
+ }
2521
2542
  const forwardedUserId = forwardedHeaders["x-lunora-userid"];
2522
2543
  const forwardedIdentity = forwardedHeaders["x-lunora-identity"];
2523
2544
  const forwardedExp = forwardedHeaders["x-lunora-identity-exp"];
@@ -2602,12 +2623,6 @@ const createWorker = (options) => {
2602
2623
  },
2603
2624
  sinkContext
2604
2625
  );
2605
- const responseBookmark = response.headers.get("x-d1-bookmark");
2606
- if (responseBookmark) {
2607
- const headers = new Headers(response.headers);
2608
- headers.set("x-d1-bookmark", responseBookmark);
2609
- return new Response(response.body, { headers, status: response.status });
2610
- }
2611
2626
  return response;
2612
2627
  } catch (error) {
2613
2628
  emitRpcEvent(observability, buildErrorEvent(functionPath, Date.now() - rpcStartedAt, error, { shardKey }), sinkContext);
@@ -2714,7 +2729,7 @@ const createWorker = (options) => {
2714
2729
  if (!Array.isArray(calls)) {
2715
2730
  throw new LunoraError("RPC batch `calls` must be an array", { code: "BAD_REQUEST", status: 400 });
2716
2731
  }
2717
- const { headers: forwardedHeaders, identity } = await resolveForwardContext(request, env, options.resolveIdentity);
2732
+ const { headers: forwardedHeaders, identity } = await resolveForwardContext(request, env, publicResolveIdentity);
2718
2733
  const groups = groupBatchCallsByShard(calls, defaultShard);
2719
2734
  for (const entries of groups.values()) {
2720
2735
  for (const entry of entries) {
@@ -2741,7 +2756,7 @@ const createWorker = (options) => {
2741
2756
  }
2742
2757
  } : void 0;
2743
2758
  const results = [];
2744
- let latestBookmark;
2759
+ const bookmarks = [];
2745
2760
  const slotError = (entry, status, code, message) => {
2746
2761
  return { body: { error: { code, message } }, id: entry.id, status };
2747
2762
  };
@@ -2792,7 +2807,7 @@ const createWorker = (options) => {
2792
2807
  const durationMs = Date.now() - subStartedAt;
2793
2808
  const bookmark = response.headers.get("x-d1-bookmark");
2794
2809
  if (bookmark) {
2795
- latestBookmark = bookmark;
2810
+ bookmarks.push(bookmark);
2796
2811
  }
2797
2812
  let parsed;
2798
2813
  try {
@@ -2823,8 +2838,9 @@ const createWorker = (options) => {
2823
2838
  })
2824
2839
  );
2825
2840
  const responseHeaders = { "content-type": "application/json" };
2826
- if (latestBookmark !== void 0) {
2827
- responseHeaders["x-d1-bookmark"] = latestBookmark;
2841
+ const [onlyBookmark] = bookmarks;
2842
+ if (bookmarks.length === 1 && onlyBookmark !== void 0) {
2843
+ responseHeaders["x-d1-bookmark"] = onlyBookmark;
2828
2844
  }
2829
2845
  return Response.json({ results }, { headers: responseHeaders, status: 200 });
2830
2846
  };
@@ -2879,13 +2895,14 @@ const createWorker = (options) => {
2879
2895
  if (!coordinator) {
2880
2896
  throw new LunoraError("scheduled backup requires a `queryCoordinator` on the worker", { code: "BACKUP_NOT_CONFIGURED", status: 500 });
2881
2897
  }
2882
- if (!options.adminToken || options.adminToken.length === 0) {
2883
- throw new LunoraError("scheduled backup requires an `adminToken` to authenticate the per-shard export gate", {
2898
+ const adminToken = effectiveAdminToken();
2899
+ if (!adminToken || adminToken.length === 0) {
2900
+ throw new LunoraError("scheduled backup requires an `adminToken` (or `env.LUNORA_ADMIN_TOKEN`) to authenticate the per-shard export gate", {
2884
2901
  code: "BACKUP_NOT_CONFIGURED",
2885
2902
  status: 500
2886
2903
  });
2887
2904
  }
2888
- const forwardedHeaders = { authorization: `Bearer ${options.adminToken}`, "content-type": "application/json" };
2905
+ const forwardedHeaders = { authorization: `Bearer ${adminToken}`, "content-type": "application/json" };
2889
2906
  const tables = options.backupTables;
2890
2907
  let rows = 0;
2891
2908
  let bytes = 0;
@@ -2931,6 +2948,7 @@ const createWorker = (options) => {
2931
2948
  await pruneBackups(store, prefix);
2932
2949
  };
2933
2950
  const handleScheduled = async (controller, env, context) => {
2951
+ resolveAdminTokenFromEnv(env);
2934
2952
  const errors = [];
2935
2953
  const toError = (error) => error instanceof Error ? error : new Error(String(error));
2936
2954
  const userHandler = options.crons?.[controller.cron];
@@ -3047,7 +3065,8 @@ const createWorker = (options) => {
3047
3065
  const url = new URL(request.url);
3048
3066
  if (request.method === "POST" || request.method === "PUT") {
3049
3067
  const contentLength = Number(request.headers.get("content-length") ?? "");
3050
- if (Number.isFinite(contentLength) && contentLength > MAX_BODY_BYTES) {
3068
+ const maxBodyBytes = url.pathname === KV_VALUE_PATH ? KV_VALUE_MAX_BODY_BYTES : MAX_BODY_BYTES;
3069
+ if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {
3051
3070
  throw new LunoraError("Body too large", { code: "PAYLOAD_TOO_LARGE", status: 413 });
3052
3071
  }
3053
3072
  }
@@ -3148,4 +3167,4 @@ const resolveLunoraOptions = (options, env) => {
3148
3167
  const createLunoraHandler = (options = {}) => (request, env, context) => createWorker(resolveLunoraOptions(options, env)).fetch(request, env, context ?? NOOP_EXECUTION_CONTEXT);
3149
3168
  const defineRpcEnvelope = (envelope) => envelope;
3150
3169
 
3151
- export { NOOP_EXECUTION_CONTEXT, composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker };
3170
+ export { NOOP_EXECUTION_CONTEXT, composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, probeRelayCount, resolveLunoraOptions, withFrameworkWorker };
@@ -202,7 +202,14 @@ const handleCorsPreflight = (request, resolved) => {
202
202
  const headers = corsResponseHeaders(origin, resolved.cors);
203
203
  const requested = request.headers.get("access-control-request-headers");
204
204
  headers.set("access-control-allow-methods", resolved.cors.allowedMethods.join(", "));
205
- headers.set("access-control-allow-headers", requested ?? resolved.cors.allowedHeaders.join(", "));
205
+ let allowedHeaders;
206
+ if (requested === null) {
207
+ allowedHeaders = resolved.cors.allowedHeaders.join(", ");
208
+ } else {
209
+ const permitted = new Set(resolved.cors.allowedHeaders.map((name) => name.toLowerCase()));
210
+ allowedHeaders = requested.split(",").map((name) => name.trim()).filter((name) => name.length > 0 && permitted.has(name.toLowerCase())).join(", ");
211
+ }
212
+ headers.set("access-control-allow-headers", allowedHeaders);
206
213
  headers.set("access-control-max-age", String(resolved.cors.maxAge));
207
214
  return new Response(null, { headers, status: 204 });
208
215
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/runtime",
3
- "version": "1.0.0-alpha.23",
3
+ "version": "1.0.0-alpha.24",
4
4
  "description": "Lunora Worker runtime: the RPC router, shard resolver, and query coordinator",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,7 +46,7 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/errors": "1.0.0-alpha.3"
49
+ "@lunora/errors": "1.0.0-alpha.4"
50
50
  },
51
51
  "engines": {
52
52
  "node": "^22.15.0 || >=24.11.0"