@spfn/core 0.2.0-beta.5 → 0.2.0-beta.51

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 (64) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +181 -1281
  3. package/dist/{boss-BO8ty33K.d.ts → boss-Cxqc-Oiw.d.ts} +37 -7
  4. package/dist/cache/index.js +32 -29
  5. package/dist/cache/index.js.map +1 -1
  6. package/dist/codegen/index.d.ts +55 -8
  7. package/dist/codegen/index.js +179 -5
  8. package/dist/codegen/index.js.map +1 -1
  9. package/dist/config/index.d.ts +168 -6
  10. package/dist/config/index.js +29 -5
  11. package/dist/config/index.js.map +1 -1
  12. package/dist/db/index.d.ts +218 -4
  13. package/dist/db/index.js +351 -57
  14. package/dist/db/index.js.map +1 -1
  15. package/dist/env/index.d.ts +26 -2
  16. package/dist/env/index.js +11 -2
  17. package/dist/env/index.js.map +1 -1
  18. package/dist/env/loader.d.ts +26 -19
  19. package/dist/env/loader.js +32 -25
  20. package/dist/env/loader.js.map +1 -1
  21. package/dist/errors/index.js.map +1 -1
  22. package/dist/event/index.d.ts +33 -3
  23. package/dist/event/index.js +17 -1
  24. package/dist/event/index.js.map +1 -1
  25. package/dist/event/sse/client.d.ts +42 -3
  26. package/dist/event/sse/client.js +128 -45
  27. package/dist/event/sse/client.js.map +1 -1
  28. package/dist/event/sse/index.d.ts +12 -5
  29. package/dist/event/sse/index.js +188 -20
  30. package/dist/event/sse/index.js.map +1 -1
  31. package/dist/event/ws/client.d.ts +59 -0
  32. package/dist/event/ws/client.js +273 -0
  33. package/dist/event/ws/client.js.map +1 -0
  34. package/dist/event/ws/index.d.ts +94 -0
  35. package/dist/event/ws/index.js +213 -0
  36. package/dist/event/ws/index.js.map +1 -0
  37. package/dist/job/index.d.ts +23 -8
  38. package/dist/job/index.js +154 -44
  39. package/dist/job/index.js.map +1 -1
  40. package/dist/logger/index.d.ts +5 -0
  41. package/dist/logger/index.js +14 -0
  42. package/dist/logger/index.js.map +1 -1
  43. package/dist/middleware/index.d.ts +23 -1
  44. package/dist/middleware/index.js +58 -5
  45. package/dist/middleware/index.js.map +1 -1
  46. package/dist/nextjs/index.d.ts +2 -2
  47. package/dist/nextjs/index.js +77 -31
  48. package/dist/nextjs/index.js.map +1 -1
  49. package/dist/nextjs/server.d.ts +44 -23
  50. package/dist/nextjs/server.js +83 -65
  51. package/dist/nextjs/server.js.map +1 -1
  52. package/dist/route/index.d.ts +158 -4
  53. package/dist/route/index.js +238 -22
  54. package/dist/route/index.js.map +1 -1
  55. package/dist/server/index.d.ts +308 -17
  56. package/dist/server/index.js +1128 -261
  57. package/dist/server/index.js.map +1 -1
  58. package/dist/{router-Di7ENoah.d.ts → token-manager-CyG7la3p.d.ts} +116 -1
  59. package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
  60. package/dist/types-C1jMLGwK.d.ts +257 -0
  61. package/dist/types-Cfj--lfr.d.ts +151 -0
  62. package/docs/file-upload.md +717 -0
  63. package/package.json +18 -5
  64. 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,5 +1,5 @@
1
1
  import { logger } from '@spfn/core/logger';
2
- import { FormatRegistry, Type } from '@sinclair/typebox';
2
+ import { FormatRegistry, Type, Kind } from '@sinclair/typebox';
3
3
  import { Value } from '@sinclair/typebox/value';
4
4
  import { ValidationError } from '@spfn/core/errors';
5
5
 
@@ -260,26 +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
263
306
  FormatRegistry.Set(
264
307
  "email",
265
- (value) => typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
308
+ (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
266
309
  );
267
310
  FormatRegistry.Set(
268
311
  "uri",
269
- (value) => typeof value === "string" && /^https?:\/\/.+/.test(value)
312
+ (value) => /^https?:\/\/.+/.test(value)
270
313
  );
271
314
  FormatRegistry.Set(
272
315
  "uuid",
273
- (value) => typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)
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)
274
317
  );
275
318
  FormatRegistry.Set(
276
319
  "date",
277
- (value) => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)
320
+ (value) => /^\d{4}-\d{2}-\d{2}$/.test(value)
278
321
  );
279
322
  FormatRegistry.Set(
280
323
  "date-time",
281
- (value) => typeof value === "string" && !isNaN(Date.parse(value))
324
+ (value) => !isNaN(Date.parse(value))
282
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
+ }
283
365
  function validateField(schema, rawValue, fieldName) {
284
366
  if (!schema) {
285
367
  return {};
@@ -298,6 +380,87 @@ function validateField(schema, rawValue, fieldName) {
298
380
  }
299
381
  return converted;
300
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
+ }
301
464
  function extractQueryParams(c) {
302
465
  const url = new URL(c.req.url);
303
466
  const queryObj = {};
@@ -345,6 +508,34 @@ async function parseJsonBody(c) {
345
508
  });
346
509
  }
347
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
+ }
348
539
 
349
540
  // src/route/register-routes.ts
350
541
  function isRouter(value) {
@@ -411,18 +602,28 @@ function registerRoute(app, name, routeDef, namedMiddlewares) {
411
602
  const registeredNames = /* @__PURE__ */ new Set();
412
603
  const registeredHandlers = /* @__PURE__ */ new Set();
413
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
+ }
414
613
  if (namedMiddlewares && namedMiddlewares.length > 0) {
415
614
  if (skipAll) {
416
615
  logger.debug(`\u23ED\uFE0F Skipping all middlewares (*) for route: ${method} ${path}`, { name });
417
616
  } else {
418
617
  const skipSet = new Set(Array.isArray(skipMiddlewares) ? skipMiddlewares : []);
419
618
  for (const middleware of namedMiddlewares) {
420
- 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 {
421
624
  allMiddlewares.push(middleware.handler);
422
625
  registeredNames.add(middleware.name);
423
626
  registeredHandlers.add(middleware.handler);
424
- } else {
425
- logger.debug(`\u23ED\uFE0F Skipping middleware '${middleware.name}' for route: ${method} ${path}`, { name });
426
627
  }
427
628
  }
428
629
  }
@@ -445,11 +646,8 @@ function registerRoute(app, name, routeDef, namedMiddlewares) {
445
646
  }
446
647
  }
447
648
  const methodLower = method.toLowerCase();
448
- if (allMiddlewares.length > 0) {
449
- app[methodLower](path, ...allMiddlewares, wrappedHandler);
450
- } else {
451
- app[methodLower](path, wrappedHandler);
452
- }
649
+ const handlers = [...allMiddlewares, wrappedHandler];
650
+ app.on([methodLower], [path], ...handlers);
453
651
  logger.debug(`Registered route: ${method} ${path}`, { name });
454
652
  return { method, path, name };
455
653
  }
@@ -459,9 +657,16 @@ async function createRouteBuilderContext(c, input) {
459
657
  const headers = validateField(input.headers, extractHeaders(c), "headers");
460
658
  const cookies = validateField(input.cookies, extractCookies(c), "cookies");
461
659
  let body = {};
462
- if (input.body) {
463
- const rawBody = await parseJsonBody(c);
464
- 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
+ }
465
670
  }
466
671
  let cachedData = null;
467
672
  const responseMeta = {
@@ -472,7 +677,7 @@ async function createRouteBuilderContext(c, input) {
472
677
  const context = {
473
678
  data: async () => {
474
679
  if (!cachedData) {
475
- cachedData = { params, query, body, headers, cookies };
680
+ cachedData = { params, query, body, formData, headers, cookies };
476
681
  }
477
682
  return cachedData;
478
683
  },
@@ -522,14 +727,16 @@ async function createRouteBuilderContext(c, input) {
522
727
  }
523
728
 
524
729
  // src/route/define-middleware.ts
525
- function defineMiddleware(name, handlerOrFactory) {
730
+ function defineMiddleware(name, handlerOrFactory, options) {
731
+ const skips = options?.skips;
526
732
  if (typeof handlerOrFactory === "function") {
527
733
  const paramCount = handlerOrFactory.length;
528
734
  if (paramCount === 2) {
529
735
  return {
530
736
  name,
531
737
  handler: handlerOrFactory,
532
- _name: name
738
+ _name: name,
739
+ ...skips && { skips }
533
740
  };
534
741
  } else {
535
742
  const factory = handlerOrFactory;
@@ -546,13 +753,22 @@ function defineMiddleware(name, handlerOrFactory) {
546
753
  enumerable: false,
547
754
  configurable: true
548
755
  });
756
+ if (skips) {
757
+ Object.defineProperty(wrapper, "skips", {
758
+ value: skips,
759
+ writable: false,
760
+ enumerable: false,
761
+ configurable: true
762
+ });
763
+ }
549
764
  return wrapper;
550
765
  }
551
766
  }
552
767
  return {
553
768
  name,
554
769
  handler: handlerOrFactory,
555
- _name: name
770
+ _name: name,
771
+ ...skips && { skips }
556
772
  };
557
773
  }
558
774
  function defineMiddlewareFactory(name, factory) {
@@ -577,6 +793,6 @@ function isHttpMethod(value) {
577
793
  var Nullable = (schema) => Type.Union([schema, Type.Null()]);
578
794
  var OptionalNullable = (schema) => Type.Optional(Type.Union([schema, Type.Null()]));
579
795
 
580
- 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 };
581
797
  //# sourceMappingURL=index.js.map
582
798
  //# sourceMappingURL=index.js.map