@spfn/core 0.2.0-beta.4 → 0.2.0-beta.42

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 (68) hide show
  1. package/README.md +260 -1175
  2. package/dist/{boss-BO8ty33K.d.ts → boss-DI1r4kTS.d.ts} +24 -7
  3. package/dist/cache/index.js +32 -29
  4. package/dist/cache/index.js.map +1 -1
  5. package/dist/codegen/index.d.ts +55 -8
  6. package/dist/codegen/index.js +179 -5
  7. package/dist/codegen/index.js.map +1 -1
  8. package/dist/config/index.d.ts +168 -6
  9. package/dist/config/index.js +29 -5
  10. package/dist/config/index.js.map +1 -1
  11. package/dist/db/index.d.ts +128 -4
  12. package/dist/db/index.js +177 -50
  13. package/dist/db/index.js.map +1 -1
  14. package/dist/env/index.d.ts +55 -1
  15. package/dist/env/index.js +71 -3
  16. package/dist/env/index.js.map +1 -1
  17. package/dist/env/loader.d.ts +27 -19
  18. package/dist/env/loader.js +33 -25
  19. package/dist/env/loader.js.map +1 -1
  20. package/dist/event/index.d.ts +27 -1
  21. package/dist/event/index.js +6 -1
  22. package/dist/event/index.js.map +1 -1
  23. package/dist/event/sse/client.d.ts +77 -2
  24. package/dist/event/sse/client.js +87 -24
  25. package/dist/event/sse/client.js.map +1 -1
  26. package/dist/event/sse/index.d.ts +10 -4
  27. package/dist/event/sse/index.js +158 -12
  28. package/dist/event/sse/index.js.map +1 -1
  29. package/dist/job/index.d.ts +23 -8
  30. package/dist/job/index.js +96 -20
  31. package/dist/job/index.js.map +1 -1
  32. package/dist/logger/index.d.ts +5 -0
  33. package/dist/logger/index.js +14 -0
  34. package/dist/logger/index.js.map +1 -1
  35. package/dist/middleware/index.d.ts +23 -1
  36. package/dist/middleware/index.js +58 -5
  37. package/dist/middleware/index.js.map +1 -1
  38. package/dist/nextjs/index.d.ts +2 -2
  39. package/dist/nextjs/index.js +77 -31
  40. package/dist/nextjs/index.js.map +1 -1
  41. package/dist/nextjs/server.d.ts +44 -23
  42. package/dist/nextjs/server.js +83 -65
  43. package/dist/nextjs/server.js.map +1 -1
  44. package/dist/route/index.d.ts +158 -4
  45. package/dist/route/index.js +253 -17
  46. package/dist/route/index.js.map +1 -1
  47. package/dist/server/index.d.ts +251 -16
  48. package/dist/server/index.js +774 -228
  49. package/dist/server/index.js.map +1 -1
  50. package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
  51. package/dist/types-DKQ90YL7.d.ts +372 -0
  52. package/docs/cache.md +133 -0
  53. package/docs/codegen.md +74 -0
  54. package/docs/database.md +370 -0
  55. package/docs/entity.md +539 -0
  56. package/docs/env.md +499 -0
  57. package/docs/errors.md +319 -0
  58. package/docs/event.md +443 -0
  59. package/docs/file-upload.md +717 -0
  60. package/docs/job.md +131 -0
  61. package/docs/logger.md +108 -0
  62. package/docs/middleware.md +337 -0
  63. package/docs/nextjs.md +247 -0
  64. package/docs/repository.md +496 -0
  65. package/docs/route.md +497 -0
  66. package/docs/server.md +429 -0
  67. package/package.json +3 -2
  68. package/dist/types-B-e_f2dQ.d.ts +0 -121
@@ -1,5 +1,5 @@
1
1
  import * as _sinclair_typebox from '@sinclair/typebox';
2
- import { TSchema, Static } from '@sinclair/typebox';
2
+ import { TSchema, Static, Kind } from '@sinclair/typebox';
3
3
  import { Context, MiddlewareHandler, Hono } from 'hono';
4
4
  import { ContentfulStatusCode, RedirectStatusCode } from 'hono/utils/http-status';
5
5
  import { HttpMethod } from './types.js';
@@ -22,6 +22,8 @@ type RouteInput = {
22
22
  query?: TSchema;
23
23
  /** Request body (JSON) */
24
24
  body?: TSchema;
25
+ /** Form data (multipart/form-data) for file uploads */
26
+ formData?: TSchema;
25
27
  /** HTTP headers */
26
28
  headers?: TSchema;
27
29
  /** Cookies */
@@ -61,6 +63,7 @@ type MergedInput<TInput extends RouteInput, TInterceptor extends RouteInput> = {
61
63
  params: (TInput['params'] extends TSchema ? Static<TInput['params']> : {}) & (TInterceptor['params'] extends TSchema ? Static<TInterceptor['params']> : {});
62
64
  query: (TInput['query'] extends TSchema ? Static<TInput['query']> : {}) & (TInterceptor['query'] extends TSchema ? Static<TInterceptor['query']> : {});
63
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']> : {});
64
67
  headers: (TInput['headers'] extends TSchema ? Static<TInput['headers']> : {}) & (TInterceptor['headers'] extends TSchema ? Static<TInterceptor['headers']> : {});
65
68
  cookies: (TInput['cookies'] extends TSchema ? Static<TInput['cookies']> : {}) & (TInterceptor['cookies'] extends TSchema ? Static<TInterceptor['cookies']> : {});
66
69
  };
@@ -233,6 +236,7 @@ type NamedMiddleware<TName extends string = string> = {
233
236
  name: TName;
234
237
  handler: MiddlewareHandler;
235
238
  _name: TName;
239
+ skips?: string[];
236
240
  };
237
241
  /**
238
242
  * Named middleware factory with type inference
@@ -307,8 +311,27 @@ type NamedMiddlewareFactory<TName extends string = string, TArgs extends any[] =
307
311
  * .handler(async (c) => { ... });
308
312
  * ```
309
313
  */
310
- declare function defineMiddleware<TName extends string>(name: TName, handler: MiddlewareHandler): NamedMiddleware<TName>;
311
- declare function defineMiddleware<TName extends string, TArgs extends any[]>(name: TName, factory: (...args: TArgs) => MiddlewareHandler): NamedMiddlewareFactory<TName, TArgs>;
314
+ /**
315
+ * Options for defineMiddleware
316
+ */
317
+ interface DefineMiddlewareOptions {
318
+ /**
319
+ * Server-level middleware names to auto-skip when this middleware is used at route level.
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * // optionalAuth auto-skips the global 'auth' middleware
324
+ * export const optionalAuth = defineMiddleware('optionalAuth', handler, {
325
+ * skips: ['auth']
326
+ * });
327
+ *
328
+ * // Usage: .use([optionalAuth]) — no need for .skip(['auth'])
329
+ * ```
330
+ */
331
+ skips?: string[];
332
+ }
333
+ declare function defineMiddleware<TName extends string>(name: TName, handler: MiddlewareHandler, options?: DefineMiddlewareOptions): NamedMiddleware<TName>;
334
+ declare function defineMiddleware<TName extends string, TArgs extends any[]>(name: TName, factory: (...args: TArgs) => MiddlewareHandler, options?: DefineMiddlewareOptions): NamedMiddlewareFactory<TName, TArgs>;
312
335
  /**
313
336
  * Define a middleware factory explicitly
314
337
  *
@@ -757,4 +780,135 @@ declare const Nullable: <T extends TSchema>(schema: T) => _sinclair_typebox.TUni
757
780
  */
758
781
  declare const OptionalNullable: <T extends TSchema>(schema: T) => _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<[T, _sinclair_typebox.TNull]>>;
759
782
 
760
- export { type ExtractMiddlewareNames, HttpMethod, type MergedInput, type NamedMiddleware, type NamedMiddlewareFactory, Nullable, OptionalNullable, type PaginatedResult, type RegisteredRoute, type RouteBuilderContext, type RouteDef, type RouteHandlerFn, type RouteInput, type Router, defineMiddleware, defineMiddlewareFactory, defineRouter, isHttpMethod, registerRoutes, route };
783
+ /**
784
+ * File Schema Helpers for TypeBox
785
+ *
786
+ * Provides TypeBox schema definitions for file upload handling
787
+ * with optional validation constraints.
788
+ */
789
+
790
+ /**
791
+ * File validation options
792
+ */
793
+ interface FileSchemaOptions {
794
+ /**
795
+ * Maximum file size in bytes
796
+ *
797
+ * @example 5 * 1024 * 1024 // 5MB
798
+ */
799
+ maxSize?: number;
800
+ /**
801
+ * Allowed MIME types
802
+ *
803
+ * @example ['image/jpeg', 'image/png', 'image/webp']
804
+ */
805
+ allowedTypes?: string[];
806
+ /**
807
+ * Minimum file size in bytes (optional)
808
+ *
809
+ * @example 1024 // 1KB minimum
810
+ */
811
+ minSize?: number;
812
+ }
813
+ /**
814
+ * File array validation options
815
+ */
816
+ interface FileArraySchemaOptions extends FileSchemaOptions {
817
+ /**
818
+ * Maximum number of files
819
+ *
820
+ * @example 5
821
+ */
822
+ maxFiles?: number;
823
+ /**
824
+ * Minimum number of files (optional)
825
+ *
826
+ * @example 1
827
+ */
828
+ minFiles?: number;
829
+ }
830
+ /**
831
+ * Internal schema type with file validation metadata
832
+ */
833
+ interface FileSchemaType extends TSchema {
834
+ [Kind]: 'File';
835
+ fileOptions?: FileSchemaOptions;
836
+ }
837
+ interface FileArraySchemaType extends TSchema {
838
+ [Kind]: 'FileArray';
839
+ fileOptions?: FileArraySchemaOptions;
840
+ }
841
+ /**
842
+ * Create a File schema with optional validation
843
+ *
844
+ * @example
845
+ * ```ts
846
+ * // Basic usage (no validation)
847
+ * formData: Type.Object({
848
+ * file: FileSchema()
849
+ * })
850
+ *
851
+ * // With validation
852
+ * formData: Type.Object({
853
+ * avatar: FileSchema({
854
+ * maxSize: 5 * 1024 * 1024, // 5MB
855
+ * allowedTypes: ['image/jpeg', 'image/png', 'image/webp']
856
+ * })
857
+ * })
858
+ * ```
859
+ */
860
+ declare function FileSchema(options?: FileSchemaOptions): FileSchemaType;
861
+ /**
862
+ * Create a File array schema with optional validation
863
+ *
864
+ * @example
865
+ * ```ts
866
+ * // Basic usage (no validation)
867
+ * formData: Type.Object({
868
+ * files: FileArraySchema()
869
+ * })
870
+ *
871
+ * // With validation
872
+ * formData: Type.Object({
873
+ * documents: FileArraySchema({
874
+ * maxSize: 10 * 1024 * 1024, // 10MB per file
875
+ * maxFiles: 5,
876
+ * allowedTypes: ['application/pdf', 'application/msword']
877
+ * })
878
+ * })
879
+ * ```
880
+ */
881
+ declare function FileArraySchema(options?: FileArraySchemaOptions): FileArraySchemaType;
882
+ /**
883
+ * Create an optional File schema with validation
884
+ *
885
+ * @example
886
+ * ```ts
887
+ * formData: Type.Object({
888
+ * name: Type.String(),
889
+ * avatar: OptionalFileSchema({
890
+ * maxSize: 2 * 1024 * 1024,
891
+ * allowedTypes: ['image/jpeg', 'image/png']
892
+ * })
893
+ * })
894
+ * ```
895
+ */
896
+ declare function OptionalFileSchema(options?: FileSchemaOptions): TSchema;
897
+ /**
898
+ * Check if a schema is a File schema
899
+ */
900
+ declare function isFileSchema(schema: TSchema): schema is FileSchemaType;
901
+ /**
902
+ * Check if a schema is a FileArray schema
903
+ */
904
+ declare function isFileArraySchema(schema: TSchema): schema is FileArraySchemaType;
905
+ /**
906
+ * Get file options from schema
907
+ */
908
+ declare function getFileOptions(schema: TSchema): FileSchemaOptions | FileArraySchemaOptions | undefined;
909
+ /**
910
+ * Format file size for error messages
911
+ */
912
+ declare function formatFileSize(bytes: number): string;
913
+
914
+ 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 };
@@ -1,7 +1,7 @@
1
1
  import { logger } from '@spfn/core/logger';
2
+ import { FormatRegistry, Type, Kind } from '@sinclair/typebox';
2
3
  import { Value } from '@sinclair/typebox/value';
3
4
  import { ValidationError } from '@spfn/core/errors';
4
- import { Type } from '@sinclair/typebox';
5
5
 
6
6
  // src/route/route-builder.ts
7
7
  var RouteBuilder = class _RouteBuilder {
@@ -260,6 +260,108 @@ function createRouterInstance(routes, packageRouters = [], globalMiddlewares = [
260
260
  function defineRouter(routes) {
261
261
  return createRouterInstance(routes);
262
262
  }
263
+ function FileSchema(options) {
264
+ return Type.Unsafe({
265
+ [Kind]: "File",
266
+ type: "object",
267
+ fileOptions: options
268
+ });
269
+ }
270
+ function FileArraySchema(options) {
271
+ return Type.Unsafe({
272
+ [Kind]: "FileArray",
273
+ type: "array",
274
+ items: { [Kind]: "File", type: "object" },
275
+ fileOptions: options
276
+ });
277
+ }
278
+ function OptionalFileSchema(options) {
279
+ return Type.Optional(FileSchema(options));
280
+ }
281
+ function isFileSchema(schema) {
282
+ const kind = schema[Symbol.for("TypeBox.Kind")];
283
+ return kind === "File";
284
+ }
285
+ function isFileArraySchema(schema) {
286
+ const kind = schema[Symbol.for("TypeBox.Kind")];
287
+ return kind === "FileArray";
288
+ }
289
+ function getFileOptions(schema) {
290
+ return schema.fileOptions;
291
+ }
292
+ function formatFileSize(bytes) {
293
+ if (bytes >= 1024 * 1024 * 1024) {
294
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
295
+ }
296
+ if (bytes >= 1024 * 1024) {
297
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
298
+ }
299
+ if (bytes >= 1024) {
300
+ return `${(bytes / 1024).toFixed(1)}KB`;
301
+ }
302
+ return `${bytes}B`;
303
+ }
304
+
305
+ // src/route/validation.ts
306
+ FormatRegistry.Set(
307
+ "email",
308
+ (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
309
+ );
310
+ FormatRegistry.Set(
311
+ "uri",
312
+ (value) => /^https?:\/\/.+/.test(value)
313
+ );
314
+ FormatRegistry.Set(
315
+ "uuid",
316
+ (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)
317
+ );
318
+ FormatRegistry.Set(
319
+ "date",
320
+ (value) => /^\d{4}-\d{2}-\d{2}$/.test(value)
321
+ );
322
+ FormatRegistry.Set(
323
+ "date-time",
324
+ (value) => !isNaN(Date.parse(value))
325
+ );
326
+ function isFile(value) {
327
+ return value instanceof File || typeof value === "object" && value !== null && "name" in value && "size" in value && "type" in value && typeof value.arrayBuffer === "function";
328
+ }
329
+ function isFileSchemaDef(schema) {
330
+ const kind = schema[Symbol.for("TypeBox.Kind")];
331
+ return kind === "File";
332
+ }
333
+ function isFileArraySchemaDef(schema) {
334
+ const kind = schema[Symbol.for("TypeBox.Kind")];
335
+ return kind === "FileArray";
336
+ }
337
+ function getSchemaFileOptions(schema) {
338
+ return schema.fileOptions;
339
+ }
340
+ function validateSingleFile(file, fieldPath, options, errors) {
341
+ if (!options) return;
342
+ const { maxSize, minSize, allowedTypes } = options;
343
+ if (maxSize !== void 0 && file.size > maxSize) {
344
+ errors.push({
345
+ path: fieldPath,
346
+ message: `File size ${formatFileSize(file.size)} exceeds maximum ${formatFileSize(maxSize)}`,
347
+ value: file.size
348
+ });
349
+ }
350
+ if (minSize !== void 0 && file.size < minSize) {
351
+ errors.push({
352
+ path: fieldPath,
353
+ message: `File size ${formatFileSize(file.size)} is below minimum ${formatFileSize(minSize)}`,
354
+ value: file.size
355
+ });
356
+ }
357
+ if (allowedTypes && allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
358
+ errors.push({
359
+ path: fieldPath,
360
+ message: `File type "${file.type}" is not allowed. Allowed: ${allowedTypes.join(", ")}`,
361
+ value: file.type
362
+ });
363
+ }
364
+ }
263
365
  function validateField(schema, rawValue, fieldName) {
264
366
  if (!schema) {
265
367
  return {};
@@ -278,6 +380,87 @@ function validateField(schema, rawValue, fieldName) {
278
380
  }
279
381
  return converted;
280
382
  }
383
+ function validateFormData(schema, rawValue, fieldName) {
384
+ if (!schema) {
385
+ return {};
386
+ }
387
+ const schemaProps = schema.properties;
388
+ if (!schemaProps) {
389
+ return rawValue;
390
+ }
391
+ const result = {};
392
+ const nonFileData = {};
393
+ const nonFileSchema = {};
394
+ const fileErrors = [];
395
+ for (const [key, value] of Object.entries(rawValue)) {
396
+ const propSchema = schemaProps[key];
397
+ if (propSchema && isFileSchemaDef(propSchema)) {
398
+ result[key] = value;
399
+ if (isFile(value)) {
400
+ const fileOptions = getSchemaFileOptions(propSchema);
401
+ validateSingleFile(value, `/${key}`, fileOptions, fileErrors);
402
+ }
403
+ } else if (propSchema && isFileArraySchemaDef(propSchema)) {
404
+ result[key] = value;
405
+ const fileOptions = getSchemaFileOptions(propSchema);
406
+ const files = Array.isArray(value) ? value : [value];
407
+ const fileArray = files.filter(isFile);
408
+ if (fileOptions?.maxFiles !== void 0 && fileArray.length > fileOptions.maxFiles) {
409
+ fileErrors.push({
410
+ path: `/${key}`,
411
+ message: `Too many files. Maximum: ${fileOptions.maxFiles}, received: ${fileArray.length}`,
412
+ value: fileArray.length
413
+ });
414
+ }
415
+ if (fileOptions?.minFiles !== void 0 && fileArray.length < fileOptions.minFiles) {
416
+ fileErrors.push({
417
+ path: `/${key}`,
418
+ message: `Too few files. Minimum: ${fileOptions.minFiles}, received: ${fileArray.length}`,
419
+ value: fileArray.length
420
+ });
421
+ }
422
+ fileArray.forEach((file, index) => {
423
+ validateSingleFile(file, `/${key}/${index}`, fileOptions, fileErrors);
424
+ });
425
+ } else if (isFile(value) || Array.isArray(value) && value.some(isFile)) {
426
+ result[key] = value;
427
+ } else {
428
+ nonFileData[key] = value;
429
+ if (propSchema) {
430
+ nonFileSchema[key] = propSchema;
431
+ }
432
+ }
433
+ }
434
+ if (fileErrors.length > 0) {
435
+ throw new ValidationError({
436
+ message: `Invalid ${fieldName}`,
437
+ fields: fileErrors
438
+ });
439
+ }
440
+ if (Object.keys(nonFileSchema).length > 0) {
441
+ const tempSchema = {
442
+ ...schema,
443
+ properties: nonFileSchema,
444
+ required: schema.required?.filter((r) => r in nonFileSchema) ?? []
445
+ };
446
+ const converted = Value.Convert(tempSchema, nonFileData);
447
+ const errors = [...Value.Errors(tempSchema, converted)];
448
+ if (errors.length > 0) {
449
+ throw new ValidationError({
450
+ message: `Invalid ${fieldName}`,
451
+ fields: errors.map((e) => ({
452
+ path: e.path,
453
+ message: e.message,
454
+ value: e.value
455
+ }))
456
+ });
457
+ }
458
+ Object.assign(result, converted);
459
+ } else {
460
+ Object.assign(result, nonFileData);
461
+ }
462
+ return result;
463
+ }
281
464
  function extractQueryParams(c) {
282
465
  const url = new URL(c.req.url);
283
466
  const queryObj = {};
@@ -325,6 +508,34 @@ async function parseJsonBody(c) {
325
508
  });
326
509
  }
327
510
  }
511
+ async function parseFormData(c) {
512
+ try {
513
+ const formData = await c.req.formData();
514
+ const result = {};
515
+ formData.forEach((value, key) => {
516
+ const existing = result[key];
517
+ if (existing !== void 0) {
518
+ if (Array.isArray(existing)) {
519
+ existing.push(value);
520
+ } else {
521
+ result[key] = [existing, value];
522
+ }
523
+ } else {
524
+ result[key] = value;
525
+ }
526
+ });
527
+ return result;
528
+ } catch (error) {
529
+ throw new ValidationError({
530
+ message: "Invalid form data",
531
+ fields: [{
532
+ path: "/",
533
+ message: "Failed to parse form data",
534
+ value: error instanceof Error ? error.message : "Unknown error"
535
+ }]
536
+ });
537
+ }
538
+ }
328
539
 
329
540
  // src/route/register-routes.ts
330
541
  function isRouter(value) {
@@ -391,18 +602,28 @@ function registerRoute(app, name, routeDef, namedMiddlewares) {
391
602
  const registeredNames = /* @__PURE__ */ new Set();
392
603
  const registeredHandlers = /* @__PURE__ */ new Set();
393
604
  const skipAll = skipMiddlewares === "*";
605
+ const autoSkips = /* @__PURE__ */ new Set();
606
+ for (const mw of middlewares) {
607
+ if (isNamedMiddleware(mw) && mw.skips) {
608
+ for (const skipName of mw.skips) {
609
+ autoSkips.add(skipName);
610
+ }
611
+ }
612
+ }
394
613
  if (namedMiddlewares && namedMiddlewares.length > 0) {
395
614
  if (skipAll) {
396
615
  logger.debug(`\u23ED\uFE0F Skipping all middlewares (*) for route: ${method} ${path}`, { name });
397
616
  } else {
398
617
  const skipSet = new Set(Array.isArray(skipMiddlewares) ? skipMiddlewares : []);
399
618
  for (const middleware of namedMiddlewares) {
400
- if (!skipSet.has(middleware.name)) {
619
+ if (skipSet.has(middleware.name)) {
620
+ logger.debug(`\u23ED\uFE0F Skipping middleware '${middleware.name}' for route: ${method} ${path}`, { name });
621
+ } else if (autoSkips.has(middleware.name)) {
622
+ logger.debug(`\u23ED\uFE0F Auto-skipping middleware '${middleware.name}' for route: ${method} ${path}`, { name });
623
+ } else {
401
624
  allMiddlewares.push(middleware.handler);
402
625
  registeredNames.add(middleware.name);
403
626
  registeredHandlers.add(middleware.handler);
404
- } else {
405
- logger.debug(`\u23ED\uFE0F Skipping middleware '${middleware.name}' for route: ${method} ${path}`, { name });
406
627
  }
407
628
  }
408
629
  }
@@ -425,11 +646,8 @@ function registerRoute(app, name, routeDef, namedMiddlewares) {
425
646
  }
426
647
  }
427
648
  const methodLower = method.toLowerCase();
428
- if (allMiddlewares.length > 0) {
429
- app[methodLower](path, ...allMiddlewares, wrappedHandler);
430
- } else {
431
- app[methodLower](path, wrappedHandler);
432
- }
649
+ const handlers = [...allMiddlewares, wrappedHandler];
650
+ app.on([methodLower], [path], ...handlers);
433
651
  logger.debug(`Registered route: ${method} ${path}`, { name });
434
652
  return { method, path, name };
435
653
  }
@@ -439,9 +657,16 @@ async function createRouteBuilderContext(c, input) {
439
657
  const headers = validateField(input.headers, extractHeaders(c), "headers");
440
658
  const cookies = validateField(input.cookies, extractCookies(c), "cookies");
441
659
  let body = {};
442
- if (input.body) {
443
- const rawBody = await parseJsonBody(c);
444
- body = validateField(input.body, rawBody, "request body");
660
+ let formData = {};
661
+ if (input.body || input.formData) {
662
+ const contentType = c.req.header("content-type") || "";
663
+ if (contentType.includes("multipart/form-data") && input.formData) {
664
+ const rawFormData = await parseFormData(c);
665
+ formData = validateFormData(input.formData, rawFormData, "form data");
666
+ } else if (input.body) {
667
+ const rawBody = await parseJsonBody(c);
668
+ body = validateField(input.body, rawBody, "request body");
669
+ }
445
670
  }
446
671
  let cachedData = null;
447
672
  const responseMeta = {
@@ -452,7 +677,7 @@ async function createRouteBuilderContext(c, input) {
452
677
  const context = {
453
678
  data: async () => {
454
679
  if (!cachedData) {
455
- cachedData = { params, query, body, headers, cookies };
680
+ cachedData = { params, query, body, formData, headers, cookies };
456
681
  }
457
682
  return cachedData;
458
683
  },
@@ -502,14 +727,16 @@ async function createRouteBuilderContext(c, input) {
502
727
  }
503
728
 
504
729
  // src/route/define-middleware.ts
505
- function defineMiddleware(name, handlerOrFactory) {
730
+ function defineMiddleware(name, handlerOrFactory, options) {
731
+ const skips = options?.skips;
506
732
  if (typeof handlerOrFactory === "function") {
507
733
  const paramCount = handlerOrFactory.length;
508
734
  if (paramCount === 2) {
509
735
  return {
510
736
  name,
511
737
  handler: handlerOrFactory,
512
- _name: name
738
+ _name: name,
739
+ ...skips && { skips }
513
740
  };
514
741
  } else {
515
742
  const factory = handlerOrFactory;
@@ -526,13 +753,22 @@ function defineMiddleware(name, handlerOrFactory) {
526
753
  enumerable: false,
527
754
  configurable: true
528
755
  });
756
+ if (skips) {
757
+ Object.defineProperty(wrapper, "skips", {
758
+ value: skips,
759
+ writable: false,
760
+ enumerable: false,
761
+ configurable: true
762
+ });
763
+ }
529
764
  return wrapper;
530
765
  }
531
766
  }
532
767
  return {
533
768
  name,
534
769
  handler: handlerOrFactory,
535
- _name: name
770
+ _name: name,
771
+ ...skips && { skips }
536
772
  };
537
773
  }
538
774
  function defineMiddlewareFactory(name, factory) {
@@ -557,6 +793,6 @@ function isHttpMethod(value) {
557
793
  var Nullable = (schema) => Type.Union([schema, Type.Null()]);
558
794
  var OptionalNullable = (schema) => Type.Optional(Type.Union([schema, Type.Null()]));
559
795
 
560
- export { Nullable, OptionalNullable, defineMiddleware, defineMiddlewareFactory, defineRouter, isHttpMethod, registerRoutes, route };
796
+ export { FileArraySchema, FileSchema, Nullable, OptionalFileSchema, OptionalNullable, defineMiddleware, defineMiddlewareFactory, defineRouter, formatFileSize, getFileOptions, isFileArraySchema, isFileSchema, isHttpMethod, registerRoutes, route };
561
797
  //# sourceMappingURL=index.js.map
562
798
  //# sourceMappingURL=index.js.map