@expressots/core 4.0.0-preview.1 → 4.0.0-preview.3

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 (134) hide show
  1. package/LICENSE.md +21 -21
  2. package/README.md +66 -66
  3. package/lib/CHANGELOG.md +774 -774
  4. package/lib/README.md +66 -66
  5. package/lib/cjs/application/application-factory.js +6 -0
  6. package/lib/cjs/application/bootstrap.js +117 -213
  7. package/lib/cjs/config/define-config.js +1 -1
  8. package/lib/cjs/config/env-field-builders.js +47 -0
  9. package/lib/cjs/config/index.js +7 -1
  10. package/lib/cjs/framework-version.js +10 -0
  11. package/lib/cjs/lazy-loading/index.js +5 -1
  12. package/lib/cjs/lazy-loading/lazy-module-helpers.js +49 -0
  13. package/lib/cjs/middleware/index.js +8 -9
  14. package/lib/cjs/middleware/middleware-service.js +68 -12
  15. package/lib/cjs/middleware/presets-standalone.js +93 -0
  16. package/lib/cjs/provider/db-in-memory/adapter/in-memory.adapter.js +23 -0
  17. package/lib/cjs/provider/db-in-memory/index.js +11 -1
  18. package/lib/cjs/provider/db-in-memory/query/query-engine.js +28 -0
  19. package/lib/cjs/provider/db-in-memory/schema/decorators.js +18 -0
  20. package/lib/cjs/provider/db-in-memory/storage/index.js +3 -1
  21. package/lib/cjs/provider/db-in-memory/storage/memory-store.js +72 -1
  22. package/lib/cjs/provider/logger/logger.banner.js +40 -31
  23. package/lib/cjs/provider/logger/logger.config.js +11 -1
  24. package/lib/cjs/provider/logger/logger.formatter.js +22 -1
  25. package/lib/cjs/provider/logger/logger.provider.js +59 -9
  26. package/lib/cjs/provider/logger/transports/console.transport.js +69 -6
  27. package/lib/cjs/provider/logger/transports/file.transport.js +27 -18
  28. package/lib/cjs/provider/logger/utils/log-levels.js +6 -5
  29. package/lib/cjs/provider/validation/adapters/index.js +12 -5
  30. package/lib/cjs/provider/validation/adapters/yup.adapter.js +118 -0
  31. package/lib/cjs/provider/validation/adapters/zod.adapter.js +137 -0
  32. package/lib/cjs/provider/validation/index.js +5 -1
  33. package/lib/cjs/render/adapters/react-adapter.js +14 -14
  34. package/lib/cjs/render/features/type-generator.js +30 -30
  35. package/lib/cjs/render/features/view-debugger.js +75 -55
  36. package/lib/cjs/testing/fluent-request.js +7 -0
  37. package/lib/cjs/testing/snapshot-request.js +2 -0
  38. package/lib/cjs/types/application/application-factory.d.ts +6 -0
  39. package/lib/cjs/types/application/bootstrap.d.ts +196 -24
  40. package/lib/cjs/types/config/config.interfaces.d.ts +7 -1
  41. package/lib/cjs/types/config/env-field-builders.d.ts +39 -0
  42. package/lib/cjs/types/config/index.d.ts +1 -1
  43. package/lib/cjs/types/framework-version.d.ts +7 -0
  44. package/lib/cjs/types/lazy-loading/index.d.ts +1 -0
  45. package/lib/cjs/types/lazy-loading/lazy-module-helpers.d.ts +42 -0
  46. package/lib/cjs/types/middleware/index.d.ts +1 -1
  47. package/lib/cjs/types/middleware/middleware-service.d.ts +21 -0
  48. package/lib/cjs/types/middleware/presets-standalone.d.ts +75 -0
  49. package/lib/cjs/types/provider/db-in-memory/adapter/in-memory.adapter.d.ts +2 -0
  50. package/lib/cjs/types/provider/db-in-memory/index.d.ts +9 -1
  51. package/lib/cjs/types/provider/db-in-memory/query/query-engine.d.ts +14 -1
  52. package/lib/cjs/types/provider/db-in-memory/schema/decorators.d.ts +14 -0
  53. package/lib/cjs/types/provider/db-in-memory/storage/index.d.ts +1 -1
  54. package/lib/cjs/types/provider/db-in-memory/storage/memory-store.d.ts +34 -0
  55. package/lib/cjs/types/provider/logger/logger.banner.d.ts +1 -1
  56. package/lib/cjs/types/provider/logger/logger.config.d.ts +7 -0
  57. package/lib/cjs/types/provider/logger/logger.formatter.d.ts +10 -0
  58. package/lib/cjs/types/provider/logger/logger.provider.d.ts +32 -1
  59. package/lib/cjs/types/provider/logger/transports/console.transport.d.ts +7 -0
  60. package/lib/cjs/types/provider/logger/utils/log-levels.d.ts +3 -3
  61. package/lib/cjs/types/provider/validation/adapters/index.d.ts +7 -4
  62. package/lib/cjs/types/provider/validation/adapters/yup.adapter.d.ts +65 -0
  63. package/lib/cjs/types/provider/validation/adapters/zod.adapter.d.ts +84 -0
  64. package/lib/cjs/types/provider/validation/index.d.ts +1 -1
  65. package/lib/cjs/types/render/features/view-debugger.d.ts +10 -0
  66. package/lib/cjs/types/testing/testing.interfaces.d.ts +31 -6
  67. package/lib/esm/application/application-factory.js +6 -0
  68. package/lib/esm/application/bootstrap.js +117 -213
  69. package/lib/esm/config/define-config.js +1 -1
  70. package/lib/esm/config/env-field-builders.js +48 -0
  71. package/lib/esm/config/index.js +6 -1
  72. package/lib/esm/framework-version.js +7 -0
  73. package/lib/esm/lazy-loading/index.js +2 -0
  74. package/lib/esm/lazy-loading/lazy-module-helpers.js +45 -0
  75. package/lib/esm/middleware/index.js +3 -2
  76. package/lib/esm/middleware/middleware-service.js +68 -12
  77. package/lib/esm/middleware/presets-standalone.js +87 -0
  78. package/lib/esm/provider/db-in-memory/adapter/in-memory.adapter.js +23 -0
  79. package/lib/esm/provider/db-in-memory/index.js +9 -1
  80. package/lib/esm/provider/db-in-memory/query/query-engine.js +28 -0
  81. package/lib/esm/provider/db-in-memory/schema/decorators.js +18 -0
  82. package/lib/esm/provider/db-in-memory/storage/index.js +1 -1
  83. package/lib/esm/provider/db-in-memory/storage/memory-store.js +75 -0
  84. package/lib/esm/provider/logger/logger.banner.js +40 -31
  85. package/lib/esm/provider/logger/logger.config.js +12 -2
  86. package/lib/esm/provider/logger/logger.formatter.js +22 -1
  87. package/lib/esm/provider/logger/logger.provider.js +61 -10
  88. package/lib/esm/provider/logger/transports/console.transport.js +69 -6
  89. package/lib/esm/provider/logger/transports/file.transport.js +27 -18
  90. package/lib/esm/provider/logger/utils/log-levels.js +6 -5
  91. package/lib/esm/provider/validation/adapters/index.js +7 -4
  92. package/lib/esm/provider/validation/adapters/yup.adapter.js +111 -0
  93. package/lib/esm/provider/validation/adapters/zod.adapter.js +130 -0
  94. package/lib/esm/provider/validation/index.js +1 -1
  95. package/lib/esm/render/adapters/react-adapter.js +14 -14
  96. package/lib/esm/render/features/type-generator.js +30 -30
  97. package/lib/esm/render/features/view-debugger.js +75 -55
  98. package/lib/esm/testing/fluent-request.js +7 -0
  99. package/lib/esm/testing/snapshot-request.js +2 -0
  100. package/lib/esm/types/application/application-factory.d.ts +6 -0
  101. package/lib/esm/types/application/bootstrap.d.ts +196 -24
  102. package/lib/esm/types/config/config.interfaces.d.ts +7 -1
  103. package/lib/esm/types/config/env-field-builders.d.ts +39 -0
  104. package/lib/esm/types/config/index.d.ts +1 -1
  105. package/lib/esm/types/framework-version.d.ts +7 -0
  106. package/lib/esm/types/lazy-loading/index.d.ts +1 -0
  107. package/lib/esm/types/lazy-loading/lazy-module-helpers.d.ts +42 -0
  108. package/lib/esm/types/middleware/index.d.ts +1 -1
  109. package/lib/esm/types/middleware/middleware-service.d.ts +21 -0
  110. package/lib/esm/types/middleware/presets-standalone.d.ts +75 -0
  111. package/lib/esm/types/provider/db-in-memory/adapter/in-memory.adapter.d.ts +2 -0
  112. package/lib/esm/types/provider/db-in-memory/index.d.ts +9 -1
  113. package/lib/esm/types/provider/db-in-memory/query/query-engine.d.ts +14 -1
  114. package/lib/esm/types/provider/db-in-memory/schema/decorators.d.ts +14 -0
  115. package/lib/esm/types/provider/db-in-memory/storage/index.d.ts +1 -1
  116. package/lib/esm/types/provider/db-in-memory/storage/memory-store.d.ts +34 -0
  117. package/lib/esm/types/provider/logger/logger.banner.d.ts +1 -1
  118. package/lib/esm/types/provider/logger/logger.config.d.ts +7 -0
  119. package/lib/esm/types/provider/logger/logger.formatter.d.ts +10 -0
  120. package/lib/esm/types/provider/logger/logger.provider.d.ts +32 -1
  121. package/lib/esm/types/provider/logger/transports/console.transport.d.ts +7 -0
  122. package/lib/esm/types/provider/logger/utils/log-levels.d.ts +3 -3
  123. package/lib/esm/types/provider/validation/adapters/index.d.ts +7 -4
  124. package/lib/esm/types/provider/validation/adapters/yup.adapter.d.ts +65 -0
  125. package/lib/esm/types/provider/validation/adapters/zod.adapter.d.ts +84 -0
  126. package/lib/esm/types/provider/validation/index.d.ts +1 -1
  127. package/lib/esm/types/render/features/view-debugger.d.ts +10 -0
  128. package/lib/esm/types/testing/testing.interfaces.d.ts +31 -6
  129. package/lib/package.json +23 -8
  130. package/package.json +23 -8
  131. package/lib/cjs/middleware/middleware-presets.js +0 -294
  132. package/lib/cjs/types/middleware/middleware-presets.d.ts +0 -90
  133. package/lib/esm/middleware/middleware-presets.js +0 -286
  134. package/lib/esm/types/middleware/middleware-presets.d.ts +0 -90
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FRAMEWORK_VERSION = void 0;
4
+ /**
5
+ * Framework version string surfaced in startup banners and diagnostics.
6
+ *
7
+ * This file is auto-synced from the root `package.json` by
8
+ * `scripts/sync-version.js` before each build. Do not edit by hand.
9
+ */
10
+ exports.FRAMEWORK_VERSION = "4.0.0-preview.3";
@@ -36,7 +36,7 @@
36
36
  * ```
37
37
  */
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.createLazyModuleWarmup = exports.LazyModuleWarmup = exports.createLazyLoadMetrics = exports.LazyLoadMetrics = exports.createLazyModuleManager = exports.LazyModuleManager = exports.createLazyModuleLoader = exports.LazyModuleLoader = exports.LAZY_MODULE_METADATA_KEY = exports.getModuleName = exports.isLazyModule = exports.createLazyModule = exports.CreateLazyModule = exports.LazyModule = void 0;
39
+ exports.createLazyModuleWarmup = exports.LazyModuleWarmup = exports.createLazyLoadMetrics = exports.LazyLoadMetrics = exports.createLazyModuleManager = exports.LazyModuleManager = exports.createLazyModuleLoader = exports.LazyModuleLoader = exports.withLazyConfig = exports.withPreloadHint = exports.LAZY_MODULE_METADATA_KEY = exports.getModuleName = exports.isLazyModule = exports.createLazyModule = exports.CreateLazyModule = exports.LazyModule = void 0;
40
40
  // ============================================================================
41
41
  // Lazy Module
42
42
  // ============================================================================
@@ -47,6 +47,10 @@ Object.defineProperty(exports, "createLazyModule", { enumerable: true, get: func
47
47
  Object.defineProperty(exports, "isLazyModule", { enumerable: true, get: function () { return lazy_module_js_1.isLazyModule; } });
48
48
  Object.defineProperty(exports, "getModuleName", { enumerable: true, get: function () { return lazy_module_js_1.getModuleName; } });
49
49
  Object.defineProperty(exports, "LAZY_MODULE_METADATA_KEY", { enumerable: true, get: function () { return lazy_module_js_1.LAZY_MODULE_METADATA_KEY; } });
50
+ // Free-function wrappers for the LazyModule chain methods.
51
+ var lazy_module_helpers_js_1 = require("./lazy-module-helpers.js");
52
+ Object.defineProperty(exports, "withPreloadHint", { enumerable: true, get: function () { return lazy_module_helpers_js_1.withPreloadHint; } });
53
+ Object.defineProperty(exports, "withLazyConfig", { enumerable: true, get: function () { return lazy_module_helpers_js_1.withLazyConfig; } });
50
54
  // ============================================================================
51
55
  // Lazy Module Loader
52
56
  // ============================================================================
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * Standalone (free-function) wrappers around `LazyModule` chain methods.
4
+ *
5
+ * The fluent API (`module.withPreloadHint(...).withLazyConfig(...)`) remains
6
+ * the recommended style. These helpers exist so users can compose lazy
7
+ * configurations with point-free style or apply the same hint to a list of
8
+ * modules:
9
+ *
10
+ * ```ts
11
+ * import { CreateLazyModule, withPreloadHint, withLazyConfig } from "@expressots/core";
12
+ *
13
+ * const Admin = withPreloadHint(
14
+ * CreateLazyModule([AdminController]),
15
+ * "low",
16
+ * );
17
+ *
18
+ * const Analytics = withLazyConfig(
19
+ * CreateLazyModule([AnalyticsController]),
20
+ * { preloadHint: "medium", prefetchAfterIdle: 5000 },
21
+ * );
22
+ * ```
23
+ *
24
+ * @public API
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.withPreloadHint = withPreloadHint;
28
+ exports.withLazyConfig = withLazyConfig;
29
+ /**
30
+ * Set a preload hint on the supplied lazy module and return it.
31
+ *
32
+ * Equivalent to `module.withPreloadHint(hint)`. Returned reference is the same
33
+ * instance — no copy is made.
34
+ *
35
+ * @public API
36
+ */
37
+ function withPreloadHint(module, hint) {
38
+ return module.withPreloadHint(hint);
39
+ }
40
+ /**
41
+ * Merge the supplied partial config into the lazy module and return it.
42
+ *
43
+ * Equivalent to `module.withLazyConfig(config)`.
44
+ *
45
+ * @public API
46
+ */
47
+ function withLazyConfig(module, config) {
48
+ return module.withLazyConfig(config);
49
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.mergeUploadConfigs = exports.clearGlobalUploadConfig = exports.hasGlobalUploadConfig = exports.getGlobalUploadConfig = exports.setGlobalUploadConfig = exports.resetMiddlewareRegistry = exports.getMiddlewareRegistry = exports.MiddlewareRegistry = exports.timeout = exports.parallel = exports.when = exports.compose = exports.use = exports.PlainTextFormatter = exports.YamlFormatter = exports.CsvFormatter = exports.XmlFormatter = exports.JsonFormatter = exports.AcceptHeaderParser = exports.FormatterRegistry = exports.ContentNegotiationService = exports.mergePresets = exports.createPreset = exports.getPresetsByTag = exports.getPresetNames = exports.getPreset = exports.MIDDLEWARE_PRESETS = exports.MiddlewareProfiler = exports.MIDDLEWARE_REGISTRY = exports.getPackageName = exports.clearMiddlewareCache = exports.getRegisteredMiddleware = exports.getAvailableMiddleware = exports.resolvePackage = exports.isPackageAvailable = exports.isMiddlewareAvailable = exports.middlewareResolver = exports.ExpressoMiddleware = exports.Middleware = void 0;
3
+ exports.mergeUploadConfigs = exports.clearGlobalUploadConfig = exports.hasGlobalUploadConfig = exports.getGlobalUploadConfig = exports.setGlobalUploadConfig = exports.resetMiddlewareRegistry = exports.getMiddlewareRegistry = exports.MiddlewareRegistry = exports.clearStandalonePresets = exports.getStandalonePresetNames = exports.applyPreset = exports.definePreset = exports.timeout = exports.parallel = exports.when = exports.compose = exports.use = exports.PlainTextFormatter = exports.YamlFormatter = exports.CsvFormatter = exports.XmlFormatter = exports.JsonFormatter = exports.AcceptHeaderParser = exports.FormatterRegistry = exports.ContentNegotiationService = exports.MiddlewareProfiler = exports.MIDDLEWARE_REGISTRY = exports.getPackageName = exports.clearMiddlewareCache = exports.getRegisteredMiddleware = exports.getAvailableMiddleware = exports.resolvePackage = exports.isPackageAvailable = exports.isMiddlewareAvailable = exports.middlewareResolver = exports.ExpressoMiddleware = exports.Middleware = void 0;
4
4
  // Core middleware service and types
5
5
  var middleware_service_js_1 = require("./middleware-service.js");
6
6
  Object.defineProperty(exports, "Middleware", { enumerable: true, get: function () { return middleware_service_js_1.Middleware; } });
@@ -19,14 +19,6 @@ Object.defineProperty(exports, "MIDDLEWARE_REGISTRY", { enumerable: true, get: f
19
19
  // Middleware profiler
20
20
  var middleware_profiler_js_1 = require("./middleware-profiler.js");
21
21
  Object.defineProperty(exports, "MiddlewareProfiler", { enumerable: true, get: function () { return middleware_profiler_js_1.MiddlewareProfiler; } });
22
- // Middleware presets
23
- var middleware_presets_js_1 = require("./middleware-presets.js");
24
- Object.defineProperty(exports, "MIDDLEWARE_PRESETS", { enumerable: true, get: function () { return middleware_presets_js_1.MIDDLEWARE_PRESETS; } });
25
- Object.defineProperty(exports, "getPreset", { enumerable: true, get: function () { return middleware_presets_js_1.getPreset; } });
26
- Object.defineProperty(exports, "getPresetNames", { enumerable: true, get: function () { return middleware_presets_js_1.getPresetNames; } });
27
- Object.defineProperty(exports, "getPresetsByTag", { enumerable: true, get: function () { return middleware_presets_js_1.getPresetsByTag; } });
28
- Object.defineProperty(exports, "createPreset", { enumerable: true, get: function () { return middleware_presets_js_1.createPreset; } });
29
- Object.defineProperty(exports, "mergePresets", { enumerable: true, get: function () { return middleware_presets_js_1.mergePresets; } });
30
22
  // Content Negotiation exports
31
23
  var index_js_1 = require("./content-negotiation/index.js");
32
24
  Object.defineProperty(exports, "ContentNegotiationService", { enumerable: true, get: function () { return index_js_1.ContentNegotiationService; } });
@@ -47,6 +39,13 @@ Object.defineProperty(exports, "compose", { enumerable: true, get: function () {
47
39
  Object.defineProperty(exports, "when", { enumerable: true, get: function () { return middleware_utils_js_1.when; } });
48
40
  Object.defineProperty(exports, "parallel", { enumerable: true, get: function () { return middleware_utils_js_1.parallel; } });
49
41
  Object.defineProperty(exports, "timeout", { enumerable: true, get: function () { return middleware_utils_js_1.timeout; } });
42
+ // V4 Standalone preset helpers (free-function wrappers around
43
+ // Middleware.definePreset / Middleware.applyPreset)
44
+ var presets_standalone_js_1 = require("./presets-standalone.js");
45
+ Object.defineProperty(exports, "definePreset", { enumerable: true, get: function () { return presets_standalone_js_1.definePreset; } });
46
+ Object.defineProperty(exports, "applyPreset", { enumerable: true, get: function () { return presets_standalone_js_1.applyPreset; } });
47
+ Object.defineProperty(exports, "getStandalonePresetNames", { enumerable: true, get: function () { return presets_standalone_js_1.getStandalonePresetNames; } });
48
+ Object.defineProperty(exports, "clearStandalonePresets", { enumerable: true, get: function () { return presets_standalone_js_1.clearStandalonePresets; } });
50
49
  // V4 Middleware registry
51
50
  var middleware_registry_js_1 = require("./middleware-registry.js");
52
51
  Object.defineProperty(exports, "MiddlewareRegistry", { enumerable: true, get: function () { return middleware_registry_js_1.MiddlewareRegistry; } });
@@ -245,6 +245,8 @@ class Middleware {
245
245
  this.profilingEnabled = false;
246
246
  // v4: Custom presets storage
247
247
  this.customPresets = new Map();
248
+ // v4: Last applied preset info (for Studio integration)
249
+ this._lastPreset = null;
248
250
  // v4: Middleware registry reference
249
251
  this.registry = (0, middleware_registry_js_1.getMiddlewareRegistry)();
250
252
  // v4: Buffered startup logs (displayed after banner)
@@ -304,7 +306,9 @@ class Middleware {
304
306
  * @returns The type of the middleware.
305
307
  */
306
308
  getMiddlewareType(middleware) {
307
- if (middleware && typeof middleware === "object" && "path" in middleware) {
309
+ if (middleware &&
310
+ typeof middleware === "object" &&
311
+ ("path" in middleware || "middlewares" in middleware)) {
308
312
  return MiddlewareType.Config;
309
313
  }
310
314
  if (typeof middleware === "function") {
@@ -367,7 +371,12 @@ class Middleware {
367
371
  const middlewareType = this.getMiddlewareType(m.middleware);
368
372
  if (middlewareType === MiddlewareType.Config) {
369
373
  const config = m.middleware;
370
- return config.path || "ConfigMiddleware";
374
+ if (config.path)
375
+ return config.path;
376
+ const names = config.middlewares
377
+ .map((fn) => typeof fn === "function" ? fn.name : fn?.constructor?.name)
378
+ .filter((n) => n && n !== "anonymous" && n !== "");
379
+ return names.length > 0 ? names.join(", ") : "ConfigMiddleware";
371
380
  }
372
381
  else if (middlewareType === MiddlewareType.IExpressoMiddleware) {
373
382
  return m.middleware.constructor.name;
@@ -464,7 +473,11 @@ class Middleware {
464
473
  this._logger.warn(`No middlewares in the route [${config.path}]. Skipping...`, "middleware-service");
465
474
  return;
466
475
  }
467
- const configKey = config.path || `config_${this.insertionOrder}`;
476
+ const inferredName = config.middlewares
477
+ .map((fn) => (typeof fn === "function" ? fn.name : fn?.constructor?.name))
478
+ .filter((n) => n && n !== "anonymous" && n !== "")
479
+ .join(", ");
480
+ const configKey = config.path || inferredName || `custom_${this.insertionOrder}`;
468
481
  if (this.middlewareExists(configKey)) {
469
482
  this._logger.warn(`[${config.path}] route already exists. Skipping...`, "middleware-service");
470
483
  return;
@@ -1231,6 +1244,7 @@ class Middleware {
1231
1244
  origin: true,
1232
1245
  credentials: true,
1233
1246
  methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
1247
+ allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
1234
1248
  },
1235
1249
  rateLimit: { windowMs: 60000, max: 100 },
1236
1250
  },
@@ -1484,6 +1498,11 @@ class Middleware {
1484
1498
  const finalConfig = overrides
1485
1499
  ? this.mergeConfigs(config, overrides)
1486
1500
  : config;
1501
+ this._lastPreset = {
1502
+ name: preset,
1503
+ hasOverrides: !!overrides,
1504
+ config: finalConfig,
1505
+ };
1487
1506
  // Apply each category
1488
1507
  if (finalConfig.parse) {
1489
1508
  if (typeof finalConfig.parse === "boolean") {
@@ -1533,31 +1552,62 @@ class Middleware {
1533
1552
  const custom = Object.fromEntries(this.customPresets);
1534
1553
  return { ...builtIn, ...custom };
1535
1554
  }
1555
+ /**
1556
+ * Returns info about the last applied preset (name, whether overrides
1557
+ * were used, and the effective merged config). Used by the adapter to
1558
+ * forward preset metadata to Studio.
1559
+ */
1560
+ getLastAppliedPreset() {
1561
+ return this._lastPreset;
1562
+ }
1536
1563
  /**
1537
1564
  * Get built-in presets.
1565
+ *
1566
+ * Each preset is tuned for a specific workload:
1567
+ * - api: REST APIs (large payloads, rate-limited, strict CORS)
1568
+ * - web: traditional server-rendered apps (cookies, sessions, relaxed CORS)
1569
+ * - spa: single-page apps served with static fallback
1570
+ * - microservice: internal service-to-service (minimal surface, no security)
1571
+ * - graphql: single endpoint with large JSON payloads
1572
+ * - minimal: parsing only, no security or compression
1573
+ * - development: relaxed for local iteration, verbose logging
1574
+ * - production: hardened defaults for shipped deployments
1538
1575
  */
1539
1576
  getBuiltInPresets() {
1540
1577
  return {
1541
1578
  api: {
1542
- parse: true,
1579
+ parse: {
1580
+ json: { limit: "10mb" },
1581
+ urlencoded: { extended: true, limit: "10mb" },
1582
+ },
1543
1583
  logger: { implementation: "auto" },
1544
1584
  security: "api",
1545
- compress: true,
1585
+ compress: { level: 6 },
1546
1586
  },
1547
1587
  web: {
1548
- parse: { json: true, urlencoded: true, cookies: true },
1588
+ parse: {
1589
+ json: { limit: "5mb" },
1590
+ urlencoded: { extended: true, limit: "5mb" },
1591
+ cookies: true,
1592
+ },
1549
1593
  logger: { implementation: "auto" },
1550
1594
  security: "standard",
1551
1595
  compress: true,
1552
1596
  },
1553
1597
  spa: {
1554
- parse: { json: true, urlencoded: true },
1598
+ parse: {
1599
+ json: { limit: "5mb" },
1600
+ urlencoded: { extended: true, limit: "5mb" },
1601
+ },
1555
1602
  security: "standard",
1556
1603
  compress: true,
1557
1604
  },
1558
1605
  microservice: {
1559
- parse: { json: { limit: "1mb" } },
1560
- compress: true,
1606
+ parse: {
1607
+ json: { limit: "1mb" },
1608
+ urlencoded: { extended: false, limit: "1mb" },
1609
+ },
1610
+ compress: { level: 6 },
1561
1611
  },
1562
1612
  graphql: {
1563
1613
  parse: { json: { limit: "50mb" } },
@@ -1571,15 +1621,21 @@ class Middleware {
1571
1621
  parse: true,
1572
1622
  },
1573
1623
  development: {
1574
- parse: true,
1624
+ parse: {
1625
+ json: { limit: "50mb" },
1626
+ urlencoded: { extended: true, limit: "50mb" },
1627
+ },
1575
1628
  logger: { implementation: "morgan", options: { format: "dev" } },
1576
1629
  security: "relaxed",
1577
1630
  },
1578
1631
  production: {
1579
- parse: true,
1632
+ parse: {
1633
+ json: { limit: "10mb" },
1634
+ urlencoded: { extended: true, limit: "10mb" },
1635
+ },
1580
1636
  logger: { implementation: "auto", disableInTest: true },
1581
1637
  security: "strict",
1582
- compress: true,
1638
+ compress: { level: 6 },
1583
1639
  },
1584
1640
  };
1585
1641
  }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ /**
3
+ * Standalone (free-function) wrappers around the v4 middleware preset system.
4
+ *
5
+ * These exist alongside the `Middleware.definePreset` / `Middleware.applyPreset`
6
+ * instance methods so user code that doesn't have DI access to the `Middleware`
7
+ * provider — for example, declarative config files outside of the request
8
+ * lifecycle — can still register and apply presets.
9
+ *
10
+ * Usage:
11
+ * ```ts
12
+ * import { definePreset, applyPreset } from "@expressots/core";
13
+ *
14
+ * definePreset("my-api", {
15
+ * parse: { json: { limit: "1mb" } },
16
+ * security: { cors: { origin: "https://app.example.com" } },
17
+ * });
18
+ *
19
+ * // ...later, inside configureServices(), with the Middleware DI provider:
20
+ * applyPreset(this.services.middleware, "my-api");
21
+ * ```
22
+ *
23
+ * @public API
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.definePreset = definePreset;
27
+ exports.applyPreset = applyPreset;
28
+ exports.getStandalonePresetNames = getStandalonePresetNames;
29
+ exports.clearStandalonePresets = clearStandalonePresets;
30
+ /** Module-level registry of presets defined via the standalone helper. */
31
+ const standalonePresets = new Map();
32
+ /**
33
+ * Register a custom middleware preset under the given name.
34
+ *
35
+ * The preset is stored in a module-level registry so it can be referenced
36
+ * later by `applyPreset(middleware, name)`. Calling `definePreset` with an
37
+ * existing name overwrites the previous definition.
38
+ *
39
+ * @param name unique identifier for the preset
40
+ * @param config v4 middleware config object describing the preset
41
+ *
42
+ * @public API
43
+ */
44
+ function definePreset(name, config) {
45
+ standalonePresets.set(name, config);
46
+ }
47
+ /**
48
+ * Apply a previously defined preset (built-in or registered via
49
+ * `definePreset`) to the supplied `Middleware` instance.
50
+ *
51
+ * Resolution order:
52
+ * 1. Built-in v4 presets (`api`, `web`, `spa`, `microservice`, `graphql`,
53
+ * `minimal`, `development`, `production`) are matched by the
54
+ * `Middleware` instance itself.
55
+ * 2. Custom presets previously registered via `Middleware.definePreset`
56
+ * on the same instance.
57
+ * 3. Custom presets registered via the standalone `definePreset` here —
58
+ * these are forwarded to the instance on demand.
59
+ *
60
+ * @param middleware the active `Middleware` provider (typically resolved from
61
+ * the DI container inside `configureServices()`)
62
+ * @param name preset to apply
63
+ * @param overrides optional partial config that is merged on top of the
64
+ * preset before application
65
+ *
66
+ * @public API
67
+ */
68
+ function applyPreset(middleware, name, overrides) {
69
+ const standalone = standalonePresets.get(name);
70
+ if (standalone !== undefined) {
71
+ middleware.definePreset(name, standalone);
72
+ }
73
+ middleware.applyPreset(name, overrides);
74
+ }
75
+ /**
76
+ * Returns the names of every preset registered through the standalone
77
+ * `definePreset` helper. Built-in presets and per-instance custom presets
78
+ * are NOT included — those live on the `Middleware` instance.
79
+ *
80
+ * @public API
81
+ */
82
+ function getStandalonePresetNames() {
83
+ return Array.from(standalonePresets.keys());
84
+ }
85
+ /**
86
+ * Remove every preset registered through the standalone `definePreset`
87
+ * helper. Useful in tests; rarely needed at runtime.
88
+ *
89
+ * @public API
90
+ */
91
+ function clearStandalonePresets() {
92
+ standalonePresets.clear();
93
+ }
@@ -381,6 +381,28 @@ class InMemoryAdapter {
381
381
  where: { id: foreignKeyValue },
382
382
  });
383
383
  }
384
+ case "manyToMany": {
385
+ // Resolve through a join table. Convention: the `through` table holds
386
+ // link records with two foreign keys named `<sourceClass>Id` and
387
+ // `<targetClass>Id` (class names lowercased). For example, a
388
+ // Post <-> Tag relation through "post_tags" stores rows shaped like
389
+ // `{ postId, tagId }`.
390
+ if (!relation.through || !this.entityClass)
391
+ return [];
392
+ const sourceKey = `${this.entityClass.name.toLowerCase()}Id`;
393
+ const targetKey = `${relation.target().name.toLowerCase()}Id`;
394
+ const joinAdapter = this.database.table(relation.through);
395
+ const joinRecords = await joinAdapter.findMany({
396
+ where: { [sourceKey]: entity.id },
397
+ });
398
+ const targetIds = joinRecords
399
+ .map((record) => record[targetKey])
400
+ .filter((value) => typeof value === "string");
401
+ if (targetIds.length === 0)
402
+ return [];
403
+ const related = await Promise.all(targetIds.map((id) => relatedAdapter.findUnique({ where: { id } })));
404
+ return related.filter((item) => item !== null);
405
+ }
384
406
  default:
385
407
  return null;
386
408
  }
@@ -530,6 +552,7 @@ class InMemoryDatabase {
530
552
  entityClass,
531
553
  timestamps: this.options.timestamps,
532
554
  softDelete: this.options.softDelete,
555
+ maxRecordsPerTable: this.options.maxRecordsPerTable,
533
556
  }));
534
557
  }
535
558
  return this.tables.get(tableName);
@@ -5,6 +5,14 @@
5
5
  * A high-performance, Prisma-compatible in-memory database
6
6
  * for ExpressoTS applications.
7
7
  *
8
+ * Scope: this database is intended for development, testing, and
9
+ * prototyping. Data lives in process memory (with optional file
10
+ * snapshots) and does not provide the crash safety, concurrency, or
11
+ * multi-process guarantees of a real database engine. It implements the
12
+ * universal `IDataAdapter` contract so it can be swapped for a
13
+ * production adapter (Prisma, TypeORM, etc.) without rewriting
14
+ * repositories.
15
+ *
8
16
  * Features:
9
17
  * - Prisma-like query API
10
18
  * - Type-safe queries with TypeScript
@@ -54,7 +62,7 @@
54
62
  * @module db-in-memory
55
63
  */
56
64
  Object.defineProperty(exports, "__esModule", { value: true });
57
- exports.LegacyBaseRepository = exports.LegacyEntityAlreadyExistsError = exports.LegacyEntityNotFoundError = exports.InMemoryDataTable = exports.InMemoryDataProvider = exports.EntityAlreadyExistsError = exports.EntityNotFoundError = exports.UniqueConstraintError = exports.IdGenerator = exports.IndexManager = exports.MemoryStore = exports.QueryEngine = exports.DB_METADATA_KEYS = exports.SchemaRegistry = exports.ManyToMany = exports.BelongsTo = exports.HasOne = exports.HasMany = exports.Default = exports.Nullable = exports.Unique = exports.Index = exports.AutoGenerate = exports.PrimaryKey = exports.Entity = exports.InMemoryDatabase = exports.InMemoryAdapter = exports.BaseRepository = exports.InMemoryDBProvider = void 0;
65
+ exports.LegacyBaseRepository = exports.LegacyEntityAlreadyExistsError = exports.LegacyEntityNotFoundError = exports.InMemoryDataTable = exports.InMemoryDataProvider = exports.EntityValidationError = exports.MaxRecordsExceededError = exports.EntityAlreadyExistsError = exports.EntityNotFoundError = exports.UniqueConstraintError = exports.IdGenerator = exports.IndexManager = exports.MemoryStore = exports.QueryEngine = exports.DB_METADATA_KEYS = exports.SchemaRegistry = exports.ManyToMany = exports.BelongsTo = exports.HasOne = exports.HasMany = exports.Default = exports.Nullable = exports.Unique = exports.Index = exports.AutoGenerate = exports.PrimaryKey = exports.Entity = exports.InMemoryDatabase = exports.InMemoryAdapter = exports.BaseRepository = exports.InMemoryDBProvider = void 0;
58
66
  // ═══════════════════════════════════════════════════════════════════════════
59
67
  // MAIN PROVIDER
60
68
  // ═══════════════════════════════════════════════════════════════════════════
@@ -104,6 +112,8 @@ Object.defineProperty(exports, "IdGenerator", { enumerable: true, get: function
104
112
  Object.defineProperty(exports, "UniqueConstraintError", { enumerable: true, get: function () { return index_js_4.UniqueConstraintError; } });
105
113
  Object.defineProperty(exports, "EntityNotFoundError", { enumerable: true, get: function () { return index_js_4.EntityNotFoundError; } });
106
114
  Object.defineProperty(exports, "EntityAlreadyExistsError", { enumerable: true, get: function () { return index_js_4.EntityAlreadyExistsError; } });
115
+ Object.defineProperty(exports, "MaxRecordsExceededError", { enumerable: true, get: function () { return index_js_4.MaxRecordsExceededError; } });
116
+ Object.defineProperty(exports, "EntityValidationError", { enumerable: true, get: function () { return index_js_4.EntityValidationError; } });
107
117
  // Legacy provider (deprecated, use InMemoryDBProvider)
108
118
  var db_in_memory_provider_js_1 = require("./db-in-memory.provider.js");
109
119
  Object.defineProperty(exports, "InMemoryDataProvider", { enumerable: true, get: function () { return db_in_memory_provider_js_1.InMemoryDataProvider; } });
@@ -411,6 +411,33 @@ class QueryEngine {
411
411
  return result;
412
412
  }
413
413
  // ═══════════════════════════════════════════════════════════════════════════
414
+ // CURSOR
415
+ // ═══════════════════════════════════════════════════════════════════════════
416
+ /**
417
+ * Apply cursor-based pagination. The cursor identifies a record (by id or
418
+ * any other field combination) within the ordered result set; the returned
419
+ * slice starts at that record. Combine with `skip` (typically `skip: 1` to
420
+ * exclude the cursor itself) and `take`.
421
+ *
422
+ * Should be applied after `orderBy` and before `skip`/`take`.
423
+ *
424
+ * @param entities - Ordered entities to slice
425
+ * @param cursor - Unique cursor identifying the start record
426
+ * @returns Entities starting at the cursor (empty array if not found)
427
+ */
428
+ executeCursor(entities, cursor) {
429
+ if (!cursor)
430
+ return entities;
431
+ const keys = Object.keys(cursor).filter((key) => cursor[key] !== undefined);
432
+ if (keys.length === 0)
433
+ return entities;
434
+ const index = entities.findIndex((entity) => keys.every((key) => entity[key] ===
435
+ cursor[key]));
436
+ if (index === -1)
437
+ return [];
438
+ return entities.slice(index);
439
+ }
440
+ // ═══════════════════════════════════════════════════════════════════════════
414
441
  // DISTINCT
415
442
  // ═══════════════════════════════════════════════════════════════════════════
416
443
  /**
@@ -563,6 +590,7 @@ class QueryEngine {
563
590
  let entities = this.executeWhere(args.where);
564
591
  entities = this.executeDistinct(entities, args.distinct);
565
592
  entities = this.executeOrderBy(entities, args.orderBy);
593
+ entities = this.executeCursor(entities, args.cursor);
566
594
  entities = this.executePagination(entities, args.skip, args.take);
567
595
  if (args.select) {
568
596
  return this.executeSelect(entities, args.select);
@@ -388,6 +388,24 @@ class SchemaRegistry {
388
388
  static getRelations(target) {
389
389
  return Reflect.getMetadata(exports.DB_METADATA_KEYS.relation, target) || [];
390
390
  }
391
+ /**
392
+ * Get primary key field names for an entity.
393
+ * @param target - Entity class
394
+ * @returns Array of primary key field names
395
+ * @public API
396
+ */
397
+ static getPrimaryKeys(target) {
398
+ return Reflect.getMetadata(exports.DB_METADATA_KEYS.primaryKey, target) || [];
399
+ }
400
+ /**
401
+ * Get nullable field names for an entity.
402
+ * @param target - Entity class
403
+ * @returns Array of nullable field names
404
+ * @public API
405
+ */
406
+ static getNullableFields(target) {
407
+ return Reflect.getMetadata(exports.DB_METADATA_KEYS.nullable, target) || [];
408
+ }
391
409
  /**
392
410
  * Clear all registered entities (useful for testing).
393
411
  * @public API
@@ -4,7 +4,7 @@
4
4
  * @module db-in-memory/storage
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.EntityAlreadyExistsError = exports.EntityNotFoundError = exports.UniqueConstraintError = exports.IdGenerator = exports.IndexManager = exports.MemoryStore = void 0;
7
+ exports.EntityValidationError = exports.MaxRecordsExceededError = exports.EntityAlreadyExistsError = exports.EntityNotFoundError = exports.UniqueConstraintError = exports.IdGenerator = exports.IndexManager = exports.MemoryStore = void 0;
8
8
  var memory_store_js_1 = require("./memory-store.js");
9
9
  Object.defineProperty(exports, "MemoryStore", { enumerable: true, get: function () { return memory_store_js_1.MemoryStore; } });
10
10
  Object.defineProperty(exports, "IndexManager", { enumerable: true, get: function () { return memory_store_js_1.IndexManager; } });
@@ -12,3 +12,5 @@ Object.defineProperty(exports, "IdGenerator", { enumerable: true, get: function
12
12
  Object.defineProperty(exports, "UniqueConstraintError", { enumerable: true, get: function () { return memory_store_js_1.UniqueConstraintError; } });
13
13
  Object.defineProperty(exports, "EntityNotFoundError", { enumerable: true, get: function () { return memory_store_js_1.EntityNotFoundError; } });
14
14
  Object.defineProperty(exports, "EntityAlreadyExistsError", { enumerable: true, get: function () { return memory_store_js_1.EntityAlreadyExistsError; } });
15
+ Object.defineProperty(exports, "MaxRecordsExceededError", { enumerable: true, get: function () { return memory_store_js_1.MaxRecordsExceededError; } });
16
+ Object.defineProperty(exports, "EntityValidationError", { enumerable: true, get: function () { return memory_store_js_1.EntityValidationError; } });
@@ -7,7 +7,7 @@
7
7
  * @module db-in-memory/storage
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.MemoryStore = exports.IdGenerator = exports.EntityAlreadyExistsError = exports.EntityNotFoundError = exports.UniqueConstraintError = exports.IndexManager = void 0;
10
+ exports.MemoryStore = exports.IdGenerator = exports.EntityAlreadyExistsError = exports.EntityValidationError = exports.MaxRecordsExceededError = exports.EntityNotFoundError = exports.UniqueConstraintError = exports.IndexManager = void 0;
11
11
  const node_crypto_1 = require("node:crypto");
12
12
  const decorators_js_1 = require("../schema/decorators.js");
13
13
  // ═══════════════════════════════════════════════════════════════════════════
@@ -194,6 +194,33 @@ class EntityNotFoundError extends Error {
194
194
  }
195
195
  }
196
196
  exports.EntityNotFoundError = EntityNotFoundError;
197
+ /**
198
+ * Error thrown when a table reaches its configured record limit.
199
+ * @public API
200
+ */
201
+ class MaxRecordsExceededError extends Error {
202
+ constructor(tableName, limit) {
203
+ super(`Table '${tableName}' has reached its maximum of ${limit} records`);
204
+ this.name = "MaxRecordsExceededError";
205
+ this.tableName = tableName;
206
+ this.limit = limit;
207
+ }
208
+ }
209
+ exports.MaxRecordsExceededError = MaxRecordsExceededError;
210
+ /**
211
+ * Error thrown when entity validation fails (when `@Entity({ validate: true })`).
212
+ * @public API
213
+ */
214
+ class EntityValidationError extends Error {
215
+ constructor(tableName, field, message) {
216
+ super(message ??
217
+ `Validation failed on '${tableName}': field '${field}' is required`);
218
+ this.name = "EntityValidationError";
219
+ this.tableName = tableName;
220
+ this.field = field;
221
+ }
222
+ }
223
+ exports.EntityValidationError = EntityValidationError;
197
224
  /**
198
225
  * Error thrown when an entity already exists.
199
226
  * @public API
@@ -288,10 +315,15 @@ class MemoryStore {
288
315
  this.autoGenerateFields = {};
289
316
  /** Default values */
290
317
  this.defaultValues = {};
318
+ /** Enable runtime validation (from @Entity({ validate: true })) */
319
+ this.validate = false;
320
+ /** Fields that must be present and non-null when validation is enabled */
321
+ this.requiredFields = [];
291
322
  this.tableName = tableName;
292
323
  this.entityClass = options.entityClass;
293
324
  this.timestamps = options.timestamps ?? true;
294
325
  this.softDelete = options.softDelete ?? false;
326
+ this.maxRecords = options.maxRecordsPerTable ?? 0;
295
327
  // Load schema metadata if entity class is provided
296
328
  if (this.entityClass) {
297
329
  this.loadSchemaMetadata();
@@ -331,6 +363,37 @@ class MemoryStore {
331
363
  if (entityMeta) {
332
364
  this.timestamps = entityMeta.timestamps;
333
365
  this.softDelete = entityMeta.softDelete;
366
+ this.validate = entityMeta.validate;
367
+ }
368
+ // When validation is enabled, treat primary-key and unique fields as
369
+ // required (must be present and non-null) unless explicitly @Nullable.
370
+ if (this.validate) {
371
+ const nullable = new Set(decorators_js_1.SchemaRegistry.getNullableFields(this.entityClass).map(String));
372
+ const required = new Set();
373
+ for (const field of decorators_js_1.SchemaRegistry.getPrimaryKeys(this.entityClass)) {
374
+ required.add(String(field));
375
+ }
376
+ for (const field of decorators_js_1.SchemaRegistry.getUniqueFields(this.entityClass)) {
377
+ required.add(String(field));
378
+ }
379
+ this.requiredFields = Array.from(required).filter((field) => !nullable.has(field));
380
+ }
381
+ }
382
+ /**
383
+ * Validate an entity against the schema (only when `validate` is enabled).
384
+ * Ensures required fields (primary key + unique, excluding @Nullable) are
385
+ * present and non-null.
386
+ * @private
387
+ * @throws ValidationError when a required field is missing or null
388
+ */
389
+ validateEntity(entity) {
390
+ if (!this.validate)
391
+ return;
392
+ for (const field of this.requiredFields) {
393
+ const value = entity[field];
394
+ if (value === undefined || value === null) {
395
+ throw new EntityValidationError(this.tableName, field);
396
+ }
334
397
  }
335
398
  }
336
399
  /**
@@ -380,6 +443,12 @@ class MemoryStore {
380
443
  if (this.data.has(prepared.id)) {
381
444
  throw new EntityAlreadyExistsError(this.tableName, prepared.id);
382
445
  }
446
+ // Enforce per-table record limit (0 = unlimited)
447
+ if (this.maxRecords > 0 && this.data.size >= this.maxRecords) {
448
+ throw new MaxRecordsExceededError(this.tableName, this.maxRecords);
449
+ }
450
+ // Validate required fields (no-op unless @Entity({ validate: true }))
451
+ this.validateEntity(prepared);
383
452
  // Index first (will throw if unique constraint violated)
384
453
  this.indexManager.indexEntity(prepared);
385
454
  // Then store
@@ -426,6 +495,8 @@ class MemoryStore {
426
495
  if (this.timestamps) {
427
496
  updated.updatedAt = new Date();
428
497
  }
498
+ // Validate required fields (no-op unless @Entity({ validate: true }))
499
+ this.validateEntity(updated);
429
500
  // Update indexes
430
501
  this.indexManager.updateIndex(existing, updated);
431
502
  // Store updated entity