@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
@@ -4,35 +4,75 @@ import { Logger } from "../provider/logger/logger.provider.js";
4
4
  import fs from "fs";
5
5
  import path from "path";
6
6
  /**
7
- * Synchronously load .env files BEFORE defineConfig() resolves.
7
+ * Synchronously load `.env` files into `process.env`.
8
8
  *
9
- * Call this at the top of your config file to ensure environment variables
10
- * are available when defineConfig() runs.
9
+ * Call this **before** `bootstrap()` or `defineConfig()` so that every
10
+ * environment variable is available when the rest of the application
11
+ * resolves configuration, binds services, or reads `Env.*` builders.
11
12
  *
12
- * @param options - Environment file configuration
13
+ * ### How it works
13
14
  *
14
- * @example
15
+ * 1. Reads `process.env.NODE_ENV` (defaults to `"development"`).
16
+ * 2. Loads files in priority order (last file wins on conflicts):
17
+ * `.env` → `.env.local` → `.env.{env}.local` → `.env.{env}`
18
+ * 3. If `options.files` maps the current environment to a custom name
19
+ * (e.g. `{ production: ".env.prod" }`), that name replaces `.env.{env}`.
20
+ * 4. Missing files are silently skipped.
21
+ * 5. Sets `process.env._EXPRESSOTS_ENV_LOADED = "true"` so subsequent
22
+ * calls are no-ops (unless `force: true`).
23
+ *
24
+ * ### Important
25
+ *
26
+ * `NODE_ENV` must already be set in the **shell** (or inherited from
27
+ * the process) before `loadEnvSync()` runs. The function reads
28
+ * `process.env.NODE_ENV` to decide which file to load; it does **not**
29
+ * derive the environment from file contents.
30
+ *
31
+ * @param options - See {@link LoadEnvSyncOptions} for all fields.
32
+ *
33
+ * @example Zero-config (recommended for most apps)
15
34
  * ```typescript
16
- * // config.ts
17
- * import { defineConfig, Env, loadEnvSync } from "@expressots/core";
35
+ * // src/main.ts
36
+ * import { bootstrap, loadEnvSync } from "@expressots/core";
37
+ * import { App } from "./app";
38
+ *
39
+ * loadEnvSync(); // .env + .env.development (or .env.production, etc.)
40
+ * void bootstrap(App);
41
+ * ```
18
42
  *
19
- * // Load .env files first
43
+ * @example Custom file mapping
44
+ * ```typescript
20
45
  * loadEnvSync({
21
46
  * files: {
22
- * development: ".env.dev",
23
- * production: ".env.prod",
47
+ * development: ".env",
48
+ * production: ".env.prod",
49
+ * staging: ".env.staging",
24
50
  * },
25
51
  * });
52
+ * // With NODE_ENV=production -> .env + .env.prod
53
+ * // With NODE_ENV=development -> .env (mapped to itself, so loaded once)
54
+ * ```
55
+ *
56
+ * @example With defineConfig()
57
+ * ```typescript
58
+ * // src/config.ts
59
+ * import { defineConfig, Env, loadEnvSync } from "@expressots/core";
60
+ *
61
+ * loadEnvSync({ files: { production: ".env.prod" } });
26
62
  *
27
- * // Now defineConfig() will read from loaded .env files
28
63
  * export const appConfig = defineConfig({
29
- * server: {
30
- * port: Env.port("PORT", { default: 3000 }),
31
- * },
64
+ * server: { port: Env.port("PORT", { default: 3000 }) },
32
65
  * });
33
66
  * ```
34
67
  *
35
- * @public API
68
+ * @example Force reload (hot-module replacement)
69
+ * ```typescript
70
+ * loadEnvSync({ force: true });
71
+ * ```
72
+ *
73
+ * @see {@link LoadEnvSyncOptions} for option details
74
+ * @see {@link BootstrapOptions.envFileConfig} for the bootstrap()-level alternative
75
+ * @public
36
76
  */
37
77
  export function loadEnvSync(options) {
38
78
  // Skip if already loaded (unless force reload)
@@ -95,26 +135,26 @@ const CI_PLATFORM_MAP = new Map([
95
135
  const PLATFORM_HINTS = new Map([
96
136
  [
97
137
  "GitHub Actions",
98
- (missingVars) => `
99
- 🔧 GitHub Actions Setup:
100
- - Go to: Settings → Secrets and variables → Actions
101
- - Add repository secrets for: ${missingVars}
138
+ (missingVars) => `
139
+ 🔧 GitHub Actions Setup:
140
+ - Go to: Settings → Secrets and variables → Actions
141
+ - Add repository secrets for: ${missingVars}
102
142
  - Use: \${{ secrets.VARIABLE_NAME }} in workflow files`,
103
143
  ],
104
144
  [
105
145
  "GitLab CI",
106
- (missingVars) => `
107
- 🔧 GitLab CI Setup:
108
- - Go to: Settings → CI/CD → Variables
109
- - Add CI/CD variables for: ${missingVars}
146
+ (missingVars) => `
147
+ 🔧 GitLab CI Setup:
148
+ - Go to: Settings → CI/CD → Variables
149
+ - Add CI/CD variables for: ${missingVars}
110
150
  - Use: $VARIABLE_NAME in .gitlab-ci.yml`,
111
151
  ],
112
152
  [
113
153
  "Jenkins",
114
- (missingVars) => `
115
- 🔧 Jenkins Setup:
116
- - Configure: Manage Jenkins → Credentials
117
- - Add credentials for: ${missingVars}
154
+ (missingVars) => `
155
+ 🔧 Jenkins Setup:
156
+ - Configure: Manage Jenkins → Credentials
157
+ - Add credentials for: ${missingVars}
118
158
  - Use: env.VARIABLE_NAME in pipeline`,
119
159
  ],
120
160
  ]);
@@ -179,14 +219,14 @@ function getPlatformHint(platform, missing) {
179
219
  class EnvFileNotFoundError extends Error {
180
220
  constructor(fileName, environment) {
181
221
  const template = getEnvTemplate(environment);
182
- super(`
183
- ❌ Missing required environment file: ${fileName}
184
-
185
- 💡 Create ${fileName} with:
186
- ${template}
187
-
188
- 📖 Docs: https://expresso-ts.com/docs/env
189
- 🔍 Check existing files: ls -la .env*
222
+ super(`
223
+ ❌ Missing required environment file: ${fileName}
224
+
225
+ 💡 Create ${fileName} with:
226
+ ${template}
227
+
228
+ 📖 Docs: https://expresso-ts.com/docs/env
229
+ 🔍 Check existing files: ls -la .env*
190
230
  `.trim());
191
231
  this.name = "EnvFileNotFoundError";
192
232
  }
@@ -199,20 +239,20 @@ class CIEnvValidationError extends Error {
199
239
  constructor(missing, environment) {
200
240
  const ciPlatform = detectCIPlatform();
201
241
  const platformHint = getPlatformHint(ciPlatform, missing);
202
- super(`
203
- ❌ CI/CD Environment Validation Failed
204
-
205
- Missing required environment variables in ${environment}:
206
- ${missing.map((key) => ` • ${key}`).join("\n")}
207
-
208
- ${platformHint}
209
-
210
- 💡 Action Required:
211
- 1. Add missing variables to your CI/CD platform secrets
212
- 2. Ensure variables are available in ${environment} environment
213
- 3. Check variable names match exactly (case-sensitive)
214
-
215
- 📖 Docs: https://expresso-ts.com/docs/ci-cd
242
+ super(`
243
+ ❌ CI/CD Environment Validation Failed
244
+
245
+ Missing required environment variables in ${environment}:
246
+ ${missing.map((key) => ` • ${key}`).join("\n")}
247
+
248
+ ${platformHint}
249
+
250
+ 💡 Action Required:
251
+ 1. Add missing variables to your CI/CD platform secrets
252
+ 2. Ensure variables are available in ${environment} environment
253
+ 3. Check variable names match exactly (case-sensitive)
254
+
255
+ 📖 Docs: https://expresso-ts.com/docs/ci-cd
216
256
  `.trim());
217
257
  this.name = "CIEnvValidationError";
218
258
  }
@@ -223,16 +263,16 @@ ${platformHint}
223
263
  */
224
264
  class EnvValidationError extends Error {
225
265
  constructor(missing, fileName) {
226
- super(`
227
- ❌ Environment validation failed
228
-
229
- Missing values in ${fileName}:
230
- ${missing.map((key) => ` • ${key} (required but empty)`).join("\n")}
231
-
232
- 💡 Add values to ${fileName}:
233
- ${missing.map((key) => ` ${key}=your-value-here`).join("\n")}
234
-
235
- 📖 Docs: https://expresso-ts.com/docs/env
266
+ super(`
267
+ ❌ Environment validation failed
268
+
269
+ Missing values in ${fileName}:
270
+ ${missing.map((key) => ` • ${key} (required but empty)`).join("\n")}
271
+
272
+ 💡 Add values to ${fileName}:
273
+ ${missing.map((key) => ` ${key}=your-value-here`).join("\n")}
274
+
275
+ 📖 Docs: https://expresso-ts.com/docs/env
236
276
  `.trim());
237
277
  this.name = "EnvValidationError";
238
278
  }
@@ -244,8 +284,8 @@ ${missing.map((key) => ` ${key}=your-value-here`).join("\n")}
244
284
  * @private
245
285
  */
246
286
  function getEnvTemplate(environment) {
247
- return `PORT=3000
248
- NODE_ENV=${environment}
287
+ return `PORT=3000
288
+ NODE_ENV=${environment}
249
289
  # Add your environment variables here`;
250
290
  }
251
291
  /**
@@ -410,11 +450,11 @@ async function loadAndValidateEnvironment(currentEnvironment, envFileConfig) {
410
450
  // 🎯 STEP 2: Determine and load the file for the current environment
411
451
  // Only ONE file is loaded at runtime - the file for the current environment
412
452
  const envFileName = envFileConfig.files?.[currentEnvironment] ?? `.env.${currentEnvironment}`;
413
- // Load optional files silently
453
+ // Load optional files silently (override: true so later files win)
414
454
  const optionalFiles = [".env", ".env.local", `${envFileName}.local`];
415
455
  for (const file of optionalFiles) {
416
456
  try {
417
- config({ path: file });
457
+ config({ path: file, override: true });
418
458
  result.loaded.push(file);
419
459
  }
420
460
  catch {
@@ -457,8 +497,8 @@ async function loadAndValidateEnvironment(currentEnvironment, envFileConfig) {
457
497
  }
458
498
  }
459
499
  else {
460
- // Load the file
461
- config({ path: envFileName });
500
+ // Load the environment-specific file (highest priority, overrides all previous)
501
+ config({ path: envFileName, override: true });
462
502
  result.loaded.push(envFileName);
463
503
  }
464
504
  // Validate variables have values
@@ -522,151 +562,6 @@ async function readPackageJson() {
522
562
  return _packageCache;
523
563
  }
524
564
  }
525
- /**
526
- * Bootstrap the ExpressoTS application with zero configuration.
527
- *
528
- * @layer public
529
- * @audience application-developers
530
- * @concept bootstrap
531
- * @difficulty beginner
532
- *
533
- * @summary Quick Start
534
- * The simplest way to start your application:
535
- * ```typescript
536
- * await bootstrap(App);
537
- * ```
538
- *
539
- * This function orchestrates 8 critical startup phases:
540
- * 1. Environment detection (CI/CD vs local)
541
- * 2. Smart .env loading with opt-in behavior
542
- * 3. Port determination (priority chain)
543
- * 4. Package.json metadata extraction
544
- * 5. DI container initialization via AppFactory
545
- * 6. Environment injection into app instance
546
- * 7. API version detection from decorators
547
- * 8. Server startup with graceful shutdown
548
- *
549
- * @param AppClass - Application class extending AppExpress
550
- * @param options - Optional bootstrap configuration
551
- * @returns Promise resolving to IWebServerPublic instance
552
- *
553
- * @example
554
- * ```typescript
555
- * // Simplest usage - zero config (no .env file loading)
556
- * await bootstrap(App);
557
- *
558
- * // With overrides (still no .env file loading)
559
- * await bootstrap(App, {
560
- * port: 4000,
561
- * appName: "My API",
562
- * appVersion: "2.0.0"
563
- * });
564
- *
565
- * // Opt-in to .env file loading and auto-creation
566
- * await bootstrap(App, {
567
- * currentEnvironment: "development",
568
- * envFileConfig: {
569
- * files: {
570
- * development: ".env.dev",
571
- * production: ".env.prod"
572
- * },
573
- * required: ["DATABASE_URL", "API_KEY"],
574
- * autoCreateTemplate: true, // Explicitly enable file creation
575
- * validateValues: true
576
- * }
577
- * });
578
- *
579
- * // Auto-assign port (useful for testing)
580
- * await bootstrap(App, { port: 0 });
581
- * ```
582
- *
583
- * @layer internal
584
- * @audience framework-developers
585
- *
586
- * **Internal Architecture**
587
- *
588
- * Bootstrap orchestrates 8 critical steps:
589
- * 1. Environment detection (CI/CD vs local)
590
- * 2. Smart .env loading with opt-in behavior
591
- * 3. Port determination (priority chain)
592
- * 4. Package.json metadata extraction
593
- * 5. DI container initialization via AppFactory
594
- * 6. Environment injection into app instance
595
- * 7. API version detection from decorators
596
- * 8. Server startup with graceful shutdown
597
- *
598
- * **Design Decisions**
599
- * - Opt-in .env loading prevents breaking changes for containerized deployments
600
- * - Port 0 support enables parallel testing without conflicts
601
- * - Early validation fails fast with actionable error messages
602
- * - CI/CD auto-detection provides zero-config for containerized environments
603
- *
604
- * **Performance Characteristics**
605
- * - Startup time: ~8-25ms typical (optimized)
606
- * - Environment loading: ~2-5ms (file I/O)
607
- * - Package.json read: ~1-2ms first call, cached thereafter
608
- * - CI detection: cached after first call
609
- * - App instantiation: ~5-10ms (DI container setup)
610
- * - Logger instances: lazy initialization (only created when needed)
611
- *
612
- * @see {@link loadAndValidateEnvironment} for environment loading logic
613
- * @see {@link AppFactory.create} for DI container initialization
614
- * @see {@link determinePort} for port resolution logic
615
- *
616
- * @layer advanced
617
- * @audience power-users
618
- *
619
- * **Advanced Patterns**
620
- *
621
- * Multi-environment setup with validation:
622
- * ```typescript
623
- * await bootstrap(App, {
624
- * currentEnvironment: process.env.NODE_ENV || "development",
625
- * envFileConfig: {
626
- * files: {
627
- * development: ".env.dev",
628
- * staging: ".env.staging",
629
- * production: ".env.prod"
630
- * },
631
- * required: ["DATABASE_URL", "JWT_SECRET"],
632
- * autoCreateTemplate: true,
633
- * validateValues: process.env.NODE_ENV === "production"
634
- * }
635
- * });
636
- * ```
637
- *
638
- * Containerized deployment (Docker/K8s):
639
- * ```typescript
640
- * await bootstrap(App, {
641
- * envFileConfig: {
642
- * skipFileLoading: true, // Use process.env only
643
- * required: ["DATABASE_URL", "REDIS_URL"]
644
- * }
645
- * });
646
- * ```
647
- *
648
- * Testing with dynamic ports:
649
- * ```typescript
650
- * const server = await bootstrap(App, { port: 0 });
651
- * const actualPort = server.port; // Use in tests
652
- * ```
653
- *
654
- * @troubleshooting
655
- * **Common Issues**
656
- * - ❌ PORT not detected → Use options.port or envFileConfig
657
- * - ❌ CI validation fails → Check platform secrets configuration
658
- * - ❌ Template not created → Set autoCreateTemplate: true explicitly
659
- * - ❌ Port already in use → Use port: 0 for testing
660
- *
661
- * @performance
662
- * - Async initialization: ~5-15ms (typical)
663
- * - Package.json read: ~2ms first call, cached thereafter
664
- * - CI detection: cached after first call
665
- * - Port binding: varies by OS
666
- * - Total startup: ~8-25ms (optimized with caching)
667
- *
668
- * @public API
669
- */
670
565
  /**
671
566
  * Type guard to check if argument is BootstrapConfig.
672
567
  * Detects config objects by checking for the required structure.
@@ -707,9 +602,7 @@ function transformConfigToOptions(config) {
707
602
  envFileConfig: config.bootstrap?.envFileConfig,
708
603
  };
709
604
  }
710
- /**
711
- * Implementation.
712
- */
605
+ /** @internal Implementation overload. */
713
606
  export async function bootstrap(AppClass, optionsOrConfig) {
714
607
  let logger;
715
608
  // Note: Path resolution is auto-initialized as a side effect when
@@ -769,6 +662,17 @@ export async function bootstrap(AppClass, optionsOrConfig) {
769
662
  // STEP 5: Create app instance
770
663
  // App's globalConfiguration() will run in constructor
771
664
  // initEnvironment() can skip if .env already loaded
665
+ //
666
+ // Activate log buffering BEFORE constructing the app so any logs the
667
+ // application emits during construction (e.g. inside `globalConfiguration()`
668
+ // or `configureServices()`) are captured and replayed in the right order
669
+ // after the startup banner. AppClass extends AppExpress for the standard
670
+ // adapter, so the static method is inherited; we duck-type to avoid a
671
+ // hard dependency on adapter-express from core.
672
+ const appClassWithBuffering = AppClass;
673
+ if (typeof appClassWithBuffering.startLogBuffering === "function") {
674
+ appClassWithBuffering.startLogBuffering();
675
+ }
772
676
  const app = await AppFactory.create(AppClass);
773
677
  // Set environment on app instance (for this.environment access)
774
678
  app.environment =
@@ -120,7 +120,7 @@ class ConfigInstance {
120
120
  this._options = {
121
121
  validateOnAccess: true,
122
122
  throwOnError: process.env.NODE_ENV === "production",
123
- logLevel: "info",
123
+ logLevel: "warn",
124
124
  ...options,
125
125
  };
126
126
  // Lazy resolution: Don't resolve immediately
@@ -377,6 +377,49 @@ function array(envVar, options = {}) {
377
377
  *
378
378
  * @public API
379
379
  */
380
+ /**
381
+ * Returns true when the current Node environment (`NODE_ENV`) matches the
382
+ * supplied name. Pass an array to match any of several names. The comparison
383
+ * is case-insensitive and falls back to `"development"` when `NODE_ENV` is
384
+ * unset, mirroring the convention used elsewhere in the framework.
385
+ *
386
+ * ```ts
387
+ * const config = defineConfig({
388
+ * server: { port: when(Env.is("production"), 443, 3000) },
389
+ * });
390
+ * ```
391
+ *
392
+ * @public API
393
+ */
394
+ function isEnvironment(name) {
395
+ const current = (process.env.NODE_ENV ?? "development").toLowerCase();
396
+ const targets = Array.isArray(name) ? name : [name];
397
+ return targets.some((target) => target.toLowerCase() === current);
398
+ }
399
+ /**
400
+ * Conditional helper for environment-specific config values.
401
+ *
402
+ * `when(condition, value, fallback)` returns `value` when `condition` is
403
+ * truthy, otherwise `fallback`. The condition can be either a boolean
404
+ * (typically the result of `Env.is(...)`) or a callable that's evaluated
405
+ * lazily. The latter form lets you defer side-effectful checks until the
406
+ * config is actually resolved.
407
+ *
408
+ * ```ts
409
+ * const config = defineConfig({
410
+ * logging: {
411
+ * level: when(Env.is("production"), "info", "debug"),
412
+ * pretty: when(() => process.env.NO_COLOR !== "1", true, false),
413
+ * },
414
+ * });
415
+ * ```
416
+ *
417
+ * @public API
418
+ */
419
+ function envWhen(condition, value, fallback) {
420
+ const ok = typeof condition === "function" ? condition() : condition;
421
+ return ok ? value : fallback;
422
+ }
380
423
  export const Env = {
381
424
  string,
382
425
  number,
@@ -387,6 +430,11 @@ export const Env = {
387
430
  secret,
388
431
  json,
389
432
  array,
433
+ is: isEnvironment,
434
+ when: envWhen,
390
435
  };
436
+ // Re-export the conditional helpers as top-level free functions so users can
437
+ // import them without going through the `Env` namespace if they prefer.
438
+ export { isEnvironment as envIs, envWhen };
391
439
  // Re-export individual builders for tree-shaking
392
440
  export { string as envString, number as envNumber, boolean as envBoolean, enumField as envEnum, url as envUrl, port as envPort, secret as envSecret, json as envJson, array as envArray, };
@@ -60,7 +60,12 @@
60
60
  // ============================================================================
61
61
  export { defineConfig, Env } from "./define-config.js";
62
62
  // Individual field builders (for tree-shaking)
63
- export { envString, envNumber, envBoolean, envEnum, envUrl, envPort, envSecret, envJson, envArray, } from "./env-field-builders.js";
63
+ export { envString, envNumber, envBoolean, envEnum, envUrl, envPort, envSecret, envJson, envArray,
64
+ // Environment-aware value selection helpers.
65
+ // The canonical user-facing API for `when` is `Env.when(...)` to avoid
66
+ // colliding with the middleware module's `when()` helper. We still expose
67
+ // the underlying functions under disambiguated names for advanced use.
68
+ envIs, envWhen, } from "./env-field-builders.js";
64
69
  // ============================================================================
65
70
  // Secret Value
66
71
  // ============================================================================
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Framework version string surfaced in startup banners and diagnostics.
3
+ *
4
+ * This file is auto-synced from the root `package.json` by
5
+ * `scripts/sync-version.js` before each build. Do not edit by hand.
6
+ */
7
+ export const FRAMEWORK_VERSION = "4.0.0-preview.3";
@@ -38,6 +38,8 @@
38
38
  // Lazy Module
39
39
  // ============================================================================
40
40
  export { LazyModule, CreateLazyModule, createLazyModule, isLazyModule, getModuleName, LAZY_MODULE_METADATA_KEY, } from "./lazy-module.js";
41
+ // Free-function wrappers for the LazyModule chain methods.
42
+ export { withPreloadHint, withLazyConfig } from "./lazy-module-helpers.js";
41
43
  // ============================================================================
42
44
  // Lazy Module Loader
43
45
  // ============================================================================
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Standalone (free-function) wrappers around `LazyModule` chain methods.
3
+ *
4
+ * The fluent API (`module.withPreloadHint(...).withLazyConfig(...)`) remains
5
+ * the recommended style. These helpers exist so users can compose lazy
6
+ * configurations with point-free style or apply the same hint to a list of
7
+ * modules:
8
+ *
9
+ * ```ts
10
+ * import { CreateLazyModule, withPreloadHint, withLazyConfig } from "@expressots/core";
11
+ *
12
+ * const Admin = withPreloadHint(
13
+ * CreateLazyModule([AdminController]),
14
+ * "low",
15
+ * );
16
+ *
17
+ * const Analytics = withLazyConfig(
18
+ * CreateLazyModule([AnalyticsController]),
19
+ * { preloadHint: "medium", prefetchAfterIdle: 5000 },
20
+ * );
21
+ * ```
22
+ *
23
+ * @public API
24
+ */
25
+ /**
26
+ * Set a preload hint on the supplied lazy module and return it.
27
+ *
28
+ * Equivalent to `module.withPreloadHint(hint)`. Returned reference is the same
29
+ * instance — no copy is made.
30
+ *
31
+ * @public API
32
+ */
33
+ export function withPreloadHint(module, hint) {
34
+ return module.withPreloadHint(hint);
35
+ }
36
+ /**
37
+ * Merge the supplied partial config into the lazy module and return it.
38
+ *
39
+ * Equivalent to `module.withLazyConfig(config)`.
40
+ *
41
+ * @public API
42
+ */
43
+ export function withLazyConfig(module, config) {
44
+ return module.withLazyConfig(config);
45
+ }
@@ -4,8 +4,6 @@ export { Middleware, ExpressoMiddleware, } from "./middleware-service.js";
4
4
  export { middlewareResolver, isMiddlewareAvailable, isPackageAvailable, resolvePackage, getAvailableMiddleware, getRegisteredMiddleware, clearMiddlewareCache, getPackageName, MIDDLEWARE_REGISTRY, } from "./middleware-resolver.js";
5
5
  // Middleware profiler
6
6
  export { MiddlewareProfiler, } from "./middleware-profiler.js";
7
- // Middleware presets
8
- export { MIDDLEWARE_PRESETS, getPreset, getPresetNames, getPresetsByTag, createPreset, mergePresets, } from "./middleware-presets.js";
9
7
  // Content Negotiation exports
10
8
  export { ContentNegotiationService, FormatterRegistry, AcceptHeaderParser, JsonFormatter, XmlFormatter, CsvFormatter, YamlFormatter, PlainTextFormatter, } from "./content-negotiation/index.js";
11
9
  // ═══════════════════════════════════════════════════════════════════════════
@@ -13,6 +11,9 @@ export { ContentNegotiationService, FormatterRegistry, AcceptHeaderParser, JsonF
13
11
  // ═══════════════════════════════════════════════════════════════════════════
14
12
  // V4 Middleware utilities
15
13
  export { use, compose, when, parallel, timeout } from "./middleware-utils.js";
14
+ // V4 Standalone preset helpers (free-function wrappers around
15
+ // Middleware.definePreset / Middleware.applyPreset)
16
+ export { definePreset, applyPreset, getStandalonePresetNames, clearStandalonePresets, } from "./presets-standalone.js";
16
17
  // V4 Middleware registry
17
18
  export { MiddlewareRegistry, getMiddlewareRegistry, resetMiddlewareRegistry, } from "./middleware-registry.js";
18
19
  // V4 Upload registry (for @FileUpload decorator integration)