keryx 0.21.6 → 0.21.7

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/classes/API.ts CHANGED
@@ -92,6 +92,8 @@ export class API {
92
92
  }
93
93
  }
94
94
 
95
+ this.validateInitializerProperties("initialize");
96
+
95
97
  this.initialized = true;
96
98
  this.logger.warn("--- 🔄 Initializing complete ---");
97
99
  }
@@ -138,6 +140,8 @@ export class API {
138
140
  }
139
141
  }
140
142
 
143
+ this.validateInitializerProperties("start");
144
+
141
145
  this.started = true;
142
146
  this.logger.warn("--- 🔼 Starting complete ---");
143
147
  }
@@ -272,4 +276,37 @@ export class API {
272
276
  private sortInitializers(key: InitializerSortKeys) {
273
277
  this.initializers.sort((a, b) => a[key] - b[key]);
274
278
  }
279
+
280
+ /**
281
+ * Assert that every initializer which claims an API namespace (via
282
+ * `declaresAPIProperty`) has actually attached `api[initializer.name]`.
283
+ * Closes the silent-drift gap between TypeScript module augmentation
284
+ * and runtime property assignment — a missing namespace becomes a fast,
285
+ * loud startup failure instead of a confusing `Cannot read property of
286
+ * undefined` deep inside a request handler.
287
+ *
288
+ * Initializers excluded from the current `runMode` are skipped during the
289
+ * `start` phase since their `start()` method never ran.
290
+ */
291
+ private validateInitializerProperties(phase: "initialize" | "start") {
292
+ const missing: string[] = [];
293
+ for (const initializer of this.initializers) {
294
+ if (initializer.declaresAPIProperty === false) continue;
295
+ if (phase === "start" && !initializer.runModes.includes(this.runMode)) {
296
+ continue;
297
+ }
298
+ if (this[initializer.name] == null) {
299
+ missing.push(initializer.name);
300
+ }
301
+ }
302
+ if (missing.length > 0) {
303
+ throw new TypedError({
304
+ type: ErrorType.INITIALIZER_VALIDATION,
305
+ message:
306
+ `Initializers did not attach their namespace to api after the ${phase} phase: ` +
307
+ `${missing.join(", ")}. Each initializer must either return a namespace object ` +
308
+ `from initialize() (and/or populate it in start()), or set declaresAPIProperty = false.`,
309
+ });
310
+ }
311
+ }
275
312
  }
@@ -17,6 +17,13 @@ export abstract class Initializer {
17
17
  stopPriority: number;
18
18
  /** Which run modes this initializer participates in. Defaults to both SERVER and CLI. */
19
19
  runModes: RUN_MODE[];
20
+ /**
21
+ * Whether this initializer attaches a property named `this.name` on the `api` singleton.
22
+ * Runtime validation in `API.initialize()` and `API.start()` will fail if the declared
23
+ * property is missing. Default: `true`. Set to `false` for initializers that intentionally
24
+ * don't augment the `API` interface.
25
+ */
26
+ declaresAPIProperty: boolean;
20
27
 
21
28
  constructor(name: string) {
22
29
  this.name = name;
@@ -24,6 +31,7 @@ export abstract class Initializer {
24
31
  this.startPriority = 1000;
25
32
  this.stopPriority = 1000;
26
33
  this.runModes = [RUN_MODE.SERVER, RUN_MODE.CLI];
34
+ this.declaresAPIProperty = true;
27
35
  }
28
36
 
29
37
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keryx",
3
- "version": "0.21.6",
3
+ "version": "0.21.7",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "license": "MIT",