@spfn/core 0.2.0-beta.9 → 0.3.0-beta.2

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 (95) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +467 -305
  3. package/dist/authz/index.d.ts +34 -0
  4. package/dist/authz/index.js +810 -0
  5. package/dist/authz/index.js.map +1 -0
  6. package/dist/{boss-DI1r4kTS.d.ts → boss-D16fO2oG.d.ts} +41 -1
  7. package/dist/cache/index.js +42 -30
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/codegen/index.d.ts +121 -13
  10. package/dist/codegen/index.js +212 -15
  11. package/dist/codegen/index.js.map +1 -1
  12. package/dist/config/index.d.ts +615 -6
  13. package/dist/config/index.js +124 -5
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/contract/index.d.ts +220 -0
  16. package/dist/contract/index.js +558 -0
  17. package/dist/contract/index.js.map +1 -0
  18. package/dist/db/index.d.ts +528 -85
  19. package/dist/db/index.js +831 -122
  20. package/dist/db/index.js.map +1 -1
  21. package/dist/define-middleware-DfDP39Nq.d.ts +167 -0
  22. package/dist/env/index.d.ts +26 -2
  23. package/dist/env/index.js +15 -5
  24. package/dist/env/index.js.map +1 -1
  25. package/dist/env/loader.d.ts +26 -19
  26. package/dist/env/loader.js +32 -25
  27. package/dist/env/loader.js.map +1 -1
  28. package/dist/errors/index.d.ts +10 -0
  29. package/dist/errors/index.js +418 -5
  30. package/dist/errors/index.js.map +1 -1
  31. package/dist/event/index.d.ts +33 -3
  32. package/dist/event/index.js +24 -3
  33. package/dist/event/index.js.map +1 -1
  34. package/dist/event/sse/client.d.ts +42 -3
  35. package/dist/event/sse/client.js +128 -45
  36. package/dist/event/sse/client.js.map +1 -1
  37. package/dist/event/sse/index.d.ts +12 -5
  38. package/dist/event/sse/index.js +280 -32
  39. package/dist/event/sse/index.js.map +1 -1
  40. package/dist/event/ws/client.d.ts +59 -0
  41. package/dist/event/ws/client.js +273 -0
  42. package/dist/event/ws/client.js.map +1 -0
  43. package/dist/event/ws/index.d.ts +94 -0
  44. package/dist/event/ws/index.js +272 -0
  45. package/dist/event/ws/index.js.map +1 -0
  46. package/dist/job/index.d.ts +2 -2
  47. package/dist/job/index.js +155 -42
  48. package/dist/job/index.js.map +1 -1
  49. package/dist/logger/index.d.ts +5 -0
  50. package/dist/logger/index.js +14 -0
  51. package/dist/logger/index.js.map +1 -1
  52. package/dist/middleware/index.d.ts +347 -9
  53. package/dist/middleware/index.js +1462 -15
  54. package/dist/middleware/index.js.map +1 -1
  55. package/dist/nextjs/index.d.ts +2 -2
  56. package/dist/nextjs/index.js +42 -28
  57. package/dist/nextjs/index.js.map +1 -1
  58. package/dist/nextjs/server.d.ts +35 -51
  59. package/dist/nextjs/server.js +126 -60
  60. package/dist/nextjs/server.js.map +1 -1
  61. package/dist/ops/index.d.ts +152 -0
  62. package/dist/ops/index.js +500 -0
  63. package/dist/ops/index.js.map +1 -0
  64. package/dist/route/index.d.ts +8 -694
  65. package/dist/route/index.js +111 -22
  66. package/dist/route/index.js.map +1 -1
  67. package/dist/router-Qbssr11H.d.ts +676 -0
  68. package/dist/security/index.d.ts +83 -0
  69. package/dist/security/index.js +173 -0
  70. package/dist/security/index.js.map +1 -0
  71. package/dist/server/index.d.ts +491 -22
  72. package/dist/server/index.js +1887 -308
  73. package/dist/server/index.js.map +1 -1
  74. package/dist/token-manager-BT5EnUAR.d.ts +278 -0
  75. package/dist/types-2AbaW4Ie.d.ts +205 -0
  76. package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
  77. package/dist/types-D1c57Ko-.d.ts +115 -0
  78. package/dist/types-ZQODsBft.d.ts +282 -0
  79. package/package.json +244 -208
  80. package/dist/router-Di7ENoah.d.ts +0 -151
  81. package/dist/types-B-e_f2dQ.d.ts +0 -121
  82. package/docs/cache.md +0 -133
  83. package/docs/codegen.md +0 -74
  84. package/docs/database.md +0 -346
  85. package/docs/entity.md +0 -539
  86. package/docs/env.md +0 -477
  87. package/docs/errors.md +0 -319
  88. package/docs/event.md +0 -116
  89. package/docs/job.md +0 -131
  90. package/docs/logger.md +0 -108
  91. package/docs/middleware.md +0 -337
  92. package/docs/nextjs.md +0 -241
  93. package/docs/repository.md +0 -496
  94. package/docs/route.md +0 -497
  95. package/docs/server.md +0 -307
@@ -0,0 +1,676 @@
1
+ import { a as NamedMiddleware } from './define-middleware-DfDP39Nq.js';
2
+ import { Context, MiddlewareHandler } from 'hono';
3
+ import { TSchema, Static } from '@sinclair/typebox';
4
+ import { ContentfulStatusCode, RedirectStatusCode } from 'hono/utils/http-status';
5
+ import { HttpMethod } from './route/types.js';
6
+
7
+ /**
8
+ * Route Input Types
9
+ *
10
+ * Defines the structure for route input validation schemas
11
+ */
12
+
13
+ /**
14
+ * Route input schemas
15
+ *
16
+ * Defines validation schemas for different parts of an HTTP request
17
+ */
18
+ type RouteInput = {
19
+ /** Path parameters (e.g., /users/:id) */
20
+ params?: TSchema;
21
+ /** Query string parameters (e.g., ?page=1&limit=20) */
22
+ query?: TSchema;
23
+ /** Request body (JSON) */
24
+ body?: TSchema;
25
+ /** Form data (multipart/form-data) for file uploads */
26
+ formData?: TSchema;
27
+ /** HTTP headers */
28
+ headers?: TSchema;
29
+ /** Cookies */
30
+ cookies?: TSchema;
31
+ };
32
+
33
+ /**
34
+ * Route Builder Context
35
+ *
36
+ * Provides structured input access and response helpers for route handlers
37
+ */
38
+
39
+ /**
40
+ * Paginated response structure
41
+ */
42
+ type PaginatedResult<T> = {
43
+ items: T[];
44
+ pagination: {
45
+ page: number;
46
+ limit: number;
47
+ total: number;
48
+ totalPages: number;
49
+ };
50
+ };
51
+ /**
52
+ * Merge input with interceptor-injected fields
53
+ * Server receives both client input and interceptor-injected fields
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * type ClientInput = { body: { email: string, password: string } };
58
+ * type InterceptorInput = { body: { publicKey: string, keyId: string } };
59
+ * // MergedInput = { body: { email: string, password: string, publicKey: string, keyId: string } }
60
+ * ```
61
+ */
62
+ type MergedInput<TInput extends RouteInput, TInterceptor extends RouteInput> = {
63
+ params: (TInput['params'] extends TSchema ? Static<TInput['params']> : {}) & (TInterceptor['params'] extends TSchema ? Static<TInterceptor['params']> : {});
64
+ query: (TInput['query'] extends TSchema ? Static<TInput['query']> : {}) & (TInterceptor['query'] extends TSchema ? Static<TInterceptor['query']> : {});
65
+ body: (TInput['body'] extends TSchema ? Static<TInput['body']> : {}) & (TInterceptor['body'] extends TSchema ? Static<TInterceptor['body']> : {});
66
+ formData: (TInput['formData'] extends TSchema ? Static<TInput['formData']> : {}) & (TInterceptor['formData'] extends TSchema ? Static<TInterceptor['formData']> : {});
67
+ headers: (TInput['headers'] extends TSchema ? Static<TInput['headers']> : {}) & (TInterceptor['headers'] extends TSchema ? Static<TInterceptor['headers']> : {});
68
+ cookies: (TInput['cookies'] extends TSchema ? Static<TInput['cookies']> : {}) & (TInterceptor['cookies'] extends TSchema ? Static<TInterceptor['cookies']> : {});
69
+ };
70
+ /**
71
+ * RouteBuilderContext - define-route dedicated context
72
+ *
73
+ * Provides structured input access through data() method
74
+ */
75
+ type RouteBuilderContext<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}> = {
76
+ /**
77
+ * Get structured input data
78
+ *
79
+ * Returns an object with separate params, query, body, headers, cookies
80
+ * If interceptor fields are defined, they are merged with input fields
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * // GET /users/:id?page=1
85
+ * const { params, query } = await c.data();
86
+ * // params = { id: string }
87
+ * // query = { page: number }
88
+ *
89
+ * // POST /users with headers
90
+ * const { body, headers } = await c.data();
91
+ * // body = { name: string }
92
+ * // headers = { authorization: string }
93
+ *
94
+ * // With interceptor-injected fields
95
+ * const { body } = await c.data();
96
+ * // body = { email: string, password: string, publicKey: string, keyId: string }
97
+ * ```
98
+ */
99
+ data(): Promise<MergedInput<TInput, TInterceptor>>;
100
+ /**
101
+ * Return JSON response with custom status and headers
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * return c.json({ message: 'Custom response' }, 200);
106
+ * ```
107
+ */
108
+ json(data: unknown, status?: ContentfulStatusCode, headers?: Record<string, string | string[]>): Response;
109
+ /**
110
+ * Return 201 Created response with optional Location header
111
+ * Returns data directly for type inference
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * const user = await createUser(body);
116
+ * return c.created(user, `/users/${user.id}`);
117
+ * // Response: 201 Created
118
+ * // Header: Location: /users/123
119
+ * // Body: { id: '123', name: 'John' }
120
+ * // Type: User (inferred from data)
121
+ * ```
122
+ */
123
+ created<T>(data: T, location?: string): T;
124
+ /**
125
+ * Return 202 Accepted response
126
+ * Returns data directly for type inference
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * // With data
131
+ * return c.accepted({ jobId: '123' });
132
+ * // Response: 202 Accepted, Body: { jobId: '123' }
133
+ * // Type: { jobId: string }
134
+ *
135
+ * // Without data
136
+ * return c.accepted();
137
+ * // Response: 202 Accepted, Body: (empty)
138
+ * // Type: void
139
+ * ```
140
+ */
141
+ accepted(): void;
142
+ accepted<T>(data: T): T;
143
+ /**
144
+ * Return 204 No Content response (empty body)
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * await deleteUser(id);
149
+ * return c.noContent();
150
+ * // Response: 204 No Content, Body: (empty)
151
+ * // Type: void
152
+ * ```
153
+ */
154
+ noContent(): void;
155
+ /**
156
+ * Return 304 Not Modified response (empty body)
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * if (etag === requestEtag) {
161
+ * return c.notModified();
162
+ * }
163
+ * // Response: 304 Not Modified, Body: (empty)
164
+ * // Type: void
165
+ * ```
166
+ */
167
+ notModified(): void;
168
+ /**
169
+ * Return paginated response with metadata
170
+ * Returns `{ items: [...], pagination: {...} }` format with type inference
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * const users = await getUsers(page, limit);
175
+ * const total = await countUsers();
176
+ * return c.paginated(users, page, limit, total);
177
+ * // Response: {
178
+ * // items: [...],
179
+ * // pagination: {
180
+ * // page: 1,
181
+ * // limit: 20,
182
+ * // total: 100,
183
+ * // totalPages: 5
184
+ * // }
185
+ * // }
186
+ * // Type: PaginatedResult<User>
187
+ * ```
188
+ */
189
+ paginated<T>(data: T[], page: number, limit: number, total: number): PaginatedResult<T>;
190
+ /**
191
+ * Redirect to another URL
192
+ *
193
+ * @param url - Target URL to redirect to
194
+ * @param status - HTTP status code (301, 302, 303, 307, 308). Default: 302
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * // Temporary redirect (302)
199
+ * return c.redirect('/login');
200
+ *
201
+ * // Permanent redirect (301)
202
+ * return c.redirect('/new-path', 301);
203
+ *
204
+ * // See Other (303) - useful after POST
205
+ * return c.redirect('/success', 303);
206
+ * ```
207
+ */
208
+ redirect(url: string, status?: RedirectStatusCode): Response;
209
+ raw: Context;
210
+ };
211
+
212
+ /**
213
+ * Route Contract
214
+ *
215
+ * A contract marks a route as a versioned public promise to clients that are
216
+ * compiled and deployed separately from the server — a mobile app, an external
217
+ * API consumer. Those clients cannot be fixed by redeploying the server, so the
218
+ * shape they read has to survive server changes.
219
+ *
220
+ * A web client does not need this. `createApi<AppRouter>()` derives its types
221
+ * from the router in the same build, so a removed response field breaks the
222
+ * TypeScript compile instead of a running app.
223
+ *
224
+ * The response shape is declared here rather than inferred from the handler's
225
+ * return type: a declared schema exists at runtime, which is what the generator
226
+ * and the compatibility gate read. `_response` on RouteDef disappears after
227
+ * compilation.
228
+ */
229
+
230
+ /**
231
+ * Authentication profile a contracted operation is admitted under.
232
+ *
233
+ * - `none` — the operation is called before any key exists to sign with
234
+ * (enrollment, login), so it carries neither proof nor session headers.
235
+ * - `clientProofV1` — admitted by the @spfn/auth client-proof admission order.
236
+ *
237
+ * The union is deliberately closed. A profile name is part of what the contract
238
+ * publishes to external clients, so adding one is a change to this file rather
239
+ * than a string a route can invent.
240
+ */
241
+ type RouteAuthProfile = 'none' | 'clientProofV1';
242
+ /**
243
+ * The public promise a contracted route makes.
244
+ */
245
+ interface RouteContract {
246
+ /** Contract version this operation first appeared in (e.g. '1.2.0'). */
247
+ since: string;
248
+ /**
249
+ * Response shape. TypeBox schema, declared — not inferred.
250
+ *
251
+ * An operation that answers with no body declares `Type.Null()`.
252
+ */
253
+ response: TSchema;
254
+ /** Authentication profile. Defaults to `'none'`. */
255
+ auth?: RouteAuthProfile;
256
+ /** Whether the call carries a session. Defaults to `false`. */
257
+ requiresSession?: boolean;
258
+ /** Contract version this operation was announced for removal in. */
259
+ deprecatedIn?: string;
260
+ /**
261
+ * Contract version this operation was removed in.
262
+ *
263
+ * Set on a route kept alive only to carry the record. A client generated
264
+ * before the removal still calls it, and this is what tells that client the
265
+ * operation went and when — a route that simply disappears says nothing.
266
+ */
267
+ removedIn?: string;
268
+ }
269
+
270
+ /**
271
+ * Route Builder
272
+ *
273
+ * Provides tRPC-style chainable API for route definition
274
+ */
275
+
276
+ /**
277
+ * Route handler function
278
+ */
279
+ type RouteHandlerFn<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}, TResponse = unknown> = (c: RouteBuilderContext<TInput, TInterceptor>) => Response | Promise<Response> | TResponse | Promise<TResponse>;
280
+ /**
281
+ * Route definition result
282
+ *
283
+ * Contains all information needed for type inference and registration
284
+ */
285
+ type RouteDef<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}, TResponse = unknown> = {
286
+ method?: HttpMethod;
287
+ path?: string;
288
+ input?: TInput;
289
+ interceptor?: TInterceptor;
290
+ middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];
291
+ skipMiddlewares?: string[] | '*';
292
+ /**
293
+ * Public promise this route makes to separately deployed clients.
294
+ *
295
+ * Present as a runtime value, unlike `_response`: the contract generator and
296
+ * the compatibility gate read it.
297
+ */
298
+ contract?: RouteContract;
299
+ handler: RouteHandlerFn<TInput, TInterceptor, TResponse>;
300
+ _input: TInput;
301
+ _interceptor: TInterceptor;
302
+ _response: TResponse;
303
+ };
304
+ /**
305
+ * Route builder with chainable API (tRPC-style)
306
+ */
307
+ declare class RouteBuilder<TInput extends RouteInput = {}, TInterceptor extends RouteInput = {}, TResponse = never> {
308
+ _method?: HttpMethod;
309
+ _path?: string;
310
+ _input?: TInput;
311
+ _interceptor?: TInterceptor;
312
+ _middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];
313
+ _skipMiddlewares?: string[] | '*';
314
+ _contract?: RouteContract;
315
+ /**
316
+ * Create a new RouteBuilder with copied properties and optional overrides
317
+ */
318
+ private clone;
319
+ /**
320
+ * Define input schemas
321
+ *
322
+ * @example
323
+ * ```ts
324
+ * route.get('/users/:id')
325
+ * .input({
326
+ * params: Type.Object({ id: Type.String() }),
327
+ * query: Type.Object({ page: Type.Number() }),
328
+ * headers: Type.Object({ authorization: Type.String() })
329
+ * })
330
+ * .handler(async (c) => {
331
+ * const { params, query, headers } = await c.data();
332
+ * // params = { id: string }
333
+ * // query = { page: number }
334
+ * // headers = { authorization: string }
335
+ * })
336
+ * ```
337
+ */
338
+ input<TNewInput extends RouteInput>(input: TNewInput): RouteBuilder<TNewInput, TInterceptor, TResponse>;
339
+ /**
340
+ * Define fields injected by interceptors
341
+ *
342
+ * These fields are:
343
+ * - Available in the handler (merged with input)
344
+ * - Excluded from client types (codegen uses only input)
345
+ * - Not validated by route input schema (injected by middleware)
346
+ *
347
+ * Use this when middleware/interceptors add fields to the request
348
+ * before it reaches the handler.
349
+ *
350
+ * @example
351
+ * ```ts
352
+ * // Auth interceptor injects crypto key fields
353
+ * route.post('/_auth/login')
354
+ * .input({
355
+ * body: Type.Object({
356
+ * email: Type.String(),
357
+ * password: Type.String()
358
+ * })
359
+ * })
360
+ * .interceptor({
361
+ * body: Type.Object({
362
+ * publicKey: Type.String(),
363
+ * keyId: Type.String(),
364
+ * fingerprint: Type.String()
365
+ * })
366
+ * })
367
+ * .handler(async (c) => {
368
+ * const { body } = await c.data();
369
+ * // body type: { email, password, publicKey, keyId, fingerprint }
370
+ * // Client only sees: { email, password }
371
+ * return loginService(body);
372
+ * });
373
+ * ```
374
+ */
375
+ interceptor<TNewInterceptor extends RouteInput>(interceptor: TNewInterceptor): RouteBuilder<TInput, TNewInterceptor, TResponse>;
376
+ /**
377
+ * Add middlewares to the route
378
+ *
379
+ * Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
380
+ * Named middlewares that are already registered globally will be automatically
381
+ * deduplicated to prevent double execution.
382
+ *
383
+ * @example
384
+ * ```ts
385
+ * import { authenticate } from '@spfn/auth/server/middleware';
386
+ *
387
+ * // With NamedMiddleware (auto-deduped if registered globally)
388
+ * route.get('/users')
389
+ * .use([authenticate, RateLimitMiddleware()])
390
+ *
391
+ * // With regular middleware handlers
392
+ * route.get('/users')
393
+ * .use([AuthMiddleware(), RateLimitMiddleware()])
394
+ * ```
395
+ */
396
+ middleware(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>;
397
+ /**
398
+ * Add middlewares to the route (alias for `.middleware()`)
399
+ *
400
+ * Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
401
+ * Named middlewares that are already registered globally will be automatically
402
+ * deduplicated to prevent double execution.
403
+ *
404
+ * @example
405
+ * ```ts
406
+ * import { authenticate } from '@spfn/auth/server/middleware';
407
+ *
408
+ * // With NamedMiddleware (auto-deduped if registered globally)
409
+ * route.get('/users')
410
+ * .use([authenticate, RateLimitMiddleware()])
411
+ *
412
+ * // With regular middleware handlers
413
+ * route.get('/users')
414
+ * .use([AuthMiddleware(), RateLimitMiddleware()])
415
+ * ```
416
+ */
417
+ use(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>;
418
+ /**
419
+ * Skip server-level named middlewares
420
+ *
421
+ * Useful for public endpoints that should bypass auth or rate limiting
422
+ *
423
+ * @param middlewareNames - Array of middleware names to skip, or '*' to skip all
424
+ *
425
+ * @example
426
+ * ```ts
427
+ * // Skip specific middlewares
428
+ * route.get('/health')
429
+ * .skip(['auth', 'rateLimit'])
430
+ * .handler(async (c) => c.json({ status: 'ok' }));
431
+ *
432
+ * // Skip only auth (still apply rate limiting)
433
+ * route.get('/public-data')
434
+ * .skip(['auth'])
435
+ * .handler(async (c) => { ... });
436
+ *
437
+ * // Skip all middlewares
438
+ * route.get('/public-health')
439
+ * .skip('*')
440
+ * .handler(async (c) => c.json({ status: 'ok' }));
441
+ * ```
442
+ */
443
+ skip(middlewareNames: string[] | '*'): RouteBuilder<TInput, TInterceptor, TResponse>;
444
+ /**
445
+ * Publish this route as a versioned contract operation
446
+ *
447
+ * Marks the route as a promise to clients that are compiled and deployed
448
+ * separately from the server — a mobile app, an external API consumer.
449
+ * The `@spfn/core:contract` generator writes every contracted route into
450
+ * `contracts/current.json`, and the build refuses a change that would break
451
+ * an already-released client.
452
+ *
453
+ * Routes without `.contract()` are unaffected: they simply do not appear in
454
+ * the contract. A web client needs nothing here — it derives its types from
455
+ * the router in the same build.
456
+ *
457
+ * @example
458
+ * ```ts
459
+ * export const getUser = route.get('/users/:id')
460
+ * .input({ params: Type.Object({ id: Type.String() }) })
461
+ * .contract({
462
+ * since: '1.2.0',
463
+ * auth: 'clientProofV1',
464
+ * requiresSession: true,
465
+ * response: Type.Object({
466
+ * id: Type.String(),
467
+ * name: Type.String(),
468
+ * email: Type.Optional(Type.String()),
469
+ * }),
470
+ * })
471
+ * .handler(async (c) => { ... });
472
+ * ```
473
+ */
474
+ contract(contract: RouteContract): RouteBuilder<TInput, TInterceptor, TResponse>;
475
+ /**
476
+ * Define handler function
477
+ *
478
+ * Response type is automatically inferred from the return value.
479
+ * Use helper methods like `c.created()`, `c.paginated()` for proper type inference.
480
+ *
481
+ * @example
482
+ * ```ts
483
+ * // Direct return - type inferred from data
484
+ * route.get('/users/:id')
485
+ * .input({ params: Type.Object({ id: Type.String() }) })
486
+ * .handler(async (c) => {
487
+ * const { params } = await c.data();
488
+ * return await getUser(params.id); // Type: User
489
+ * })
490
+ *
491
+ * // Using c.created() - returns data with 201 status, type preserved
492
+ * route.post('/users')
493
+ * .input({ body: Type.Object({ name: Type.String() }) })
494
+ * .handler(async (c) => {
495
+ * const { body } = await c.data();
496
+ * return c.created(await createUser(body)); // Type: User
497
+ * })
498
+ *
499
+ * // Using c.paginated() - returns PaginatedResult<T>
500
+ * route.get('/users')
501
+ * .handler(async (c) => {
502
+ * const users = await getUsers();
503
+ * return c.paginated(users, 1, 20, 100); // Type: PaginatedResult<User>
504
+ * })
505
+ *
506
+ * // Using c.noContent() - returns void
507
+ * route.delete('/users/:id')
508
+ * .handler(async (c) => {
509
+ * await deleteUser(params.id);
510
+ * return c.noContent(); // Type: void
511
+ * })
512
+ *
513
+ * // Using c.json() - returns Response (type inference lost)
514
+ * // Use only when you need custom status codes not covered by helpers
515
+ * route.get('/custom')
516
+ * .handler(async (c) => {
517
+ * return c.json({ data }, 418); // Type: Response
518
+ * })
519
+ * ```
520
+ */
521
+ handler<THandlerResponse>(fn: RouteHandlerFn<TInput, TInterceptor, THandlerResponse>): RouteDef<TInput, TInterceptor, THandlerResponse>;
522
+ }
523
+ /**
524
+ * Route builder entry point
525
+ *
526
+ * @example
527
+ * ```ts
528
+ * // GET request
529
+ * export const getUser = route.get('/users/:id')
530
+ * .input({ params: Type.Object({ id: Type.String() }) })
531
+ * .handler(async (c) => {
532
+ * const { params } = await c.data();
533
+ * return await db.user.findUnique({ where: { id: params.id } });
534
+ * });
535
+ *
536
+ * // POST request
537
+ * export const createUser = route.post('/users')
538
+ * .input({ body: Type.Object({ name: Type.String(), email: Type.String() }) })
539
+ * .handler(async (c) => {
540
+ * const { body } = await c.data();
541
+ * return c.created(await db.user.create({ data: body }));
542
+ * });
543
+ * ```
544
+ */
545
+ declare const route: {
546
+ get: (path: string) => RouteBuilder;
547
+ post: (path: string) => RouteBuilder;
548
+ put: (path: string) => RouteBuilder;
549
+ patch: (path: string) => RouteBuilder;
550
+ delete: (path: string) => RouteBuilder;
551
+ };
552
+
553
+ /**
554
+ * Router Definition
555
+ *
556
+ * Provides router composition and middleware management
557
+ */
558
+
559
+ /**
560
+ * Router definition - holds all routes
561
+ */
562
+ interface Router<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>> {
563
+ routes: TRoutes;
564
+ _routes: TRoutes;
565
+ _packageRouters: Router<any>[];
566
+ _globalMiddlewares: NamedMiddleware<string>[];
567
+ /** The contract version these routes publish, or null when uncontracted. */
568
+ _contractVersion: string | null;
569
+ /**
570
+ * Register package routers (type-hidden)
571
+ *
572
+ * Package routes are:
573
+ * - Recognized by RPC proxy and backend
574
+ * - NOT exposed in client types (use package's own API like authApi, cmsApi)
575
+ *
576
+ * @example
577
+ * ```ts
578
+ * import { authRouter } from '@spfn/auth/server';
579
+ * import { cmsAppRouter } from '@spfn/cms/server';
580
+ *
581
+ * export const appRouter = defineRouter({
582
+ * getRoot,
583
+ * getHealth,
584
+ * })
585
+ * .packages([authRouter, cmsAppRouter]);
586
+ *
587
+ * // Client usage:
588
+ * // api.getRoot.call({}) - app routes
589
+ * // authApi.login.call({}) - package API
590
+ * ```
591
+ */
592
+ packages(routers: Router<any>[]): Router<TRoutes>;
593
+ /**
594
+ * Register global middlewares
595
+ *
596
+ * Applied to all routes unless explicitly skipped via .skip()
597
+ *
598
+ * @example
599
+ * ```ts
600
+ * import { authMiddleware, loggingMiddleware } from './middlewares';
601
+ *
602
+ * export const appRouter = defineRouter({
603
+ * getRoot,
604
+ * getHealth,
605
+ * })
606
+ * .packages([authRouter])
607
+ * .use([authMiddleware, loggingMiddleware]);
608
+ * ```
609
+ */
610
+ use(middlewares: NamedMiddleware<string>[]): Router<TRoutes>;
611
+ /**
612
+ * Declare the contract version these routes publish.
613
+ *
614
+ * A client compiled against this server — a mobile app in a store — is
615
+ * generated from one version of the contract and cannot be updated when the
616
+ * server changes. The server announces this version on every response so
617
+ * that client can tell whether the two ends still agree.
618
+ *
619
+ * This is the version's source. A released snapshot is written to
620
+ * `contracts/released/<version>.json` from what is declared here, so the
621
+ * filename follows the code rather than the code having to be told what the
622
+ * filename said.
623
+ *
624
+ * Only a server with contracted routes needs it. Without it the contract
625
+ * generator still writes `current.json` and still runs the compatibility
626
+ * gate; what it cannot do is cut a release or announce a version.
627
+ *
628
+ * @example
629
+ * ```ts
630
+ * export const appRouter = defineRouter({ getRoot, listItems })
631
+ * .contractVersion('1.2.0')
632
+ * .packages([authRouter]);
633
+ * ```
634
+ */
635
+ contractVersion(version: string): Router<TRoutes>;
636
+ }
637
+ /**
638
+ * Define a router with multiple routes (tRPC-style)
639
+ *
640
+ * Supports chainable API for packages and middlewares:
641
+ *
642
+ * @example
643
+ * ```ts
644
+ * // Basic usage
645
+ * export const appRouter = defineRouter({
646
+ * getRoot,
647
+ * getHealth,
648
+ * listExamples,
649
+ * });
650
+ *
651
+ * // With package routers (type-hidden)
652
+ * export const appRouter = defineRouter({
653
+ * getRoot,
654
+ * getHealth,
655
+ * })
656
+ * .packages([authRouter, cmsAppRouter]);
657
+ *
658
+ * // With global middlewares
659
+ * export const appRouter = defineRouter({
660
+ * getRoot,
661
+ * getHealth,
662
+ * })
663
+ * .packages([authRouter])
664
+ * .use([authMiddleware, loggingMiddleware]);
665
+ *
666
+ * export type AppRouter = typeof appRouter;
667
+ * ```
668
+ *
669
+ * Package routes:
670
+ * - Recognized by RPC proxy and backend for routing
671
+ * - NOT included in AppRouter type (use authApi, cmsApi instead)
672
+ * - Prevents confusion between app API and package APIs
673
+ */
674
+ declare function defineRouter<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(routes: TRoutes): Router<TRoutes>;
675
+
676
+ export { type MergedInput as M, type PaginatedResult as P, type Router as R, type RouteDef as a, RouteBuilder as b, type RouteAuthProfile as c, type RouteBuilderContext as d, type RouteContract as e, type RouteHandlerFn as f, type RouteInput as g, defineRouter as h, route as r };