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

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
@@ -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;
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;
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-Dpw9d1s5.mjs';
2
+ export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-CB3pLcCP.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';
@@ -2028,6 +2028,22 @@ const logRpcDebug = (env, envelope) => {
2028
2028
  }
2029
2029
  console.warn(`[lunora:rpc] ${envelope.fanOut ? "fan-out" : `shard=${envelope.shardKey ?? "(root)"}`} ${envelope.functionPath}`);
2030
2030
  };
2031
+ const resolveX402Charge = (envelope, options) => {
2032
+ const x402Tag = options.functions?.[envelope.functionPath]?.x402;
2033
+ if (!x402Tag) {
2034
+ return void 0;
2035
+ }
2036
+ if (envelope.fanOut) {
2037
+ throw new LunoraError("a paid (`.x402`) function cannot be fanned out", { code: "BAD_REQUEST", status: 400 });
2038
+ }
2039
+ if (!options.x402Charge) {
2040
+ throw new LunoraError(`function "${envelope.functionPath}" is marked paid (.x402) but no x402Charge gate is configured on the worker`, {
2041
+ code: "MISCONFIGURED",
2042
+ status: 500
2043
+ });
2044
+ }
2045
+ return x402Tag;
2046
+ };
2031
2047
  const parseEnvelope = async (request) => {
2032
2048
  const text = await readBodyTextWithLimit(request);
2033
2049
  let body;
@@ -2621,6 +2637,7 @@ const createWorker = (options) => {
2621
2637
  }
2622
2638
  const { headers: forwardedHeaders, identity } = await resolveForwardContext(request, env, publicResolveIdentity);
2623
2639
  await authorizeRpcEnvelope(envelope, identity);
2640
+ const x402Tag = resolveX402Charge(envelope, options);
2624
2641
  {
2625
2642
  const rpcStartedAt = Date.now();
2626
2643
  const { observability } = options;
@@ -2672,7 +2689,11 @@ const createWorker = (options) => {
2672
2689
  }
2673
2690
  }
2674
2691
  const shardKey = envelope.shardKey ?? defaultShard;
2675
- return dispatchSingleShard(envelope.functionPath, envelope.args ?? {}, shardKey, forwardedHeaders, sinkContext);
2692
+ const dispatch = () => dispatchSingleShard(envelope.functionPath, envelope.args ?? {}, shardKey, forwardedHeaders, sinkContext);
2693
+ if (x402Tag && options.x402Charge) {
2694
+ return options.x402Charge(request, { functionPath: envelope.functionPath, price: x402Tag.price }, dispatch);
2695
+ }
2696
+ return dispatch();
2676
2697
  }
2677
2698
  };
2678
2699
  const handleBatchRpc = async (request, env, context) => {
@@ -2695,6 +2716,19 @@ const createWorker = (options) => {
2695
2716
  }
2696
2717
  const { headers: forwardedHeaders, identity } = await resolveForwardContext(request, env, options.resolveIdentity);
2697
2718
  const groups = groupBatchCallsByShard(calls, defaultShard);
2719
+ for (const entries of groups.values()) {
2720
+ for (const entry of entries) {
2721
+ if (options.functions?.[entry.functionPath]?.x402) {
2722
+ throw new LunoraError(
2723
+ `paid (\`.x402\`) function "${entry.functionPath}" cannot be called in a batch; dispatch it individually over ${RPC_PATH}`,
2724
+ {
2725
+ code: "BAD_REQUEST",
2726
+ status: 400
2727
+ }
2728
+ );
2729
+ }
2730
+ }
2731
+ }
2698
2732
  await Promise.all(
2699
2733
  [...groups.entries()].flatMap(
2700
2734
  ([shardKey, entries]) => entries.map((entry) => authorizeRpcEnvelope({ functionPath: entry.functionPath, shardKey }, identity))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/runtime",
3
- "version": "1.0.0-alpha.21",
3
+ "version": "1.0.0-alpha.23",
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.2"
49
+ "@lunora/errors": "1.0.0-alpha.3"
50
50
  },
51
51
  "engines": {
52
52
  "node": "^22.15.0 || >=24.11.0"