@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
@@ -268,20 +268,20 @@ export class ReactAdapter extends BaseEngineAdapter {
268
268
  .replace(/</g, "\\u003c")
269
269
  .replace(/>/g, "\\u003e")
270
270
  .replace(/&/g, "\\u0026");
271
- return `<!DOCTYPE html>
272
- <html lang="en">
273
- <head>
274
- <meta charset="UTF-8">
275
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
276
- <script>
277
- window.__INITIAL_DATA__ = ${safeData};
278
- window.__COMPONENT__ = "${view}";
279
- </script>
280
- </head>
281
- <body>
282
- <div id="root">${html}</div>
283
- <script src="/assets/client.js" defer></script>
284
- </body>
271
+ return `<!DOCTYPE html>
272
+ <html lang="en">
273
+ <head>
274
+ <meta charset="UTF-8">
275
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
276
+ <script>
277
+ window.__INITIAL_DATA__ = ${safeData};
278
+ window.__COMPONENT__ = "${view}";
279
+ </script>
280
+ </head>
281
+ <body>
282
+ <div id="root">${html}</div>
283
+ <script src="/assets/client.js" defer></script>
284
+ </body>
285
285
  </html>`;
286
286
  }
287
287
  /**
@@ -89,36 +89,36 @@ export class TypeGenerator {
89
89
  const propsType = v.propsType || "Record<string, unknown>";
90
90
  return ` "${v.name}": ${propsType};`;
91
91
  });
92
- return `/**
93
- * Auto-generated view types
94
- * Generated by ExpressoTS Render Engine
95
- * Do not edit this file manually
96
- */
97
-
98
- /**
99
- * View name to props type mapping
100
- */
101
- export interface Views {
102
- ${viewTypes.join("\n")}
103
- }
104
-
105
- /**
106
- * All view names
107
- */
108
- export type ViewName = keyof Views;
109
-
110
- /**
111
- * Get props type for a specific view
112
- */
113
- export type ViewProps<T extends ViewName> = Views[T];
114
-
115
- /**
116
- * Type-safe render function signature
117
- */
118
- export type RenderFunction = <T extends ViewName>(
119
- view: T,
120
- data: Views[T],
121
- ) => Promise<string>;
92
+ return `/**
93
+ * Auto-generated view types
94
+ * Generated by ExpressoTS Render Engine
95
+ * Do not edit this file manually
96
+ */
97
+
98
+ /**
99
+ * View name to props type mapping
100
+ */
101
+ export interface Views {
102
+ ${viewTypes.join("\n")}
103
+ }
104
+
105
+ /**
106
+ * All view names
107
+ */
108
+ export type ViewName = keyof Views;
109
+
110
+ /**
111
+ * Get props type for a specific view
112
+ */
113
+ export type ViewProps<T extends ViewName> = Views[T];
114
+
115
+ /**
116
+ * Type-safe render function signature
117
+ */
118
+ export type RenderFunction = <T extends ViewName>(
119
+ view: T,
120
+ data: Views[T],
121
+ ) => Promise<string>;
122
122
  `;
123
123
  }
124
124
  /**
@@ -28,12 +28,18 @@ export class ViewDebugger {
28
28
  app.get("/__views", (req, res) => {
29
29
  this.handleViewsEndpoint(req, res);
30
30
  });
31
- // Preview a view
32
- app.get("/__views/preview/:view(*)", (req, res) => {
31
+ // Preview a view. The view name can contain slashes (e.g.
32
+ // `users/profile`), so we use a named splat (`*view`) the
33
+ // path-to-regexp v6 form `:view(*)` shipped by Express 4 is not
34
+ // valid in path-to-regexp v8 (Express 5) and now throws
35
+ // `Missing parameter name`. Path-to-regexp v8 captures the splat
36
+ // segments as `req.params.view: string[]`; the helpers below
37
+ // normalise that to a slash-joined string for backward compat.
38
+ app.get("/__views/preview/*view", (req, res) => {
33
39
  this.handlePreviewEndpoint(req, res);
34
40
  });
35
- // Get view info
36
- app.get("/__views/info/:view(*)", (req, res) => {
41
+ // Get view info — same shape as preview.
42
+ app.get("/__views/info/*view", (req, res) => {
37
43
  this.handleInfoEndpoint(req, res);
38
44
  });
39
45
  // Get metrics
@@ -72,7 +78,7 @@ export class ViewDebugger {
72
78
  */
73
79
  async handlePreviewEndpoint(req, res) {
74
80
  try {
75
- const view = req.params.view;
81
+ const view = this.resolveViewParam(req.params.view);
76
82
  const data = req.query.data ? JSON.parse(req.query.data) : {};
77
83
  const html = await this.renderService.render(view, data);
78
84
  res.setHeader("Content-Type", "text/html");
@@ -81,16 +87,30 @@ export class ViewDebugger {
81
87
  catch (error) {
82
88
  res.status(500).json({
83
89
  error: error.message,
84
- view: req.params.view,
90
+ view: this.resolveViewParam(req.params.view),
85
91
  });
86
92
  }
87
93
  }
94
+ /**
95
+ * Normalise the `view` route param across Express 4 and 5.
96
+ *
97
+ * Express 4 / path-to-regexp v6 captured `/__views/preview/:view(*)`
98
+ * as a single string. Express 5 / path-to-regexp v8 changed the
99
+ * named-splat capture to `string[]` (one entry per slash-separated
100
+ * segment). The view engine needs the slash-joined form, so we always
101
+ * collapse the array shape back to that.
102
+ */
103
+ resolveViewParam(value) {
104
+ if (Array.isArray(value))
105
+ return value.join("/");
106
+ return typeof value === "string" ? value : "";
107
+ }
88
108
  /**
89
109
  * Handle the view info endpoint.
90
110
  */
91
111
  handleInfoEndpoint(req, res) {
92
112
  try {
93
- const view = req.params.view;
113
+ const view = this.resolveViewParam(req.params.view);
94
114
  const viewFiles = this.renderService.getViewFiles();
95
115
  // Find matching view
96
116
  const matchingFiles = viewFiles.filter((file) => file.includes(view));
@@ -119,54 +139,54 @@ export class ViewDebugger {
119
139
  * Wrap preview HTML with debug info.
120
140
  */
121
141
  wrapPreview(view, html) {
122
- return `<!DOCTYPE html>
123
- <html>
124
- <head>
125
- <title>View Preview: ${view}</title>
126
- <style>
127
- .debug-bar {
128
- position: fixed;
129
- top: 0;
130
- left: 0;
131
- right: 0;
132
- background: #1a1a2e;
133
- color: #eee;
134
- padding: 8px 16px;
135
- font-family: system-ui, -apple-system, sans-serif;
136
- font-size: 13px;
137
- z-index: 99999;
138
- display: flex;
139
- align-items: center;
140
- gap: 16px;
141
- box-shadow: 0 2px 8px rgba(0,0,0,0.2);
142
- }
143
- .debug-bar strong {
144
- color: #00d9ff;
145
- }
146
- .debug-bar a {
147
- color: #ff6b6b;
148
- text-decoration: none;
149
- }
150
- .debug-bar a:hover {
151
- text-decoration: underline;
152
- }
153
- .content-wrapper {
154
- padding-top: 50px;
155
- }
156
- </style>
157
- </head>
158
- <body>
159
- <div class="debug-bar">
160
- <span>📁 <strong>${view}</strong></span>
161
- <span>|</span>
162
- <span>Engine: <strong>${this.renderService.getActiveEngine().name}</strong></span>
163
- <span>|</span>
164
- <a href="/__views">← Back to Views</a>
165
- </div>
166
- <div class="content-wrapper">
167
- ${html}
168
- </div>
169
- </body>
142
+ return `<!DOCTYPE html>
143
+ <html>
144
+ <head>
145
+ <title>View Preview: ${view}</title>
146
+ <style>
147
+ .debug-bar {
148
+ position: fixed;
149
+ top: 0;
150
+ left: 0;
151
+ right: 0;
152
+ background: #1a1a2e;
153
+ color: #eee;
154
+ padding: 8px 16px;
155
+ font-family: system-ui, -apple-system, sans-serif;
156
+ font-size: 13px;
157
+ z-index: 99999;
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 16px;
161
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
162
+ }
163
+ .debug-bar strong {
164
+ color: #00d9ff;
165
+ }
166
+ .debug-bar a {
167
+ color: #ff6b6b;
168
+ text-decoration: none;
169
+ }
170
+ .debug-bar a:hover {
171
+ text-decoration: underline;
172
+ }
173
+ .content-wrapper {
174
+ padding-top: 50px;
175
+ }
176
+ </style>
177
+ </head>
178
+ <body>
179
+ <div class="debug-bar">
180
+ <span>📁 <strong>${view}</strong></span>
181
+ <span>|</span>
182
+ <span>Engine: <strong>${this.renderService.getActiveEngine().name}</strong></span>
183
+ <span>|</span>
184
+ <a href="/__views">← Back to Views</a>
185
+ </div>
186
+ <div class="content-wrapper">
187
+ ${html}
188
+ </div>
189
+ </body>
170
190
  </html>`;
171
191
  }
172
192
  }
@@ -231,7 +231,13 @@ class FluentRequestImpl {
231
231
  }
232
232
  /**
233
233
  * Execute the request and return the response.
234
+ *
235
+ * The generic defaults to `any` (see {@link FluentResponse} for the
236
+ * rationale) so test code can read `response.body.foo` directly. Pass
237
+ * an explicit type — `.execute<UserDto>()` — to opt into strict
238
+ * typing.
234
239
  */
240
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
235
241
  async execute() {
236
242
  const startTime = Date.now();
237
243
  // Build URL with query parameters
@@ -315,6 +321,7 @@ class FluentRequestImpl {
315
321
  /**
316
322
  * Alias for execute().
317
323
  */
324
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
318
325
  end() {
319
326
  return this.execute();
320
327
  }
@@ -98,9 +98,11 @@ class SnapshotRequestImpl {
98
98
  this.fluentRequest.expect(assertion);
99
99
  return this;
100
100
  }
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
102
  execute() {
102
103
  return this.fluentRequest.execute();
103
104
  }
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
106
  end() {
105
107
  return this.fluentRequest.end();
106
108
  }
@@ -112,6 +112,12 @@ export declare class AppFactory {
112
112
  *
113
113
  * @throws {Error} If webServerType is not a valid constructor
114
114
  *
115
+ * @deprecated Use `bootstrap()` from `@expressots/core` instead. `bootstrap()`
116
+ * additionally handles environment file loading, port configuration,
117
+ * graceful shutdown, the startup banner, and configuration validation.
118
+ * `AppFactory.create()` will keep working for advanced use cases but is
119
+ * scheduled for removal in a future major release.
120
+ *
115
121
  * @public API
116
122
  */
117
123
  static create<T extends IWebServer>(webServerType: IWebServerConstructor<T>): Promise<IWebServerBuilder>;
@@ -216,52 +216,147 @@ export interface EnvironmentFileConfig {
216
216
  skipFileLoading?: boolean;
217
217
  }
218
218
  /**
219
- * Options for loadEnvSync().
220
- * @public API
219
+ * Options for {@link loadEnvSync}.
220
+ *
221
+ * `loadEnvSync()` is the recommended way to populate `process.env` before
222
+ * `bootstrap()` runs. Call it at the top of `src/main.ts` (or your config
223
+ * module) so every subsequent `Env.*` builder, `defineConfig()` call, and
224
+ * DI binding can read the loaded values.
225
+ *
226
+ * ### File-loading order (last wins)
227
+ *
228
+ * | Step | File | Purpose |
229
+ * |------|------|---------|
230
+ * | 1 | `.env` | Shared defaults (committed) |
231
+ * | 2 | `.env.local` | Local-only overrides (gitignored) |
232
+ * | 3 | `.env.{env}.local` | Per-environment local override |
233
+ * | 4 | `.env.{env}` | Per-environment values (highest priority) |
234
+ *
235
+ * `{env}` is resolved from `process.env.NODE_ENV` (defaults to `"development"`).
236
+ * If a `files` mapping is provided, the mapped filename replaces `.env.{env}`.
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * // 1. Zero-config (loads .env + .env.development by convention)
241
+ * loadEnvSync();
242
+ *
243
+ * // 2. Custom file names per environment
244
+ * loadEnvSync({
245
+ * files: {
246
+ * development: ".env",
247
+ * production: ".env.prod",
248
+ * staging: ".env.staging",
249
+ * },
250
+ * });
251
+ *
252
+ * // 3. Force reload after hot-module replacement
253
+ * loadEnvSync({ force: true });
254
+ * ```
255
+ *
256
+ * @see {@link loadEnvSync} for the function itself
257
+ * @see {@link BootstrapOptions.envFileConfig} for the bootstrap()-level alternative
258
+ * @public
221
259
  */
222
260
  export interface LoadEnvSyncOptions {
223
261
  /**
224
- * Custom mapping of environment names to .env file names.
225
- * @default Uses convention: `.env.{currentEnvironment}`
262
+ * Map environment names to custom `.env` file paths.
263
+ *
264
+ * Only the entry matching the **current** `NODE_ENV` is used at runtime.
265
+ * Unmapped environments fall back to the convention `.env.{NODE_ENV}`.
266
+ *
267
+ * @default undefined (convention: `.env.{NODE_ENV}`)
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * files: {
272
+ * development: ".env", // NODE_ENV=development -> loads .env
273
+ * production: ".env.prod", // NODE_ENV=production -> loads .env.prod
274
+ * test: ".env.test", // NODE_ENV=test -> loads .env.test
275
+ * }
276
+ * ```
226
277
  */
227
278
  files?: EnvironmentFileMap;
228
279
  /**
229
- * Force reload even if already loaded.
230
- * Useful for hot reload scenarios.
280
+ * Reload environment files even if they were already loaded in this process.
281
+ *
282
+ * By default `loadEnvSync()` is a no-op after the first successful call
283
+ * (tracked via `process.env._EXPRESSOTS_ENV_LOADED`). Set `force: true`
284
+ * to re-read all files, useful after a hot-module replacement cycle.
285
+ *
231
286
  * @default false
232
287
  */
233
288
  force?: boolean;
234
289
  }
235
290
  /**
236
- * Synchronously load .env files BEFORE defineConfig() resolves.
291
+ * Synchronously load `.env` files into `process.env`.
237
292
  *
238
- * Call this at the top of your config file to ensure environment variables
239
- * are available when defineConfig() runs.
293
+ * Call this **before** `bootstrap()` or `defineConfig()` so that every
294
+ * environment variable is available when the rest of the application
295
+ * resolves configuration, binds services, or reads `Env.*` builders.
240
296
  *
241
- * @param options - Environment file configuration
297
+ * ### How it works
242
298
  *
243
- * @example
299
+ * 1. Reads `process.env.NODE_ENV` (defaults to `"development"`).
300
+ * 2. Loads files in priority order (last file wins on conflicts):
301
+ * `.env` → `.env.local` → `.env.{env}.local` → `.env.{env}`
302
+ * 3. If `options.files` maps the current environment to a custom name
303
+ * (e.g. `{ production: ".env.prod" }`), that name replaces `.env.{env}`.
304
+ * 4. Missing files are silently skipped.
305
+ * 5. Sets `process.env._EXPRESSOTS_ENV_LOADED = "true"` so subsequent
306
+ * calls are no-ops (unless `force: true`).
307
+ *
308
+ * ### Important
309
+ *
310
+ * `NODE_ENV` must already be set in the **shell** (or inherited from
311
+ * the process) before `loadEnvSync()` runs. The function reads
312
+ * `process.env.NODE_ENV` to decide which file to load; it does **not**
313
+ * derive the environment from file contents.
314
+ *
315
+ * @param options - See {@link LoadEnvSyncOptions} for all fields.
316
+ *
317
+ * @example Zero-config (recommended for most apps)
244
318
  * ```typescript
245
- * // config.ts
246
- * import { defineConfig, Env, loadEnvSync } from "@expressots/core";
319
+ * // src/main.ts
320
+ * import { bootstrap, loadEnvSync } from "@expressots/core";
321
+ * import { App } from "./app";
322
+ *
323
+ * loadEnvSync(); // .env + .env.development (or .env.production, etc.)
324
+ * void bootstrap(App);
325
+ * ```
247
326
  *
248
- * // Load .env files first
327
+ * @example Custom file mapping
328
+ * ```typescript
249
329
  * loadEnvSync({
250
330
  * files: {
251
- * development: ".env.dev",
252
- * production: ".env.prod",
331
+ * development: ".env",
332
+ * production: ".env.prod",
333
+ * staging: ".env.staging",
253
334
  * },
254
335
  * });
336
+ * // With NODE_ENV=production -> .env + .env.prod
337
+ * // With NODE_ENV=development -> .env (mapped to itself, so loaded once)
338
+ * ```
339
+ *
340
+ * @example With defineConfig()
341
+ * ```typescript
342
+ * // src/config.ts
343
+ * import { defineConfig, Env, loadEnvSync } from "@expressots/core";
344
+ *
345
+ * loadEnvSync({ files: { production: ".env.prod" } });
255
346
  *
256
- * // Now defineConfig() will read from loaded .env files
257
347
  * export const appConfig = defineConfig({
258
- * server: {
259
- * port: Env.port("PORT", { default: 3000 }),
260
- * },
348
+ * server: { port: Env.port("PORT", { default: 3000 }) },
261
349
  * });
262
350
  * ```
263
351
  *
264
- * @public API
352
+ * @example Force reload (hot-module replacement)
353
+ * ```typescript
354
+ * loadEnvSync({ force: true });
355
+ * ```
356
+ *
357
+ * @see {@link LoadEnvSyncOptions} for option details
358
+ * @see {@link BootstrapOptions.envFileConfig} for the bootstrap()-level alternative
359
+ * @public
265
360
  */
266
361
  export declare function loadEnvSync(options?: LoadEnvSyncOptions): void;
267
362
  /**
@@ -471,14 +566,91 @@ export interface BootstrapOptions {
471
566
  envFileConfig?: EnvironmentFileConfig;
472
567
  }
473
568
  /**
474
- * Bootstrap with BootstrapOptions (existing behavior).
569
+ * Start the application with explicit options.
570
+ *
571
+ * Use this overload when you need to control the port, app metadata,
572
+ * environment selection, or `.env` file loading/validation.
573
+ *
574
+ * @param AppClass - Your class that extends `AppExpress`.
575
+ * @param options - See {@link BootstrapOptions} for every field.
576
+ * @returns The running server instance.
577
+ *
578
+ * @example Port + environment + validation
579
+ * ```typescript
580
+ * await bootstrap(App, {
581
+ * port: 4000,
582
+ * appName: "My API",
583
+ * currentEnvironment: "production",
584
+ * envFileConfig: {
585
+ * files: { production: ".env.prod" },
586
+ * required: ["DATABASE_URL", "JWT_SECRET"],
587
+ * validateValues: true,
588
+ * },
589
+ * });
590
+ * ```
591
+ *
592
+ * @example Dynamic port for testing
593
+ * ```typescript
594
+ * const server = await bootstrap(App, { port: 0 });
595
+ * const port = await server.getPort();
596
+ * ```
597
+ *
598
+ * @example Container / CI (no file I/O)
599
+ * ```typescript
600
+ * await bootstrap(App, {
601
+ * envFileConfig: {
602
+ * skipFileLoading: true,
603
+ * required: ["DATABASE_URL"],
604
+ * },
605
+ * });
606
+ * ```
607
+ *
608
+ * @public
475
609
  */
476
610
  export declare function bootstrap(AppClass: new () => IWebServer, options: BootstrapOptions): Promise<IWebServerPublic>;
477
611
  /**
478
- * Bootstrap with config object (new convenience overload).
612
+ * Start the application with a type-safe config object from `defineConfig()`.
613
+ *
614
+ * Pass the resolved config directly; `bootstrap()` extracts `app`, `server`,
615
+ * and `bootstrap.envFileConfig` automatically.
616
+ *
617
+ * @param AppClass - Your class that extends `AppExpress`.
618
+ * @param config - Object returned by `defineConfig().values` (or matching
619
+ * the {@link BootstrapConfig} shape).
620
+ * @returns The running server instance.
621
+ *
622
+ * @example
623
+ * ```typescript
624
+ * import { appConfig } from "./config";
625
+ *
626
+ * await bootstrap(App, appConfig.values);
627
+ * // appConfig.values has { app, server, bootstrap } from defineConfig()
628
+ * ```
629
+ *
630
+ * @public
479
631
  */
480
632
  export declare function bootstrap(AppClass: new () => IWebServer, config: BootstrapConfig): Promise<IWebServerPublic>;
481
633
  /**
482
- * Bootstrap with no options (zero-config).
634
+ * Start the application with zero configuration.
635
+ *
636
+ * - Listens on `process.env.PORT` or **3000**.
637
+ * - Reads `appName` and `appVersion` from `package.json`.
638
+ * - Does **not** load any `.env` files (opt-in via {@link BootstrapOptions.envFileConfig}
639
+ * or by calling {@link loadEnvSync} before this).
640
+ * - Sets up graceful shutdown on `SIGINT` / `SIGTERM`.
641
+ *
642
+ * @param AppClass - Your class that extends `AppExpress`.
643
+ * @returns The running server instance.
644
+ *
645
+ * @example
646
+ * ```typescript
647
+ * import { bootstrap, loadEnvSync } from "@expressots/core";
648
+ * import { App } from "./app";
649
+ *
650
+ * loadEnvSync(); // optional: load .env files first
651
+ * await bootstrap(App); // starts on PORT from .env or 3000
652
+ * ```
653
+ *
654
+ * @public
483
655
  */
484
656
  export declare function bootstrap(AppClass: new () => IWebServer): Promise<IWebServerPublic>;
@@ -409,7 +409,13 @@ export interface DefineConfigOptions {
409
409
  throwOnError?: boolean;
410
410
  /**
411
411
  * Log validation results.
412
- * @default "info"
412
+ *
413
+ * - `"warn"` (default): only warnings and errors are printed.
414
+ * - `"info"` / `"debug"`: prints the "CONFIGURATION LOADED" success banner
415
+ * plus warnings and errors.
416
+ * - `"error"`: only errors.
417
+ * - `"none"`: completely silent.
418
+ * @default "warn"
413
419
  */
414
420
  logLevel?: ConfigLogLevel;
415
421
  /**
@@ -274,6 +274,42 @@ declare function array<T extends string | number>(envVar: string, options?: Arra
274
274
  *
275
275
  * @public API
276
276
  */
277
+ /**
278
+ * Returns true when the current Node environment (`NODE_ENV`) matches the
279
+ * supplied name. Pass an array to match any of several names. The comparison
280
+ * is case-insensitive and falls back to `"development"` when `NODE_ENV` is
281
+ * unset, mirroring the convention used elsewhere in the framework.
282
+ *
283
+ * ```ts
284
+ * const config = defineConfig({
285
+ * server: { port: when(Env.is("production"), 443, 3000) },
286
+ * });
287
+ * ```
288
+ *
289
+ * @public API
290
+ */
291
+ declare function isEnvironment(name: string | Array<string>): boolean;
292
+ /**
293
+ * Conditional helper for environment-specific config values.
294
+ *
295
+ * `when(condition, value, fallback)` returns `value` when `condition` is
296
+ * truthy, otherwise `fallback`. The condition can be either a boolean
297
+ * (typically the result of `Env.is(...)`) or a callable that's evaluated
298
+ * lazily. The latter form lets you defer side-effectful checks until the
299
+ * config is actually resolved.
300
+ *
301
+ * ```ts
302
+ * const config = defineConfig({
303
+ * logging: {
304
+ * level: when(Env.is("production"), "info", "debug"),
305
+ * pretty: when(() => process.env.NO_COLOR !== "1", true, false),
306
+ * },
307
+ * });
308
+ * ```
309
+ *
310
+ * @public API
311
+ */
312
+ declare function envWhen<TValue, TFallback>(condition: boolean | (() => boolean), value: TValue, fallback: TFallback): TValue | TFallback;
277
313
  export declare const Env: {
278
314
  readonly string: typeof string;
279
315
  readonly number: typeof number;
@@ -284,5 +320,8 @@ export declare const Env: {
284
320
  readonly secret: typeof secret;
285
321
  readonly json: typeof json;
286
322
  readonly array: typeof array;
323
+ readonly is: typeof isEnvironment;
324
+ readonly when: typeof envWhen;
287
325
  };
326
+ export { isEnvironment as envIs, envWhen };
288
327
  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, };
@@ -56,6 +56,6 @@
56
56
  * ```
57
57
  */
58
58
  export { defineConfig, Env } from "./define-config.js";
59
- export { envString, envNumber, envBoolean, envEnum, envUrl, envPort, envSecret, envJson, envArray, } from "./env-field-builders.js";
59
+ export { envString, envNumber, envBoolean, envEnum, envUrl, envPort, envSecret, envJson, envArray, envIs, envWhen, } from "./env-field-builders.js";
60
60
  export type { ConfigValueType, ConfigEnvironment, ConfigLogLevel, BaseFieldOptions, StringFieldOptions, NumberFieldOptions, BooleanFieldOptions, EnumFieldOptions, UrlFieldOptions, PortFieldOptions, SecretFieldOptions, JsonFieldOptions, ArrayFieldOptions, ConfigField, ConfigValidationError, ConfigValidationResult, ConfigSchemaValue, ConfigSchema, DefineConfigOptions, ConfigChangeEvent, SecretValue, ResolvedConfig, IConfigInstance, } from "./config.interfaces.js";
61
61
  export { createSecretValue, isSecretValue } from "./secret-value.js";
@@ -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 declare const FRAMEWORK_VERSION = "4.0.0-preview.3";
@@ -36,6 +36,7 @@
36
36
  */
37
37
  export type { PreloadHint, ModuleLoadStatus, LazyModuleConfig, LazyModuleFactory, ILazyModule, ILazyModuleLoader, ModuleLoadStatistics, ILazyModuleManager, ModuleRecommendation, ApplyRecommendationsOptions, ILazyLoadMetrics, WarmupStrategy, WarmupConfig, ILazyModuleWarmup, LoadingPhase, ProgressiveLoadingConfig, LazyLoadingOptions, LazyLoadingSetupResult, } from "./lazy.interfaces.js";
38
38
  export { LazyModule, CreateLazyModule, createLazyModule, isLazyModule, getModuleName, LAZY_MODULE_METADATA_KEY, } from "./lazy-module.js";
39
+ export { withPreloadHint, withLazyConfig } from "./lazy-module-helpers.js";
39
40
  export { LazyModuleLoader, createLazyModuleLoader, } from "./lazy-module-loader.js";
40
41
  export { LazyModuleManager, createLazyModuleManager, } from "./lazy-module-manager.js";
41
42
  export { LazyLoadMetrics, createLazyLoadMetrics } from "./lazy-load-metrics.js";