@spfn/core 0.1.0-alpha.88 → 0.2.0-beta.10

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 (97) hide show
  1. package/README.md +298 -466
  2. package/dist/boss-DI1r4kTS.d.ts +244 -0
  3. package/dist/cache/index.d.ts +13 -33
  4. package/dist/cache/index.js +14 -703
  5. package/dist/cache/index.js.map +1 -1
  6. package/dist/codegen/index.d.ts +214 -17
  7. package/dist/codegen/index.js +231 -1420
  8. package/dist/codegen/index.js.map +1 -1
  9. package/dist/config/index.d.ts +1227 -0
  10. package/dist/config/index.js +273 -0
  11. package/dist/config/index.js.map +1 -0
  12. package/dist/db/index.d.ts +741 -59
  13. package/dist/db/index.js +1063 -1226
  14. package/dist/db/index.js.map +1 -1
  15. package/dist/env/index.d.ts +658 -308
  16. package/dist/env/index.js +503 -928
  17. package/dist/env/index.js.map +1 -1
  18. package/dist/env/loader.d.ts +87 -0
  19. package/dist/env/loader.js +70 -0
  20. package/dist/env/loader.js.map +1 -0
  21. package/dist/errors/index.d.ts +417 -29
  22. package/dist/errors/index.js +359 -98
  23. package/dist/errors/index.js.map +1 -1
  24. package/dist/event/index.d.ts +41 -0
  25. package/dist/event/index.js +131 -0
  26. package/dist/event/index.js.map +1 -0
  27. package/dist/event/sse/client.d.ts +82 -0
  28. package/dist/event/sse/client.js +115 -0
  29. package/dist/event/sse/client.js.map +1 -0
  30. package/dist/event/sse/index.d.ts +40 -0
  31. package/dist/event/sse/index.js +92 -0
  32. package/dist/event/sse/index.js.map +1 -0
  33. package/dist/job/index.d.ts +218 -0
  34. package/dist/job/index.js +410 -0
  35. package/dist/job/index.js.map +1 -0
  36. package/dist/logger/index.d.ts +20 -79
  37. package/dist/logger/index.js +82 -387
  38. package/dist/logger/index.js.map +1 -1
  39. package/dist/middleware/index.d.ts +102 -20
  40. package/dist/middleware/index.js +51 -705
  41. package/dist/middleware/index.js.map +1 -1
  42. package/dist/nextjs/index.d.ts +120 -0
  43. package/dist/nextjs/index.js +448 -0
  44. package/dist/nextjs/index.js.map +1 -0
  45. package/dist/{client/nextjs/index.d.ts → nextjs/server.d.ts} +335 -262
  46. package/dist/nextjs/server.js +637 -0
  47. package/dist/nextjs/server.js.map +1 -0
  48. package/dist/route/index.d.ts +879 -25
  49. package/dist/route/index.js +697 -1271
  50. package/dist/route/index.js.map +1 -1
  51. package/dist/route/types.d.ts +9 -0
  52. package/dist/route/types.js +3 -0
  53. package/dist/route/types.js.map +1 -0
  54. package/dist/router-Di7ENoah.d.ts +151 -0
  55. package/dist/server/index.d.ts +345 -64
  56. package/dist/server/index.js +1174 -3233
  57. package/dist/server/index.js.map +1 -1
  58. package/dist/types-B-e_f2dQ.d.ts +121 -0
  59. package/dist/types-BGl4QL1w.d.ts +77 -0
  60. package/dist/types-BOPTApC2.d.ts +245 -0
  61. package/docs/cache.md +133 -0
  62. package/docs/codegen.md +74 -0
  63. package/docs/database.md +346 -0
  64. package/docs/entity.md +539 -0
  65. package/docs/env.md +477 -0
  66. package/docs/errors.md +319 -0
  67. package/docs/event.md +116 -0
  68. package/docs/file-upload.md +717 -0
  69. package/docs/job.md +131 -0
  70. package/docs/logger.md +108 -0
  71. package/docs/middleware.md +337 -0
  72. package/docs/nextjs.md +241 -0
  73. package/docs/repository.md +496 -0
  74. package/docs/route.md +497 -0
  75. package/docs/server.md +307 -0
  76. package/package.json +68 -48
  77. package/dist/auto-loader-JFaZ9gON.d.ts +0 -80
  78. package/dist/client/index.d.ts +0 -358
  79. package/dist/client/index.js +0 -357
  80. package/dist/client/index.js.map +0 -1
  81. package/dist/client/nextjs/index.js +0 -371
  82. package/dist/client/nextjs/index.js.map +0 -1
  83. package/dist/codegen/generators/index.d.ts +0 -19
  84. package/dist/codegen/generators/index.js +0 -1404
  85. package/dist/codegen/generators/index.js.map +0 -1
  86. package/dist/database-errors-BNNmLTJE.d.ts +0 -86
  87. package/dist/events/index.d.ts +0 -183
  88. package/dist/events/index.js +0 -77
  89. package/dist/events/index.js.map +0 -1
  90. package/dist/index-DHiAqhKv.d.ts +0 -101
  91. package/dist/index.d.ts +0 -8
  92. package/dist/index.js +0 -3674
  93. package/dist/index.js.map +0 -1
  94. package/dist/types/index.d.ts +0 -121
  95. package/dist/types/index.js +0 -38
  96. package/dist/types/index.js.map +0 -1
  97. package/dist/types-BXibIEyj.d.ts +0 -60
@@ -1,40 +1,894 @@
1
- export { A as AutoRouteLoader, R as RouteInfo, a as RouteStats, l as loadRoutes } from '../auto-loader-JFaZ9gON.js';
2
- import { Context, Hono, MiddlewareHandler } from 'hono';
3
- import { a as RouteContract, R as RouteContext, b as RouteHandler } from '../types-BXibIEyj.js';
4
- export { c as HeaderRecord, H as HttpMethod, I as InferContract, d as RouteMeta, i as isHttpMethod } from '../types-BXibIEyj.js';
5
- export { ApiErrorResponse, ApiErrorSchema, ApiResponse, ApiResponseSchema, ApiSuccessResponse, ApiSuccessSchema } from '../types/index.js';
6
- import 'hono/utils/http-status';
7
- import '@sinclair/typebox';
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 { HttpMethod } from './types.js';
8
6
 
9
7
  /**
10
- * Contract-based Route Handler Wrapper
8
+ * Route Input Types
11
9
  *
12
- * Binds a contract to a route handler, providing automatic validation
13
- * and type-safe context creation.
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
+ * Middleware Definition Helper
214
+ *
215
+ * Provides type-safe middleware definition with name inference
216
+ */
217
+
218
+ /**
219
+ * Named middleware with type inference
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * export const authMiddleware = defineMiddleware('auth', async (c, next) => {
224
+ * // auth logic
225
+ * c.set('user', { id: 1 });
226
+ * await next();
227
+ * });
228
+ *
229
+ * export const rateLimitMiddleware = defineMiddleware('rateLimit', async (c, next) => {
230
+ * // rate limit logic
231
+ * await next();
232
+ * });
233
+ * ```
234
+ */
235
+ type NamedMiddleware<TName extends string = string> = {
236
+ name: TName;
237
+ handler: MiddlewareHandler;
238
+ _name: TName;
239
+ };
240
+ /**
241
+ * Named middleware factory with type inference
242
+ *
243
+ * Factory function that creates middleware instances with parameters
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * export const requirePermissions = defineMiddleware('permission',
248
+ * (...permissions: string[]) => async (c, next) => {
249
+ * // permission check logic
250
+ * await next();
251
+ * }
252
+ * );
253
+ * ```
254
+ */
255
+ type NamedMiddlewareFactory<TName extends string = string, TArgs extends any[] = any[]> = {
256
+ name: TName;
257
+ _name: TName;
258
+ } & ((...args: TArgs) => MiddlewareHandler);
259
+ /**
260
+ * Define a named middleware
261
+ *
262
+ * Creates a middleware with a unique name that can be referenced
263
+ * in route-level skip() calls with full type safety.
264
+ *
265
+ * Supports two patterns:
266
+ * 1. Regular middleware: Direct middleware handler
267
+ * 2. Factory middleware: Function that creates middleware with parameters
268
+ *
269
+ * @param name - Unique middleware name
270
+ * @param handler - Middleware handler function
271
+ * @returns Named middleware with type inference
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * // Regular middleware
276
+ * export const authMiddleware = defineMiddleware('auth', async (c, next) => {
277
+ * const token = c.req.header('authorization');
278
+ * if (!token) {
279
+ * return c.json({ error: 'Unauthorized' }, 401);
280
+ * }
281
+ * c.set('user', await verifyToken(token));
282
+ * await next();
283
+ * });
284
+ *
285
+ * // Factory middleware
286
+ * export const requirePermissions = defineMiddleware('permission',
287
+ * (...permissions: string[]) => async (c, next) => {
288
+ * const user = c.get('user');
289
+ * if (!hasPermissions(user, permissions)) {
290
+ * return c.json({ error: 'Forbidden' }, 403);
291
+ * }
292
+ * await next();
293
+ * }
294
+ * );
295
+ *
296
+ * // server.config.ts
297
+ * export default defineServerConfig()
298
+ * .middlewares([authMiddleware])
299
+ * .routes(appRouter)
300
+ * .build();
301
+ *
302
+ * // routes.ts - skip by name
303
+ * export const publicRoute = route.get('/health')
304
+ * .skip(['auth']) // ✅ Type-safe! Autocomplete!
305
+ * .handler(async (c) => c.success({ status: 'ok' }));
306
+ *
307
+ * // Use factory middleware inline
308
+ * export const protectedRoute = route.get('/admin')
309
+ * .use([requirePermissions('admin:write')])
310
+ * .handler(async (c) => { ... });
311
+ * ```
312
+ */
313
+ declare function defineMiddleware<TName extends string>(name: TName, handler: MiddlewareHandler): NamedMiddleware<TName>;
314
+ declare function defineMiddleware<TName extends string, TArgs extends any[]>(name: TName, factory: (...args: TArgs) => MiddlewareHandler): NamedMiddlewareFactory<TName, TArgs>;
315
+ /**
316
+ * Define a middleware factory explicitly
317
+ *
318
+ * Use this when your factory function has exactly 2 parameters,
319
+ * which would be incorrectly detected as a regular middleware handler.
14
320
  *
15
- * Features:
16
- * - Automatic params/query/body validation using TypeBox
17
- * - Type-safe RouteContext with contract-based inference
18
- * - Clean separation: bind() for validation, Hono for middleware
321
+ * @param name - Unique middleware name
322
+ * @param factory - Factory function that returns a middleware handler
323
+ * @returns Named middleware factory with type inference
324
+ *
325
+ * @example
326
+ * ```ts
327
+ * // Factory with 2 params (would be misdetected by defineMiddleware)
328
+ * export const rateLimiter = defineMiddlewareFactory('rateLimit',
329
+ * (limit: number, window: number) => async (c, next) => {
330
+ * // rate limit logic using limit and window
331
+ * await next();
332
+ * }
333
+ * );
334
+ *
335
+ * // Usage
336
+ * route.get('/api')
337
+ * .use([rateLimiter(100, 60000)]) // 100 requests per minute
338
+ * .handler(...)
339
+ * ```
340
+ */
341
+ declare function defineMiddlewareFactory<TName extends string, TArgs extends unknown[]>(name: TName, factory: (...args: TArgs) => MiddlewareHandler): NamedMiddlewareFactory<TName, TArgs>;
342
+ /**
343
+ * Extract middleware names from an array of named middlewares
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * type Middlewares = [
348
+ * NamedMiddleware<'auth'>,
349
+ * NamedMiddleware<'rateLimit'>
350
+ * ];
351
+ * type Names = ExtractMiddlewareNames<Middlewares>; // 'auth' | 'rateLimit'
352
+ * ```
19
353
  */
20
- declare function bind<TContract extends RouteContract>(contract: TContract, handler: (c: RouteContext<TContract>) => Response | Promise<Response>): (rawContext: Context) => Promise<Response>;
354
+ type ExtractMiddlewareNames<T extends readonly NamedMiddleware<string>[]> = T[number]['_name'];
21
355
 
22
356
  /**
23
- * Create App - Hono Wrapper for Contract-based Routing
357
+ * Route Builder
24
358
  *
25
- * Provides a cleaner API for registering routes with contracts
359
+ * Provides tRPC-style chainable API for route definition
26
360
  */
27
361
 
28
- type SPFNApp = Hono & {
29
- bind<TContract extends RouteContract>(contract: TContract, handler: RouteHandler<TContract>): void;
30
- bind<TContract extends RouteContract>(contract: TContract, middlewares: MiddlewareHandler[], handler: RouteHandler<TContract>): void;
31
- _contractMetas?: Map<string, RouteContract['meta']>;
362
+ /**
363
+ * Route handler function
364
+ */
365
+ type RouteHandlerFn<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}, TResponse = unknown> = (c: RouteBuilderContext<TInput, TInterceptor>) => Response | Promise<Response> | TResponse | Promise<TResponse>;
366
+ /**
367
+ * Route definition result
368
+ *
369
+ * Contains all information needed for type inference and registration
370
+ */
371
+ type RouteDef<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}, TResponse = unknown> = {
372
+ method?: HttpMethod;
373
+ path?: string;
374
+ input?: TInput;
375
+ interceptor?: TInterceptor;
376
+ middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];
377
+ skipMiddlewares?: string[] | '*';
378
+ handler: RouteHandlerFn<TInput, TInterceptor, TResponse>;
379
+ _input: TInput;
380
+ _interceptor: TInterceptor;
381
+ _response: TResponse;
382
+ };
383
+ /**
384
+ * Route builder with chainable API (tRPC-style)
385
+ */
386
+ declare class RouteBuilder<TInput extends RouteInput = {}, TInterceptor extends RouteInput = {}, TResponse = never> {
387
+ _method?: HttpMethod;
388
+ _path?: string;
389
+ _input?: TInput;
390
+ _interceptor?: TInterceptor;
391
+ _middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];
392
+ _skipMiddlewares?: string[] | '*';
393
+ /**
394
+ * Create a new RouteBuilder with copied properties and optional overrides
395
+ */
396
+ private clone;
397
+ /**
398
+ * Define input schemas
399
+ *
400
+ * @example
401
+ * ```ts
402
+ * route.get('/users/:id')
403
+ * .input({
404
+ * params: Type.Object({ id: Type.String() }),
405
+ * query: Type.Object({ page: Type.Number() }),
406
+ * headers: Type.Object({ authorization: Type.String() })
407
+ * })
408
+ * .handler(async (c) => {
409
+ * const { params, query, headers } = await c.data();
410
+ * // params = { id: string }
411
+ * // query = { page: number }
412
+ * // headers = { authorization: string }
413
+ * })
414
+ * ```
415
+ */
416
+ input<TNewInput extends RouteInput>(input: TNewInput): RouteBuilder<TNewInput, TInterceptor, TResponse>;
417
+ /**
418
+ * Define fields injected by interceptors
419
+ *
420
+ * These fields are:
421
+ * - Available in the handler (merged with input)
422
+ * - Excluded from client types (codegen uses only input)
423
+ * - Not validated by route input schema (injected by middleware)
424
+ *
425
+ * Use this when middleware/interceptors add fields to the request
426
+ * before it reaches the handler.
427
+ *
428
+ * @example
429
+ * ```ts
430
+ * // Auth interceptor injects crypto key fields
431
+ * route.post('/_auth/login')
432
+ * .input({
433
+ * body: Type.Object({
434
+ * email: Type.String(),
435
+ * password: Type.String()
436
+ * })
437
+ * })
438
+ * .interceptor({
439
+ * body: Type.Object({
440
+ * publicKey: Type.String(),
441
+ * keyId: Type.String(),
442
+ * fingerprint: Type.String()
443
+ * })
444
+ * })
445
+ * .handler(async (c) => {
446
+ * const { body } = await c.data();
447
+ * // body type: { email, password, publicKey, keyId, fingerprint }
448
+ * // Client only sees: { email, password }
449
+ * return loginService(body);
450
+ * });
451
+ * ```
452
+ */
453
+ interceptor<TNewInterceptor extends RouteInput>(interceptor: TNewInterceptor): RouteBuilder<TInput, TNewInterceptor, TResponse>;
454
+ /**
455
+ * Add middlewares to the route
456
+ *
457
+ * Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
458
+ * Named middlewares that are already registered globally will be automatically
459
+ * deduplicated to prevent double execution.
460
+ *
461
+ * @example
462
+ * ```ts
463
+ * import { authenticate } from '@spfn/auth/server/middleware';
464
+ *
465
+ * // With NamedMiddleware (auto-deduped if registered globally)
466
+ * route.get('/users')
467
+ * .use([authenticate, RateLimitMiddleware()])
468
+ *
469
+ * // With regular middleware handlers
470
+ * route.get('/users')
471
+ * .use([AuthMiddleware(), RateLimitMiddleware()])
472
+ * ```
473
+ */
474
+ middleware(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>;
475
+ /**
476
+ * Add middlewares to the route (alias for `.middleware()`)
477
+ *
478
+ * Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
479
+ * Named middlewares that are already registered globally will be automatically
480
+ * deduplicated to prevent double execution.
481
+ *
482
+ * @example
483
+ * ```ts
484
+ * import { authenticate } from '@spfn/auth/server/middleware';
485
+ *
486
+ * // With NamedMiddleware (auto-deduped if registered globally)
487
+ * route.get('/users')
488
+ * .use([authenticate, RateLimitMiddleware()])
489
+ *
490
+ * // With regular middleware handlers
491
+ * route.get('/users')
492
+ * .use([AuthMiddleware(), RateLimitMiddleware()])
493
+ * ```
494
+ */
495
+ use(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>;
496
+ /**
497
+ * Skip server-level named middlewares
498
+ *
499
+ * Useful for public endpoints that should bypass auth or rate limiting
500
+ *
501
+ * @param middlewareNames - Array of middleware names to skip, or '*' to skip all
502
+ *
503
+ * @example
504
+ * ```ts
505
+ * // Skip specific middlewares
506
+ * route.get('/health')
507
+ * .skip(['auth', 'rateLimit'])
508
+ * .handler(async (c) => c.json({ status: 'ok' }));
509
+ *
510
+ * // Skip only auth (still apply rate limiting)
511
+ * route.get('/public-data')
512
+ * .skip(['auth'])
513
+ * .handler(async (c) => { ... });
514
+ *
515
+ * // Skip all middlewares
516
+ * route.get('/public-health')
517
+ * .skip('*')
518
+ * .handler(async (c) => c.json({ status: 'ok' }));
519
+ * ```
520
+ */
521
+ skip(middlewareNames: string[] | '*'): RouteBuilder<TInput, TInterceptor, TResponse>;
522
+ /**
523
+ * Define handler function
524
+ *
525
+ * Response type is automatically inferred from the return value.
526
+ * Use helper methods like `c.created()`, `c.paginated()` for proper type inference.
527
+ *
528
+ * @example
529
+ * ```ts
530
+ * // Direct return - type inferred from data
531
+ * route.get('/users/:id')
532
+ * .input({ params: Type.Object({ id: Type.String() }) })
533
+ * .handler(async (c) => {
534
+ * const { params } = await c.data();
535
+ * return await getUser(params.id); // Type: User
536
+ * })
537
+ *
538
+ * // Using c.created() - returns data with 201 status, type preserved
539
+ * route.post('/users')
540
+ * .input({ body: Type.Object({ name: Type.String() }) })
541
+ * .handler(async (c) => {
542
+ * const { body } = await c.data();
543
+ * return c.created(await createUser(body)); // Type: User
544
+ * })
545
+ *
546
+ * // Using c.paginated() - returns PaginatedResult<T>
547
+ * route.get('/users')
548
+ * .handler(async (c) => {
549
+ * const users = await getUsers();
550
+ * return c.paginated(users, 1, 20, 100); // Type: PaginatedResult<User>
551
+ * })
552
+ *
553
+ * // Using c.noContent() - returns void
554
+ * route.delete('/users/:id')
555
+ * .handler(async (c) => {
556
+ * await deleteUser(params.id);
557
+ * return c.noContent(); // Type: void
558
+ * })
559
+ *
560
+ * // Using c.json() - returns Response (type inference lost)
561
+ * // Use only when you need custom status codes not covered by helpers
562
+ * route.get('/custom')
563
+ * .handler(async (c) => {
564
+ * return c.json({ data }, 418); // Type: Response
565
+ * })
566
+ * ```
567
+ */
568
+ handler<THandlerResponse>(fn: RouteHandlerFn<TInput, TInterceptor, THandlerResponse>): RouteDef<TInput, TInterceptor, THandlerResponse>;
569
+ }
570
+ /**
571
+ * Route builder entry point
572
+ *
573
+ * @example
574
+ * ```ts
575
+ * // GET request
576
+ * export const getUser = route.get('/users/:id')
577
+ * .input({ params: Type.Object({ id: Type.String() }) })
578
+ * .handler(async (c) => {
579
+ * const { params } = await c.data();
580
+ * return await db.user.findUnique({ where: { id: params.id } });
581
+ * });
582
+ *
583
+ * // POST request
584
+ * export const createUser = route.post('/users')
585
+ * .input({ body: Type.Object({ name: Type.String(), email: Type.String() }) })
586
+ * .handler(async (c) => {
587
+ * const { body } = await c.data();
588
+ * return c.created(await db.user.create({ data: body }));
589
+ * });
590
+ * ```
591
+ */
592
+ declare const route: {
593
+ get: (path: string) => RouteBuilder;
594
+ post: (path: string) => RouteBuilder;
595
+ put: (path: string) => RouteBuilder;
596
+ patch: (path: string) => RouteBuilder;
597
+ delete: (path: string) => RouteBuilder;
32
598
  };
599
+
600
+ /**
601
+ * Router Definition
602
+ *
603
+ * Provides router composition and middleware management
604
+ */
605
+
606
+ /**
607
+ * Router definition - holds all routes
608
+ */
609
+ interface Router<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>> {
610
+ routes: TRoutes;
611
+ _routes: TRoutes;
612
+ _packageRouters: Router<any>[];
613
+ _globalMiddlewares: NamedMiddleware<string>[];
614
+ /**
615
+ * Register package routers (type-hidden)
616
+ *
617
+ * Package routes are:
618
+ * - Recognized by RPC proxy and backend
619
+ * - NOT exposed in client types (use package's own API like authApi, cmsApi)
620
+ *
621
+ * @example
622
+ * ```ts
623
+ * import { authRouter } from '@spfn/auth/server';
624
+ * import { cmsAppRouter } from '@spfn/cms/server';
625
+ *
626
+ * export const appRouter = defineRouter({
627
+ * getRoot,
628
+ * getHealth,
629
+ * })
630
+ * .packages([authRouter, cmsAppRouter]);
631
+ *
632
+ * // Client usage:
633
+ * // api.getRoot.call({}) - app routes
634
+ * // authApi.login.call({}) - package API
635
+ * ```
636
+ */
637
+ packages(routers: Router<any>[]): Router<TRoutes>;
638
+ /**
639
+ * Register global middlewares
640
+ *
641
+ * Applied to all routes unless explicitly skipped via .skip()
642
+ *
643
+ * @example
644
+ * ```ts
645
+ * import { authMiddleware, loggingMiddleware } from './middlewares';
646
+ *
647
+ * export const appRouter = defineRouter({
648
+ * getRoot,
649
+ * getHealth,
650
+ * })
651
+ * .packages([authRouter])
652
+ * .use([authMiddleware, loggingMiddleware]);
653
+ * ```
654
+ */
655
+ use(middlewares: NamedMiddleware<string>[]): Router<TRoutes>;
656
+ }
657
+ /**
658
+ * Define a router with multiple routes (tRPC-style)
659
+ *
660
+ * Supports chainable API for packages and middlewares:
661
+ *
662
+ * @example
663
+ * ```ts
664
+ * // Basic usage
665
+ * export const appRouter = defineRouter({
666
+ * getRoot,
667
+ * getHealth,
668
+ * listExamples,
669
+ * });
670
+ *
671
+ * // With package routers (type-hidden)
672
+ * export const appRouter = defineRouter({
673
+ * getRoot,
674
+ * getHealth,
675
+ * })
676
+ * .packages([authRouter, cmsAppRouter]);
677
+ *
678
+ * // With global middlewares
679
+ * export const appRouter = defineRouter({
680
+ * getRoot,
681
+ * getHealth,
682
+ * })
683
+ * .packages([authRouter])
684
+ * .use([authMiddleware, loggingMiddleware]);
685
+ *
686
+ * export type AppRouter = typeof appRouter;
687
+ * ```
688
+ *
689
+ * Package routes:
690
+ * - Recognized by RPC proxy and backend for routing
691
+ * - NOT included in AppRouter type (use authApi, cmsApi instead)
692
+ * - Prevents confusion between app API and package APIs
693
+ */
694
+ declare function defineRouter<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(routes: TRoutes): Router<TRoutes>;
695
+
696
+ /**
697
+ * Route Registration for define-route style routing
698
+ *
699
+ * Registers routes defined with route.get()...handler() to Hono app
700
+ */
701
+
702
+ /**
703
+ * Registered route information for logging
704
+ */
705
+ interface RegisteredRoute {
706
+ method: HttpMethod;
707
+ path: string;
708
+ name: string;
709
+ }
710
+ /**
711
+ * Register routes from defineRouter() to Hono app
712
+ *
713
+ * @param app - Hono app instance
714
+ * @param router - Router definition
715
+ * @param namedMiddlewares - Optional server-level named middlewares
716
+ *
717
+ * @param collectedRoutes
718
+ * @example
719
+ * ```ts
720
+ * const appRouter = defineRouter({
721
+ * getUser: route.get('/users/:id')...
722
+ * createUser: route.post('/users')...
723
+ * });
724
+ *
725
+ * const app = new Hono();
726
+ * const namedMiddlewares = [
727
+ * { name: 'auth', handler: AuthMiddleware() },
728
+ * { name: 'rateLimit', handler: RateLimitMiddleware() },
729
+ * ];
730
+ * registerRoutes(app, appRouter, namedMiddlewares);
731
+ * ```
732
+ */
733
+ declare function registerRoutes<TRoutes extends Record<string, RouteDef<any> | Router<any>>>(app: Hono, router: Router<TRoutes>, namedMiddlewares?: ReadonlyArray<{
734
+ name: string;
735
+ handler: MiddlewareHandler;
736
+ }>, collectedRoutes?: RegisteredRoute[]): RegisteredRoute[];
737
+
738
+ /**
739
+ * Type guard for HttpMethod
740
+ */
741
+ declare function isHttpMethod(value: unknown): value is HttpMethod;
742
+ /**
743
+ * Nullable - Creates a union of T | null
744
+ *
745
+ * @example
746
+ * ```typescript
747
+ * // string | null
748
+ * firstName: Nullable(Type.String())
749
+ * ```
750
+ */
751
+ declare const Nullable: <T extends TSchema>(schema: T) => _sinclair_typebox.TUnion<[T, _sinclair_typebox.TNull]>;
752
+ /**
753
+ * OptionalNullable - Creates a union of T | null | undefined
754
+ *
755
+ * @example
756
+ * ```typescript
757
+ * // string | null | undefined
758
+ * lastName: OptionalNullable(Type.String())
759
+ * ```
760
+ */
761
+ declare const OptionalNullable: <T extends TSchema>(schema: T) => _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<[T, _sinclair_typebox.TNull]>>;
762
+
763
+ /**
764
+ * File Schema Helpers for TypeBox
765
+ *
766
+ * Provides TypeBox schema definitions for file upload handling
767
+ * with optional validation constraints.
768
+ */
769
+
770
+ /**
771
+ * File validation options
772
+ */
773
+ interface FileSchemaOptions {
774
+ /**
775
+ * Maximum file size in bytes
776
+ *
777
+ * @example 5 * 1024 * 1024 // 5MB
778
+ */
779
+ maxSize?: number;
780
+ /**
781
+ * Allowed MIME types
782
+ *
783
+ * @example ['image/jpeg', 'image/png', 'image/webp']
784
+ */
785
+ allowedTypes?: string[];
786
+ /**
787
+ * Minimum file size in bytes (optional)
788
+ *
789
+ * @example 1024 // 1KB minimum
790
+ */
791
+ minSize?: number;
792
+ }
793
+ /**
794
+ * File array validation options
795
+ */
796
+ interface FileArraySchemaOptions extends FileSchemaOptions {
797
+ /**
798
+ * Maximum number of files
799
+ *
800
+ * @example 5
801
+ */
802
+ maxFiles?: number;
803
+ /**
804
+ * Minimum number of files (optional)
805
+ *
806
+ * @example 1
807
+ */
808
+ minFiles?: number;
809
+ }
810
+ /**
811
+ * Internal schema type with file validation metadata
812
+ */
813
+ interface FileSchemaType extends TSchema {
814
+ [Kind]: 'File';
815
+ fileOptions?: FileSchemaOptions;
816
+ }
817
+ interface FileArraySchemaType extends TSchema {
818
+ [Kind]: 'FileArray';
819
+ fileOptions?: FileArraySchemaOptions;
820
+ }
33
821
  /**
34
- * Create SPFN app instance
822
+ * Create a File schema with optional validation
35
823
  *
36
- * Wraps Hono with contract-based routing support
824
+ * @example
825
+ * ```ts
826
+ * // Basic usage (no validation)
827
+ * formData: Type.Object({
828
+ * file: FileSchema()
829
+ * })
830
+ *
831
+ * // With validation
832
+ * formData: Type.Object({
833
+ * avatar: FileSchema({
834
+ * maxSize: 5 * 1024 * 1024, // 5MB
835
+ * allowedTypes: ['image/jpeg', 'image/png', 'image/webp']
836
+ * })
837
+ * })
838
+ * ```
839
+ */
840
+ declare function FileSchema(options?: FileSchemaOptions): FileSchemaType;
841
+ /**
842
+ * Create a File array schema with optional validation
843
+ *
844
+ * @example
845
+ * ```ts
846
+ * // Basic usage (no validation)
847
+ * formData: Type.Object({
848
+ * files: FileArraySchema()
849
+ * })
850
+ *
851
+ * // With validation
852
+ * formData: Type.Object({
853
+ * documents: FileArraySchema({
854
+ * maxSize: 10 * 1024 * 1024, // 10MB per file
855
+ * maxFiles: 5,
856
+ * allowedTypes: ['application/pdf', 'application/msword']
857
+ * })
858
+ * })
859
+ * ```
860
+ */
861
+ declare function FileArraySchema(options?: FileArraySchemaOptions): FileArraySchemaType;
862
+ /**
863
+ * Create an optional File schema with validation
864
+ *
865
+ * @example
866
+ * ```ts
867
+ * formData: Type.Object({
868
+ * name: Type.String(),
869
+ * avatar: OptionalFileSchema({
870
+ * maxSize: 2 * 1024 * 1024,
871
+ * allowedTypes: ['image/jpeg', 'image/png']
872
+ * })
873
+ * })
874
+ * ```
875
+ */
876
+ declare function OptionalFileSchema(options?: FileSchemaOptions): TSchema;
877
+ /**
878
+ * Check if a schema is a File schema
879
+ */
880
+ declare function isFileSchema(schema: TSchema): schema is FileSchemaType;
881
+ /**
882
+ * Check if a schema is a FileArray schema
883
+ */
884
+ declare function isFileArraySchema(schema: TSchema): schema is FileArraySchemaType;
885
+ /**
886
+ * Get file options from schema
887
+ */
888
+ declare function getFileOptions(schema: TSchema): FileSchemaOptions | FileArraySchemaOptions | undefined;
889
+ /**
890
+ * Format file size for error messages
37
891
  */
38
- declare function createApp(): SPFNApp;
892
+ declare function formatFileSize(bytes: number): string;
39
893
 
40
- export { RouteContext, RouteContract, RouteHandler, type SPFNApp, bind, createApp };
894
+ export { type ExtractMiddlewareNames, FileArraySchema, type FileArraySchemaOptions, type FileArraySchemaType, FileSchema, type FileSchemaOptions, type FileSchemaType, HttpMethod, type MergedInput, type NamedMiddleware, type NamedMiddlewareFactory, Nullable, OptionalFileSchema, OptionalNullable, type PaginatedResult, type RegisteredRoute, type RouteBuilderContext, type RouteDef, type RouteHandlerFn, type RouteInput, type Router, defineMiddleware, defineMiddlewareFactory, defineRouter, formatFileSize, getFileOptions, isFileArraySchema, isFileSchema, isHttpMethod, registerRoutes, route };