@lunora/runtime 1.0.0-alpha.1 → 1.0.0-alpha.10

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.
@@ -1,8 +1,12 @@
1
+ import { NOOP_EXECUTION_CONTEXT } from './NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs';
1
2
  import { LunoraError, toErrorResponse, isStructuralLunoraError, isStructuralConflictError } from './LunoraError-CL0aOtpo.mjs';
2
- import { emitRpcEvent } from './emitRpcEvent-pEdtqAK8.mjs';
3
- import { resolveShard } from './resolveShard-DDkzWtrU.mjs';
3
+ import { emitRpcEvent } from './emitLogEvent-pEdtqAK8.mjs';
4
+ import { resolveShard, applyJurisdiction } from './applyJurisdiction-BkZtTkct.mjs';
4
5
  import { resolveSecurity, handleCorsPreflight, enforceOrigin, decorateResponse } from './decorateResponse-DbISh_Wi.mjs';
5
6
 
7
+ const RELAY_NAME_INFIX = "::relay::";
8
+ const relayName = (ownerKey, index) => `${ownerKey}${RELAY_NAME_INFIX}${String(index)}`;
9
+
6
10
  const AUTH_BASE = "/_lunora/admin/auth";
7
11
  const AUTH_ADMIN_ERROR_STATUS = {
8
12
  PASSWORD_TOO_LONG: 400,
@@ -597,14 +601,14 @@ const partitionExportTables = (options, tables) => {
597
601
  }
598
602
  return { globalTables, shardLocalTables };
599
603
  };
600
- const exportShardLocalRows = async (options, coordinator, forwardedHeaders, tables, shardLocalTables, writeRow) => {
604
+ const exportShardLocalRows = async (options, coordinator, forwardedHeaders, tables, shardLocalTables, writeRow, namespace) => {
601
605
  if (tables !== void 0 && shardLocalTables.length === 0) {
602
606
  return;
603
607
  }
604
608
  const exportTables = tables === void 0 ? [] : shardLocalTables;
605
609
  const probeFallback = tables === void 0 ? collectKnownTables() : [];
606
610
  const probeTables = exportTables.length > 0 ? exportTables : probeFallback;
607
- const result = await coordinator.orchestrateExport(options.shardDO, {
611
+ const result = await coordinator.orchestrateExport(namespace, {
608
612
  args: { tables: exportTables },
609
613
  headers: forwardedHeaders,
610
614
  tables: probeTables
@@ -618,9 +622,9 @@ const exportShardLocalRows = async (options, coordinator, forwardedHeaders, tabl
618
622
  }
619
623
  }
620
624
  };
621
- const streamExportRows = async (options, coordinator, forwardedHeaders, tables, writeRow) => {
625
+ const streamExportRows = async (options, coordinator, forwardedHeaders, tables, writeRow, namespace) => {
622
626
  const { globalTables, shardLocalTables } = partitionExportTables(options, tables);
623
- await exportShardLocalRows(options, coordinator, forwardedHeaders, tables, shardLocalTables, writeRow);
627
+ await exportShardLocalRows(options, coordinator, forwardedHeaders, tables, shardLocalTables, writeRow, namespace);
624
628
  const exportGlobalsFunction = options.exportGlobals;
625
629
  const wantGlobals = tables === void 0 || globalTables.length > 0;
626
630
  if (wantGlobals && exportGlobalsFunction) {
@@ -740,7 +744,7 @@ const mergeImportResult = (totals, result) => {
740
744
  }
741
745
  totals.conflicts += result.conflicts;
742
746
  };
743
- const streamingImport = async (request, options, forwardedHeaders) => {
747
+ const streamingImport = async (request, options, forwardedHeaders, namespace) => {
744
748
  const defaultShard = options.defaultShardKey ?? "__root__";
745
749
  const { errors, globalRows, perShard } = await bucketImportStream(request, options, defaultShard);
746
750
  const totals = { conflicts: 0, errors, inserted: {} };
@@ -749,7 +753,7 @@ const streamingImport = async (request, options, forwardedHeaders) => {
749
753
  if (!coordinator) {
750
754
  throw new LunoraError("Import endpoint requires a `queryCoordinator` on the worker", { code: "BAD_REQUEST", status: 400 });
751
755
  }
752
- const result = await coordinator.orchestrateImport(options.shardDO, {
756
+ const result = await coordinator.orchestrateImport(namespace, {
753
757
  batches: [...perShard.values()],
754
758
  headers: forwardedHeaders
755
759
  });
@@ -966,7 +970,7 @@ const buildIntrospectionAdminRoutes = (deps) => {
966
970
  };
967
971
  };
968
972
 
969
- const MIGRATE_PATH = "/_lunora/migrate";
973
+ const MIGRATE_PATH$1 = "/_lunora/migrate";
970
974
  const PITR_PATH = "/_lunora/admin/pitr";
971
975
  const RANK_PATH = "/_lunora/admin/rank";
972
976
  const RANKPAGE_PATH = "/_lunora/admin/rankpage";
@@ -1223,7 +1227,7 @@ const buildOrchestrationAdminRoutes = (deps) => {
1223
1227
  return forwardToShard(shardDO, pitr.shardKey ?? defaultShard, forwarded);
1224
1228
  };
1225
1229
  return {
1226
- [MIGRATE_PATH]: handleMigrate,
1230
+ [MIGRATE_PATH$1]: handleMigrate,
1227
1231
  [PITR_PATH]: handlePitr,
1228
1232
  [RANK_PATH]: handleRank,
1229
1233
  [RANKPAGE_PATH]: handleRankPage,
@@ -1502,7 +1506,7 @@ const buildWorkflowsAdminRoutes = (deps) => {
1502
1506
  assertAdmin(request);
1503
1507
  const client = resolveWorkflowsClient(env);
1504
1508
  if (!client) {
1505
- return throwNotConfigured();
1509
+ return Response.json({ configured: false, instances: [], page: 1, perPage: 0, totalCount: 0 });
1506
1510
  }
1507
1511
  const workflowName = requireQuery(url, "name");
1508
1512
  const status = toInstanceStatus(url.searchParams.get("status"));
@@ -1557,6 +1561,9 @@ const RPC_PATH = "/_lunora/rpc";
1557
1561
  const WS_PATH = "/_lunora/ws";
1558
1562
  const SCHEDULER_DISPATCH_PATH = "/_lunora/scheduler/dispatch";
1559
1563
  const CRON_JOBS_RUN_PATH = "/_lunora/admin/cron-jobs/run";
1564
+ const ADMIN_PATH_PREFIX = "/_lunora/admin/";
1565
+ const MIGRATE_PATH = "/_lunora/migrate";
1566
+ const isAdminPath = (pathname) => pathname.startsWith(ADMIN_PATH_PREFIX) || pathname === MIGRATE_PATH;
1560
1567
  const DEFAULT_AUTH_BASE_PATH = "/api/auth";
1561
1568
  const RECORD_AUTH_EVENT_OP = "__lunora_admin__:recordAuthEvent";
1562
1569
  const AUTH_ATTEMPT_SEGMENTS = ["/sign-in", "/sign-up", "/callback"];
@@ -1598,6 +1605,8 @@ const resolveForwardContext = async (request, env, resolveIdentity) => {
1598
1605
  const cookie = request.headers.get("cookie");
1599
1606
  const bookmark = request.headers.get("x-d1-bookmark");
1600
1607
  const mutationId = request.headers.get("x-lunora-mutation-id");
1608
+ const clientId = request.headers.get("x-lunora-client-id");
1609
+ const clientSeq = request.headers.get("x-lunora-client-seq");
1601
1610
  if (authorization) {
1602
1611
  headers["authorization"] = authorization;
1603
1612
  }
@@ -1610,6 +1619,12 @@ const resolveForwardContext = async (request, env, resolveIdentity) => {
1610
1619
  if (mutationId) {
1611
1620
  headers["x-lunora-mutation-id"] = mutationId;
1612
1621
  }
1622
+ if (clientId) {
1623
+ headers["x-lunora-client-id"] = clientId;
1624
+ }
1625
+ if (clientSeq) {
1626
+ headers["x-lunora-client-seq"] = clientSeq;
1627
+ }
1613
1628
  const clientIp = request.headers.get("cf-connecting-ip");
1614
1629
  if (clientIp) {
1615
1630
  headers["x-lunora-client-ip"] = clientIp;
@@ -1662,6 +1677,12 @@ const validateFanOut = (fanOut) => {
1662
1677
  }
1663
1678
  return spec;
1664
1679
  };
1680
+ const logRpcDebug = (env, envelope) => {
1681
+ if (!env?.LUNORA_DEBUG_RPC) {
1682
+ return;
1683
+ }
1684
+ console.warn(`[lunora:rpc] ${envelope.fanOut ? "fan-out" : `shard=${envelope.shardKey ?? "(root)"}`} ${envelope.functionPath}`);
1685
+ };
1665
1686
  const parseEnvelope = async (request) => {
1666
1687
  const text = await readBodyTextWithLimit(request);
1667
1688
  let body;
@@ -1692,6 +1713,41 @@ const forwardToShard = async (namespace, shardKey, request) => {
1692
1713
  const stub = resolveShard(namespace, shardKey);
1693
1714
  return stub.fetch(request);
1694
1715
  };
1716
+ const relayProbeCache = /* @__PURE__ */ new Map();
1717
+ const RELAY_PROBE_TTL_MS = 5e3;
1718
+ const probeRelayCount = async (namespace, shardKey) => {
1719
+ const now = Date.now();
1720
+ const cached = relayProbeCache.get(shardKey);
1721
+ if (cached !== void 0 && cached.expiresMs > now) {
1722
+ return cached.relayCount;
1723
+ }
1724
+ let relayCount = 0;
1725
+ try {
1726
+ const response = await resolveShard(namespace, shardKey).fetch(new Request("https://shard.internal/_lunora/route"));
1727
+ if (response.ok) {
1728
+ const body = await response.json();
1729
+ const reported = body.relayCount;
1730
+ if (typeof reported === "number" && reported > 0) {
1731
+ relayCount = Math.floor(reported);
1732
+ }
1733
+ }
1734
+ } catch {
1735
+ relayCount = 0;
1736
+ }
1737
+ relayProbeCache.set(shardKey, { expiresMs: now + RELAY_PROBE_TTL_MS, relayCount });
1738
+ return relayCount;
1739
+ };
1740
+ const resolveShardBindingName = (env, namespace) => {
1741
+ if (env === null || typeof env !== "object") {
1742
+ return void 0;
1743
+ }
1744
+ for (const [key, value] of Object.entries(env)) {
1745
+ if (value === namespace) {
1746
+ return key;
1747
+ }
1748
+ }
1749
+ return void 0;
1750
+ };
1695
1751
  const constantTimeEqual = (expected, supplied) => {
1696
1752
  const max = Math.max(expected.length, supplied.length);
1697
1753
  let diff = expected.length ^ supplied.length;
@@ -1740,6 +1796,31 @@ const checkAdminWsToken = (request, expected) => {
1740
1796
  };
1741
1797
  const createWorker = (options) => {
1742
1798
  const defaultShard = options.defaultShardKey ?? "__root__";
1799
+ const shardDO = applyJurisdiction(options.shardDO, options.jurisdiction);
1800
+ const schedulerDO = options.schedulerDO === void 0 ? void 0 : applyJurisdiction(options.schedulerDO, options.jurisdiction);
1801
+ let envAdminToken;
1802
+ const effectiveAdminToken = () => options.adminToken ?? envAdminToken;
1803
+ const resolveAdminTokenFromEnv = (env) => {
1804
+ if (envAdminToken !== void 0 || options.adminToken !== void 0) {
1805
+ return;
1806
+ }
1807
+ const value = (env ?? {})["LUNORA_ADMIN_TOKEN"];
1808
+ if (typeof value === "string" && value.length > 0) {
1809
+ envAdminToken = value;
1810
+ }
1811
+ };
1812
+ const accessAdminGrants = /* @__PURE__ */ new WeakSet();
1813
+ const requestIsAdmin = (request) => checkAdminAuth(request, effectiveAdminToken()) || accessAdminGrants.has(request);
1814
+ const resolveAdminForwardContext = async (request, env) => {
1815
+ const context = await resolveForwardContext(request, env, options.resolveIdentity);
1816
+ if (accessAdminGrants.has(request) && context.headers["authorization"] === void 0) {
1817
+ const token = effectiveAdminToken();
1818
+ if (token !== void 0) {
1819
+ context.headers["authorization"] = `Bearer ${token}`;
1820
+ }
1821
+ }
1822
+ return context;
1823
+ };
1743
1824
  const hasAnyShardAuth = Boolean(options.authorizeShard) || Boolean(options.authorizeFanOut);
1744
1825
  let warnedUnauthenticatedShardAccess = false;
1745
1826
  const warnUnauthenticatedShardAccessOnce = (kind) => {
@@ -1758,10 +1839,10 @@ const createWorker = (options) => {
1758
1839
  const orchestrationAdminRoutes = buildOrchestrationAdminRoutes({
1759
1840
  defaultShard,
1760
1841
  forwardToShard,
1761
- isAdmin: (request) => checkAdminAuth(request, options.adminToken),
1842
+ isAdmin: requestIsAdmin,
1762
1843
  queryCoordinator: options.queryCoordinator,
1763
- resolveForwardContext: (request, env) => resolveForwardContext(request, env, options.resolveIdentity),
1764
- shardDO: options.shardDO
1844
+ resolveForwardContext: resolveAdminForwardContext,
1845
+ shardDO
1765
1846
  });
1766
1847
  const dispatchToShard = async (functionPath, args, shardKey) => {
1767
1848
  if (options.authorizeShard) {
@@ -1779,7 +1860,7 @@ const createWorker = (options) => {
1779
1860
  headers: { "content-type": "application/json", "x-lunora-system": "1" },
1780
1861
  method: "POST"
1781
1862
  });
1782
- return forwardToShard(options.shardDO, shardKey, forwarded);
1863
+ return forwardToShard(shardDO, shardKey, forwarded);
1783
1864
  };
1784
1865
  const startCronWorkflow = async (binding, job, env) => {
1785
1866
  const candidate = env?.[binding];
@@ -1824,7 +1905,7 @@ const createWorker = (options) => {
1824
1905
  }
1825
1906
  };
1826
1907
  const handleRunCronJob = async (request, env) => {
1827
- if (!checkAdminAuth(request, options.adminToken)) {
1908
+ if (!requestIsAdmin(request)) {
1828
1909
  throw new LunoraError("admin endpoint requires a valid admin bearer", { code: "ADMIN_FORBIDDEN", status: 403 });
1829
1910
  }
1830
1911
  if (request.method !== "POST") {
@@ -1847,12 +1928,12 @@ const createWorker = (options) => {
1847
1928
  };
1848
1929
  const releasePoolSlot = async (candidate) => {
1849
1930
  const pool = typeof candidate.pool === "string" && candidate.pool.length > 0 ? candidate.pool : void 0;
1850
- if (!pool || !options.schedulerDO || typeof candidate.id !== "string") {
1931
+ if (!pool || !schedulerDO || typeof candidate.id !== "string") {
1851
1932
  return;
1852
1933
  }
1853
1934
  const instanceName = typeof candidate.instanceName === "string" && candidate.instanceName.length > 0 ? candidate.instanceName : "default";
1854
1935
  try {
1855
- await options.schedulerDO.get(options.schedulerDO.idFromName(instanceName)).fetch(
1936
+ await schedulerDO.get(schedulerDO.idFromName(instanceName)).fetch(
1856
1937
  new Request("https://scheduler.internal/complete", {
1857
1938
  body: JSON.stringify({ id: candidate.id, pool }),
1858
1939
  headers: { "content-type": "application/json" },
@@ -1898,17 +1979,17 @@ const createWorker = (options) => {
1898
1979
  };
1899
1980
  const dataMovementAdminRoutes = buildDataMovementAdminRoutes({
1900
1981
  applyGlobals: options.applyGlobals,
1901
- isAdmin: (request) => checkAdminAuth(request, options.adminToken),
1982
+ isAdmin: requestIsAdmin,
1902
1983
  knownTables: () => collectKnownTables(),
1903
1984
  queryCoordinator: options.queryCoordinator,
1904
- resolveForwardContext: (request, env) => resolveForwardContext(request, env, options.resolveIdentity),
1905
- shardDO: options.shardDO,
1906
- streamExportRows: (coordinator, headers, tables, writeRow) => streamExportRows(options, coordinator, headers, tables, writeRow),
1907
- streamingImport: (request, headers) => streamingImport(request, options, headers),
1985
+ resolveForwardContext: resolveAdminForwardContext,
1986
+ shardDO,
1987
+ streamExportRows: (coordinator, headers, tables, writeRow) => streamExportRows(options, coordinator, headers, tables, writeRow, shardDO),
1988
+ streamingImport: (request, headers) => streamingImport(request, options, headers, shardDO),
1908
1989
  syncGlobals: options.syncGlobals
1909
1990
  });
1910
1991
  const assertAdminAuthorized = (request) => {
1911
- if (!checkAdminAuth(request, options.adminToken)) {
1992
+ if (!requestIsAdmin(request)) {
1912
1993
  throw new LunoraError("admin endpoint requires a valid admin bearer", { code: "ADMIN_FORBIDDEN", status: 403 });
1913
1994
  }
1914
1995
  };
@@ -1935,17 +2016,17 @@ const createWorker = (options) => {
1935
2016
  };
1936
2017
  };
1937
2018
  const requireSchedulerNamespace = () => {
1938
- if (options.schedulerDO === void 0) {
2019
+ if (schedulerDO === void 0) {
1939
2020
  throw new LunoraError("scheduled endpoints require a `schedulerDO` namespace on the worker", { code: "SCHEDULER_NOT_CONFIGURED", status: 400 });
1940
2021
  }
1941
- return options.schedulerDO;
2022
+ return schedulerDO;
1942
2023
  };
1943
2024
  const resolveSchedulerStub = (request) => {
1944
2025
  assertAdminAuthorized(request);
1945
2026
  return resolveShard(requireSchedulerNamespace(), options.schedulerInstanceName ?? "default");
1946
2027
  };
1947
2028
  const scheduledAdminRoutes = buildScheduledAdminRoutes({
1948
- checkWsAdmin: (request) => checkAdminAuth(request, options.adminToken) || checkAdminWsToken(request, options.adminToken),
2029
+ checkWsAdmin: (request) => requestIsAdmin(request) || checkAdminWsToken(request, effectiveAdminToken()),
1949
2030
  requireSchedulerNamespace,
1950
2031
  resolveSchedulerStub,
1951
2032
  schedulerInstanceName: options.schedulerInstanceName ?? "default"
@@ -1998,7 +2079,7 @@ const createWorker = (options) => {
1998
2079
  headers,
1999
2080
  method: "POST"
2000
2081
  });
2001
- const response = await forwardToShard(options.shardDO, defaultShard, forwarded);
2082
+ const response = await forwardToShard(shardDO, defaultShard, forwarded);
2002
2083
  const payload = await response.json();
2003
2084
  if (payload.error) {
2004
2085
  throw new LunoraError(payload.error.message ?? "shard RPC failed", {
@@ -2061,7 +2142,16 @@ const createWorker = (options) => {
2061
2142
  if (forwardedExp !== void 0) {
2062
2143
  upgradeHeaders.set("x-lunora-identity-exp", forwardedExp);
2063
2144
  }
2064
- return forwardToShard(options.shardDO, shardKey, new Request(request, { headers: upgradeHeaders }));
2145
+ const binding = resolveShardBindingName(env, options.shardDO);
2146
+ if (binding !== void 0) {
2147
+ upgradeHeaders.set("x-lunora-shard-binding", binding);
2148
+ const relayCount = await probeRelayCount(shardDO, shardKey);
2149
+ if (relayCount > 0) {
2150
+ const target = relayName(shardKey, Math.floor(Math.random() * relayCount));
2151
+ return forwardToShard(shardDO, target, new Request(request, { headers: upgradeHeaders }));
2152
+ }
2153
+ }
2154
+ return forwardToShard(shardDO, shardKey, new Request(request, { headers: upgradeHeaders }));
2065
2155
  };
2066
2156
  const authorizeRpcEnvelope = async (envelope, identity) => {
2067
2157
  if (envelope.fanOut) {
@@ -2107,7 +2197,7 @@ const createWorker = (options) => {
2107
2197
  method: "POST"
2108
2198
  });
2109
2199
  try {
2110
- const response = await forwardToShard(options.shardDO, shardKey, forwarded);
2200
+ const response = await forwardToShard(shardDO, shardKey, forwarded);
2111
2201
  emitRpcEvent(
2112
2202
  observability,
2113
2203
  {
@@ -2136,6 +2226,7 @@ const createWorker = (options) => {
2136
2226
  throw new LunoraError("RPC endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
2137
2227
  }
2138
2228
  const envelope = await parseEnvelope(request);
2229
+ logRpcDebug(env, envelope);
2139
2230
  if (envelope.fanOut && envelope.shardKey) {
2140
2231
  throw new LunoraError("RPC envelope cannot set both `shardKey` and `fanOut`", { code: "BAD_REQUEST", status: 400 });
2141
2232
  }
@@ -2158,7 +2249,7 @@ const createWorker = (options) => {
2158
2249
  const { observability } = options;
2159
2250
  const sinkContext = context ? {
2160
2251
  waitUntil: (promise) => {
2161
- context.waitUntil(promise);
2252
+ context.waitUntil?.(promise);
2162
2253
  }
2163
2254
  } : void 0;
2164
2255
  if (envelope.fanOut) {
@@ -2170,7 +2261,7 @@ const createWorker = (options) => {
2170
2261
  });
2171
2262
  }
2172
2263
  try {
2173
- const result = await coordinator.fanOut(options.shardDO, {
2264
+ const result = await coordinator.fanOut(shardDO, {
2174
2265
  args: envelope.args ?? {},
2175
2266
  fanOut: envelope.fanOut,
2176
2267
  functionPath: envelope.functionPath,
@@ -2279,7 +2370,7 @@ const createWorker = (options) => {
2279
2370
  streamController.enqueue(encoded);
2280
2371
  };
2281
2372
  try {
2282
- await streamExportRows(options, coordinator, forwardedHeaders, tables, writeRow);
2373
+ await streamExportRows(options, coordinator, forwardedHeaders, tables, writeRow, shardDO);
2283
2374
  streamController.close();
2284
2375
  } catch (error) {
2285
2376
  streamError = error instanceof Error ? error : new Error(String(error));
@@ -2348,7 +2439,7 @@ const createWorker = (options) => {
2348
2439
  headers: { authorization: `Bearer ${adminBearer}`, "content-type": "application/json" },
2349
2440
  method: "POST"
2350
2441
  });
2351
- await forwardToShard(options.shardDO, defaultShard, recordRequest);
2442
+ await forwardToShard(shardDO, defaultShard, recordRequest);
2352
2443
  } catch {
2353
2444
  }
2354
2445
  };
@@ -2362,10 +2453,11 @@ const createWorker = (options) => {
2362
2453
  }
2363
2454
  const basePath = options.authBasePath ?? DEFAULT_AUTH_BASE_PATH;
2364
2455
  if (isAuthAttemptPath(url.pathname, basePath)) {
2365
- context.waitUntil(recordAuthAttempt(env, authResponse.status >= 400 ? "fail" : "ok"));
2456
+ context.waitUntil?.(recordAuthAttempt(env, authResponse.status >= 400 ? "fail" : "ok"));
2366
2457
  }
2367
2458
  return authResponse;
2368
2459
  };
2460
+ const customRoutes = options.routes !== void 0 && Object.keys(options.routes).length > 0 ? options.routes : void 0;
2369
2461
  const internalRoutes = {
2370
2462
  [WS_PATH]: (request, env, url) => handleWebSocketUpgrade(request, env, url),
2371
2463
  [RPC_PATH]: (request, env, _url, context) => handleRpc(request, env, context),
@@ -2402,6 +2494,17 @@ const createWorker = (options) => {
2402
2494
  resolvedSecurity = resolveSecurity(options.security, env ?? {});
2403
2495
  }
2404
2496
  };
2497
+ const applyAdminGate = async (request, pathname) => {
2498
+ if (options.adminGate === void 0 || !isAdminPath(pathname)) {
2499
+ return;
2500
+ }
2501
+ try {
2502
+ if (await options.adminGate(request)) {
2503
+ accessAdminGrants.add(request);
2504
+ }
2505
+ } catch {
2506
+ }
2507
+ };
2405
2508
  const handle = async (request, env, context) => {
2406
2509
  const url = new URL(request.url);
2407
2510
  if (request.method === "POST" || request.method === "PUT") {
@@ -2414,13 +2517,16 @@ const createWorker = (options) => {
2414
2517
  if (authResponse) {
2415
2518
  return authResponse;
2416
2519
  }
2417
- const methodAndPath = `${request.method} ${url.pathname}`;
2418
- const route = options.routes?.[methodAndPath] ?? options.routes?.[url.pathname];
2419
- if (route) {
2420
- return route(request, env, context);
2520
+ if (customRoutes) {
2521
+ const methodAndPath = `${request.method} ${url.pathname}`;
2522
+ const route = customRoutes[methodAndPath] ?? customRoutes[url.pathname];
2523
+ if (route) {
2524
+ return route(request, env, context);
2525
+ }
2421
2526
  }
2422
2527
  const internalRoute = internalRoutes[url.pathname];
2423
2528
  if (internalRoute) {
2529
+ await applyAdminGate(request, url.pathname);
2424
2530
  return internalRoute(request, env, url, context);
2425
2531
  }
2426
2532
  const httpRouteResponse = await dispatchHttpRoute(request, env, context);
@@ -2432,9 +2538,10 @@ const createWorker = (options) => {
2432
2538
  return {
2433
2539
  async fetch(request, env, context) {
2434
2540
  if (options.passThroughOnException) {
2435
- context.passThroughOnException();
2541
+ context.passThroughOnException?.();
2436
2542
  }
2437
2543
  ensureSecurityResolved(env);
2544
+ resolveAdminTokenFromEnv(env);
2438
2545
  const preflight = handleCorsPreflight(request, resolvedSecurity);
2439
2546
  if (preflight) {
2440
2547
  return preflight;
@@ -2450,6 +2557,9 @@ const createWorker = (options) => {
2450
2557
  return decorateResponse(toErrorResponse(error), request, resolvedSecurity);
2451
2558
  }
2452
2559
  },
2560
+ async queue(batch, env, context) {
2561
+ await options.queue?.(batch, env, context);
2562
+ },
2453
2563
  async scheduled(controller, env, context) {
2454
2564
  await handleScheduled(controller, env, context);
2455
2565
  },
@@ -2480,10 +2590,24 @@ const withFrameworkWorker = (host, optionsInput) => {
2480
2590
  const optionsFactory = optionsInput;
2481
2591
  return {
2482
2592
  fetch: (request, env, context) => build(optionsFactory(env)).fetch(request, env, context),
2593
+ queue: (batch, env, context) => build(optionsFactory(env)).queue?.(batch, env, context) ?? Promise.resolve(),
2483
2594
  scheduled: (controller, env, context) => build(optionsFactory(env)).scheduled(controller, env, context),
2484
2595
  serverQuery: (request, env, reference, args, options) => build(optionsFactory(env)).serverQuery(request, env, reference, args, options)
2485
2596
  };
2486
2597
  };
2598
+ const resolveLunoraOptions = (options, env) => {
2599
+ if (typeof options === "function") {
2600
+ return options(env);
2601
+ }
2602
+ const shardDO = options.shardDO ?? env?.SHARD;
2603
+ if (!shardDO) {
2604
+ throw new Error(
2605
+ "@lunora/runtime: no shard Durable Object namespace found. Bind `SHARD` in wrangler.jsonc, or pass `createLunoraHandler({ shardDO: env.MY_SHARD })`."
2606
+ );
2607
+ }
2608
+ return { ...options, shardDO };
2609
+ };
2610
+ const createLunoraHandler = (options = {}) => (request, env, context) => createWorker(resolveLunoraOptions(options, env)).fetch(request, env, context ?? NOOP_EXECUTION_CONTEXT);
2487
2611
  const defineRpcEnvelope = (envelope) => envelope;
2488
2612
 
2489
- export { composeWorker, createWorker, defineRpcEnvelope, withFrameworkWorker };
2613
+ export { NOOP_EXECUTION_CONTEXT, composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker };
@@ -1,5 +1,5 @@
1
1
  import { LunoraError } from './LunoraError-CL0aOtpo.mjs';
2
- import { resolveShard } from './resolveShard-DDkzWtrU.mjs';
2
+ import { resolveShard } from './applyJurisdiction-BkZtTkct.mjs';
3
3
 
4
4
  const createStaticShardRegistry = (table_to_keys) => {
5
5
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/runtime",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.10",
4
4
  "description": "Lunora Worker runtime: the RPC router, shard resolver, and query coordinator",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -25,7 +25,7 @@
25
25
  "directory": "packages/runtime"
26
26
  },
27
27
  "files": [
28
- "dist",
28
+ "./dist",
29
29
  "README.md",
30
30
  "LICENSE.md",
31
31
  "__assets__"
@@ -1,9 +0,0 @@
1
- const resolveShard = (namespace, shardKey) => {
2
- if (typeof namespace.getByName === "function") {
3
- return namespace.getByName(shardKey);
4
- }
5
- const id = namespace.idFromName(shardKey);
6
- return namespace.get(id);
7
- };
8
-
9
- export { resolveShard };