@lunora/runtime 1.0.0-alpha.22 → 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 +50 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.mjs +2 -2
- package/dist/packem_shared/{composeWorker-Dpw9d1s5.mjs → composeWorker-CxwkPZUl.mjs} +74 -21
- package/dist/packem_shared/{decorateResponse-CsZc49QC.mjs → decorateResponse-DRWQFNhF.mjs} +8 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1643,6 +1643,19 @@ interface FunctionRegistryEntry {
|
|
|
1643
1643
|
*/
|
|
1644
1644
|
kind: "action" | "mutation" | "query" | "stream";
|
|
1645
1645
|
visibility?: "internal" | "public";
|
|
1646
|
+
/**
|
|
1647
|
+
* x402 payment tag set by the `.x402({ price })` builder modifier. Present
|
|
1648
|
+
* only on paid public procedures; the origin worker answers an unpaid RPC
|
|
1649
|
+
* for such a function with a real `402` challenge (via the injected
|
|
1650
|
+
* {@link WorkerOptions.x402Charge} gate) before dispatching, then verifies +
|
|
1651
|
+
* settles at the origin boundary so the shard never sees payment state.
|
|
1652
|
+
* Rides along on the registered function object's identity — codegen casts
|
|
1653
|
+
* the real `fn` into `LUNORA_FUNCTIONS`, so reading it needs no change to the
|
|
1654
|
+
* generated shape (same as `fn.rls`).
|
|
1655
|
+
*/
|
|
1656
|
+
x402?: {
|
|
1657
|
+
readonly price: number | string;
|
|
1658
|
+
};
|
|
1646
1659
|
}
|
|
1647
1660
|
/**
|
|
1648
1661
|
* The generated `LUNORA_FUNCTIONS` dispatch table, narrowed to what the
|
|
@@ -1650,6 +1663,25 @@ interface FunctionRegistryEntry {
|
|
|
1650
1663
|
*/
|
|
1651
1664
|
type FunctionRegistryLike = Record<string, FunctionRegistryEntry>;
|
|
1652
1665
|
/**
|
|
1666
|
+
* Injected x402 charge gate — the seam that paywalls a `.x402({ price })`-tagged
|
|
1667
|
+
* procedure at the origin worker without the runtime importing `@lunora/x402`
|
|
1668
|
+
* (which would pull viem/solana into every worker bundle). Build it with
|
|
1669
|
+
* `createProcedureChargeGate(config)` from `@lunora/x402/charge` and pass it as
|
|
1670
|
+
* {@link WorkerOptions.x402Charge}.
|
|
1671
|
+
*
|
|
1672
|
+
* Given the inbound `request`, the paid procedure's `spec` (its `functionPath` —
|
|
1673
|
+
* used as the x402 challenge `resource` — and USD `price`), and a `dispatch`
|
|
1674
|
+
* that runs the real shard forward, it returns a real `402` + `PAYMENT-REQUIRED`
|
|
1675
|
+
* challenge when the request is unpaid, or the dispatched response (with
|
|
1676
|
+
* `X-PAYMENT-RESPONSE` attached) once the client's `X-PAYMENT` is verified and
|
|
1677
|
+
* settled. `dispatch` runs only after payment is verified — an unpaid or
|
|
1678
|
+
* invalid request never reaches the shard.
|
|
1679
|
+
*/
|
|
1680
|
+
type X402ChargeGate = (request: Request, spec: {
|
|
1681
|
+
functionPath: string;
|
|
1682
|
+
price: number | string;
|
|
1683
|
+
}, dispatch: () => Promise<Response>) => Promise<Response>;
|
|
1684
|
+
/**
|
|
1653
1685
|
* Lists objects in the storage bucket for the admin file browser. Structurally
|
|
1654
1686
|
* compatible with `@lunora/storage`'s `Storage["list"]` — the runtime stays free
|
|
1655
1687
|
* of a hard dependency on the storage package.
|
|
@@ -2313,6 +2345,18 @@ interface WorkerOptions {
|
|
|
2313
2345
|
* reports "not configured" and the studio shows the credentials empty state.
|
|
2314
2346
|
*/
|
|
2315
2347
|
workflowsClient?: (env: unknown) => undefined | WorkflowsRestClient;
|
|
2348
|
+
/**
|
|
2349
|
+
* Injected x402 charge gate for paid (`.x402({ price })`) procedures. Build
|
|
2350
|
+
* it with `createProcedureChargeGate(config)` from `@lunora/x402/charge` and
|
|
2351
|
+
* pass it here; the runtime stays free of a hard `@lunora/x402` dependency
|
|
2352
|
+
* (and its viem/solana deps).
|
|
2353
|
+
*
|
|
2354
|
+
* **Required whenever any registered function is `.x402()`-tagged.** The
|
|
2355
|
+
* origin worker refuses to dispatch a paid procedure with a config error
|
|
2356
|
+
* (`500`) when this is absent, rather than serving it free — the paywall is
|
|
2357
|
+
* fail-closed by construction. See {@link X402ChargeGate}.
|
|
2358
|
+
*/
|
|
2359
|
+
x402Charge?: X402ChargeGate;
|
|
2316
2360
|
}
|
|
2317
2361
|
interface RpcContext {
|
|
2318
2362
|
ctx: ExecutionContextLike;
|
|
@@ -2321,6 +2365,12 @@ interface RpcContext {
|
|
|
2321
2365
|
shardKey: string;
|
|
2322
2366
|
}
|
|
2323
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
|
+
/**
|
|
2324
2374
|
* The composed Lunora worker. `fetch` / `scheduled` are the standard Cloudflare
|
|
2325
2375
|
* module-worker entrypoints (so the object can be re-exported directly as
|
|
2326
2376
|
* `export default createWorker(...)`). `serverQuery` is the in-process fast-path
|
package/dist/index.d.ts
CHANGED
|
@@ -1643,6 +1643,19 @@ interface FunctionRegistryEntry {
|
|
|
1643
1643
|
*/
|
|
1644
1644
|
kind: "action" | "mutation" | "query" | "stream";
|
|
1645
1645
|
visibility?: "internal" | "public";
|
|
1646
|
+
/**
|
|
1647
|
+
* x402 payment tag set by the `.x402({ price })` builder modifier. Present
|
|
1648
|
+
* only on paid public procedures; the origin worker answers an unpaid RPC
|
|
1649
|
+
* for such a function with a real `402` challenge (via the injected
|
|
1650
|
+
* {@link WorkerOptions.x402Charge} gate) before dispatching, then verifies +
|
|
1651
|
+
* settles at the origin boundary so the shard never sees payment state.
|
|
1652
|
+
* Rides along on the registered function object's identity — codegen casts
|
|
1653
|
+
* the real `fn` into `LUNORA_FUNCTIONS`, so reading it needs no change to the
|
|
1654
|
+
* generated shape (same as `fn.rls`).
|
|
1655
|
+
*/
|
|
1656
|
+
x402?: {
|
|
1657
|
+
readonly price: number | string;
|
|
1658
|
+
};
|
|
1646
1659
|
}
|
|
1647
1660
|
/**
|
|
1648
1661
|
* The generated `LUNORA_FUNCTIONS` dispatch table, narrowed to what the
|
|
@@ -1650,6 +1663,25 @@ interface FunctionRegistryEntry {
|
|
|
1650
1663
|
*/
|
|
1651
1664
|
type FunctionRegistryLike = Record<string, FunctionRegistryEntry>;
|
|
1652
1665
|
/**
|
|
1666
|
+
* Injected x402 charge gate — the seam that paywalls a `.x402({ price })`-tagged
|
|
1667
|
+
* procedure at the origin worker without the runtime importing `@lunora/x402`
|
|
1668
|
+
* (which would pull viem/solana into every worker bundle). Build it with
|
|
1669
|
+
* `createProcedureChargeGate(config)` from `@lunora/x402/charge` and pass it as
|
|
1670
|
+
* {@link WorkerOptions.x402Charge}.
|
|
1671
|
+
*
|
|
1672
|
+
* Given the inbound `request`, the paid procedure's `spec` (its `functionPath` —
|
|
1673
|
+
* used as the x402 challenge `resource` — and USD `price`), and a `dispatch`
|
|
1674
|
+
* that runs the real shard forward, it returns a real `402` + `PAYMENT-REQUIRED`
|
|
1675
|
+
* challenge when the request is unpaid, or the dispatched response (with
|
|
1676
|
+
* `X-PAYMENT-RESPONSE` attached) once the client's `X-PAYMENT` is verified and
|
|
1677
|
+
* settled. `dispatch` runs only after payment is verified — an unpaid or
|
|
1678
|
+
* invalid request never reaches the shard.
|
|
1679
|
+
*/
|
|
1680
|
+
type X402ChargeGate = (request: Request, spec: {
|
|
1681
|
+
functionPath: string;
|
|
1682
|
+
price: number | string;
|
|
1683
|
+
}, dispatch: () => Promise<Response>) => Promise<Response>;
|
|
1684
|
+
/**
|
|
1653
1685
|
* Lists objects in the storage bucket for the admin file browser. Structurally
|
|
1654
1686
|
* compatible with `@lunora/storage`'s `Storage["list"]` — the runtime stays free
|
|
1655
1687
|
* of a hard dependency on the storage package.
|
|
@@ -2313,6 +2345,18 @@ interface WorkerOptions {
|
|
|
2313
2345
|
* reports "not configured" and the studio shows the credentials empty state.
|
|
2314
2346
|
*/
|
|
2315
2347
|
workflowsClient?: (env: unknown) => undefined | WorkflowsRestClient;
|
|
2348
|
+
/**
|
|
2349
|
+
* Injected x402 charge gate for paid (`.x402({ price })`) procedures. Build
|
|
2350
|
+
* it with `createProcedureChargeGate(config)` from `@lunora/x402/charge` and
|
|
2351
|
+
* pass it here; the runtime stays free of a hard `@lunora/x402` dependency
|
|
2352
|
+
* (and its viem/solana deps).
|
|
2353
|
+
*
|
|
2354
|
+
* **Required whenever any registered function is `.x402()`-tagged.** The
|
|
2355
|
+
* origin worker refuses to dispatch a paid procedure with a config error
|
|
2356
|
+
* (`500`) when this is absent, rather than serving it free — the paywall is
|
|
2357
|
+
* fail-closed by construction. See {@link X402ChargeGate}.
|
|
2358
|
+
*/
|
|
2359
|
+
x402Charge?: X402ChargeGate;
|
|
2316
2360
|
}
|
|
2317
2361
|
interface RpcContext {
|
|
2318
2362
|
ctx: ExecutionContextLike;
|
|
@@ -2321,6 +2365,12 @@ interface RpcContext {
|
|
|
2321
2365
|
shardKey: string;
|
|
2322
2366
|
}
|
|
2323
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
|
+
/**
|
|
2324
2374
|
* The composed Lunora worker. `fetch` / `scheduled` are the standard Cloudflare
|
|
2325
2375
|
* module-worker entrypoints (so the object can be re-exported directly as
|
|
2326
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-
|
|
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-
|
|
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-
|
|
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,
|
|
@@ -2028,6 +2041,22 @@ const logRpcDebug = (env, envelope) => {
|
|
|
2028
2041
|
}
|
|
2029
2042
|
console.warn(`[lunora:rpc] ${envelope.fanOut ? "fan-out" : `shard=${envelope.shardKey ?? "(root)"}`} ${envelope.functionPath}`);
|
|
2030
2043
|
};
|
|
2044
|
+
const resolveX402Charge = (envelope, options) => {
|
|
2045
|
+
const x402Tag = options.functions?.[envelope.functionPath]?.x402;
|
|
2046
|
+
if (!x402Tag) {
|
|
2047
|
+
return void 0;
|
|
2048
|
+
}
|
|
2049
|
+
if (envelope.fanOut) {
|
|
2050
|
+
throw new LunoraError("a paid (`.x402`) function cannot be fanned out", { code: "BAD_REQUEST", status: 400 });
|
|
2051
|
+
}
|
|
2052
|
+
if (!options.x402Charge) {
|
|
2053
|
+
throw new LunoraError(`function "${envelope.functionPath}" is marked paid (.x402) but no x402Charge gate is configured on the worker`, {
|
|
2054
|
+
code: "MISCONFIGURED",
|
|
2055
|
+
status: 500
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
return x402Tag;
|
|
2059
|
+
};
|
|
2031
2060
|
const parseEnvelope = async (request) => {
|
|
2032
2061
|
const text = await readBodyTextWithLimit(request);
|
|
2033
2062
|
let body;
|
|
@@ -2069,12 +2098,16 @@ const forwardToShard = async (namespace, shardKey, request) => {
|
|
|
2069
2098
|
};
|
|
2070
2099
|
const relayProbeCache = /* @__PURE__ */ new Map();
|
|
2071
2100
|
const RELAY_PROBE_TTL_MS = 5e3;
|
|
2101
|
+
const RELAY_PROBE_MAX_ENTRIES = 4096;
|
|
2072
2102
|
const probeRelayCount = async (namespace, shardKey) => {
|
|
2073
2103
|
const now = Date.now();
|
|
2074
2104
|
const cached = relayProbeCache.get(shardKey);
|
|
2075
2105
|
if (cached !== void 0 && cached.expiresMs > now) {
|
|
2076
2106
|
return cached.relayCount;
|
|
2077
2107
|
}
|
|
2108
|
+
if (cached !== void 0) {
|
|
2109
|
+
relayProbeCache.delete(shardKey);
|
|
2110
|
+
}
|
|
2078
2111
|
let relayCount = 0;
|
|
2079
2112
|
try {
|
|
2080
2113
|
const response = await resolveShard(namespace, shardKey).fetch(new Request("https://shard.internal/_lunora/route"));
|
|
@@ -2088,6 +2121,7 @@ const probeRelayCount = async (namespace, shardKey) => {
|
|
|
2088
2121
|
} catch {
|
|
2089
2122
|
relayCount = 0;
|
|
2090
2123
|
}
|
|
2124
|
+
evictOldestEntry(relayProbeCache, RELAY_PROBE_MAX_ENTRIES);
|
|
2091
2125
|
relayProbeCache.set(shardKey, { expiresMs: now + RELAY_PROBE_TTL_MS, relayCount });
|
|
2092
2126
|
return relayCount;
|
|
2093
2127
|
};
|
|
@@ -2499,9 +2533,12 @@ const createWorker = (options) => {
|
|
|
2499
2533
|
guardUnauthenticatedShardAccess("shard");
|
|
2500
2534
|
}
|
|
2501
2535
|
const upgradeHeaders = new Headers(request.headers);
|
|
2502
|
-
upgradeHeaders.
|
|
2503
|
-
|
|
2504
|
-
|
|
2536
|
+
const clientHeaderNames = [...upgradeHeaders.keys()];
|
|
2537
|
+
for (const name of clientHeaderNames) {
|
|
2538
|
+
if (name.startsWith("x-lunora-")) {
|
|
2539
|
+
upgradeHeaders.delete(name);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2505
2542
|
const forwardedUserId = forwardedHeaders["x-lunora-userid"];
|
|
2506
2543
|
const forwardedIdentity = forwardedHeaders["x-lunora-identity"];
|
|
2507
2544
|
const forwardedExp = forwardedHeaders["x-lunora-identity-exp"];
|
|
@@ -2586,12 +2623,6 @@ const createWorker = (options) => {
|
|
|
2586
2623
|
},
|
|
2587
2624
|
sinkContext
|
|
2588
2625
|
);
|
|
2589
|
-
const responseBookmark = response.headers.get("x-d1-bookmark");
|
|
2590
|
-
if (responseBookmark) {
|
|
2591
|
-
const headers = new Headers(response.headers);
|
|
2592
|
-
headers.set("x-d1-bookmark", responseBookmark);
|
|
2593
|
-
return new Response(response.body, { headers, status: response.status });
|
|
2594
|
-
}
|
|
2595
2626
|
return response;
|
|
2596
2627
|
} catch (error) {
|
|
2597
2628
|
emitRpcEvent(observability, buildErrorEvent(functionPath, Date.now() - rpcStartedAt, error, { shardKey }), sinkContext);
|
|
@@ -2621,6 +2652,7 @@ const createWorker = (options) => {
|
|
|
2621
2652
|
}
|
|
2622
2653
|
const { headers: forwardedHeaders, identity } = await resolveForwardContext(request, env, publicResolveIdentity);
|
|
2623
2654
|
await authorizeRpcEnvelope(envelope, identity);
|
|
2655
|
+
const x402Tag = resolveX402Charge(envelope, options);
|
|
2624
2656
|
{
|
|
2625
2657
|
const rpcStartedAt = Date.now();
|
|
2626
2658
|
const { observability } = options;
|
|
@@ -2672,7 +2704,11 @@ const createWorker = (options) => {
|
|
|
2672
2704
|
}
|
|
2673
2705
|
}
|
|
2674
2706
|
const shardKey = envelope.shardKey ?? defaultShard;
|
|
2675
|
-
|
|
2707
|
+
const dispatch = () => dispatchSingleShard(envelope.functionPath, envelope.args ?? {}, shardKey, forwardedHeaders, sinkContext);
|
|
2708
|
+
if (x402Tag && options.x402Charge) {
|
|
2709
|
+
return options.x402Charge(request, { functionPath: envelope.functionPath, price: x402Tag.price }, dispatch);
|
|
2710
|
+
}
|
|
2711
|
+
return dispatch();
|
|
2676
2712
|
}
|
|
2677
2713
|
};
|
|
2678
2714
|
const handleBatchRpc = async (request, env, context) => {
|
|
@@ -2693,8 +2729,21 @@ const createWorker = (options) => {
|
|
|
2693
2729
|
if (!Array.isArray(calls)) {
|
|
2694
2730
|
throw new LunoraError("RPC batch `calls` must be an array", { code: "BAD_REQUEST", status: 400 });
|
|
2695
2731
|
}
|
|
2696
|
-
const { headers: forwardedHeaders, identity } = await resolveForwardContext(request, env,
|
|
2732
|
+
const { headers: forwardedHeaders, identity } = await resolveForwardContext(request, env, publicResolveIdentity);
|
|
2697
2733
|
const groups = groupBatchCallsByShard(calls, defaultShard);
|
|
2734
|
+
for (const entries of groups.values()) {
|
|
2735
|
+
for (const entry of entries) {
|
|
2736
|
+
if (options.functions?.[entry.functionPath]?.x402) {
|
|
2737
|
+
throw new LunoraError(
|
|
2738
|
+
`paid (\`.x402\`) function "${entry.functionPath}" cannot be called in a batch; dispatch it individually over ${RPC_PATH}`,
|
|
2739
|
+
{
|
|
2740
|
+
code: "BAD_REQUEST",
|
|
2741
|
+
status: 400
|
|
2742
|
+
}
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2698
2747
|
await Promise.all(
|
|
2699
2748
|
[...groups.entries()].flatMap(
|
|
2700
2749
|
([shardKey, entries]) => entries.map((entry) => authorizeRpcEnvelope({ functionPath: entry.functionPath, shardKey }, identity))
|
|
@@ -2707,7 +2756,7 @@ const createWorker = (options) => {
|
|
|
2707
2756
|
}
|
|
2708
2757
|
} : void 0;
|
|
2709
2758
|
const results = [];
|
|
2710
|
-
|
|
2759
|
+
const bookmarks = [];
|
|
2711
2760
|
const slotError = (entry, status, code, message) => {
|
|
2712
2761
|
return { body: { error: { code, message } }, id: entry.id, status };
|
|
2713
2762
|
};
|
|
@@ -2758,7 +2807,7 @@ const createWorker = (options) => {
|
|
|
2758
2807
|
const durationMs = Date.now() - subStartedAt;
|
|
2759
2808
|
const bookmark = response.headers.get("x-d1-bookmark");
|
|
2760
2809
|
if (bookmark) {
|
|
2761
|
-
|
|
2810
|
+
bookmarks.push(bookmark);
|
|
2762
2811
|
}
|
|
2763
2812
|
let parsed;
|
|
2764
2813
|
try {
|
|
@@ -2789,8 +2838,9 @@ const createWorker = (options) => {
|
|
|
2789
2838
|
})
|
|
2790
2839
|
);
|
|
2791
2840
|
const responseHeaders = { "content-type": "application/json" };
|
|
2792
|
-
|
|
2793
|
-
|
|
2841
|
+
const [onlyBookmark] = bookmarks;
|
|
2842
|
+
if (bookmarks.length === 1 && onlyBookmark !== void 0) {
|
|
2843
|
+
responseHeaders["x-d1-bookmark"] = onlyBookmark;
|
|
2794
2844
|
}
|
|
2795
2845
|
return Response.json({ results }, { headers: responseHeaders, status: 200 });
|
|
2796
2846
|
};
|
|
@@ -2845,13 +2895,14 @@ const createWorker = (options) => {
|
|
|
2845
2895
|
if (!coordinator) {
|
|
2846
2896
|
throw new LunoraError("scheduled backup requires a `queryCoordinator` on the worker", { code: "BACKUP_NOT_CONFIGURED", status: 500 });
|
|
2847
2897
|
}
|
|
2848
|
-
|
|
2849
|
-
|
|
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", {
|
|
2850
2901
|
code: "BACKUP_NOT_CONFIGURED",
|
|
2851
2902
|
status: 500
|
|
2852
2903
|
});
|
|
2853
2904
|
}
|
|
2854
|
-
const forwardedHeaders = { authorization: `Bearer ${
|
|
2905
|
+
const forwardedHeaders = { authorization: `Bearer ${adminToken}`, "content-type": "application/json" };
|
|
2855
2906
|
const tables = options.backupTables;
|
|
2856
2907
|
let rows = 0;
|
|
2857
2908
|
let bytes = 0;
|
|
@@ -2897,6 +2948,7 @@ const createWorker = (options) => {
|
|
|
2897
2948
|
await pruneBackups(store, prefix);
|
|
2898
2949
|
};
|
|
2899
2950
|
const handleScheduled = async (controller, env, context) => {
|
|
2951
|
+
resolveAdminTokenFromEnv(env);
|
|
2900
2952
|
const errors = [];
|
|
2901
2953
|
const toError = (error) => error instanceof Error ? error : new Error(String(error));
|
|
2902
2954
|
const userHandler = options.crons?.[controller.cron];
|
|
@@ -3013,7 +3065,8 @@ const createWorker = (options) => {
|
|
|
3013
3065
|
const url = new URL(request.url);
|
|
3014
3066
|
if (request.method === "POST" || request.method === "PUT") {
|
|
3015
3067
|
const contentLength = Number(request.headers.get("content-length") ?? "");
|
|
3016
|
-
|
|
3068
|
+
const maxBodyBytes = url.pathname === KV_VALUE_PATH ? KV_VALUE_MAX_BODY_BYTES : MAX_BODY_BYTES;
|
|
3069
|
+
if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {
|
|
3017
3070
|
throw new LunoraError("Body too large", { code: "PAYLOAD_TOO_LARGE", status: 413 });
|
|
3018
3071
|
}
|
|
3019
3072
|
}
|
|
@@ -3114,4 +3167,4 @@ const resolveLunoraOptions = (options, env) => {
|
|
|
3114
3167
|
const createLunoraHandler = (options = {}) => (request, env, context) => createWorker(resolveLunoraOptions(options, env)).fetch(request, env, context ?? NOOP_EXECUTION_CONTEXT);
|
|
3115
3168
|
const defineRpcEnvelope = (envelope) => envelope;
|
|
3116
3169
|
|
|
3117
|
-
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
|
-
|
|
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.
|
|
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.
|
|
49
|
+
"@lunora/errors": "1.0.0-alpha.4"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": "^22.15.0 || >=24.11.0"
|