@spfn/core 0.2.0-beta.66 → 0.2.0-beta.68

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.
Files changed (37) hide show
  1. package/README.md +309 -116
  2. package/dist/authz/index.js +398 -3
  3. package/dist/authz/index.js.map +1 -1
  4. package/dist/codegen/index.d.ts +114 -8
  5. package/dist/codegen/index.js +162 -3
  6. package/dist/codegen/index.js.map +1 -1
  7. package/dist/config/index.js +1 -1
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/contract/index.d.ts +288 -0
  10. package/dist/contract/index.js +534 -0
  11. package/dist/contract/index.js.map +1 -0
  12. package/dist/db/index.js +13 -10
  13. package/dist/db/index.js.map +1 -1
  14. package/dist/{define-middleware-DuXD8Hvu.d.ts → define-middleware-B9bFuXVU.d.ts} +1 -1
  15. package/dist/errors/index.js +398 -3
  16. package/dist/errors/index.js.map +1 -1
  17. package/dist/event/index.d.ts +3 -3
  18. package/dist/event/sse/client.d.ts +2 -2
  19. package/dist/event/sse/index.d.ts +4 -4
  20. package/dist/event/sse/index.js +9 -0
  21. package/dist/event/sse/index.js.map +1 -1
  22. package/dist/event/ws/client.d.ts +2 -2
  23. package/dist/event/ws/index.d.ts +3 -3
  24. package/dist/middleware/index.d.ts +108 -11
  25. package/dist/middleware/index.js +769 -632
  26. package/dist/middleware/index.js.map +1 -1
  27. package/dist/route/index.d.ts +8 -552
  28. package/dist/route/index.js +36 -0
  29. package/dist/route/index.js.map +1 -1
  30. package/dist/router-DhvbMhef.d.ts +641 -0
  31. package/dist/server/index.d.ts +3 -3
  32. package/dist/server/index.js +9 -0
  33. package/dist/server/index.js.map +1 -1
  34. package/dist/{token-manager-jKD_EsSE.d.ts → token-manager-vZeqBbtA.d.ts} +7 -0
  35. package/dist/{types-DVjf37yO.d.ts → types-CF-37KAG.d.ts} +1 -1
  36. package/dist/{types-BFB72jbM.d.ts → types-D9uMxeQS.d.ts} +1 -1
  37. package/package.json +11 -9
@@ -1,555 +1,11 @@
1
- import * as _sinclair_typebox from '@sinclair/typebox';
2
- import { TSchema, Static, Kind } from '@sinclair/typebox';
3
- import { Context, MiddlewareHandler, Hono } from 'hono';
4
- import { ContentfulStatusCode, RedirectStatusCode } from 'hono/utils/http-status';
5
- import { N as NamedMiddleware } from '../define-middleware-DuXD8Hvu.js';
6
- export { E as ExtractMiddlewareNames, b as NamedMiddlewareFactory, d as defineMiddleware, a as defineMiddlewareFactory } from '../define-middleware-DuXD8Hvu.js';
1
+ import { R as RouteDef, a as Router } from '../router-DhvbMhef.js';
2
+ export { M as MergedInput, P as PaginatedResult, f as RouteAuthProfile, c as RouteBuilderContext, e as RouteContract, d as RouteHandlerFn, b as RouteInput, g as defineRouter, r as route } from '../router-DhvbMhef.js';
3
+ import { Hono, MiddlewareHandler } from 'hono';
7
4
  import { HttpMethod } from './types.js';
8
-
9
- /**
10
- * Route Input Types
11
- *
12
- * Defines the structure for route input validation schemas
13
- */
14
-
15
- /**
16
- * Route input schemas
17
- *
18
- * Defines validation schemas for different parts of an HTTP request
19
- */
20
- type RouteInput = {
21
- /** Path parameters (e.g., /users/:id) */
22
- params?: TSchema;
23
- /** Query string parameters (e.g., ?page=1&limit=20) */
24
- query?: TSchema;
25
- /** Request body (JSON) */
26
- body?: TSchema;
27
- /** Form data (multipart/form-data) for file uploads */
28
- formData?: TSchema;
29
- /** HTTP headers */
30
- headers?: TSchema;
31
- /** Cookies */
32
- cookies?: TSchema;
33
- };
34
-
35
- /**
36
- * Route Builder Context
37
- *
38
- * Provides structured input access and response helpers for route handlers
39
- */
40
-
41
- /**
42
- * Paginated response structure
43
- */
44
- type PaginatedResult<T> = {
45
- items: T[];
46
- pagination: {
47
- page: number;
48
- limit: number;
49
- total: number;
50
- totalPages: number;
51
- };
52
- };
53
- /**
54
- * Merge input with interceptor-injected fields
55
- * Server receives both client input and interceptor-injected fields
56
- *
57
- * @example
58
- * ```ts
59
- * type ClientInput = { body: { email: string, password: string } };
60
- * type InterceptorInput = { body: { publicKey: string, keyId: string } };
61
- * // MergedInput = { body: { email: string, password: string, publicKey: string, keyId: string } }
62
- * ```
63
- */
64
- type MergedInput<TInput extends RouteInput, TInterceptor extends RouteInput> = {
65
- params: (TInput['params'] extends TSchema ? Static<TInput['params']> : {}) & (TInterceptor['params'] extends TSchema ? Static<TInterceptor['params']> : {});
66
- query: (TInput['query'] extends TSchema ? Static<TInput['query']> : {}) & (TInterceptor['query'] extends TSchema ? Static<TInterceptor['query']> : {});
67
- body: (TInput['body'] extends TSchema ? Static<TInput['body']> : {}) & (TInterceptor['body'] extends TSchema ? Static<TInterceptor['body']> : {});
68
- formData: (TInput['formData'] extends TSchema ? Static<TInput['formData']> : {}) & (TInterceptor['formData'] extends TSchema ? Static<TInterceptor['formData']> : {});
69
- headers: (TInput['headers'] extends TSchema ? Static<TInput['headers']> : {}) & (TInterceptor['headers'] extends TSchema ? Static<TInterceptor['headers']> : {});
70
- cookies: (TInput['cookies'] extends TSchema ? Static<TInput['cookies']> : {}) & (TInterceptor['cookies'] extends TSchema ? Static<TInterceptor['cookies']> : {});
71
- };
72
- /**
73
- * RouteBuilderContext - define-route dedicated context
74
- *
75
- * Provides structured input access through data() method
76
- */
77
- type RouteBuilderContext<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}> = {
78
- /**
79
- * Get structured input data
80
- *
81
- * Returns an object with separate params, query, body, headers, cookies
82
- * If interceptor fields are defined, they are merged with input fields
83
- *
84
- * @example
85
- * ```ts
86
- * // GET /users/:id?page=1
87
- * const { params, query } = await c.data();
88
- * // params = { id: string }
89
- * // query = { page: number }
90
- *
91
- * // POST /users with headers
92
- * const { body, headers } = await c.data();
93
- * // body = { name: string }
94
- * // headers = { authorization: string }
95
- *
96
- * // With interceptor-injected fields
97
- * const { body } = await c.data();
98
- * // body = { email: string, password: string, publicKey: string, keyId: string }
99
- * ```
100
- */
101
- data(): Promise<MergedInput<TInput, TInterceptor>>;
102
- /**
103
- * Return JSON response with custom status and headers
104
- *
105
- * @example
106
- * ```ts
107
- * return c.json({ message: 'Custom response' }, 200);
108
- * ```
109
- */
110
- json(data: unknown, status?: ContentfulStatusCode, headers?: Record<string, string | string[]>): Response;
111
- /**
112
- * Return 201 Created response with optional Location header
113
- * Returns data directly for type inference
114
- *
115
- * @example
116
- * ```ts
117
- * const user = await createUser(body);
118
- * return c.created(user, `/users/${user.id}`);
119
- * // Response: 201 Created
120
- * // Header: Location: /users/123
121
- * // Body: { id: '123', name: 'John' }
122
- * // Type: User (inferred from data)
123
- * ```
124
- */
125
- created<T>(data: T, location?: string): T;
126
- /**
127
- * Return 202 Accepted response
128
- * Returns data directly for type inference
129
- *
130
- * @example
131
- * ```ts
132
- * // With data
133
- * return c.accepted({ jobId: '123' });
134
- * // Response: 202 Accepted, Body: { jobId: '123' }
135
- * // Type: { jobId: string }
136
- *
137
- * // Without data
138
- * return c.accepted();
139
- * // Response: 202 Accepted, Body: (empty)
140
- * // Type: void
141
- * ```
142
- */
143
- accepted(): void;
144
- accepted<T>(data: T): T;
145
- /**
146
- * Return 204 No Content response (empty body)
147
- *
148
- * @example
149
- * ```ts
150
- * await deleteUser(id);
151
- * return c.noContent();
152
- * // Response: 204 No Content, Body: (empty)
153
- * // Type: void
154
- * ```
155
- */
156
- noContent(): void;
157
- /**
158
- * Return 304 Not Modified response (empty body)
159
- *
160
- * @example
161
- * ```ts
162
- * if (etag === requestEtag) {
163
- * return c.notModified();
164
- * }
165
- * // Response: 304 Not Modified, Body: (empty)
166
- * // Type: void
167
- * ```
168
- */
169
- notModified(): void;
170
- /**
171
- * Return paginated response with metadata
172
- * Returns `{ items: [...], pagination: {...} }` format with type inference
173
- *
174
- * @example
175
- * ```ts
176
- * const users = await getUsers(page, limit);
177
- * const total = await countUsers();
178
- * return c.paginated(users, page, limit, total);
179
- * // Response: {
180
- * // items: [...],
181
- * // pagination: {
182
- * // page: 1,
183
- * // limit: 20,
184
- * // total: 100,
185
- * // totalPages: 5
186
- * // }
187
- * // }
188
- * // Type: PaginatedResult<User>
189
- * ```
190
- */
191
- paginated<T>(data: T[], page: number, limit: number, total: number): PaginatedResult<T>;
192
- /**
193
- * Redirect to another URL
194
- *
195
- * @param url - Target URL to redirect to
196
- * @param status - HTTP status code (301, 302, 303, 307, 308). Default: 302
197
- *
198
- * @example
199
- * ```ts
200
- * // Temporary redirect (302)
201
- * return c.redirect('/login');
202
- *
203
- * // Permanent redirect (301)
204
- * return c.redirect('/new-path', 301);
205
- *
206
- * // See Other (303) - useful after POST
207
- * return c.redirect('/success', 303);
208
- * ```
209
- */
210
- redirect(url: string, status?: RedirectStatusCode): Response;
211
- raw: Context;
212
- };
213
-
214
- /**
215
- * Route Builder
216
- *
217
- * Provides tRPC-style chainable API for route definition
218
- */
219
-
220
- /**
221
- * Route handler function
222
- */
223
- type RouteHandlerFn<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}, TResponse = unknown> = (c: RouteBuilderContext<TInput, TInterceptor>) => Response | Promise<Response> | TResponse | Promise<TResponse>;
224
- /**
225
- * Route definition result
226
- *
227
- * Contains all information needed for type inference and registration
228
- */
229
- type RouteDef<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}, TResponse = unknown> = {
230
- method?: HttpMethod;
231
- path?: string;
232
- input?: TInput;
233
- interceptor?: TInterceptor;
234
- middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];
235
- skipMiddlewares?: string[] | '*';
236
- handler: RouteHandlerFn<TInput, TInterceptor, TResponse>;
237
- _input: TInput;
238
- _interceptor: TInterceptor;
239
- _response: TResponse;
240
- };
241
- /**
242
- * Route builder with chainable API (tRPC-style)
243
- */
244
- declare class RouteBuilder<TInput extends RouteInput = {}, TInterceptor extends RouteInput = {}, TResponse = never> {
245
- _method?: HttpMethod;
246
- _path?: string;
247
- _input?: TInput;
248
- _interceptor?: TInterceptor;
249
- _middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];
250
- _skipMiddlewares?: string[] | '*';
251
- /**
252
- * Create a new RouteBuilder with copied properties and optional overrides
253
- */
254
- private clone;
255
- /**
256
- * Define input schemas
257
- *
258
- * @example
259
- * ```ts
260
- * route.get('/users/:id')
261
- * .input({
262
- * params: Type.Object({ id: Type.String() }),
263
- * query: Type.Object({ page: Type.Number() }),
264
- * headers: Type.Object({ authorization: Type.String() })
265
- * })
266
- * .handler(async (c) => {
267
- * const { params, query, headers } = await c.data();
268
- * // params = { id: string }
269
- * // query = { page: number }
270
- * // headers = { authorization: string }
271
- * })
272
- * ```
273
- */
274
- input<TNewInput extends RouteInput>(input: TNewInput): RouteBuilder<TNewInput, TInterceptor, TResponse>;
275
- /**
276
- * Define fields injected by interceptors
277
- *
278
- * These fields are:
279
- * - Available in the handler (merged with input)
280
- * - Excluded from client types (codegen uses only input)
281
- * - Not validated by route input schema (injected by middleware)
282
- *
283
- * Use this when middleware/interceptors add fields to the request
284
- * before it reaches the handler.
285
- *
286
- * @example
287
- * ```ts
288
- * // Auth interceptor injects crypto key fields
289
- * route.post('/_auth/login')
290
- * .input({
291
- * body: Type.Object({
292
- * email: Type.String(),
293
- * password: Type.String()
294
- * })
295
- * })
296
- * .interceptor({
297
- * body: Type.Object({
298
- * publicKey: Type.String(),
299
- * keyId: Type.String(),
300
- * fingerprint: Type.String()
301
- * })
302
- * })
303
- * .handler(async (c) => {
304
- * const { body } = await c.data();
305
- * // body type: { email, password, publicKey, keyId, fingerprint }
306
- * // Client only sees: { email, password }
307
- * return loginService(body);
308
- * });
309
- * ```
310
- */
311
- interceptor<TNewInterceptor extends RouteInput>(interceptor: TNewInterceptor): RouteBuilder<TInput, TNewInterceptor, TResponse>;
312
- /**
313
- * Add middlewares to the route
314
- *
315
- * Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
316
- * Named middlewares that are already registered globally will be automatically
317
- * deduplicated to prevent double execution.
318
- *
319
- * @example
320
- * ```ts
321
- * import { authenticate } from '@spfn/auth/server/middleware';
322
- *
323
- * // With NamedMiddleware (auto-deduped if registered globally)
324
- * route.get('/users')
325
- * .use([authenticate, RateLimitMiddleware()])
326
- *
327
- * // With regular middleware handlers
328
- * route.get('/users')
329
- * .use([AuthMiddleware(), RateLimitMiddleware()])
330
- * ```
331
- */
332
- middleware(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>;
333
- /**
334
- * Add middlewares to the route (alias for `.middleware()`)
335
- *
336
- * Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
337
- * Named middlewares that are already registered globally will be automatically
338
- * deduplicated to prevent double execution.
339
- *
340
- * @example
341
- * ```ts
342
- * import { authenticate } from '@spfn/auth/server/middleware';
343
- *
344
- * // With NamedMiddleware (auto-deduped if registered globally)
345
- * route.get('/users')
346
- * .use([authenticate, RateLimitMiddleware()])
347
- *
348
- * // With regular middleware handlers
349
- * route.get('/users')
350
- * .use([AuthMiddleware(), RateLimitMiddleware()])
351
- * ```
352
- */
353
- use(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>;
354
- /**
355
- * Skip server-level named middlewares
356
- *
357
- * Useful for public endpoints that should bypass auth or rate limiting
358
- *
359
- * @param middlewareNames - Array of middleware names to skip, or '*' to skip all
360
- *
361
- * @example
362
- * ```ts
363
- * // Skip specific middlewares
364
- * route.get('/health')
365
- * .skip(['auth', 'rateLimit'])
366
- * .handler(async (c) => c.json({ status: 'ok' }));
367
- *
368
- * // Skip only auth (still apply rate limiting)
369
- * route.get('/public-data')
370
- * .skip(['auth'])
371
- * .handler(async (c) => { ... });
372
- *
373
- * // Skip all middlewares
374
- * route.get('/public-health')
375
- * .skip('*')
376
- * .handler(async (c) => c.json({ status: 'ok' }));
377
- * ```
378
- */
379
- skip(middlewareNames: string[] | '*'): RouteBuilder<TInput, TInterceptor, TResponse>;
380
- /**
381
- * Define handler function
382
- *
383
- * Response type is automatically inferred from the return value.
384
- * Use helper methods like `c.created()`, `c.paginated()` for proper type inference.
385
- *
386
- * @example
387
- * ```ts
388
- * // Direct return - type inferred from data
389
- * route.get('/users/:id')
390
- * .input({ params: Type.Object({ id: Type.String() }) })
391
- * .handler(async (c) => {
392
- * const { params } = await c.data();
393
- * return await getUser(params.id); // Type: User
394
- * })
395
- *
396
- * // Using c.created() - returns data with 201 status, type preserved
397
- * route.post('/users')
398
- * .input({ body: Type.Object({ name: Type.String() }) })
399
- * .handler(async (c) => {
400
- * const { body } = await c.data();
401
- * return c.created(await createUser(body)); // Type: User
402
- * })
403
- *
404
- * // Using c.paginated() - returns PaginatedResult<T>
405
- * route.get('/users')
406
- * .handler(async (c) => {
407
- * const users = await getUsers();
408
- * return c.paginated(users, 1, 20, 100); // Type: PaginatedResult<User>
409
- * })
410
- *
411
- * // Using c.noContent() - returns void
412
- * route.delete('/users/:id')
413
- * .handler(async (c) => {
414
- * await deleteUser(params.id);
415
- * return c.noContent(); // Type: void
416
- * })
417
- *
418
- * // Using c.json() - returns Response (type inference lost)
419
- * // Use only when you need custom status codes not covered by helpers
420
- * route.get('/custom')
421
- * .handler(async (c) => {
422
- * return c.json({ data }, 418); // Type: Response
423
- * })
424
- * ```
425
- */
426
- handler<THandlerResponse>(fn: RouteHandlerFn<TInput, TInterceptor, THandlerResponse>): RouteDef<TInput, TInterceptor, THandlerResponse>;
427
- }
428
- /**
429
- * Route builder entry point
430
- *
431
- * @example
432
- * ```ts
433
- * // GET request
434
- * export const getUser = route.get('/users/:id')
435
- * .input({ params: Type.Object({ id: Type.String() }) })
436
- * .handler(async (c) => {
437
- * const { params } = await c.data();
438
- * return await db.user.findUnique({ where: { id: params.id } });
439
- * });
440
- *
441
- * // POST request
442
- * export const createUser = route.post('/users')
443
- * .input({ body: Type.Object({ name: Type.String(), email: Type.String() }) })
444
- * .handler(async (c) => {
445
- * const { body } = await c.data();
446
- * return c.created(await db.user.create({ data: body }));
447
- * });
448
- * ```
449
- */
450
- declare const route: {
451
- get: (path: string) => RouteBuilder;
452
- post: (path: string) => RouteBuilder;
453
- put: (path: string) => RouteBuilder;
454
- patch: (path: string) => RouteBuilder;
455
- delete: (path: string) => RouteBuilder;
456
- };
457
-
458
- /**
459
- * Router Definition
460
- *
461
- * Provides router composition and middleware management
462
- */
463
-
464
- /**
465
- * Router definition - holds all routes
466
- */
467
- interface Router<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>> {
468
- routes: TRoutes;
469
- _routes: TRoutes;
470
- _packageRouters: Router<any>[];
471
- _globalMiddlewares: NamedMiddleware<string>[];
472
- /**
473
- * Register package routers (type-hidden)
474
- *
475
- * Package routes are:
476
- * - Recognized by RPC proxy and backend
477
- * - NOT exposed in client types (use package's own API like authApi, cmsApi)
478
- *
479
- * @example
480
- * ```ts
481
- * import { authRouter } from '@spfn/auth/server';
482
- * import { cmsAppRouter } from '@spfn/cms/server';
483
- *
484
- * export const appRouter = defineRouter({
485
- * getRoot,
486
- * getHealth,
487
- * })
488
- * .packages([authRouter, cmsAppRouter]);
489
- *
490
- * // Client usage:
491
- * // api.getRoot.call({}) - app routes
492
- * // authApi.login.call({}) - package API
493
- * ```
494
- */
495
- packages(routers: Router<any>[]): Router<TRoutes>;
496
- /**
497
- * Register global middlewares
498
- *
499
- * Applied to all routes unless explicitly skipped via .skip()
500
- *
501
- * @example
502
- * ```ts
503
- * import { authMiddleware, loggingMiddleware } from './middlewares';
504
- *
505
- * export const appRouter = defineRouter({
506
- * getRoot,
507
- * getHealth,
508
- * })
509
- * .packages([authRouter])
510
- * .use([authMiddleware, loggingMiddleware]);
511
- * ```
512
- */
513
- use(middlewares: NamedMiddleware<string>[]): Router<TRoutes>;
514
- }
515
- /**
516
- * Define a router with multiple routes (tRPC-style)
517
- *
518
- * Supports chainable API for packages and middlewares:
519
- *
520
- * @example
521
- * ```ts
522
- * // Basic usage
523
- * export const appRouter = defineRouter({
524
- * getRoot,
525
- * getHealth,
526
- * listExamples,
527
- * });
528
- *
529
- * // With package routers (type-hidden)
530
- * export const appRouter = defineRouter({
531
- * getRoot,
532
- * getHealth,
533
- * })
534
- * .packages([authRouter, cmsAppRouter]);
535
- *
536
- * // With global middlewares
537
- * export const appRouter = defineRouter({
538
- * getRoot,
539
- * getHealth,
540
- * })
541
- * .packages([authRouter])
542
- * .use([authMiddleware, loggingMiddleware]);
543
- *
544
- * export type AppRouter = typeof appRouter;
545
- * ```
546
- *
547
- * Package routes:
548
- * - Recognized by RPC proxy and backend for routing
549
- * - NOT included in AppRouter type (use authApi, cmsApi instead)
550
- * - Prevents confusion between app API and package APIs
551
- */
552
- declare function defineRouter<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(routes: TRoutes): Router<TRoutes>;
5
+ export { E as ExtractMiddlewareNames, b as NamedMiddleware, N as NamedMiddlewareFactory, d as defineMiddleware, a as defineMiddlewareFactory } from '../define-middleware-B9bFuXVU.js';
6
+ import * as _sinclair_typebox from '@sinclair/typebox';
7
+ import { TSchema, Kind } from '@sinclair/typebox';
8
+ import 'hono/utils/http-status';
553
9
 
554
10
  /**
555
11
  * Route Registration for define-route style routing
@@ -749,4 +205,4 @@ declare function getFileOptions(schema: TSchema): FileSchemaOptions | FileArrayS
749
205
  */
750
206
  declare function formatFileSize(bytes: number): string;
751
207
 
752
- export { FileArraySchema, type FileArraySchemaOptions, type FileArraySchemaType, FileSchema, type FileSchemaOptions, type FileSchemaType, HttpMethod, type MergedInput, NamedMiddleware, Nullable, OptionalFileSchema, OptionalNullable, type PaginatedResult, type RegisteredRoute, type RouteBuilderContext, type RouteDef, type RouteHandlerFn, type RouteInput, type Router, defineRouter, formatFileSize, getFileOptions, isFileArraySchema, isFileSchema, isHttpMethod, registerRoutes, route };
208
+ export { FileArraySchema, type FileArraySchemaOptions, type FileArraySchemaType, FileSchema, type FileSchemaOptions, type FileSchemaType, HttpMethod, Nullable, OptionalFileSchema, OptionalNullable, type RegisteredRoute, RouteDef, Router, formatFileSize, getFileOptions, isFileArraySchema, isFileSchema, isHttpMethod, registerRoutes };
@@ -11,6 +11,7 @@ var RouteBuilder = class _RouteBuilder {
11
11
  _interceptor;
12
12
  _middlewares;
13
13
  _skipMiddlewares;
14
+ _contract;
14
15
  /**
15
16
  * Create a new RouteBuilder with copied properties and optional overrides
16
17
  */
@@ -22,6 +23,7 @@ var RouteBuilder = class _RouteBuilder {
22
23
  builder._interceptor = overrides?.interceptor ?? this._interceptor;
23
24
  builder._middlewares = overrides?.middlewares ?? this._middlewares;
24
25
  builder._skipMiddlewares = overrides?.skipMiddlewares ?? this._skipMiddlewares;
26
+ builder._contract = overrides?.contract ?? this._contract;
25
27
  return builder;
26
28
  }
27
29
  /**
@@ -159,6 +161,39 @@ var RouteBuilder = class _RouteBuilder {
159
161
  skip(middlewareNames) {
160
162
  return this.clone({ skipMiddlewares: middlewareNames });
161
163
  }
164
+ /**
165
+ * Publish this route as a versioned contract operation
166
+ *
167
+ * Marks the route as a promise to clients that are compiled and deployed
168
+ * separately from the server — a mobile app, an external API consumer.
169
+ * The `@spfn/core:contract` generator writes every contracted route into
170
+ * `contracts/current.json`, and the build refuses a change that would break
171
+ * an already-released client.
172
+ *
173
+ * Routes without `.contract()` are unaffected: they simply do not appear in
174
+ * the contract. A web client needs nothing here — it derives its types from
175
+ * the router in the same build.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * export const getUser = route.get('/users/:id')
180
+ * .input({ params: Type.Object({ id: Type.String() }) })
181
+ * .contract({
182
+ * since: '1.2.0',
183
+ * auth: 'clientProofV1',
184
+ * requiresSession: true,
185
+ * response: Type.Object({
186
+ * id: Type.String(),
187
+ * name: Type.String(),
188
+ * email: Type.Optional(Type.String()),
189
+ * }),
190
+ * })
191
+ * .handler(async (c) => { ... });
192
+ * ```
193
+ */
194
+ contract(contract) {
195
+ return this.clone({ contract });
196
+ }
162
197
  /**
163
198
  * Define handler function
164
199
  *
@@ -213,6 +248,7 @@ var RouteBuilder = class _RouteBuilder {
213
248
  interceptor: this._interceptor,
214
249
  middlewares: this._middlewares,
215
250
  skipMiddlewares: this._skipMiddlewares,
251
+ contract: this._contract,
216
252
  handler: fn,
217
253
  _input: {},
218
254
  _interceptor: {},