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.
|
|
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> {
|
package/core/kernel/README.md
CHANGED
|
@@ -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)}
|
|
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)}
|
|
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)}
|
|
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)}
|
|
173
|
+
else issues.push(`${fullKey(key)} — expected a boolean`);
|
|
174
174
|
} else if (typeof raw === "string") value = raw;
|
|
175
|
-
else issues.push(`${fullKey(key)}
|
|
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)}
|
|
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)}
|
|
181
|
-
} catch { issues.push(`${fullKey(key)}
|
|
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