@agentcash/router 0.7.0 → 0.7.1
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/README.md +24 -4
- package/dist/index.cjs +36 -4
- package/dist/index.d.cts +41 -2
- package/dist/index.d.ts +41 -2
- package/dist/index.js +36 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -58,6 +58,8 @@ import { createRouter } from '@agentcash/router';
|
|
|
58
58
|
|
|
59
59
|
export const router = createRouter({
|
|
60
60
|
payeeAddress: process.env.X402_PAYEE_ADDRESS!,
|
|
61
|
+
baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
|
|
62
|
+
strictRoutes: true, // recommended
|
|
61
63
|
});
|
|
62
64
|
```
|
|
63
65
|
|
|
@@ -70,7 +72,7 @@ export const router = createRouter({
|
|
|
70
72
|
import { router } from '@/lib/routes';
|
|
71
73
|
import { searchSchema, searchResponseSchema } from '@/lib/schemas';
|
|
72
74
|
|
|
73
|
-
export const POST = router.route('search')
|
|
75
|
+
export const POST = router.route({ path: 'search' })
|
|
74
76
|
.paid('0.01')
|
|
75
77
|
.body(searchSchema)
|
|
76
78
|
.output(searchResponseSchema)
|
|
@@ -81,7 +83,7 @@ export const POST = router.route('search')
|
|
|
81
83
|
**SIWX-authenticated route**
|
|
82
84
|
|
|
83
85
|
```typescript
|
|
84
|
-
export const GET = router.route('inbox/status')
|
|
86
|
+
export const GET = router.route({ path: 'inbox/status' })
|
|
85
87
|
.siwx()
|
|
86
88
|
.query(statusQuerySchema)
|
|
87
89
|
.handler(async ({ query, wallet }) => getStatus(query, wallet));
|
|
@@ -90,7 +92,7 @@ export const GET = router.route('inbox/status')
|
|
|
90
92
|
**Unprotected route**
|
|
91
93
|
|
|
92
94
|
```typescript
|
|
93
|
-
export const GET = router.route('health')
|
|
95
|
+
export const GET = router.route({ path: 'health' })
|
|
94
96
|
.unprotected()
|
|
95
97
|
.handler(async () => ({ status: 'ok' }));
|
|
96
98
|
```
|
|
@@ -101,7 +103,7 @@ export const GET = router.route('health')
|
|
|
101
103
|
// app/.well-known/x402/route.ts
|
|
102
104
|
import { router } from '@/lib/routes';
|
|
103
105
|
import '@/lib/routes/barrel'; // ensures all routes are imported
|
|
104
|
-
export const GET = router.wellKnown();
|
|
106
|
+
export const GET = router.wellKnown({ methodHints: 'non-default' });
|
|
105
107
|
|
|
106
108
|
// app/openapi.json/route.ts
|
|
107
109
|
import { router } from '@/lib/routes';
|
|
@@ -129,11 +131,29 @@ Creates a `ServiceRouter` instance.
|
|
|
129
131
|
| Option | Type | Default | Description |
|
|
130
132
|
|--------|------|---------|-------------|
|
|
131
133
|
| `payeeAddress` | `string` | **required** | Wallet address to receive payments |
|
|
134
|
+
| `baseUrl` | `string` | **required** | Service origin used for discovery/OpenAPI/realm |
|
|
132
135
|
| `network` | `string` | `'eip155:8453'` | Blockchain network |
|
|
133
136
|
| `plugin` | `RouterPlugin` | `undefined` | Observability plugin |
|
|
134
137
|
| `prices` | `Record<string, string>` | `undefined` | Central pricing map (auto-applied) |
|
|
135
138
|
| `siwx.nonceStore` | `NonceStore` | `MemoryNonceStore` | Custom nonce store |
|
|
136
139
|
| `mpp` | `{ secretKey, currency, recipient? }` | `undefined` | MPP config |
|
|
140
|
+
| `strictRoutes` | `boolean` | `false` | Enforce `route({ path })` and prevent key/path divergence |
|
|
141
|
+
|
|
142
|
+
### Path-First Routing
|
|
143
|
+
|
|
144
|
+
Use path-first route definitions to keep runtime, OpenAPI, and discovery aligned:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
router.route({ path: 'flightaware/airports/id/flights/arrivals', method: 'GET' })
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
If you need a custom internal key (legacy pricing map), you can pass:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
router.route({ path: 'public/path', key: 'legacy/key' })
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
In `strictRoutes` mode, custom keys are rejected to prevent discovery drift.
|
|
137
157
|
|
|
138
158
|
### Route Builder
|
|
139
159
|
|
package/dist/index.cjs
CHANGED
|
@@ -1466,10 +1466,12 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
|
|
|
1466
1466
|
}
|
|
1467
1467
|
const x402Set = /* @__PURE__ */ new Set();
|
|
1468
1468
|
const mppSet = /* @__PURE__ */ new Set();
|
|
1469
|
+
const methodHints = options.methodHints ?? "non-default";
|
|
1469
1470
|
for (const [key, entry] of registry.entries()) {
|
|
1470
1471
|
const url = `${normalizedBase}/api/${entry.path ?? key}`;
|
|
1471
|
-
|
|
1472
|
-
if (entry.
|
|
1472
|
+
const resource = toDiscoveryResource(entry.method, url, methodHints);
|
|
1473
|
+
if (entry.authMode !== "unprotected") x402Set.add(resource);
|
|
1474
|
+
if (entry.protocols.includes("mpp")) mppSet.add(resource);
|
|
1473
1475
|
}
|
|
1474
1476
|
let instructions;
|
|
1475
1477
|
if (typeof options.instructions === "function") {
|
|
@@ -1503,6 +1505,12 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
|
|
|
1503
1505
|
});
|
|
1504
1506
|
};
|
|
1505
1507
|
}
|
|
1508
|
+
function toDiscoveryResource(method, url, mode) {
|
|
1509
|
+
if (mode === "off") return url;
|
|
1510
|
+
if (mode === "always") return `${method} ${url}`;
|
|
1511
|
+
const isDefaultProbeMethod = method === "GET" || method === "POST";
|
|
1512
|
+
return isDefaultProbeMethod ? url : `${method} ${url}`;
|
|
1513
|
+
}
|
|
1506
1514
|
|
|
1507
1515
|
// src/discovery/openapi.ts
|
|
1508
1516
|
var import_server4 = require("next/server");
|
|
@@ -1783,8 +1791,26 @@ function createRouter(config) {
|
|
|
1783
1791
|
})();
|
|
1784
1792
|
const pricesKeys = config.prices ? Object.keys(config.prices) : void 0;
|
|
1785
1793
|
return {
|
|
1786
|
-
route(
|
|
1787
|
-
const
|
|
1794
|
+
route(keyOrDefinition) {
|
|
1795
|
+
const isDefinition = typeof keyOrDefinition !== "string";
|
|
1796
|
+
if (config.strictRoutes && !isDefinition) {
|
|
1797
|
+
throw new Error(
|
|
1798
|
+
"[router] strictRoutes=true requires route({ path }) form. Replace route('my/key') with route({ path: 'my/key' })."
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
const definition = isDefinition ? keyOrDefinition : { path: keyOrDefinition, key: keyOrDefinition };
|
|
1802
|
+
const normalizedPath = normalizePath(definition.path);
|
|
1803
|
+
const key = definition.key ?? normalizedPath;
|
|
1804
|
+
if (config.strictRoutes && definition.key && definition.key !== definition.path) {
|
|
1805
|
+
throw new Error(
|
|
1806
|
+
`[router] strictRoutes=true forbids key/path divergence for route '${definition.path}'. Remove custom \`key\` or make it equal to \`path\`.`
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
let builder = new RouteBuilder(key, registry, deps);
|
|
1810
|
+
builder = builder.path(normalizedPath);
|
|
1811
|
+
if (definition.method) {
|
|
1812
|
+
builder = builder.method(definition.method);
|
|
1813
|
+
}
|
|
1788
1814
|
if (config.prices && key in config.prices) {
|
|
1789
1815
|
const options = config.protocols ? { protocols: config.protocols } : void 0;
|
|
1790
1816
|
return builder.paid(config.prices[key], options);
|
|
@@ -1816,6 +1842,12 @@ function createRouter(config) {
|
|
|
1816
1842
|
registry
|
|
1817
1843
|
};
|
|
1818
1844
|
}
|
|
1845
|
+
function normalizePath(path) {
|
|
1846
|
+
let normalized = path.trim();
|
|
1847
|
+
normalized = normalized.replace(/^\/+/, "");
|
|
1848
|
+
normalized = normalized.replace(/^api\/+/, "");
|
|
1849
|
+
return normalized.replace(/\/+$/, "");
|
|
1850
|
+
}
|
|
1819
1851
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1820
1852
|
0 && (module.exports = {
|
|
1821
1853
|
HttpError,
|
package/dist/index.d.cts
CHANGED
|
@@ -185,6 +185,26 @@ interface X402Server {
|
|
|
185
185
|
}
|
|
186
186
|
type ProtocolType = 'x402' | 'mpp';
|
|
187
187
|
type AuthMode = 'paid' | 'siwx' | 'apiKey' | 'unprotected';
|
|
188
|
+
type RouteMethod = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
|
|
189
|
+
interface RouteDefinition<K extends string = string> {
|
|
190
|
+
/**
|
|
191
|
+
* Public API path segment (without `/api/` prefix).
|
|
192
|
+
* Example: `flightaware/airports/id/flights/arrivals`
|
|
193
|
+
*/
|
|
194
|
+
path: K;
|
|
195
|
+
/**
|
|
196
|
+
* Internal route ID for pricing maps / analytics. Defaults to `path`.
|
|
197
|
+
*
|
|
198
|
+
* In `strictRoutes` mode, custom keys are disallowed to prevent discovery
|
|
199
|
+
* drift between internal IDs and advertised paths.
|
|
200
|
+
*/
|
|
201
|
+
key?: string;
|
|
202
|
+
/**
|
|
203
|
+
* Optional explicit method. If omitted, defaults to builder behavior
|
|
204
|
+
* (`POST`, or `GET` when `.query()` is used).
|
|
205
|
+
*/
|
|
206
|
+
method?: RouteMethod;
|
|
207
|
+
}
|
|
188
208
|
interface TierConfig {
|
|
189
209
|
price: string;
|
|
190
210
|
label?: string;
|
|
@@ -251,7 +271,7 @@ interface RouteEntry {
|
|
|
251
271
|
outputSchema?: ZodType;
|
|
252
272
|
description?: string;
|
|
253
273
|
path?: string;
|
|
254
|
-
method:
|
|
274
|
+
method: RouteMethod;
|
|
255
275
|
maxPrice?: string;
|
|
256
276
|
minPrice?: string;
|
|
257
277
|
payTo?: string | ((request: Request) => string | Promise<string>);
|
|
@@ -299,6 +319,16 @@ interface RouterConfig {
|
|
|
299
319
|
* })
|
|
300
320
|
*/
|
|
301
321
|
protocols?: ProtocolType[];
|
|
322
|
+
/**
|
|
323
|
+
* Enforce explicit, path-first route definitions.
|
|
324
|
+
*
|
|
325
|
+
* When enabled:
|
|
326
|
+
* - `.route('key')` is rejected; use `.route({ path })`.
|
|
327
|
+
* - custom `key` differing from `path` is rejected.
|
|
328
|
+
*
|
|
329
|
+
* This prevents discovery/openapi drift caused by shorthand internal keys.
|
|
330
|
+
*/
|
|
331
|
+
strictRoutes?: boolean;
|
|
302
332
|
}
|
|
303
333
|
|
|
304
334
|
declare class RouteRegistry {
|
|
@@ -315,6 +345,15 @@ interface WellKnownOptions {
|
|
|
315
345
|
description?: string;
|
|
316
346
|
instructions?: string | (() => string | Promise<string>);
|
|
317
347
|
ownershipProofs?: string[];
|
|
348
|
+
/**
|
|
349
|
+
* Whether to include explicit HTTP method prefixes in `resources`.
|
|
350
|
+
* - `off`: always emit plain URLs.
|
|
351
|
+
* - `non-default`: emit `METHOD url` for PUT/PATCH/DELETE routes.
|
|
352
|
+
* - `always`: emit `METHOD url` for all routes.
|
|
353
|
+
*
|
|
354
|
+
* @default 'non-default'
|
|
355
|
+
*/
|
|
356
|
+
methodHints?: 'off' | 'non-default' | 'always';
|
|
318
357
|
}
|
|
319
358
|
|
|
320
359
|
interface OpenAPIOptions {
|
|
@@ -437,7 +476,7 @@ interface MonitorEntry {
|
|
|
437
476
|
critical?: number;
|
|
438
477
|
}
|
|
439
478
|
interface ServiceRouter<TPriceKeys extends string = never> {
|
|
440
|
-
route<K extends string>(
|
|
479
|
+
route<K extends string>(keyOrDefinition: K | RouteDefinition<K>): [K] extends [TPriceKeys] ? RouteBuilder<undefined, undefined, true, false, false> : RouteBuilder<undefined, undefined, false, false, false>;
|
|
441
480
|
wellKnown(options?: WellKnownOptions): (request: NextRequest) => Promise<NextResponse>;
|
|
442
481
|
openapi(options: OpenAPIOptions): (request: NextRequest) => Promise<NextResponse>;
|
|
443
482
|
monitors(): MonitorEntry[];
|
package/dist/index.d.ts
CHANGED
|
@@ -185,6 +185,26 @@ interface X402Server {
|
|
|
185
185
|
}
|
|
186
186
|
type ProtocolType = 'x402' | 'mpp';
|
|
187
187
|
type AuthMode = 'paid' | 'siwx' | 'apiKey' | 'unprotected';
|
|
188
|
+
type RouteMethod = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
|
|
189
|
+
interface RouteDefinition<K extends string = string> {
|
|
190
|
+
/**
|
|
191
|
+
* Public API path segment (without `/api/` prefix).
|
|
192
|
+
* Example: `flightaware/airports/id/flights/arrivals`
|
|
193
|
+
*/
|
|
194
|
+
path: K;
|
|
195
|
+
/**
|
|
196
|
+
* Internal route ID for pricing maps / analytics. Defaults to `path`.
|
|
197
|
+
*
|
|
198
|
+
* In `strictRoutes` mode, custom keys are disallowed to prevent discovery
|
|
199
|
+
* drift between internal IDs and advertised paths.
|
|
200
|
+
*/
|
|
201
|
+
key?: string;
|
|
202
|
+
/**
|
|
203
|
+
* Optional explicit method. If omitted, defaults to builder behavior
|
|
204
|
+
* (`POST`, or `GET` when `.query()` is used).
|
|
205
|
+
*/
|
|
206
|
+
method?: RouteMethod;
|
|
207
|
+
}
|
|
188
208
|
interface TierConfig {
|
|
189
209
|
price: string;
|
|
190
210
|
label?: string;
|
|
@@ -251,7 +271,7 @@ interface RouteEntry {
|
|
|
251
271
|
outputSchema?: ZodType;
|
|
252
272
|
description?: string;
|
|
253
273
|
path?: string;
|
|
254
|
-
method:
|
|
274
|
+
method: RouteMethod;
|
|
255
275
|
maxPrice?: string;
|
|
256
276
|
minPrice?: string;
|
|
257
277
|
payTo?: string | ((request: Request) => string | Promise<string>);
|
|
@@ -299,6 +319,16 @@ interface RouterConfig {
|
|
|
299
319
|
* })
|
|
300
320
|
*/
|
|
301
321
|
protocols?: ProtocolType[];
|
|
322
|
+
/**
|
|
323
|
+
* Enforce explicit, path-first route definitions.
|
|
324
|
+
*
|
|
325
|
+
* When enabled:
|
|
326
|
+
* - `.route('key')` is rejected; use `.route({ path })`.
|
|
327
|
+
* - custom `key` differing from `path` is rejected.
|
|
328
|
+
*
|
|
329
|
+
* This prevents discovery/openapi drift caused by shorthand internal keys.
|
|
330
|
+
*/
|
|
331
|
+
strictRoutes?: boolean;
|
|
302
332
|
}
|
|
303
333
|
|
|
304
334
|
declare class RouteRegistry {
|
|
@@ -315,6 +345,15 @@ interface WellKnownOptions {
|
|
|
315
345
|
description?: string;
|
|
316
346
|
instructions?: string | (() => string | Promise<string>);
|
|
317
347
|
ownershipProofs?: string[];
|
|
348
|
+
/**
|
|
349
|
+
* Whether to include explicit HTTP method prefixes in `resources`.
|
|
350
|
+
* - `off`: always emit plain URLs.
|
|
351
|
+
* - `non-default`: emit `METHOD url` for PUT/PATCH/DELETE routes.
|
|
352
|
+
* - `always`: emit `METHOD url` for all routes.
|
|
353
|
+
*
|
|
354
|
+
* @default 'non-default'
|
|
355
|
+
*/
|
|
356
|
+
methodHints?: 'off' | 'non-default' | 'always';
|
|
318
357
|
}
|
|
319
358
|
|
|
320
359
|
interface OpenAPIOptions {
|
|
@@ -437,7 +476,7 @@ interface MonitorEntry {
|
|
|
437
476
|
critical?: number;
|
|
438
477
|
}
|
|
439
478
|
interface ServiceRouter<TPriceKeys extends string = never> {
|
|
440
|
-
route<K extends string>(
|
|
479
|
+
route<K extends string>(keyOrDefinition: K | RouteDefinition<K>): [K] extends [TPriceKeys] ? RouteBuilder<undefined, undefined, true, false, false> : RouteBuilder<undefined, undefined, false, false, false>;
|
|
441
480
|
wellKnown(options?: WellKnownOptions): (request: NextRequest) => Promise<NextResponse>;
|
|
442
481
|
openapi(options: OpenAPIOptions): (request: NextRequest) => Promise<NextResponse>;
|
|
443
482
|
monitors(): MonitorEntry[];
|
package/dist/index.js
CHANGED
|
@@ -1427,10 +1427,12 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
|
|
|
1427
1427
|
}
|
|
1428
1428
|
const x402Set = /* @__PURE__ */ new Set();
|
|
1429
1429
|
const mppSet = /* @__PURE__ */ new Set();
|
|
1430
|
+
const methodHints = options.methodHints ?? "non-default";
|
|
1430
1431
|
for (const [key, entry] of registry.entries()) {
|
|
1431
1432
|
const url = `${normalizedBase}/api/${entry.path ?? key}`;
|
|
1432
|
-
|
|
1433
|
-
if (entry.
|
|
1433
|
+
const resource = toDiscoveryResource(entry.method, url, methodHints);
|
|
1434
|
+
if (entry.authMode !== "unprotected") x402Set.add(resource);
|
|
1435
|
+
if (entry.protocols.includes("mpp")) mppSet.add(resource);
|
|
1434
1436
|
}
|
|
1435
1437
|
let instructions;
|
|
1436
1438
|
if (typeof options.instructions === "function") {
|
|
@@ -1464,6 +1466,12 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
|
|
|
1464
1466
|
});
|
|
1465
1467
|
};
|
|
1466
1468
|
}
|
|
1469
|
+
function toDiscoveryResource(method, url, mode) {
|
|
1470
|
+
if (mode === "off") return url;
|
|
1471
|
+
if (mode === "always") return `${method} ${url}`;
|
|
1472
|
+
const isDefaultProbeMethod = method === "GET" || method === "POST";
|
|
1473
|
+
return isDefaultProbeMethod ? url : `${method} ${url}`;
|
|
1474
|
+
}
|
|
1467
1475
|
|
|
1468
1476
|
// src/discovery/openapi.ts
|
|
1469
1477
|
import { NextResponse as NextResponse4 } from "next/server";
|
|
@@ -1744,8 +1752,26 @@ function createRouter(config) {
|
|
|
1744
1752
|
})();
|
|
1745
1753
|
const pricesKeys = config.prices ? Object.keys(config.prices) : void 0;
|
|
1746
1754
|
return {
|
|
1747
|
-
route(
|
|
1748
|
-
const
|
|
1755
|
+
route(keyOrDefinition) {
|
|
1756
|
+
const isDefinition = typeof keyOrDefinition !== "string";
|
|
1757
|
+
if (config.strictRoutes && !isDefinition) {
|
|
1758
|
+
throw new Error(
|
|
1759
|
+
"[router] strictRoutes=true requires route({ path }) form. Replace route('my/key') with route({ path: 'my/key' })."
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
const definition = isDefinition ? keyOrDefinition : { path: keyOrDefinition, key: keyOrDefinition };
|
|
1763
|
+
const normalizedPath = normalizePath(definition.path);
|
|
1764
|
+
const key = definition.key ?? normalizedPath;
|
|
1765
|
+
if (config.strictRoutes && definition.key && definition.key !== definition.path) {
|
|
1766
|
+
throw new Error(
|
|
1767
|
+
`[router] strictRoutes=true forbids key/path divergence for route '${definition.path}'. Remove custom \`key\` or make it equal to \`path\`.`
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
let builder = new RouteBuilder(key, registry, deps);
|
|
1771
|
+
builder = builder.path(normalizedPath);
|
|
1772
|
+
if (definition.method) {
|
|
1773
|
+
builder = builder.method(definition.method);
|
|
1774
|
+
}
|
|
1749
1775
|
if (config.prices && key in config.prices) {
|
|
1750
1776
|
const options = config.protocols ? { protocols: config.protocols } : void 0;
|
|
1751
1777
|
return builder.paid(config.prices[key], options);
|
|
@@ -1777,6 +1803,12 @@ function createRouter(config) {
|
|
|
1777
1803
|
registry
|
|
1778
1804
|
};
|
|
1779
1805
|
}
|
|
1806
|
+
function normalizePath(path) {
|
|
1807
|
+
let normalized = path.trim();
|
|
1808
|
+
normalized = normalized.replace(/^\/+/, "");
|
|
1809
|
+
normalized = normalized.replace(/^api\/+/, "");
|
|
1810
|
+
return normalized.replace(/\/+$/, "");
|
|
1811
|
+
}
|
|
1780
1812
|
export {
|
|
1781
1813
|
HttpError,
|
|
1782
1814
|
MemoryEntitlementStore,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcash/router",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Unified route builder for Next.js App Router APIs with x402, MPP, SIWX, and API key auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@x402/evm": "^2.3.0",
|
|
49
49
|
"@x402/extensions": "^2.3.0",
|
|
50
50
|
"eslint": "^10.0.0",
|
|
51
|
-
"mppx": "^0.3.
|
|
51
|
+
"mppx": "^0.3.13",
|
|
52
52
|
"next": "^15.0.0",
|
|
53
53
|
"prettier": "^3.8.1",
|
|
54
54
|
"react": "^19.0.0",
|