@agentcash/router 1.3.2 → 1.4.0

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
@@ -1769,10 +1769,16 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
1769
1769
  const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
1770
1770
  if (inputSchema) {
1771
1771
  const config = {
1772
+ method: routeEntry.method,
1772
1773
  bodyType: routeEntry.bodySchema ? "json" : void 0,
1773
1774
  inputSchema
1774
1775
  };
1775
- if (outputSchema) config.output = { schema: outputSchema, example: {} };
1776
+ if (routeEntry.inputExample !== void 0) {
1777
+ config.input = routeEntry.inputExample;
1778
+ }
1779
+ if (outputSchema && routeEntry.outputExample !== void 0) {
1780
+ config.output = { schema: outputSchema, example: routeEntry.outputExample };
1781
+ }
1776
1782
  extensions = declareDiscoveryExtension(config);
1777
1783
  }
1778
1784
  } catch (err) {
@@ -1891,6 +1897,46 @@ function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx)
1891
1897
  }
1892
1898
  }
1893
1899
 
1900
+ // src/validate-examples.ts
1901
+ function validateExamples(key, bodySchema, querySchema, outputSchema, inputExample, hasInputExample, outputExample, hasOutputExample) {
1902
+ if (bodySchema && !hasInputExample) {
1903
+ throw new Error(
1904
+ `route '${key}': .body() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample body to advertise.`
1905
+ );
1906
+ }
1907
+ if (querySchema && !hasInputExample) {
1908
+ throw new Error(
1909
+ `route '${key}': .query() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample query to advertise.`
1910
+ );
1911
+ }
1912
+ if (outputSchema && !hasOutputExample) {
1913
+ throw new Error(
1914
+ `route '${key}': .output() requires a matching .outputExample() \u2014 the bazaar discovery extension needs a conforming sample response to advertise.`
1915
+ );
1916
+ }
1917
+ const inputSchema = bodySchema ?? querySchema;
1918
+ if (inputSchema && hasInputExample) {
1919
+ const result = inputSchema.safeParse(inputExample);
1920
+ if (!result.success) {
1921
+ const issues = result.error.issues.map((i) => ` \u2022 ${i.path.length ? i.path.join(".") : "<root>"}: ${i.message}`).join("\n");
1922
+ throw new Error(
1923
+ `route '${key}': .inputExample() does not satisfy ${bodySchema ? ".body()" : ".query()"} schema:
1924
+ ${issues}`
1925
+ );
1926
+ }
1927
+ }
1928
+ if (outputSchema && hasOutputExample) {
1929
+ const result = outputSchema.safeParse(outputExample);
1930
+ if (!result.success) {
1931
+ const issues = result.error.issues.map((i) => ` \u2022 ${i.path.length ? i.path.join(".") : "<root>"}: ${i.message}`).join("\n");
1932
+ throw new Error(
1933
+ `route '${key}': .outputExample() does not satisfy .output() schema:
1934
+ ${issues}`
1935
+ );
1936
+ }
1937
+ }
1938
+ }
1939
+
1894
1940
  // src/builder.ts
1895
1941
  var RouteBuilder = class {
1896
1942
  /** @internal */
@@ -1920,6 +1966,14 @@ var RouteBuilder = class {
1920
1966
  /** @internal */
1921
1967
  _outputSchema;
1922
1968
  /** @internal */
1969
+ _inputExample = void 0;
1970
+ /** @internal */
1971
+ _hasInputExample = false;
1972
+ /** @internal */
1973
+ _outputExample = void 0;
1974
+ /** @internal */
1975
+ _hasOutputExample = false;
1976
+ /** @internal */
1923
1977
  _description;
1924
1978
  /** @internal */
1925
1979
  _path;
@@ -2049,6 +2103,63 @@ var RouteBuilder = class {
2049
2103
  next._outputSchema = schema;
2050
2104
  return next;
2051
2105
  }
2106
+ /**
2107
+ * Provide a conforming example of the request input (body or query params).
2108
+ *
2109
+ * **Required** whenever `.body()` or `.query()` is set — enforced at compile time via
2110
+ * `.handler()` overloads, and at route-registration time via Zod validation of the
2111
+ * example against the schema. The example is embedded in the bazaar discovery extension
2112
+ * so indexers can advertise a working sample call.
2113
+ *
2114
+ * @example
2115
+ * ```ts
2116
+ * router.route('search')
2117
+ * .paid('0.01')
2118
+ * .body(z.object({ q: z.string() }))
2119
+ * .inputExample({ q: 'hello world' })
2120
+ * .handler(async ({ body }) => { ... });
2121
+ * ```
2122
+ */
2123
+ inputExample(example) {
2124
+ const next = this.fork();
2125
+ next._inputExample = example;
2126
+ next._hasInputExample = true;
2127
+ return next;
2128
+ }
2129
+ /**
2130
+ * Provide a conforming example of the response output.
2131
+ *
2132
+ * **Required** whenever `.output()` is set — enforced at compile time via `.handler()`
2133
+ * overloads, and at route-registration time via Zod validation of the example against
2134
+ * the schema. The example is embedded in the bazaar discovery extension so indexers
2135
+ * can advertise the response shape.
2136
+ *
2137
+ * Accepts any JSON value (objects, arrays, or primitives) — top-level array
2138
+ * or primitive responses (e.g. `z.array(...)`) are supported alongside the
2139
+ * common object case.
2140
+ *
2141
+ * @example
2142
+ * ```ts
2143
+ * router.route('search')
2144
+ * .paid('0.01')
2145
+ * .output(z.object({ results: z.array(z.string()) }))
2146
+ * .outputExample({ results: ['a', 'b'] })
2147
+ * .handler(async () => { ... });
2148
+ *
2149
+ * // Top-level array response
2150
+ * router.route('chains')
2151
+ * .paid('0.01')
2152
+ * .output(z.array(z.object({ name: z.string() })))
2153
+ * .outputExample([{ name: 'Ethereum' }])
2154
+ * .handler(async () => { ... });
2155
+ * ```
2156
+ */
2157
+ outputExample(example) {
2158
+ const next = this.fork();
2159
+ next._outputExample = example;
2160
+ next._hasOutputExample = true;
2161
+ return next;
2162
+ }
2052
2163
  description(text) {
2053
2164
  const next = this.fork();
2054
2165
  next._description = text;
@@ -2093,12 +2204,26 @@ var RouteBuilder = class {
2093
2204
  next._validateFn = fn;
2094
2205
  return next;
2095
2206
  }
2207
+ // -------------------------------------------------------------------------
2208
+ // Terminal method
2209
+ // -------------------------------------------------------------------------
2096
2210
  handler(fn) {
2211
+ const handlerFn = fn;
2097
2212
  if (this._validateFn && !this._bodySchema) {
2098
2213
  throw new Error(
2099
2214
  `route '${this._key}': .validate() requires .body() \u2014 validation runs on parsed body`
2100
2215
  );
2101
2216
  }
2217
+ validateExamples(
2218
+ this._key,
2219
+ this._bodySchema,
2220
+ this._querySchema,
2221
+ this._outputSchema,
2222
+ this._inputExample,
2223
+ this._hasInputExample,
2224
+ this._outputExample,
2225
+ this._hasOutputExample
2226
+ );
2102
2227
  const entry = {
2103
2228
  key: this._key,
2104
2229
  authMode: this._authMode,
@@ -2108,6 +2233,8 @@ var RouteBuilder = class {
2108
2233
  bodySchema: this._bodySchema,
2109
2234
  querySchema: this._querySchema,
2110
2235
  outputSchema: this._outputSchema,
2236
+ inputExample: this._hasInputExample ? this._inputExample : void 0,
2237
+ outputExample: this._hasOutputExample ? this._outputExample : void 0,
2111
2238
  description: this._description,
2112
2239
  path: this._path,
2113
2240
  method: this._method,
@@ -2121,7 +2248,11 @@ var RouteBuilder = class {
2121
2248
  mppInfo: this._mppInfo
2122
2249
  };
2123
2250
  this._registry.register(entry);
2124
- return createRequestHandler(entry, fn, this._deps);
2251
+ return createRequestHandler(
2252
+ entry,
2253
+ handlerFn,
2254
+ this._deps
2255
+ );
2125
2256
  }
2126
2257
  };
2127
2258
 
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
@@ -1730,10 +1730,16 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
1730
1730
  const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
1731
1731
  if (inputSchema) {
1732
1732
  const config = {
1733
+ method: routeEntry.method,
1733
1734
  bodyType: routeEntry.bodySchema ? "json" : void 0,
1734
1735
  inputSchema
1735
1736
  };
1736
- if (outputSchema) config.output = { schema: outputSchema, example: {} };
1737
+ if (routeEntry.inputExample !== void 0) {
1738
+ config.input = routeEntry.inputExample;
1739
+ }
1740
+ if (outputSchema && routeEntry.outputExample !== void 0) {
1741
+ config.output = { schema: outputSchema, example: routeEntry.outputExample };
1742
+ }
1737
1743
  extensions = declareDiscoveryExtension(config);
1738
1744
  }
1739
1745
  } catch (err) {
@@ -1852,6 +1858,46 @@ function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx)
1852
1858
  }
1853
1859
  }
1854
1860
 
1861
+ // src/validate-examples.ts
1862
+ function validateExamples(key, bodySchema, querySchema, outputSchema, inputExample, hasInputExample, outputExample, hasOutputExample) {
1863
+ if (bodySchema && !hasInputExample) {
1864
+ throw new Error(
1865
+ `route '${key}': .body() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample body to advertise.`
1866
+ );
1867
+ }
1868
+ if (querySchema && !hasInputExample) {
1869
+ throw new Error(
1870
+ `route '${key}': .query() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample query to advertise.`
1871
+ );
1872
+ }
1873
+ if (outputSchema && !hasOutputExample) {
1874
+ throw new Error(
1875
+ `route '${key}': .output() requires a matching .outputExample() \u2014 the bazaar discovery extension needs a conforming sample response to advertise.`
1876
+ );
1877
+ }
1878
+ const inputSchema = bodySchema ?? querySchema;
1879
+ if (inputSchema && hasInputExample) {
1880
+ const result = inputSchema.safeParse(inputExample);
1881
+ if (!result.success) {
1882
+ const issues = result.error.issues.map((i) => ` \u2022 ${i.path.length ? i.path.join(".") : "<root>"}: ${i.message}`).join("\n");
1883
+ throw new Error(
1884
+ `route '${key}': .inputExample() does not satisfy ${bodySchema ? ".body()" : ".query()"} schema:
1885
+ ${issues}`
1886
+ );
1887
+ }
1888
+ }
1889
+ if (outputSchema && hasOutputExample) {
1890
+ const result = outputSchema.safeParse(outputExample);
1891
+ if (!result.success) {
1892
+ const issues = result.error.issues.map((i) => ` \u2022 ${i.path.length ? i.path.join(".") : "<root>"}: ${i.message}`).join("\n");
1893
+ throw new Error(
1894
+ `route '${key}': .outputExample() does not satisfy .output() schema:
1895
+ ${issues}`
1896
+ );
1897
+ }
1898
+ }
1899
+ }
1900
+
1855
1901
  // src/builder.ts
1856
1902
  var RouteBuilder = class {
1857
1903
  /** @internal */
@@ -1881,6 +1927,14 @@ var RouteBuilder = class {
1881
1927
  /** @internal */
1882
1928
  _outputSchema;
1883
1929
  /** @internal */
1930
+ _inputExample = void 0;
1931
+ /** @internal */
1932
+ _hasInputExample = false;
1933
+ /** @internal */
1934
+ _outputExample = void 0;
1935
+ /** @internal */
1936
+ _hasOutputExample = false;
1937
+ /** @internal */
1884
1938
  _description;
1885
1939
  /** @internal */
1886
1940
  _path;
@@ -2010,6 +2064,63 @@ var RouteBuilder = class {
2010
2064
  next._outputSchema = schema;
2011
2065
  return next;
2012
2066
  }
2067
+ /**
2068
+ * Provide a conforming example of the request input (body or query params).
2069
+ *
2070
+ * **Required** whenever `.body()` or `.query()` is set — enforced at compile time via
2071
+ * `.handler()` overloads, and at route-registration time via Zod validation of the
2072
+ * example against the schema. The example is embedded in the bazaar discovery extension
2073
+ * so indexers can advertise a working sample call.
2074
+ *
2075
+ * @example
2076
+ * ```ts
2077
+ * router.route('search')
2078
+ * .paid('0.01')
2079
+ * .body(z.object({ q: z.string() }))
2080
+ * .inputExample({ q: 'hello world' })
2081
+ * .handler(async ({ body }) => { ... });
2082
+ * ```
2083
+ */
2084
+ inputExample(example) {
2085
+ const next = this.fork();
2086
+ next._inputExample = example;
2087
+ next._hasInputExample = true;
2088
+ return next;
2089
+ }
2090
+ /**
2091
+ * Provide a conforming example of the response output.
2092
+ *
2093
+ * **Required** whenever `.output()` is set — enforced at compile time via `.handler()`
2094
+ * overloads, and at route-registration time via Zod validation of the example against
2095
+ * the schema. The example is embedded in the bazaar discovery extension so indexers
2096
+ * can advertise the response shape.
2097
+ *
2098
+ * Accepts any JSON value (objects, arrays, or primitives) — top-level array
2099
+ * or primitive responses (e.g. `z.array(...)`) are supported alongside the
2100
+ * common object case.
2101
+ *
2102
+ * @example
2103
+ * ```ts
2104
+ * router.route('search')
2105
+ * .paid('0.01')
2106
+ * .output(z.object({ results: z.array(z.string()) }))
2107
+ * .outputExample({ results: ['a', 'b'] })
2108
+ * .handler(async () => { ... });
2109
+ *
2110
+ * // Top-level array response
2111
+ * router.route('chains')
2112
+ * .paid('0.01')
2113
+ * .output(z.array(z.object({ name: z.string() })))
2114
+ * .outputExample([{ name: 'Ethereum' }])
2115
+ * .handler(async () => { ... });
2116
+ * ```
2117
+ */
2118
+ outputExample(example) {
2119
+ const next = this.fork();
2120
+ next._outputExample = example;
2121
+ next._hasOutputExample = true;
2122
+ return next;
2123
+ }
2013
2124
  description(text) {
2014
2125
  const next = this.fork();
2015
2126
  next._description = text;
@@ -2054,12 +2165,26 @@ var RouteBuilder = class {
2054
2165
  next._validateFn = fn;
2055
2166
  return next;
2056
2167
  }
2168
+ // -------------------------------------------------------------------------
2169
+ // Terminal method
2170
+ // -------------------------------------------------------------------------
2057
2171
  handler(fn) {
2172
+ const handlerFn = fn;
2058
2173
  if (this._validateFn && !this._bodySchema) {
2059
2174
  throw new Error(
2060
2175
  `route '${this._key}': .validate() requires .body() \u2014 validation runs on parsed body`
2061
2176
  );
2062
2177
  }
2178
+ validateExamples(
2179
+ this._key,
2180
+ this._bodySchema,
2181
+ this._querySchema,
2182
+ this._outputSchema,
2183
+ this._inputExample,
2184
+ this._hasInputExample,
2185
+ this._outputExample,
2186
+ this._hasOutputExample
2187
+ );
2063
2188
  const entry = {
2064
2189
  key: this._key,
2065
2190
  authMode: this._authMode,
@@ -2069,6 +2194,8 @@ var RouteBuilder = class {
2069
2194
  bodySchema: this._bodySchema,
2070
2195
  querySchema: this._querySchema,
2071
2196
  outputSchema: this._outputSchema,
2197
+ inputExample: this._hasInputExample ? this._inputExample : void 0,
2198
+ outputExample: this._hasOutputExample ? this._outputExample : void 0,
2072
2199
  description: this._description,
2073
2200
  path: this._path,
2074
2201
  method: this._method,
@@ -2082,7 +2209,11 @@ var RouteBuilder = class {
2082
2209
  mppInfo: this._mppInfo
2083
2210
  };
2084
2211
  this._registry.register(entry);
2085
- return createRequestHandler(entry, fn, this._deps);
2212
+ return createRequestHandler(
2213
+ entry,
2214
+ handlerFn,
2215
+ this._deps
2216
+ );
2086
2217
  }
2087
2218
  };
2088
2219
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/router",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
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": {