@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.
- package/LICENSE.md +21 -21
- package/README.md +66 -66
- package/lib/CHANGELOG.md +774 -774
- package/lib/README.md +66 -66
- package/lib/cjs/application/application-factory.js +6 -0
- package/lib/cjs/application/bootstrap.js +117 -213
- package/lib/cjs/config/define-config.js +1 -1
- package/lib/cjs/config/env-field-builders.js +47 -0
- package/lib/cjs/config/index.js +7 -1
- package/lib/cjs/framework-version.js +10 -0
- package/lib/cjs/lazy-loading/index.js +5 -1
- package/lib/cjs/lazy-loading/lazy-module-helpers.js +49 -0
- package/lib/cjs/middleware/index.js +8 -9
- package/lib/cjs/middleware/middleware-service.js +68 -12
- package/lib/cjs/middleware/presets-standalone.js +93 -0
- package/lib/cjs/provider/db-in-memory/adapter/in-memory.adapter.js +23 -0
- package/lib/cjs/provider/db-in-memory/index.js +11 -1
- package/lib/cjs/provider/db-in-memory/query/query-engine.js +28 -0
- package/lib/cjs/provider/db-in-memory/schema/decorators.js +18 -0
- package/lib/cjs/provider/db-in-memory/storage/index.js +3 -1
- package/lib/cjs/provider/db-in-memory/storage/memory-store.js +72 -1
- package/lib/cjs/provider/logger/logger.banner.js +40 -31
- package/lib/cjs/provider/logger/logger.config.js +11 -1
- package/lib/cjs/provider/logger/logger.formatter.js +22 -1
- package/lib/cjs/provider/logger/logger.provider.js +59 -9
- package/lib/cjs/provider/logger/transports/console.transport.js +69 -6
- package/lib/cjs/provider/logger/transports/file.transport.js +27 -18
- package/lib/cjs/provider/logger/utils/log-levels.js +6 -5
- package/lib/cjs/provider/validation/adapters/index.js +12 -5
- package/lib/cjs/provider/validation/adapters/yup.adapter.js +118 -0
- package/lib/cjs/provider/validation/adapters/zod.adapter.js +137 -0
- package/lib/cjs/provider/validation/index.js +5 -1
- package/lib/cjs/render/adapters/react-adapter.js +14 -14
- package/lib/cjs/render/features/type-generator.js +30 -30
- package/lib/cjs/render/features/view-debugger.js +75 -55
- package/lib/cjs/testing/fluent-request.js +7 -0
- package/lib/cjs/testing/snapshot-request.js +2 -0
- package/lib/cjs/types/application/application-factory.d.ts +6 -0
- package/lib/cjs/types/application/bootstrap.d.ts +196 -24
- package/lib/cjs/types/config/config.interfaces.d.ts +7 -1
- package/lib/cjs/types/config/env-field-builders.d.ts +39 -0
- package/lib/cjs/types/config/index.d.ts +1 -1
- package/lib/cjs/types/framework-version.d.ts +7 -0
- package/lib/cjs/types/lazy-loading/index.d.ts +1 -0
- package/lib/cjs/types/lazy-loading/lazy-module-helpers.d.ts +42 -0
- package/lib/cjs/types/middleware/index.d.ts +1 -1
- package/lib/cjs/types/middleware/middleware-service.d.ts +21 -0
- package/lib/cjs/types/middleware/presets-standalone.d.ts +75 -0
- package/lib/cjs/types/provider/db-in-memory/adapter/in-memory.adapter.d.ts +2 -0
- package/lib/cjs/types/provider/db-in-memory/index.d.ts +9 -1
- package/lib/cjs/types/provider/db-in-memory/query/query-engine.d.ts +14 -1
- package/lib/cjs/types/provider/db-in-memory/schema/decorators.d.ts +14 -0
- package/lib/cjs/types/provider/db-in-memory/storage/index.d.ts +1 -1
- package/lib/cjs/types/provider/db-in-memory/storage/memory-store.d.ts +34 -0
- package/lib/cjs/types/provider/logger/logger.banner.d.ts +1 -1
- package/lib/cjs/types/provider/logger/logger.config.d.ts +7 -0
- package/lib/cjs/types/provider/logger/logger.formatter.d.ts +10 -0
- package/lib/cjs/types/provider/logger/logger.provider.d.ts +32 -1
- package/lib/cjs/types/provider/logger/transports/console.transport.d.ts +7 -0
- package/lib/cjs/types/provider/logger/utils/log-levels.d.ts +3 -3
- package/lib/cjs/types/provider/validation/adapters/index.d.ts +7 -4
- package/lib/cjs/types/provider/validation/adapters/yup.adapter.d.ts +65 -0
- package/lib/cjs/types/provider/validation/adapters/zod.adapter.d.ts +84 -0
- package/lib/cjs/types/provider/validation/index.d.ts +1 -1
- package/lib/cjs/types/render/features/view-debugger.d.ts +10 -0
- package/lib/cjs/types/testing/testing.interfaces.d.ts +31 -6
- package/lib/esm/application/application-factory.js +6 -0
- package/lib/esm/application/bootstrap.js +117 -213
- package/lib/esm/config/define-config.js +1 -1
- package/lib/esm/config/env-field-builders.js +48 -0
- package/lib/esm/config/index.js +6 -1
- package/lib/esm/framework-version.js +7 -0
- package/lib/esm/lazy-loading/index.js +2 -0
- package/lib/esm/lazy-loading/lazy-module-helpers.js +45 -0
- package/lib/esm/middleware/index.js +3 -2
- package/lib/esm/middleware/middleware-service.js +68 -12
- package/lib/esm/middleware/presets-standalone.js +87 -0
- package/lib/esm/provider/db-in-memory/adapter/in-memory.adapter.js +23 -0
- package/lib/esm/provider/db-in-memory/index.js +9 -1
- package/lib/esm/provider/db-in-memory/query/query-engine.js +28 -0
- package/lib/esm/provider/db-in-memory/schema/decorators.js +18 -0
- package/lib/esm/provider/db-in-memory/storage/index.js +1 -1
- package/lib/esm/provider/db-in-memory/storage/memory-store.js +75 -0
- package/lib/esm/provider/logger/logger.banner.js +40 -31
- package/lib/esm/provider/logger/logger.config.js +12 -2
- package/lib/esm/provider/logger/logger.formatter.js +22 -1
- package/lib/esm/provider/logger/logger.provider.js +61 -10
- package/lib/esm/provider/logger/transports/console.transport.js +69 -6
- package/lib/esm/provider/logger/transports/file.transport.js +27 -18
- package/lib/esm/provider/logger/utils/log-levels.js +6 -5
- package/lib/esm/provider/validation/adapters/index.js +7 -4
- package/lib/esm/provider/validation/adapters/yup.adapter.js +111 -0
- package/lib/esm/provider/validation/adapters/zod.adapter.js +130 -0
- package/lib/esm/provider/validation/index.js +1 -1
- package/lib/esm/render/adapters/react-adapter.js +14 -14
- package/lib/esm/render/features/type-generator.js +30 -30
- package/lib/esm/render/features/view-debugger.js +75 -55
- package/lib/esm/testing/fluent-request.js +7 -0
- package/lib/esm/testing/snapshot-request.js +2 -0
- package/lib/esm/types/application/application-factory.d.ts +6 -0
- package/lib/esm/types/application/bootstrap.d.ts +196 -24
- package/lib/esm/types/config/config.interfaces.d.ts +7 -1
- package/lib/esm/types/config/env-field-builders.d.ts +39 -0
- package/lib/esm/types/config/index.d.ts +1 -1
- package/lib/esm/types/framework-version.d.ts +7 -0
- package/lib/esm/types/lazy-loading/index.d.ts +1 -0
- package/lib/esm/types/lazy-loading/lazy-module-helpers.d.ts +42 -0
- package/lib/esm/types/middleware/index.d.ts +1 -1
- package/lib/esm/types/middleware/middleware-service.d.ts +21 -0
- package/lib/esm/types/middleware/presets-standalone.d.ts +75 -0
- package/lib/esm/types/provider/db-in-memory/adapter/in-memory.adapter.d.ts +2 -0
- package/lib/esm/types/provider/db-in-memory/index.d.ts +9 -1
- package/lib/esm/types/provider/db-in-memory/query/query-engine.d.ts +14 -1
- package/lib/esm/types/provider/db-in-memory/schema/decorators.d.ts +14 -0
- package/lib/esm/types/provider/db-in-memory/storage/index.d.ts +1 -1
- package/lib/esm/types/provider/db-in-memory/storage/memory-store.d.ts +34 -0
- package/lib/esm/types/provider/logger/logger.banner.d.ts +1 -1
- package/lib/esm/types/provider/logger/logger.config.d.ts +7 -0
- package/lib/esm/types/provider/logger/logger.formatter.d.ts +10 -0
- package/lib/esm/types/provider/logger/logger.provider.d.ts +32 -1
- package/lib/esm/types/provider/logger/transports/console.transport.d.ts +7 -0
- package/lib/esm/types/provider/logger/utils/log-levels.d.ts +3 -3
- package/lib/esm/types/provider/validation/adapters/index.d.ts +7 -4
- package/lib/esm/types/provider/validation/adapters/yup.adapter.d.ts +65 -0
- package/lib/esm/types/provider/validation/adapters/zod.adapter.d.ts +84 -0
- package/lib/esm/types/provider/validation/index.d.ts +1 -1
- package/lib/esm/types/render/features/view-debugger.d.ts +10 -0
- package/lib/esm/types/testing/testing.interfaces.d.ts +31 -6
- package/lib/package.json +23 -8
- package/package.json +23 -8
- package/lib/cjs/middleware/middleware-presets.js +0 -294
- package/lib/cjs/types/middleware/middleware-presets.d.ts +0 -90
- package/lib/esm/middleware/middleware-presets.js +0 -286
- 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.
|
|
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 &&
|
|
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
|
-
|
|
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
|
|
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:
|
|
1579
|
+
parse: {
|
|
1580
|
+
json: { limit: "10mb" },
|
|
1581
|
+
urlencoded: { extended: true, limit: "10mb" },
|
|
1582
|
+
},
|
|
1543
1583
|
logger: { implementation: "auto" },
|
|
1544
1584
|
security: "api",
|
|
1545
|
-
compress:
|
|
1585
|
+
compress: { level: 6 },
|
|
1546
1586
|
},
|
|
1547
1587
|
web: {
|
|
1548
|
-
parse: {
|
|
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: {
|
|
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: {
|
|
1560
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|