@spfn/core 0.2.0-beta.6 → 0.2.0-beta.66

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 (73) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +183 -1281
  3. package/dist/authz/index.d.ts +34 -0
  4. package/dist/authz/index.js +415 -0
  5. package/dist/authz/index.js.map +1 -0
  6. package/dist/{boss-DI1r4kTS.d.ts → boss-gXhgctn6.d.ts} +40 -0
  7. package/dist/cache/index.js +42 -30
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/codegen/index.d.ts +55 -8
  10. package/dist/codegen/index.js +183 -7
  11. package/dist/codegen/index.js.map +1 -1
  12. package/dist/config/index.d.ts +585 -6
  13. package/dist/config/index.js +116 -5
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/db/index.d.ts +379 -75
  16. package/dist/db/index.js +625 -113
  17. package/dist/db/index.js.map +1 -1
  18. package/dist/define-middleware-DuXD8Hvu.d.ts +167 -0
  19. package/dist/env/index.d.ts +26 -2
  20. package/dist/env/index.js +15 -5
  21. package/dist/env/index.js.map +1 -1
  22. package/dist/env/loader.d.ts +26 -19
  23. package/dist/env/loader.js +32 -25
  24. package/dist/env/loader.js.map +1 -1
  25. package/dist/errors/index.d.ts +10 -0
  26. package/dist/errors/index.js +20 -2
  27. package/dist/errors/index.js.map +1 -1
  28. package/dist/event/index.d.ts +33 -3
  29. package/dist/event/index.js +24 -3
  30. package/dist/event/index.js.map +1 -1
  31. package/dist/event/sse/client.d.ts +42 -3
  32. package/dist/event/sse/client.js +128 -45
  33. package/dist/event/sse/client.js.map +1 -1
  34. package/dist/event/sse/index.d.ts +12 -5
  35. package/dist/event/sse/index.js +271 -32
  36. package/dist/event/sse/index.js.map +1 -1
  37. package/dist/event/ws/client.d.ts +59 -0
  38. package/dist/event/ws/client.js +273 -0
  39. package/dist/event/ws/client.js.map +1 -0
  40. package/dist/event/ws/index.d.ts +94 -0
  41. package/dist/event/ws/index.js +272 -0
  42. package/dist/event/ws/index.js.map +1 -0
  43. package/dist/job/index.d.ts +2 -2
  44. package/dist/job/index.js +155 -42
  45. package/dist/job/index.js.map +1 -1
  46. package/dist/logger/index.d.ts +5 -0
  47. package/dist/logger/index.js +14 -0
  48. package/dist/logger/index.js.map +1 -1
  49. package/dist/middleware/index.d.ts +243 -2
  50. package/dist/middleware/index.js +1323 -13
  51. package/dist/middleware/index.js.map +1 -1
  52. package/dist/nextjs/index.d.ts +2 -2
  53. package/dist/nextjs/index.js +77 -31
  54. package/dist/nextjs/index.js.map +1 -1
  55. package/dist/nextjs/server.d.ts +53 -23
  56. package/dist/nextjs/server.js +197 -66
  57. package/dist/nextjs/server.js.map +1 -1
  58. package/dist/route/index.d.ts +138 -146
  59. package/dist/route/index.js +238 -22
  60. package/dist/route/index.js.map +1 -1
  61. package/dist/security/index.d.ts +83 -0
  62. package/dist/security/index.js +173 -0
  63. package/dist/security/index.js.map +1 -0
  64. package/dist/server/index.d.ts +458 -17
  65. package/dist/server/index.js +1756 -277
  66. package/dist/server/index.js.map +1 -1
  67. package/dist/{router-Di7ENoah.d.ts → token-manager-jKD_EsSE.d.ts} +121 -1
  68. package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
  69. package/dist/types-BFB72jbM.d.ts +282 -0
  70. package/dist/types-DVjf37yO.d.ts +205 -0
  71. package/docs/file-upload.md +717 -0
  72. package/package.json +237 -208
  73. package/dist/types-B-e_f2dQ.d.ts +0 -121
@@ -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