@agentcash/router 1.3.3 → 1.4.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/dist/index.cjs CHANGED
@@ -1153,6 +1153,9 @@ function createRequestHandler(routeEntry, handler, deps) {
1153
1153
  return fail(status, message, meta, pluginCtx, earlyBodyData);
1154
1154
  }
1155
1155
  }
1156
+ } else {
1157
+ firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
1158
+ return earlyBodyResult.response;
1156
1159
  }
1157
1160
  }
1158
1161
  if (routeEntry.authMode === "siwx" || routeEntry.siwxEnabled) {
@@ -1769,10 +1772,16 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
1769
1772
  const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
1770
1773
  if (inputSchema) {
1771
1774
  const config = {
1775
+ method: routeEntry.method,
1772
1776
  bodyType: routeEntry.bodySchema ? "json" : void 0,
1773
1777
  inputSchema
1774
1778
  };
1775
- if (outputSchema) config.output = { schema: outputSchema, example: {} };
1779
+ if (routeEntry.inputExample !== void 0) {
1780
+ config.input = routeEntry.inputExample;
1781
+ }
1782
+ if (outputSchema && routeEntry.outputExample !== void 0) {
1783
+ config.output = { schema: outputSchema, example: routeEntry.outputExample };
1784
+ }
1776
1785
  extensions = declareDiscoveryExtension(config);
1777
1786
  }
1778
1787
  } catch (err) {
@@ -1891,6 +1900,46 @@ function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx)
1891
1900
  }
1892
1901
  }
1893
1902
 
1903
+ // src/validate-examples.ts
1904
+ function validateExamples(key, bodySchema, querySchema, outputSchema, inputExample, hasInputExample, outputExample, hasOutputExample) {
1905
+ if (bodySchema && !hasInputExample) {
1906
+ throw new Error(
1907
+ `route '${key}': .body() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample body to advertise.`
1908
+ );
1909
+ }
1910
+ if (querySchema && !hasInputExample) {
1911
+ throw new Error(
1912
+ `route '${key}': .query() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample query to advertise.`
1913
+ );
1914
+ }
1915
+ if (outputSchema && !hasOutputExample) {
1916
+ throw new Error(
1917
+ `route '${key}': .output() requires a matching .outputExample() \u2014 the bazaar discovery extension needs a conforming sample response to advertise.`
1918
+ );
1919
+ }
1920
+ const inputSchema = bodySchema ?? querySchema;
1921
+ if (inputSchema && hasInputExample) {
1922
+ const result = inputSchema.safeParse(inputExample);
1923
+ if (!result.success) {
1924
+ const issues = result.error.issues.map((i) => ` \u2022 ${i.path.length ? i.path.join(".") : "<root>"}: ${i.message}`).join("\n");
1925
+ throw new Error(
1926
+ `route '${key}': .inputExample() does not satisfy ${bodySchema ? ".body()" : ".query()"} schema:
1927
+ ${issues}`
1928
+ );
1929
+ }
1930
+ }
1931
+ if (outputSchema && hasOutputExample) {
1932
+ const result = outputSchema.safeParse(outputExample);
1933
+ if (!result.success) {
1934
+ const issues = result.error.issues.map((i) => ` \u2022 ${i.path.length ? i.path.join(".") : "<root>"}: ${i.message}`).join("\n");
1935
+ throw new Error(
1936
+ `route '${key}': .outputExample() does not satisfy .output() schema:
1937
+ ${issues}`
1938
+ );
1939
+ }
1940
+ }
1941
+ }
1942
+
1894
1943
  // src/builder.ts
1895
1944
  var RouteBuilder = class {
1896
1945
  /** @internal */
@@ -1920,6 +1969,14 @@ var RouteBuilder = class {
1920
1969
  /** @internal */
1921
1970
  _outputSchema;
1922
1971
  /** @internal */
1972
+ _inputExample = void 0;
1973
+ /** @internal */
1974
+ _hasInputExample = false;
1975
+ /** @internal */
1976
+ _outputExample = void 0;
1977
+ /** @internal */
1978
+ _hasOutputExample = false;
1979
+ /** @internal */
1923
1980
  _description;
1924
1981
  /** @internal */
1925
1982
  _path;
@@ -2049,6 +2106,63 @@ var RouteBuilder = class {
2049
2106
  next._outputSchema = schema;
2050
2107
  return next;
2051
2108
  }
2109
+ /**
2110
+ * Provide a conforming example of the request input (body or query params).
2111
+ *
2112
+ * **Required** whenever `.body()` or `.query()` is set — enforced at compile time via
2113
+ * `.handler()` overloads, and at route-registration time via Zod validation of the
2114
+ * example against the schema. The example is embedded in the bazaar discovery extension
2115
+ * so indexers can advertise a working sample call.
2116
+ *
2117
+ * @example
2118
+ * ```ts
2119
+ * router.route('search')
2120
+ * .paid('0.01')
2121
+ * .body(z.object({ q: z.string() }))
2122
+ * .inputExample({ q: 'hello world' })
2123
+ * .handler(async ({ body }) => { ... });
2124
+ * ```
2125
+ */
2126
+ inputExample(example) {
2127
+ const next = this.fork();
2128
+ next._inputExample = example;
2129
+ next._hasInputExample = true;
2130
+ return next;
2131
+ }
2132
+ /**
2133
+ * Provide a conforming example of the response output.
2134
+ *
2135
+ * **Required** whenever `.output()` is set — enforced at compile time via `.handler()`
2136
+ * overloads, and at route-registration time via Zod validation of the example against
2137
+ * the schema. The example is embedded in the bazaar discovery extension so indexers
2138
+ * can advertise the response shape.
2139
+ *
2140
+ * Accepts any JSON value (objects, arrays, or primitives) — top-level array
2141
+ * or primitive responses (e.g. `z.array(...)`) are supported alongside the
2142
+ * common object case.
2143
+ *
2144
+ * @example
2145
+ * ```ts
2146
+ * router.route('search')
2147
+ * .paid('0.01')
2148
+ * .output(z.object({ results: z.array(z.string()) }))
2149
+ * .outputExample({ results: ['a', 'b'] })
2150
+ * .handler(async () => { ... });
2151
+ *
2152
+ * // Top-level array response
2153
+ * router.route('chains')
2154
+ * .paid('0.01')
2155
+ * .output(z.array(z.object({ name: z.string() })))
2156
+ * .outputExample([{ name: 'Ethereum' }])
2157
+ * .handler(async () => { ... });
2158
+ * ```
2159
+ */
2160
+ outputExample(example) {
2161
+ const next = this.fork();
2162
+ next._outputExample = example;
2163
+ next._hasOutputExample = true;
2164
+ return next;
2165
+ }
2052
2166
  description(text) {
2053
2167
  const next = this.fork();
2054
2168
  next._description = text;
@@ -2093,12 +2207,26 @@ var RouteBuilder = class {
2093
2207
  next._validateFn = fn;
2094
2208
  return next;
2095
2209
  }
2210
+ // -------------------------------------------------------------------------
2211
+ // Terminal method
2212
+ // -------------------------------------------------------------------------
2096
2213
  handler(fn) {
2214
+ const handlerFn = fn;
2097
2215
  if (this._validateFn && !this._bodySchema) {
2098
2216
  throw new Error(
2099
2217
  `route '${this._key}': .validate() requires .body() \u2014 validation runs on parsed body`
2100
2218
  );
2101
2219
  }
2220
+ validateExamples(
2221
+ this._key,
2222
+ this._bodySchema,
2223
+ this._querySchema,
2224
+ this._outputSchema,
2225
+ this._inputExample,
2226
+ this._hasInputExample,
2227
+ this._outputExample,
2228
+ this._hasOutputExample
2229
+ );
2102
2230
  const entry = {
2103
2231
  key: this._key,
2104
2232
  authMode: this._authMode,
@@ -2108,6 +2236,8 @@ var RouteBuilder = class {
2108
2236
  bodySchema: this._bodySchema,
2109
2237
  querySchema: this._querySchema,
2110
2238
  outputSchema: this._outputSchema,
2239
+ inputExample: this._hasInputExample ? this._inputExample : void 0,
2240
+ outputExample: this._hasOutputExample ? this._outputExample : void 0,
2111
2241
  description: this._description,
2112
2242
  path: this._path,
2113
2243
  method: this._method,
@@ -2121,7 +2251,11 @@ var RouteBuilder = class {
2121
2251
  mppInfo: this._mppInfo
2122
2252
  };
2123
2253
  this._registry.register(entry);
2124
- return createRequestHandler(entry, fn, this._deps);
2254
+ return createRequestHandler(
2255
+ entry,
2256
+ handlerFn,
2257
+ this._deps
2258
+ );
2125
2259
  }
2126
2260
  };
2127
2261
 
package/dist/index.d.cts CHANGED
@@ -163,6 +163,11 @@ interface AlertEvent {
163
163
  meta?: Record<string, unknown>;
164
164
  }
165
165
  type AlertFn = (level: AlertLevel, message: string, meta?: Record<string, unknown>) => void;
166
+ type JsonPrimitive = string | number | boolean | null;
167
+ type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
168
+ type JsonObject = {
169
+ [key: string]: JsonValue;
170
+ };
166
171
 
167
172
  interface X402Server {
168
173
  initialize(): Promise<void>;
@@ -303,6 +308,25 @@ interface RouteEntry {
303
308
  bodySchema?: ZodType;
304
309
  querySchema?: ZodType;
305
310
  outputSchema?: ZodType;
311
+ /**
312
+ * Conforming example for the request input (body for body routes, query params for query routes).
313
+ * Required whenever `bodySchema` or `querySchema` is set. Must satisfy the corresponding schema —
314
+ * validated at route-registration time via the Zod schema.
315
+ *
316
+ * Emitted in the bazaar discovery extension so indexers can advertise a working sample call.
317
+ */
318
+ inputExample?: JsonObject;
319
+ /**
320
+ * Conforming example for the response output. Required whenever `outputSchema` is set.
321
+ * Must satisfy `outputSchema` — validated at route-registration time via the Zod schema.
322
+ *
323
+ * Accepts any JSON value (object, array, or primitive) to support top-level array or
324
+ * primitive response schemas.
325
+ *
326
+ * Emitted in the bazaar discovery extension. Without it the `output` block is dropped from
327
+ * the declaration entirely (the output schema alone cannot be exposed in bazaar without an example).
328
+ */
329
+ outputExample?: JsonValue;
306
330
  description?: string;
307
331
  path?: string;
308
332
  method: RouteMethod;
@@ -468,7 +492,33 @@ interface OrchestrateDeps {
468
492
 
469
493
  type True = true;
470
494
  type False = false;
471
- declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extends boolean = false, NeedsBody extends boolean = false, HasBody extends boolean = false> {
495
+ /**
496
+ * Active request-input type at a builder position. Resolves to `TBody` when
497
+ * `.body()` has been called, `TQuery` when `.query()` has been called, and
498
+ * `never` when neither — making `.inputExample()` unusable before a schema
499
+ * is set (the literal won't assign to `never`).
500
+ */
501
+ type InputTypeFor<TBody, TQuery> = [TBody] extends [undefined] ? [TQuery] extends [undefined] ? never : TQuery : TBody;
502
+ /**
503
+ * The handler argument type. Narrows to the real handler signature when the
504
+ * builder state is valid, and to a descriptive error object when it isn't —
505
+ * the mismatch surfaces as a TS type error at the `.handler(...)` call site
506
+ * with the `__missing` string as the contextual hint.
507
+ *
508
+ * Encoded as a conditional argument rather than overload `this:` constraints
509
+ * because TypeScript doesn't reliably gate overload selection on `this` for
510
+ * generic classes (structurally identical instance types collapse).
511
+ */
512
+ type HandlerArg<TBody, TQuery, HasAuth extends boolean, NeedsBody extends boolean, HasBody extends boolean, NeedsInputExample extends boolean, NeedsOutputExample extends boolean> = HasAuth extends true ? [NeedsBody, HasBody] extends [true, false] ? {
513
+ __missing: 'Call .body(schema) — dynamic/tiered pricing requires a body schema to resolve the price against';
514
+ } : NeedsInputExample extends true ? {
515
+ __missing: 'Call .inputExample(sample) — .body()/.query() routes must advertise a conforming request example for bazaar discovery';
516
+ } : NeedsOutputExample extends true ? {
517
+ __missing: 'Call .outputExample(sample) — .output() routes must advertise a conforming response example for bazaar discovery';
518
+ } : (ctx: HandlerContext<TBody, TQuery>) => Promise<unknown> : {
519
+ __missing: 'Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()';
520
+ };
521
+ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = undefined, HasAuth extends boolean = false, NeedsBody extends boolean = false, HasBody extends boolean = false, NeedsInputExample extends boolean = false, NeedsOutputExample extends boolean = false> {
472
522
  /** @internal */ readonly _key: string;
473
523
  /** @internal */ readonly _registry: RouteRegistry;
474
524
  /** @internal */ readonly _deps: OrchestrateDeps;
@@ -482,6 +532,10 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
482
532
  /** @internal */ _bodySchema: ZodType | undefined;
483
533
  /** @internal */ _querySchema: ZodType | undefined;
484
534
  /** @internal */ _outputSchema: ZodType | undefined;
535
+ /** @internal */ _inputExample: JsonObject | undefined;
536
+ /** @internal */ _hasInputExample: boolean;
537
+ /** @internal */ _outputExample: JsonValue | undefined;
538
+ /** @internal */ _hasOutputExample: boolean;
485
539
  /** @internal */ _description: string | undefined;
486
540
  /** @internal */ _path: string | undefined;
487
541
  /** @internal */ _method: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
@@ -492,10 +546,10 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
492
546
  /** @internal */ _mppInfo: MppProtocolInfo | undefined;
493
547
  constructor(key: string, registry: RouteRegistry, deps: OrchestrateDeps);
494
548
  private fork;
495
- paid(pricing: string, options?: PaidOptions): RouteBuilder<TBody, TQuery, True, False, HasBody>;
549
+ paid(pricing: string, options?: PaidOptions): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody, NeedsInputExample, NeedsOutputExample>;
496
550
  paid<TBodyIn>(pricing: (body: TBodyIn) => string | Promise<string>, options?: PaidOptions & {
497
551
  maxPrice?: string;
498
- }): RouteBuilder<TBody, TQuery, True, True, HasBody>;
552
+ }): RouteBuilder<TBody, TQuery, TOutput, True, True, HasBody, NeedsInputExample, NeedsOutputExample>;
499
553
  paid(pricing: {
500
554
  field: string;
501
555
  tiers: Record<string, {
@@ -503,14 +557,61 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
503
557
  label?: string;
504
558
  }>;
505
559
  default?: string;
506
- }, options?: PaidOptions): RouteBuilder<TBody, TQuery, True, True, HasBody>;
507
- siwx(): RouteBuilder<TBody, TQuery, True, False, HasBody>;
508
- apiKey(resolver: (key: string) => unknown | Promise<unknown>): RouteBuilder<TBody, TQuery, True, NeedsBody, HasBody>;
509
- unprotected(): RouteBuilder<TBody, TQuery, True, False, HasBody>;
560
+ }, options?: PaidOptions): RouteBuilder<TBody, TQuery, TOutput, True, True, HasBody, NeedsInputExample, NeedsOutputExample>;
561
+ siwx(): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody, NeedsInputExample, NeedsOutputExample>;
562
+ apiKey(resolver: (key: string) => unknown | Promise<unknown>): RouteBuilder<TBody, TQuery, TOutput, True, NeedsBody, HasBody, NeedsInputExample, NeedsOutputExample>;
563
+ unprotected(): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody, NeedsInputExample, NeedsOutputExample>;
510
564
  provider(name: string, config?: ProviderConfig): this;
511
- body<T>(schema: ZodType<T>): RouteBuilder<T, TQuery, HasAuth, NeedsBody, True>;
512
- query<T>(schema: ZodType<T>): RouteBuilder<TBody, T, HasAuth, NeedsBody, HasBody>;
513
- output(schema: ZodType): this;
565
+ body<T>(schema: ZodType<T>): RouteBuilder<T, TQuery, TOutput, HasAuth, NeedsBody, True, True, NeedsOutputExample>;
566
+ query<T>(schema: ZodType<T>): RouteBuilder<TBody, T, TOutput, HasAuth, NeedsBody, HasBody, True, NeedsOutputExample>;
567
+ output<T>(schema: ZodType<T>): RouteBuilder<TBody, TQuery, T, HasAuth, NeedsBody, HasBody, NeedsInputExample, True>;
568
+ /**
569
+ * Provide a conforming example of the request input (body or query params).
570
+ *
571
+ * **Required** whenever `.body()` or `.query()` is set — enforced at compile time via
572
+ * `.handler()` overloads, and at route-registration time via Zod validation of the
573
+ * example against the schema. The example is embedded in the bazaar discovery extension
574
+ * so indexers can advertise a working sample call.
575
+ *
576
+ * @example
577
+ * ```ts
578
+ * router.route('search')
579
+ * .paid('0.01')
580
+ * .body(z.object({ q: z.string() }))
581
+ * .inputExample({ q: 'hello world' })
582
+ * .handler(async ({ body }) => { ... });
583
+ * ```
584
+ */
585
+ inputExample(example: InputTypeFor<TBody, TQuery> & JsonObject): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, False, NeedsOutputExample>;
586
+ /**
587
+ * Provide a conforming example of the response output.
588
+ *
589
+ * **Required** whenever `.output()` is set — enforced at compile time via `.handler()`
590
+ * overloads, and at route-registration time via Zod validation of the example against
591
+ * the schema. The example is embedded in the bazaar discovery extension so indexers
592
+ * can advertise the response shape.
593
+ *
594
+ * Accepts any JSON value (objects, arrays, or primitives) — top-level array
595
+ * or primitive responses (e.g. `z.array(...)`) are supported alongside the
596
+ * common object case.
597
+ *
598
+ * @example
599
+ * ```ts
600
+ * router.route('search')
601
+ * .paid('0.01')
602
+ * .output(z.object({ results: z.array(z.string()) }))
603
+ * .outputExample({ results: ['a', 'b'] })
604
+ * .handler(async () => { ... });
605
+ *
606
+ * // Top-level array response
607
+ * router.route('chains')
608
+ * .paid('0.01')
609
+ * .output(z.array(z.object({ name: z.string() })))
610
+ * .outputExample([{ name: 'Ethereum' }])
611
+ * .handler(async () => { ... });
612
+ * ```
613
+ */
614
+ outputExample(example: TOutput & JsonValue): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, NeedsInputExample, False>;
514
615
  description(text: string): this;
515
616
  path(p: string): this;
516
617
  method(m: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'): this;
@@ -535,11 +636,8 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
535
636
  * .handler(async ({ body }) => { ... });
536
637
  * ```
537
638
  */
538
- validate(fn: (body: TBody) => void | Promise<void>): RouteBuilder<TBody, TQuery, HasAuth, NeedsBody, HasBody>;
539
- handler(this: RouteBuilder<TBody, TQuery, True, true, false>, fn: never): never;
540
- handler(this: RouteBuilder<TBody, TQuery, false, boolean, boolean>, fn: never): never;
541
- handler(this: RouteBuilder<TBody, TQuery, True, False, HasBody>, fn: (ctx: HandlerContext<TBody, TQuery>) => Promise<unknown>): (request: NextRequest) => Promise<Response>;
542
- handler(this: RouteBuilder<TBody, TQuery, True, True, True>, fn: (ctx: HandlerContext<TBody, TQuery>) => Promise<unknown>): (request: NextRequest) => Promise<Response>;
639
+ validate(fn: (body: TBody) => void | Promise<void>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, NeedsInputExample, NeedsOutputExample>;
640
+ handler(fn: HandlerArg<TBody, TQuery, HasAuth, NeedsBody, HasBody, NeedsInputExample, NeedsOutputExample>): (request: NextRequest) => Promise<Response>;
543
641
  }
544
642
 
545
643
  interface MonitorEntry {
@@ -551,7 +649,7 @@ interface MonitorEntry {
551
649
  critical?: number;
552
650
  }
553
651
  interface ServiceRouter<TPriceKeys extends string = never> {
554
- route<K extends string>(keyOrDefinition: K | RouteDefinition<K>): [K] extends [TPriceKeys] ? RouteBuilder<undefined, undefined, true, false, false> : RouteBuilder<undefined, undefined, false, false, false>;
652
+ route<K extends string>(keyOrDefinition: K | RouteDefinition<K>): [K] extends [TPriceKeys] ? RouteBuilder<undefined, undefined, undefined, true, false, false, false, false> : RouteBuilder<undefined, undefined, undefined, false, false, false, false, false>;
555
653
  wellKnown(): (request: NextRequest) => Promise<NextResponse>;
556
654
  openapi(): (request: NextRequest) => Promise<NextResponse>;
557
655
  llmsTxt(): (request: NextRequest) => Promise<NextResponse>;
package/dist/index.d.ts CHANGED
@@ -163,6 +163,11 @@ interface AlertEvent {
163
163
  meta?: Record<string, unknown>;
164
164
  }
165
165
  type AlertFn = (level: AlertLevel, message: string, meta?: Record<string, unknown>) => void;
166
+ type JsonPrimitive = string | number | boolean | null;
167
+ type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
168
+ type JsonObject = {
169
+ [key: string]: JsonValue;
170
+ };
166
171
 
167
172
  interface X402Server {
168
173
  initialize(): Promise<void>;
@@ -303,6 +308,25 @@ interface RouteEntry {
303
308
  bodySchema?: ZodType;
304
309
  querySchema?: ZodType;
305
310
  outputSchema?: ZodType;
311
+ /**
312
+ * Conforming example for the request input (body for body routes, query params for query routes).
313
+ * Required whenever `bodySchema` or `querySchema` is set. Must satisfy the corresponding schema —
314
+ * validated at route-registration time via the Zod schema.
315
+ *
316
+ * Emitted in the bazaar discovery extension so indexers can advertise a working sample call.
317
+ */
318
+ inputExample?: JsonObject;
319
+ /**
320
+ * Conforming example for the response output. Required whenever `outputSchema` is set.
321
+ * Must satisfy `outputSchema` — validated at route-registration time via the Zod schema.
322
+ *
323
+ * Accepts any JSON value (object, array, or primitive) to support top-level array or
324
+ * primitive response schemas.
325
+ *
326
+ * Emitted in the bazaar discovery extension. Without it the `output` block is dropped from
327
+ * the declaration entirely (the output schema alone cannot be exposed in bazaar without an example).
328
+ */
329
+ outputExample?: JsonValue;
306
330
  description?: string;
307
331
  path?: string;
308
332
  method: RouteMethod;
@@ -468,7 +492,33 @@ interface OrchestrateDeps {
468
492
 
469
493
  type True = true;
470
494
  type False = false;
471
- declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extends boolean = false, NeedsBody extends boolean = false, HasBody extends boolean = false> {
495
+ /**
496
+ * Active request-input type at a builder position. Resolves to `TBody` when
497
+ * `.body()` has been called, `TQuery` when `.query()` has been called, and
498
+ * `never` when neither — making `.inputExample()` unusable before a schema
499
+ * is set (the literal won't assign to `never`).
500
+ */
501
+ type InputTypeFor<TBody, TQuery> = [TBody] extends [undefined] ? [TQuery] extends [undefined] ? never : TQuery : TBody;
502
+ /**
503
+ * The handler argument type. Narrows to the real handler signature when the
504
+ * builder state is valid, and to a descriptive error object when it isn't —
505
+ * the mismatch surfaces as a TS type error at the `.handler(...)` call site
506
+ * with the `__missing` string as the contextual hint.
507
+ *
508
+ * Encoded as a conditional argument rather than overload `this:` constraints
509
+ * because TypeScript doesn't reliably gate overload selection on `this` for
510
+ * generic classes (structurally identical instance types collapse).
511
+ */
512
+ type HandlerArg<TBody, TQuery, HasAuth extends boolean, NeedsBody extends boolean, HasBody extends boolean, NeedsInputExample extends boolean, NeedsOutputExample extends boolean> = HasAuth extends true ? [NeedsBody, HasBody] extends [true, false] ? {
513
+ __missing: 'Call .body(schema) — dynamic/tiered pricing requires a body schema to resolve the price against';
514
+ } : NeedsInputExample extends true ? {
515
+ __missing: 'Call .inputExample(sample) — .body()/.query() routes must advertise a conforming request example for bazaar discovery';
516
+ } : NeedsOutputExample extends true ? {
517
+ __missing: 'Call .outputExample(sample) — .output() routes must advertise a conforming response example for bazaar discovery';
518
+ } : (ctx: HandlerContext<TBody, TQuery>) => Promise<unknown> : {
519
+ __missing: 'Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()';
520
+ };
521
+ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = undefined, HasAuth extends boolean = false, NeedsBody extends boolean = false, HasBody extends boolean = false, NeedsInputExample extends boolean = false, NeedsOutputExample extends boolean = false> {
472
522
  /** @internal */ readonly _key: string;
473
523
  /** @internal */ readonly _registry: RouteRegistry;
474
524
  /** @internal */ readonly _deps: OrchestrateDeps;
@@ -482,6 +532,10 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
482
532
  /** @internal */ _bodySchema: ZodType | undefined;
483
533
  /** @internal */ _querySchema: ZodType | undefined;
484
534
  /** @internal */ _outputSchema: ZodType | undefined;
535
+ /** @internal */ _inputExample: JsonObject | undefined;
536
+ /** @internal */ _hasInputExample: boolean;
537
+ /** @internal */ _outputExample: JsonValue | undefined;
538
+ /** @internal */ _hasOutputExample: boolean;
485
539
  /** @internal */ _description: string | undefined;
486
540
  /** @internal */ _path: string | undefined;
487
541
  /** @internal */ _method: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
@@ -492,10 +546,10 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
492
546
  /** @internal */ _mppInfo: MppProtocolInfo | undefined;
493
547
  constructor(key: string, registry: RouteRegistry, deps: OrchestrateDeps);
494
548
  private fork;
495
- paid(pricing: string, options?: PaidOptions): RouteBuilder<TBody, TQuery, True, False, HasBody>;
549
+ paid(pricing: string, options?: PaidOptions): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody, NeedsInputExample, NeedsOutputExample>;
496
550
  paid<TBodyIn>(pricing: (body: TBodyIn) => string | Promise<string>, options?: PaidOptions & {
497
551
  maxPrice?: string;
498
- }): RouteBuilder<TBody, TQuery, True, True, HasBody>;
552
+ }): RouteBuilder<TBody, TQuery, TOutput, True, True, HasBody, NeedsInputExample, NeedsOutputExample>;
499
553
  paid(pricing: {
500
554
  field: string;
501
555
  tiers: Record<string, {
@@ -503,14 +557,61 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
503
557
  label?: string;
504
558
  }>;
505
559
  default?: string;
506
- }, options?: PaidOptions): RouteBuilder<TBody, TQuery, True, True, HasBody>;
507
- siwx(): RouteBuilder<TBody, TQuery, True, False, HasBody>;
508
- apiKey(resolver: (key: string) => unknown | Promise<unknown>): RouteBuilder<TBody, TQuery, True, NeedsBody, HasBody>;
509
- unprotected(): RouteBuilder<TBody, TQuery, True, False, HasBody>;
560
+ }, options?: PaidOptions): RouteBuilder<TBody, TQuery, TOutput, True, True, HasBody, NeedsInputExample, NeedsOutputExample>;
561
+ siwx(): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody, NeedsInputExample, NeedsOutputExample>;
562
+ apiKey(resolver: (key: string) => unknown | Promise<unknown>): RouteBuilder<TBody, TQuery, TOutput, True, NeedsBody, HasBody, NeedsInputExample, NeedsOutputExample>;
563
+ unprotected(): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody, NeedsInputExample, NeedsOutputExample>;
510
564
  provider(name: string, config?: ProviderConfig): this;
511
- body<T>(schema: ZodType<T>): RouteBuilder<T, TQuery, HasAuth, NeedsBody, True>;
512
- query<T>(schema: ZodType<T>): RouteBuilder<TBody, T, HasAuth, NeedsBody, HasBody>;
513
- output(schema: ZodType): this;
565
+ body<T>(schema: ZodType<T>): RouteBuilder<T, TQuery, TOutput, HasAuth, NeedsBody, True, True, NeedsOutputExample>;
566
+ query<T>(schema: ZodType<T>): RouteBuilder<TBody, T, TOutput, HasAuth, NeedsBody, HasBody, True, NeedsOutputExample>;
567
+ output<T>(schema: ZodType<T>): RouteBuilder<TBody, TQuery, T, HasAuth, NeedsBody, HasBody, NeedsInputExample, True>;
568
+ /**
569
+ * Provide a conforming example of the request input (body or query params).
570
+ *
571
+ * **Required** whenever `.body()` or `.query()` is set — enforced at compile time via
572
+ * `.handler()` overloads, and at route-registration time via Zod validation of the
573
+ * example against the schema. The example is embedded in the bazaar discovery extension
574
+ * so indexers can advertise a working sample call.
575
+ *
576
+ * @example
577
+ * ```ts
578
+ * router.route('search')
579
+ * .paid('0.01')
580
+ * .body(z.object({ q: z.string() }))
581
+ * .inputExample({ q: 'hello world' })
582
+ * .handler(async ({ body }) => { ... });
583
+ * ```
584
+ */
585
+ inputExample(example: InputTypeFor<TBody, TQuery> & JsonObject): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, False, NeedsOutputExample>;
586
+ /**
587
+ * Provide a conforming example of the response output.
588
+ *
589
+ * **Required** whenever `.output()` is set — enforced at compile time via `.handler()`
590
+ * overloads, and at route-registration time via Zod validation of the example against
591
+ * the schema. The example is embedded in the bazaar discovery extension so indexers
592
+ * can advertise the response shape.
593
+ *
594
+ * Accepts any JSON value (objects, arrays, or primitives) — top-level array
595
+ * or primitive responses (e.g. `z.array(...)`) are supported alongside the
596
+ * common object case.
597
+ *
598
+ * @example
599
+ * ```ts
600
+ * router.route('search')
601
+ * .paid('0.01')
602
+ * .output(z.object({ results: z.array(z.string()) }))
603
+ * .outputExample({ results: ['a', 'b'] })
604
+ * .handler(async () => { ... });
605
+ *
606
+ * // Top-level array response
607
+ * router.route('chains')
608
+ * .paid('0.01')
609
+ * .output(z.array(z.object({ name: z.string() })))
610
+ * .outputExample([{ name: 'Ethereum' }])
611
+ * .handler(async () => { ... });
612
+ * ```
613
+ */
614
+ outputExample(example: TOutput & JsonValue): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, NeedsInputExample, False>;
514
615
  description(text: string): this;
515
616
  path(p: string): this;
516
617
  method(m: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'): this;
@@ -535,11 +636,8 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
535
636
  * .handler(async ({ body }) => { ... });
536
637
  * ```
537
638
  */
538
- validate(fn: (body: TBody) => void | Promise<void>): RouteBuilder<TBody, TQuery, HasAuth, NeedsBody, HasBody>;
539
- handler(this: RouteBuilder<TBody, TQuery, True, true, false>, fn: never): never;
540
- handler(this: RouteBuilder<TBody, TQuery, false, boolean, boolean>, fn: never): never;
541
- handler(this: RouteBuilder<TBody, TQuery, True, False, HasBody>, fn: (ctx: HandlerContext<TBody, TQuery>) => Promise<unknown>): (request: NextRequest) => Promise<Response>;
542
- handler(this: RouteBuilder<TBody, TQuery, True, True, True>, fn: (ctx: HandlerContext<TBody, TQuery>) => Promise<unknown>): (request: NextRequest) => Promise<Response>;
639
+ validate(fn: (body: TBody) => void | Promise<void>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, NeedsInputExample, NeedsOutputExample>;
640
+ handler(fn: HandlerArg<TBody, TQuery, HasAuth, NeedsBody, HasBody, NeedsInputExample, NeedsOutputExample>): (request: NextRequest) => Promise<Response>;
543
641
  }
544
642
 
545
643
  interface MonitorEntry {
@@ -551,7 +649,7 @@ interface MonitorEntry {
551
649
  critical?: number;
552
650
  }
553
651
  interface ServiceRouter<TPriceKeys extends string = never> {
554
- route<K extends string>(keyOrDefinition: K | RouteDefinition<K>): [K] extends [TPriceKeys] ? RouteBuilder<undefined, undefined, true, false, false> : RouteBuilder<undefined, undefined, false, false, false>;
652
+ route<K extends string>(keyOrDefinition: K | RouteDefinition<K>): [K] extends [TPriceKeys] ? RouteBuilder<undefined, undefined, undefined, true, false, false, false, false> : RouteBuilder<undefined, undefined, undefined, false, false, false, false, false>;
555
653
  wellKnown(): (request: NextRequest) => Promise<NextResponse>;
556
654
  openapi(): (request: NextRequest) => Promise<NextResponse>;
557
655
  llmsTxt(): (request: NextRequest) => Promise<NextResponse>;
package/dist/index.js CHANGED
@@ -1114,6 +1114,9 @@ function createRequestHandler(routeEntry, handler, deps) {
1114
1114
  return fail(status, message, meta, pluginCtx, earlyBodyData);
1115
1115
  }
1116
1116
  }
1117
+ } else {
1118
+ firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
1119
+ return earlyBodyResult.response;
1117
1120
  }
1118
1121
  }
1119
1122
  if (routeEntry.authMode === "siwx" || routeEntry.siwxEnabled) {
@@ -1730,10 +1733,16 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
1730
1733
  const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
1731
1734
  if (inputSchema) {
1732
1735
  const config = {
1736
+ method: routeEntry.method,
1733
1737
  bodyType: routeEntry.bodySchema ? "json" : void 0,
1734
1738
  inputSchema
1735
1739
  };
1736
- if (outputSchema) config.output = { schema: outputSchema, example: {} };
1740
+ if (routeEntry.inputExample !== void 0) {
1741
+ config.input = routeEntry.inputExample;
1742
+ }
1743
+ if (outputSchema && routeEntry.outputExample !== void 0) {
1744
+ config.output = { schema: outputSchema, example: routeEntry.outputExample };
1745
+ }
1737
1746
  extensions = declareDiscoveryExtension(config);
1738
1747
  }
1739
1748
  } catch (err) {
@@ -1852,6 +1861,46 @@ function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx)
1852
1861
  }
1853
1862
  }
1854
1863
 
1864
+ // src/validate-examples.ts
1865
+ function validateExamples(key, bodySchema, querySchema, outputSchema, inputExample, hasInputExample, outputExample, hasOutputExample) {
1866
+ if (bodySchema && !hasInputExample) {
1867
+ throw new Error(
1868
+ `route '${key}': .body() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample body to advertise.`
1869
+ );
1870
+ }
1871
+ if (querySchema && !hasInputExample) {
1872
+ throw new Error(
1873
+ `route '${key}': .query() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample query to advertise.`
1874
+ );
1875
+ }
1876
+ if (outputSchema && !hasOutputExample) {
1877
+ throw new Error(
1878
+ `route '${key}': .output() requires a matching .outputExample() \u2014 the bazaar discovery extension needs a conforming sample response to advertise.`
1879
+ );
1880
+ }
1881
+ const inputSchema = bodySchema ?? querySchema;
1882
+ if (inputSchema && hasInputExample) {
1883
+ const result = inputSchema.safeParse(inputExample);
1884
+ if (!result.success) {
1885
+ const issues = result.error.issues.map((i) => ` \u2022 ${i.path.length ? i.path.join(".") : "<root>"}: ${i.message}`).join("\n");
1886
+ throw new Error(
1887
+ `route '${key}': .inputExample() does not satisfy ${bodySchema ? ".body()" : ".query()"} schema:
1888
+ ${issues}`
1889
+ );
1890
+ }
1891
+ }
1892
+ if (outputSchema && hasOutputExample) {
1893
+ const result = outputSchema.safeParse(outputExample);
1894
+ if (!result.success) {
1895
+ const issues = result.error.issues.map((i) => ` \u2022 ${i.path.length ? i.path.join(".") : "<root>"}: ${i.message}`).join("\n");
1896
+ throw new Error(
1897
+ `route '${key}': .outputExample() does not satisfy .output() schema:
1898
+ ${issues}`
1899
+ );
1900
+ }
1901
+ }
1902
+ }
1903
+
1855
1904
  // src/builder.ts
1856
1905
  var RouteBuilder = class {
1857
1906
  /** @internal */
@@ -1881,6 +1930,14 @@ var RouteBuilder = class {
1881
1930
  /** @internal */
1882
1931
  _outputSchema;
1883
1932
  /** @internal */
1933
+ _inputExample = void 0;
1934
+ /** @internal */
1935
+ _hasInputExample = false;
1936
+ /** @internal */
1937
+ _outputExample = void 0;
1938
+ /** @internal */
1939
+ _hasOutputExample = false;
1940
+ /** @internal */
1884
1941
  _description;
1885
1942
  /** @internal */
1886
1943
  _path;
@@ -2010,6 +2067,63 @@ var RouteBuilder = class {
2010
2067
  next._outputSchema = schema;
2011
2068
  return next;
2012
2069
  }
2070
+ /**
2071
+ * Provide a conforming example of the request input (body or query params).
2072
+ *
2073
+ * **Required** whenever `.body()` or `.query()` is set — enforced at compile time via
2074
+ * `.handler()` overloads, and at route-registration time via Zod validation of the
2075
+ * example against the schema. The example is embedded in the bazaar discovery extension
2076
+ * so indexers can advertise a working sample call.
2077
+ *
2078
+ * @example
2079
+ * ```ts
2080
+ * router.route('search')
2081
+ * .paid('0.01')
2082
+ * .body(z.object({ q: z.string() }))
2083
+ * .inputExample({ q: 'hello world' })
2084
+ * .handler(async ({ body }) => { ... });
2085
+ * ```
2086
+ */
2087
+ inputExample(example) {
2088
+ const next = this.fork();
2089
+ next._inputExample = example;
2090
+ next._hasInputExample = true;
2091
+ return next;
2092
+ }
2093
+ /**
2094
+ * Provide a conforming example of the response output.
2095
+ *
2096
+ * **Required** whenever `.output()` is set — enforced at compile time via `.handler()`
2097
+ * overloads, and at route-registration time via Zod validation of the example against
2098
+ * the schema. The example is embedded in the bazaar discovery extension so indexers
2099
+ * can advertise the response shape.
2100
+ *
2101
+ * Accepts any JSON value (objects, arrays, or primitives) — top-level array
2102
+ * or primitive responses (e.g. `z.array(...)`) are supported alongside the
2103
+ * common object case.
2104
+ *
2105
+ * @example
2106
+ * ```ts
2107
+ * router.route('search')
2108
+ * .paid('0.01')
2109
+ * .output(z.object({ results: z.array(z.string()) }))
2110
+ * .outputExample({ results: ['a', 'b'] })
2111
+ * .handler(async () => { ... });
2112
+ *
2113
+ * // Top-level array response
2114
+ * router.route('chains')
2115
+ * .paid('0.01')
2116
+ * .output(z.array(z.object({ name: z.string() })))
2117
+ * .outputExample([{ name: 'Ethereum' }])
2118
+ * .handler(async () => { ... });
2119
+ * ```
2120
+ */
2121
+ outputExample(example) {
2122
+ const next = this.fork();
2123
+ next._outputExample = example;
2124
+ next._hasOutputExample = true;
2125
+ return next;
2126
+ }
2013
2127
  description(text) {
2014
2128
  const next = this.fork();
2015
2129
  next._description = text;
@@ -2054,12 +2168,26 @@ var RouteBuilder = class {
2054
2168
  next._validateFn = fn;
2055
2169
  return next;
2056
2170
  }
2171
+ // -------------------------------------------------------------------------
2172
+ // Terminal method
2173
+ // -------------------------------------------------------------------------
2057
2174
  handler(fn) {
2175
+ const handlerFn = fn;
2058
2176
  if (this._validateFn && !this._bodySchema) {
2059
2177
  throw new Error(
2060
2178
  `route '${this._key}': .validate() requires .body() \u2014 validation runs on parsed body`
2061
2179
  );
2062
2180
  }
2181
+ validateExamples(
2182
+ this._key,
2183
+ this._bodySchema,
2184
+ this._querySchema,
2185
+ this._outputSchema,
2186
+ this._inputExample,
2187
+ this._hasInputExample,
2188
+ this._outputExample,
2189
+ this._hasOutputExample
2190
+ );
2063
2191
  const entry = {
2064
2192
  key: this._key,
2065
2193
  authMode: this._authMode,
@@ -2069,6 +2197,8 @@ var RouteBuilder = class {
2069
2197
  bodySchema: this._bodySchema,
2070
2198
  querySchema: this._querySchema,
2071
2199
  outputSchema: this._outputSchema,
2200
+ inputExample: this._hasInputExample ? this._inputExample : void 0,
2201
+ outputExample: this._hasOutputExample ? this._outputExample : void 0,
2072
2202
  description: this._description,
2073
2203
  path: this._path,
2074
2204
  method: this._method,
@@ -2082,7 +2212,11 @@ var RouteBuilder = class {
2082
2212
  mppInfo: this._mppInfo
2083
2213
  };
2084
2214
  this._registry.register(entry);
2085
- return createRequestHandler(entry, fn, this._deps);
2215
+ return createRequestHandler(
2216
+ entry,
2217
+ handlerFn,
2218
+ this._deps
2219
+ );
2086
2220
  }
2087
2221
  };
2088
2222
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/router",
3
- "version": "1.3.3",
3
+ "version": "1.4.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": {