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

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 +251 -12
  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 +2 -1
  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
  }
@@ -439,9 +660,16 @@ async function createRouteBuilderContext(c, input) {
439
660
  const headers = validateField(input.headers, extractHeaders(c), "headers");
440
661
  const cookies = validateField(input.cookies, extractCookies(c), "cookies");
441
662
  let body = {};
442
- if (input.body) {
443
- const rawBody = await parseJsonBody(c);
444
- body = validateField(input.body, rawBody, "request body");
663
+ let formData = {};
664
+ if (input.body || input.formData) {
665
+ const contentType = c.req.header("content-type") || "";
666
+ if (contentType.includes("multipart/form-data") && input.formData) {
667
+ const rawFormData = await parseFormData(c);
668
+ formData = validateFormData(input.formData, rawFormData, "form data");
669
+ } else if (input.body) {
670
+ const rawBody = await parseJsonBody(c);
671
+ body = validateField(input.body, rawBody, "request body");
672
+ }
445
673
  }
446
674
  let cachedData = null;
447
675
  const responseMeta = {
@@ -452,7 +680,7 @@ async function createRouteBuilderContext(c, input) {
452
680
  const context = {
453
681
  data: async () => {
454
682
  if (!cachedData) {
455
- cachedData = { params, query, body, headers, cookies };
683
+ cachedData = { params, query, body, formData, headers, cookies };
456
684
  }
457
685
  return cachedData;
458
686
  },
@@ -502,14 +730,16 @@ async function createRouteBuilderContext(c, input) {
502
730
  }
503
731
 
504
732
  // src/route/define-middleware.ts
505
- function defineMiddleware(name, handlerOrFactory) {
733
+ function defineMiddleware(name, handlerOrFactory, options) {
734
+ const skips = options?.skips;
506
735
  if (typeof handlerOrFactory === "function") {
507
736
  const paramCount = handlerOrFactory.length;
508
737
  if (paramCount === 2) {
509
738
  return {
510
739
  name,
511
740
  handler: handlerOrFactory,
512
- _name: name
741
+ _name: name,
742
+ ...skips && { skips }
513
743
  };
514
744
  } else {
515
745
  const factory = handlerOrFactory;
@@ -526,13 +756,22 @@ function defineMiddleware(name, handlerOrFactory) {
526
756
  enumerable: false,
527
757
  configurable: true
528
758
  });
759
+ if (skips) {
760
+ Object.defineProperty(wrapper, "skips", {
761
+ value: skips,
762
+ writable: false,
763
+ enumerable: false,
764
+ configurable: true
765
+ });
766
+ }
529
767
  return wrapper;
530
768
  }
531
769
  }
532
770
  return {
533
771
  name,
534
772
  handler: handlerOrFactory,
535
- _name: name
773
+ _name: name,
774
+ ...skips && { skips }
536
775
  };
537
776
  }
538
777
  function defineMiddlewareFactory(name, factory) {
@@ -557,6 +796,6 @@ function isHttpMethod(value) {
557
796
  var Nullable = (schema) => Type.Union([schema, Type.Null()]);
558
797
  var OptionalNullable = (schema) => Type.Optional(Type.Union([schema, Type.Null()]));
559
798
 
560
- export { Nullable, OptionalNullable, defineMiddleware, defineMiddlewareFactory, defineRouter, isHttpMethod, registerRoutes, route };
799
+ export { FileArraySchema, FileSchema, Nullable, OptionalFileSchema, OptionalNullable, defineMiddleware, defineMiddlewareFactory, defineRouter, formatFileSize, getFileOptions, isFileArraySchema, isFileSchema, isHttpMethod, registerRoutes, route };
561
800
  //# sourceMappingURL=index.js.map
562
801
  //# sourceMappingURL=index.js.map