bazis 0.97.1 → 0.97.2

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.
@@ -232,7 +232,7 @@ export class CodexAppServerClient implements CodexClient {
232
232
  if (method === "account/updated") this.modelCache = undefined;
233
233
  }, () => { this.clearLogin(); this.modelCache = undefined; });
234
234
  const initialized = await client.request("initialize", {
235
- clientInfo: { name: "bazis", title: "Bazis", version: "0.97.1" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.97.2" }, capabilities: { experimentalApi: true },
236
236
  }, 15_000, signal);
237
237
  if (resolve(text(initialized.codexHome, 4096)) !== this.paths.home) throw new CodexError("PROTOCOL_ERROR");
238
238
  client.notify("initialized");
@@ -14,6 +14,7 @@ export class ApplicationLifetime {
14
14
  private readonly stopRequestListeners: ((exitCode: number) => void)[] = [];
15
15
  private startedFlag = false;
16
16
  private stoppingFlag = false;
17
+ private stoppedFlag = false;
17
18
 
18
19
  public get isStarted(): boolean {
19
20
  return this.startedFlag;
@@ -23,15 +24,35 @@ export class ApplicationLifetime {
23
24
  return this.stoppingFlag;
24
25
  }
25
26
 
27
+ /**
28
+ * Runs `callback` once the application has started. A subscription made
29
+ * after that moment (for example in a service first created by a request)
30
+ * runs right away, like .NET's ApplicationStarted; a failure of such a late
31
+ * callback is an unhandled error (logged, graceful stop with exit code 1).
32
+ */
26
33
  public onStarted(callback: LifetimeCallback): void {
34
+ if (this.startedFlag) {
35
+ runLate(callback);
36
+ return;
37
+ }
27
38
  this.startedCallbacks.push(callback);
28
39
  }
29
40
 
41
+ /** Runs `callback` when graceful shutdown begins; right away if it already has. */
30
42
  public onStopping(callback: LifetimeCallback): void {
43
+ if (this.stoppingFlag) {
44
+ runLate(callback);
45
+ return;
46
+ }
31
47
  this.stoppingCallbacks.push(callback);
32
48
  }
33
49
 
50
+ /** Runs `callback` after the application has stopped; right away if it already has. */
34
51
  public onStopped(callback: LifetimeCallback): void {
52
+ if (this.stoppedFlag) {
53
+ runLate(callback);
54
+ return;
55
+ }
35
56
  this.stoppedCallbacks.push(callback);
36
57
  }
37
58
 
@@ -61,10 +82,17 @@ export class ApplicationLifetime {
61
82
 
62
83
  /** @internal */
63
84
  public async notifyStopped(): Promise<void> {
85
+ this.stoppedFlag = true;
64
86
  await runCallbacks(this.stoppedCallbacks);
65
87
  }
66
88
  }
67
89
 
90
+ // A late subscription runs on its own: a rejection surfaces as an unhandled
91
+ // error instead of disappearing silently.
92
+ function runLate(callback: LifetimeCallback): void {
93
+ void Promise.resolve().then(callback);
94
+ }
95
+
68
96
  // A failing subscriber must not break the rest of the lifecycle chain:
69
97
  // run everything, then report all failures at once.
70
98
  async function runCallbacks(callbacks: readonly LifetimeCallback[], signal?: AbortSignal): Promise<void> {
@@ -16,7 +16,7 @@ Contracts of the lifecycle and configuration fixes: [partial passport](MODULE.md
16
16
  ## Lifecycle
17
17
 
18
18
  - `LifecycleCoordinator.ts`: boot/shutdown order: options fail-fast → `onInit` → hosted services by phase → `onBootstrap`; shutdown in reverse order with `shutdownTimeout`; rollback when startup fails.
19
- - `ApplicationLifetime.ts`: injectable lifetime: `onStarted/onStopping/onStopped` + programmatic `stop(exitCode)`.
19
+ - `ApplicationLifetime.ts`: injectable lifetime: `onStarted/onStopping/onStopped` (a subscription made after its moment runs right away) + programmatic `stop(exitCode)`.
20
20
  - `lifecycleHooks.ts`: the `LIFECYCLE_HOOK` token (enumerable) + `addLifecycleHook`.
21
21
  - `Environment.ts`: the environment (`development|production|test`) from `BAZIS_ENV`/`NODE_ENV`, the `debug` flag.
22
22
  - `SupervisedHostedService.ts`: retry with exponential backoff; passes the startup signal and stops retrying on cancellation.
@@ -156,33 +156,35 @@ export function defineConfig(prefixOrSchema: string | ConfigSchema<Defaults>, ma
156
156
  const priority = Math.max(...candidates.map(candidate => candidate.priority));
157
157
  const winners = candidates.filter(candidate => candidate.priority === priority);
158
158
  const selected = winners[0];
159
- if (winners.some(candidate => candidate.raw !== selected?.raw)) issues.push(`${fullKey(key)}: conflicting environment aliases`);
159
+ if (winners.some(candidate => candidate.raw !== selected?.raw)) issues.push(`${fullKey(key)} — conflicting environment aliases`);
160
160
  const raw = selected?.raw ?? (isSecret(cell) || isEnum(cell) ? cell.default : cell);
161
161
  let value: ConfigValueType | undefined;
162
162
  if (type === "secret") {
163
- if (typeof raw !== "string" || !raw.trim()) issues.push(`${fullKey(key)}: required non-empty secret is not set (${names.get(key)!.join(", ")})`);
163
+ if (typeof raw !== "string" || !raw.trim()) issues.push(`${fullKey(key)} — required non-empty secret is not set (${names.get(key)!.join(", ")})`);
164
164
  else value = new Secret(raw);
165
165
  } else if (type === "number") {
166
166
  const parsed = Number(raw);
167
- if (raw === undefined || (typeof raw === "string" && !raw.trim()) || !Number.isFinite(parsed)) issues.push(`${fullKey(key)}: expected a finite number`);
167
+ if (raw === undefined || (typeof raw === "string" && !raw.trim()) || !Number.isFinite(parsed)) issues.push(`${fullKey(key)} — expected a finite number`);
168
168
  else value = parsed;
169
169
  } else if (type === "boolean") {
170
170
  const normalized = String(raw).toLowerCase();
171
171
  if (normalized === "true" || normalized === "1") value = true;
172
172
  else if (normalized === "false" || normalized === "0") value = false;
173
- else issues.push(`${fullKey(key)}: expected a boolean`);
173
+ else issues.push(`${fullKey(key)} — expected a boolean`);
174
174
  } else if (typeof raw === "string") value = raw;
175
- else issues.push(`${fullKey(key)}: expected a string`);
175
+ else issues.push(`${fullKey(key)} — expected a string`);
176
176
  if (value === undefined) continue;
177
- if (isEnum(base) && !base.values.includes(value as string | number)) issues.push(`${fullKey(key)}: value is not one of the declared enum values`);
177
+ if (isEnum(base) && !base.values.includes(value as string | number)) issues.push(`${fullKey(key)} — value is not one of the declared enum values`);
178
178
  try {
179
179
  const issue = validators[key]?.(value);
180
- if (issue) issues.push(`${fullKey(key)}: ${issue}`);
181
- } catch { issues.push(`${fullKey(key)}: validator failed`); }
180
+ if (issue) issues.push(`${fullKey(key)} — ${issue}`);
181
+ } catch { issues.push(`${fullKey(key)} — validator failed`); }
182
182
  values.set(key, value);
183
183
  inspection.push(Object.freeze({ key: fullKey(key), type: type as ConfigInspection["type"], env: names.get(key)!, source: selected?.source ?? (override === undefined ? "default" : environment), value: safeValue(fullKey(key), value) }));
184
184
  }
185
185
  if (issues.length) {
186
+ // "key — text", not "key: text": the console redaction would read
187
+ // "db.password: required" as a secret value and hide the word "required".
186
188
  let message = issues.join("; ");
187
189
  for (const [key, value] of values) {
188
190
  const sensitive = value instanceof Secret ? value.reveal() : typeof value === "string" && isSensitiveKey(key) ? value : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.97.1",
3
+ "version": "0.97.2",
4
4
  "description": "Modular backend framework for Bun in TypeScript: DI wired by codegen, HTTP, a PostgreSQL ORM, JWT, WebSocket, agents and a CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Alexander Alenin",