@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.
- package/README.md +260 -1175
- package/dist/{boss-BO8ty33K.d.ts → boss-DI1r4kTS.d.ts} +24 -7
- package/dist/cache/index.js +32 -29
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +55 -8
- package/dist/codegen/index.js +179 -5
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +168 -6
- package/dist/config/index.js +29 -5
- package/dist/config/index.js.map +1 -1
- package/dist/db/index.d.ts +128 -4
- package/dist/db/index.js +177 -50
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +55 -1
- package/dist/env/index.js +71 -3
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +27 -19
- package/dist/env/loader.js +33 -25
- package/dist/env/loader.js.map +1 -1
- package/dist/event/index.d.ts +27 -1
- package/dist/event/index.js +6 -1
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +77 -2
- package/dist/event/sse/client.js +87 -24
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +10 -4
- package/dist/event/sse/index.js +158 -12
- package/dist/event/sse/index.js.map +1 -1
- package/dist/job/index.d.ts +23 -8
- package/dist/job/index.js +96 -20
- package/dist/job/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -0
- package/dist/logger/index.js +14 -0
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +23 -1
- package/dist/middleware/index.js +58 -5
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +2 -2
- package/dist/nextjs/index.js +77 -31
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +44 -23
- package/dist/nextjs/server.js +83 -65
- package/dist/nextjs/server.js.map +1 -1
- package/dist/route/index.d.ts +158 -4
- package/dist/route/index.js +251 -12
- package/dist/route/index.js.map +1 -1
- package/dist/server/index.d.ts +251 -16
- package/dist/server/index.js +774 -228
- package/dist/server/index.js.map +1 -1
- package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
- package/dist/types-DKQ90YL7.d.ts +372 -0
- package/docs/cache.md +133 -0
- package/docs/codegen.md +74 -0
- package/docs/database.md +370 -0
- package/docs/entity.md +539 -0
- package/docs/env.md +499 -0
- package/docs/errors.md +319 -0
- package/docs/event.md +443 -0
- package/docs/file-upload.md +717 -0
- package/docs/job.md +131 -0
- package/docs/logger.md +108 -0
- package/docs/middleware.md +337 -0
- package/docs/nextjs.md +247 -0
- package/docs/repository.md +496 -0
- package/docs/route.md +497 -0
- package/docs/server.md +429 -0
- package/package.json +2 -1
- package/dist/types-B-e_f2dQ.d.ts +0 -121
package/dist/route/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
311
|
-
|
|
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
|
-
|
|
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 };
|
package/dist/route/index.js
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|