cabloy 5.1.221 → 5.1.222
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/.cabloy-version +1 -1
- package/CHANGELOG.md +11 -0
- package/package.json +1 -1
- package/repo-agent-governance/managed-assets.json +15 -15
- package/repo-agent-governance/policies/repository.md +1 -0
- package/repo-agent-governance/skills/cabloy-backend-scaffold/SKILL.md +5 -1
- package/repo-agent-governance/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +7 -5
- package/repo-agent-governance/skills/cabloy-frontend-scaffold/SKILL.md +5 -1
- package/repo-agent-governance/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +6 -4
- package/repo-docs/backend/unit-testing.md +5 -1
- package/repo-docs/frontend/scripts.md +8 -0
- package/vona/packages-cli/cabloy-cli/package.json +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-utils/process-helper/package.json +1 -1
- package/vona/packages-utils/process-helper/src/index.test.ts +62 -0
- package/vona/packages-utils/process-helper/src/index.ts +9 -4
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.test.ts +558 -0
- package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.ts +389 -0
- package/vona/packages-vona/vona-core/src/lib/core/context.ts +3 -0
- package/vona/packages-vona/vona-core/src/lib/core/index.ts +11 -0
- package/vona/packages-vona/vona-core/src/lib/core/meta.ts +5 -0
- package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +10 -0
- package/vona/packages-vona/vona-core/src/lib/framework/start.ts +8 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +124 -4
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +7 -0
- package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExisting.test.ts +423 -0
- package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExistingCommit.test.ts +174 -0
- package/vona/src/suite-vendor/a-vona/modules/a-instance/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/bean/bean.instance.ts +3 -1
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/lib/existingReadyAdmission.ts +125 -0
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/service/instance.ts +72 -25
- package/vona/src/suite-vendor/a-vona/modules/a-instance/test/existingReadyAdmission.test.ts +949 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +61 -59
- package/vona/src/suite-vendor/a-vona/modules/a-mail/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-mail/src/service/mail.ts +5 -0
- package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +61 -2
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/package.original.json +0 -1
- package/zova/packages-cli/cli/package.json +4 -4
- package/zova/packages-cli/cli-set-front/package.json +2 -2
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +7 -78
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { getRequestBoundary } from './requestBoundary.ts';
|
|
2
|
+
|
|
3
|
+
declare const capabilityBrand: unique symbol;
|
|
4
|
+
export interface ExistingReadyCapability {
|
|
5
|
+
readonly [capabilityBrand]: true;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CompletedReadiness {
|
|
9
|
+
snapshot(): Readonly<{ application: 'starting' | 'ready' | 'failed' | 'closing' }>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface RegistryRow {
|
|
13
|
+
readonly id: number;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly disabled: boolean;
|
|
16
|
+
readonly isolate: boolean;
|
|
17
|
+
readonly config: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Provision {
|
|
21
|
+
readonly registry: object;
|
|
22
|
+
readonly route: object;
|
|
23
|
+
readonly handle: object;
|
|
24
|
+
readonly isolate: boolean;
|
|
25
|
+
readonly functions: ReadonlySet<Function>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Publication {
|
|
29
|
+
readonly attempt: object;
|
|
30
|
+
readonly provision: Provision;
|
|
31
|
+
readonly row: RegistryRow;
|
|
32
|
+
readonly config: object;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Owner {
|
|
36
|
+
readonly app: object;
|
|
37
|
+
application: 'starting' | 'ready' | 'failed' | 'closing';
|
|
38
|
+
applicationAttempt: object;
|
|
39
|
+
readonly attempts: Map<string, object>;
|
|
40
|
+
readonly provisions: Map<string, Provision>;
|
|
41
|
+
readonly publications: Map<string, Publication>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const owners = new WeakMap<CompletedReadiness, Owner>();
|
|
45
|
+
const capabilities = new WeakMap<
|
|
46
|
+
ExistingReadyCapability,
|
|
47
|
+
{ owner: CompletedReadiness; publication: Publication; deadline: number; now: () => number }
|
|
48
|
+
>();
|
|
49
|
+
const captures = new WeakSet<object>();
|
|
50
|
+
const pins = new WeakMap<object, Publication>();
|
|
51
|
+
|
|
52
|
+
export function createCompletedReadiness(appIdentity: object): CompletedReadiness {
|
|
53
|
+
const state: Owner = {
|
|
54
|
+
app: appIdentity,
|
|
55
|
+
application: 'starting',
|
|
56
|
+
applicationAttempt: Object.freeze({}),
|
|
57
|
+
attempts: new Map(),
|
|
58
|
+
provisions: new Map(),
|
|
59
|
+
publications: new Map(),
|
|
60
|
+
};
|
|
61
|
+
const facade = Object.freeze({
|
|
62
|
+
snapshot: () => Object.freeze({ application: state.application }),
|
|
63
|
+
});
|
|
64
|
+
owners.set(facade, state);
|
|
65
|
+
return facade;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Internal framework integration only. None of the issuance/provisioning helpers are barrel exports.
|
|
69
|
+
export function beginCompletedApplication(owner: CompletedReadiness): object {
|
|
70
|
+
const state = owners.get(owner)!;
|
|
71
|
+
state.publications.clear();
|
|
72
|
+
state.attempts.clear();
|
|
73
|
+
state.applicationAttempt = Object.freeze({});
|
|
74
|
+
if (state.application !== 'closing') state.application = 'starting';
|
|
75
|
+
return state.applicationAttempt;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function finishCompletedApplication(
|
|
79
|
+
owner: CompletedReadiness,
|
|
80
|
+
attempt: object,
|
|
81
|
+
success: boolean,
|
|
82
|
+
): void {
|
|
83
|
+
const state = owners.get(owner)!;
|
|
84
|
+
if (state.application === 'closing' || state.applicationAttempt !== attempt) return;
|
|
85
|
+
state.application = success ? 'ready' : 'failed';
|
|
86
|
+
if (!success) state.publications.clear();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function closeCompletedReadiness(owner: CompletedReadiness): void {
|
|
90
|
+
const state = owners.get(owner)!;
|
|
91
|
+
state.application = 'closing';
|
|
92
|
+
state.publications.clear();
|
|
93
|
+
state.attempts.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function invalidateCompletedInstance(
|
|
97
|
+
owner: CompletedReadiness | undefined,
|
|
98
|
+
name: string,
|
|
99
|
+
): void {
|
|
100
|
+
if (!owner) return;
|
|
101
|
+
const state = owners.get(owner)!;
|
|
102
|
+
state.attempts.set(name, Object.freeze({}));
|
|
103
|
+
state.publications.delete(name);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function beginCompletedInstance(
|
|
107
|
+
owner: CompletedReadiness | undefined,
|
|
108
|
+
name: string,
|
|
109
|
+
): object | undefined {
|
|
110
|
+
if (!owner) return;
|
|
111
|
+
invalidateCompletedInstance(owner, name);
|
|
112
|
+
return owners.get(owner)!.attempts.get(name);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function rowCopy(value: unknown): RegistryRow | undefined {
|
|
116
|
+
if (!value || typeof value !== 'object') return;
|
|
117
|
+
const fields = ['id', 'name', 'disabled', 'isolate', 'config'];
|
|
118
|
+
const data: Record<string, unknown> = Object.create(null);
|
|
119
|
+
for (const key of fields) {
|
|
120
|
+
const desc = Object.getOwnPropertyDescriptor(value, key);
|
|
121
|
+
if (!desc || !('value' in desc)) return;
|
|
122
|
+
data[key] = desc.value;
|
|
123
|
+
}
|
|
124
|
+
if (
|
|
125
|
+
typeof data.id !== 'number' ||
|
|
126
|
+
!Number.isSafeInteger(data.id) ||
|
|
127
|
+
data.id <= 0 ||
|
|
128
|
+
typeof data.name !== 'string' ||
|
|
129
|
+
!data.name.isWellFormed() ||
|
|
130
|
+
data.name.includes('\0') ||
|
|
131
|
+
Buffer.byteLength(data.name) > 256 ||
|
|
132
|
+
data.disabled !== false ||
|
|
133
|
+
typeof data.isolate !== 'boolean' ||
|
|
134
|
+
!(
|
|
135
|
+
data.config === null ||
|
|
136
|
+
(typeof data.config === 'string' &&
|
|
137
|
+
data.config.isWellFormed() &&
|
|
138
|
+
Buffer.byteLength(data.config) <= 65_536)
|
|
139
|
+
)
|
|
140
|
+
) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
return Object.freeze(data) as unknown as RegistryRow;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function detach(
|
|
147
|
+
value: unknown,
|
|
148
|
+
functions: ReadonlySet<Function>,
|
|
149
|
+
active = new Set<object>(),
|
|
150
|
+
depth = 0,
|
|
151
|
+
budget = { nodes: 0 },
|
|
152
|
+
): unknown {
|
|
153
|
+
if (++budget.nodes > 100_000 || depth > 64)
|
|
154
|
+
throw new Error('Unsupported completed configuration');
|
|
155
|
+
if (
|
|
156
|
+
value === null ||
|
|
157
|
+
value === undefined ||
|
|
158
|
+
typeof value === 'string' ||
|
|
159
|
+
typeof value === 'boolean' ||
|
|
160
|
+
(typeof value === 'number' && Number.isFinite(value))
|
|
161
|
+
) {
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
if (typeof value === 'function') {
|
|
165
|
+
if (!functions.has(value) || !Object.isFrozen(value))
|
|
166
|
+
throw new Error('Unsupported completed configuration');
|
|
167
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
168
|
+
const desc = Object.getOwnPropertyDescriptor(value, key)!;
|
|
169
|
+
if (
|
|
170
|
+
!('value' in desc) ||
|
|
171
|
+
desc.writable ||
|
|
172
|
+
(desc.value && (typeof desc.value === 'object' || typeof desc.value === 'function'))
|
|
173
|
+
) {
|
|
174
|
+
throw new Error('Unsupported completed configuration');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
if (typeof value !== 'object' || !value || active.has(value))
|
|
180
|
+
throw new Error('Unsupported completed configuration');
|
|
181
|
+
const array = Array.isArray(value);
|
|
182
|
+
const proto = Object.getPrototypeOf(value);
|
|
183
|
+
if (
|
|
184
|
+
(!array && proto !== Object.prototype && proto !== null) ||
|
|
185
|
+
(array && proto !== Array.prototype)
|
|
186
|
+
) {
|
|
187
|
+
throw new Error('Unsupported completed configuration');
|
|
188
|
+
}
|
|
189
|
+
active.add(value);
|
|
190
|
+
const result = array ? [] : Object.create(null);
|
|
191
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
192
|
+
if (array && key === 'length') continue;
|
|
193
|
+
if (typeof key !== 'string') throw new Error('Unsupported completed configuration');
|
|
194
|
+
const desc = Object.getOwnPropertyDescriptor(value, key)!;
|
|
195
|
+
if (!('value' in desc)) throw new Error('Unsupported completed configuration');
|
|
196
|
+
Object.defineProperty(result, key, {
|
|
197
|
+
value: detach(desc.value, functions, active, depth + 1, budget),
|
|
198
|
+
enumerable: desc.enumerable,
|
|
199
|
+
configurable: false,
|
|
200
|
+
writable: false,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (array) Object.defineProperty(result, 'length', { value: (value as unknown[]).length });
|
|
204
|
+
active.delete(value);
|
|
205
|
+
return Object.freeze(result);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Controlled inert qualification seam. Production provisioning is intentionally not supplied.
|
|
209
|
+
export function provisionCompletedInstanceForTest(
|
|
210
|
+
owner: CompletedReadiness,
|
|
211
|
+
name: string,
|
|
212
|
+
input: {
|
|
213
|
+
readonly registry: object;
|
|
214
|
+
readonly route: object;
|
|
215
|
+
readonly handle: object;
|
|
216
|
+
readonly isolate: boolean;
|
|
217
|
+
readonly functions?: readonly Function[];
|
|
218
|
+
},
|
|
219
|
+
): void {
|
|
220
|
+
if (getRequestBoundary()) throw new Error('Provisioning requires lifecycle boundary');
|
|
221
|
+
const state = owners.get(owner)!;
|
|
222
|
+
invalidateCompletedInstance(owner, name);
|
|
223
|
+
state.provisions.set(
|
|
224
|
+
name,
|
|
225
|
+
Object.freeze({ ...input, functions: new Set(input.functions ?? []) }),
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function captureCompletedInstance(
|
|
230
|
+
owner: CompletedReadiness | undefined,
|
|
231
|
+
name: string,
|
|
232
|
+
attempt: object | undefined,
|
|
233
|
+
row: unknown,
|
|
234
|
+
config: unknown,
|
|
235
|
+
): object | undefined {
|
|
236
|
+
if (!owner || !attempt) return;
|
|
237
|
+
const state = owners.get(owner)!;
|
|
238
|
+
const provision = state.provisions.get(name);
|
|
239
|
+
if (!provision || state.application === 'closing' || state.attempts.get(name) !== attempt) return;
|
|
240
|
+
try {
|
|
241
|
+
const capturedRow = rowCopy(row);
|
|
242
|
+
if (!capturedRow || capturedRow.name !== name || capturedRow.isolate !== provision.isolate)
|
|
243
|
+
return;
|
|
244
|
+
const snapshot = detach(config, provision.functions);
|
|
245
|
+
if (!snapshot || typeof snapshot !== 'object') return;
|
|
246
|
+
const captured = Object.freeze({ attempt, provision, row: capturedRow, config: snapshot });
|
|
247
|
+
captures.add(captured);
|
|
248
|
+
return captured;
|
|
249
|
+
} catch {
|
|
250
|
+
// Unsupported snapshots leave admission unavailable without inspecting the failure.
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function finishCompletedInstance(
|
|
255
|
+
owner: CompletedReadiness | undefined,
|
|
256
|
+
name: string,
|
|
257
|
+
attempt: object | undefined,
|
|
258
|
+
captured: object | undefined,
|
|
259
|
+
): void {
|
|
260
|
+
if (!owner || !attempt || !captured || !captures.has(captured)) return;
|
|
261
|
+
const state = owners.get(owner)!;
|
|
262
|
+
const publication = captured as Publication;
|
|
263
|
+
if (
|
|
264
|
+
state.application !== 'closing' &&
|
|
265
|
+
state.application !== 'failed' &&
|
|
266
|
+
state.attempts.get(name) === attempt &&
|
|
267
|
+
publication.attempt === attempt &&
|
|
268
|
+
state.provisions.get(name) === publication.provision
|
|
269
|
+
) {
|
|
270
|
+
state.publications.set(name, publication);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function getCompletedPublication(
|
|
275
|
+
owner: CompletedReadiness,
|
|
276
|
+
name: string,
|
|
277
|
+
registry: object,
|
|
278
|
+
route: object,
|
|
279
|
+
): object | undefined {
|
|
280
|
+
const state = owners.get(owner);
|
|
281
|
+
const publication = state?.publications.get(name);
|
|
282
|
+
if (
|
|
283
|
+
state?.application !== 'ready' ||
|
|
284
|
+
!publication ||
|
|
285
|
+
publication.provision.registry !== registry ||
|
|
286
|
+
publication.provision.route !== route
|
|
287
|
+
) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
return publication;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function matchesCompletedRow(publication: object, row: unknown): boolean {
|
|
294
|
+
try {
|
|
295
|
+
const next = rowCopy(row),
|
|
296
|
+
prior = (publication as Publication).row;
|
|
297
|
+
return (
|
|
298
|
+
!!next &&
|
|
299
|
+
next.id === prior.id &&
|
|
300
|
+
next.name === prior.name &&
|
|
301
|
+
next.isolate === prior.isolate &&
|
|
302
|
+
next.config === prior.config
|
|
303
|
+
);
|
|
304
|
+
} catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function issueExistingReadyCapability(
|
|
310
|
+
owner: CompletedReadiness,
|
|
311
|
+
publication: object,
|
|
312
|
+
deadline: number,
|
|
313
|
+
now: () => number,
|
|
314
|
+
): ExistingReadyCapability {
|
|
315
|
+
if (!captures.has(publication)) throw new Error('Existing-ready unavailable');
|
|
316
|
+
const capability = Object.freeze({}) as ExistingReadyCapability;
|
|
317
|
+
capabilities.set(capability, { owner, publication: publication as Publication, deadline, now });
|
|
318
|
+
return capability;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function resolveExistingReadyCapability(
|
|
322
|
+
owner: CompletedReadiness,
|
|
323
|
+
capability: ExistingReadyCapability,
|
|
324
|
+
app?: object,
|
|
325
|
+
): { readonly publication: object; readonly name: string; readonly handle: object } | undefined {
|
|
326
|
+
const issued = capabilities.get(capability),
|
|
327
|
+
state = owners.get(owner);
|
|
328
|
+
if (
|
|
329
|
+
!issued ||
|
|
330
|
+
issued.owner !== owner ||
|
|
331
|
+
!state ||
|
|
332
|
+
state.application !== 'ready' ||
|
|
333
|
+
(app && app !== state.app)
|
|
334
|
+
) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
const current = issued.now();
|
|
339
|
+
if (!Number.isFinite(current) || current >= issued.deadline) return;
|
|
340
|
+
} catch {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const publication = issued.publication;
|
|
344
|
+
if (
|
|
345
|
+
state.publications.get(publication.row.name) !== publication ||
|
|
346
|
+
state.attempts.get(publication.row.name) !== publication.attempt
|
|
347
|
+
) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
return Object.freeze({
|
|
351
|
+
publication,
|
|
352
|
+
name: publication.row.name,
|
|
353
|
+
handle: publication.provision.handle,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function pinExistingReadyContext(
|
|
358
|
+
owner: CompletedReadiness,
|
|
359
|
+
capability: ExistingReadyCapability,
|
|
360
|
+
app: object,
|
|
361
|
+
context: object,
|
|
362
|
+
): boolean {
|
|
363
|
+
const resolved = resolveExistingReadyCapability(owner, capability, app);
|
|
364
|
+
if (!resolved || pins.has(context)) return false;
|
|
365
|
+
const publication = resolved.publication as Publication;
|
|
366
|
+
pins.set(context, publication);
|
|
367
|
+
for (const [key, value] of [
|
|
368
|
+
['config', publication.config],
|
|
369
|
+
['instance', publication.row],
|
|
370
|
+
['instanceName', publication.row.name],
|
|
371
|
+
['innerAccess', false],
|
|
372
|
+
] as const) {
|
|
373
|
+
Object.defineProperty(context, key, {
|
|
374
|
+
value,
|
|
375
|
+
enumerable: true,
|
|
376
|
+
configurable: false,
|
|
377
|
+
writable: false,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function getExistingReadyContextConfig(context: object): object | undefined {
|
|
384
|
+
return pins.get(context)?.config;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function isExistingReadyContext(context: object | undefined | null): boolean {
|
|
388
|
+
return !!context && pins.has(context);
|
|
389
|
+
}
|
|
@@ -5,6 +5,7 @@ import type { MetadataKey } from './metadata.ts';
|
|
|
5
5
|
|
|
6
6
|
import { cast } from '../../types/utils/cast.ts';
|
|
7
7
|
import { BeanContainer } from '../bean/beanContainer.ts';
|
|
8
|
+
import { getExistingReadyContextConfig } from './completedReadiness.ts';
|
|
8
9
|
import { appResource } from './resource.ts';
|
|
9
10
|
|
|
10
11
|
const BEAN = Symbol.for('Context#__bean');
|
|
@@ -46,6 +47,8 @@ export const contextBase: ContextBase = {
|
|
|
46
47
|
},
|
|
47
48
|
get config() {
|
|
48
49
|
const self = cast(this);
|
|
50
|
+
const pinned = getExistingReadyContextConfig(self);
|
|
51
|
+
if (pinned) return pinned as any;
|
|
49
52
|
const serviceInstance = cast(self.app.bean._getBean('a-instance.service.instance' as never));
|
|
50
53
|
return serviceInstance.getConfig(self.instanceName) || self.app.config;
|
|
51
54
|
},
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
export * from './application.ts';
|
|
2
2
|
export * from './config.ts';
|
|
3
|
+
export {
|
|
4
|
+
beginCompletedInstance,
|
|
5
|
+
captureCompletedInstance,
|
|
6
|
+
createCompletedReadiness,
|
|
7
|
+
finishCompletedInstance,
|
|
8
|
+
invalidateCompletedInstance,
|
|
9
|
+
isExistingReadyContext,
|
|
10
|
+
pinExistingReadyContext,
|
|
11
|
+
resolveExistingReadyCapability,
|
|
12
|
+
} from './completedReadiness.ts';
|
|
13
|
+
export type { CompletedReadiness, ExistingReadyCapability } from './completedReadiness.ts';
|
|
3
14
|
export * from './hmr.ts';
|
|
4
15
|
export * from './hmrDeps.ts';
|
|
5
16
|
export * from './isolationGuard.ts';
|
|
@@ -15,6 +15,7 @@ import type { AppResource } from './resource.ts';
|
|
|
15
15
|
import { EnumAppEvent } from '../../types/index.ts';
|
|
16
16
|
import { BeanSimple } from '../bean/beanSimple.ts';
|
|
17
17
|
import { AppLocale, BeanScopeContainer } from '../bean/index.ts';
|
|
18
|
+
import { closeCompletedReadiness, createCompletedReadiness } from './completedReadiness.ts';
|
|
18
19
|
import { CtxCounter } from './ctxCounter.ts';
|
|
19
20
|
import { AppHmr } from './hmr.ts';
|
|
20
21
|
import { AppLogger } from './logger/appLogger.ts';
|
|
@@ -51,6 +52,8 @@ export class AppMeta extends BeanSimple {
|
|
|
51
52
|
hmrCacheLocaleModules: TypeModuleResourceLocaleModules;
|
|
52
53
|
hmrCacheConfigModules: IBeanScopeConfig;
|
|
53
54
|
//
|
|
55
|
+
completedReadiness: ReturnType<typeof createCompletedReadiness>;
|
|
56
|
+
|
|
54
57
|
appReady: boolean;
|
|
55
58
|
appReadyInstances: Record<string, boolean>;
|
|
56
59
|
//
|
|
@@ -61,6 +64,7 @@ export class AppMeta extends BeanSimple {
|
|
|
61
64
|
appClosed: boolean;
|
|
62
65
|
|
|
63
66
|
protected __init__(env: VonaConfigEnv) {
|
|
67
|
+
this.completedReadiness = createCompletedReadiness(this.app);
|
|
64
68
|
// env
|
|
65
69
|
this.env = env;
|
|
66
70
|
this._prepareEnv();
|
|
@@ -121,6 +125,7 @@ export class AppMeta extends BeanSimple {
|
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
async close() {
|
|
128
|
+
closeCompletedReadiness(this.completedReadiness);
|
|
124
129
|
if (!this[SymbolClosePromise]) {
|
|
125
130
|
this[SymbolClosePromise] = this._closeInner();
|
|
126
131
|
}
|
|
@@ -31,6 +31,7 @@ const lifecycleSource = `
|
|
|
31
31
|
return nextResolve(specifier, context);
|
|
32
32
|
},
|
|
33
33
|
});
|
|
34
|
+
const { createCompletedReadiness } = await import(new URL('../core/completedReadiness.ts', startUrl).href);
|
|
34
35
|
const { Start } = await import(startUrl);
|
|
35
36
|
const [projectPath, lockPath, lockContents, failingPhase] = process.argv.slice(1);
|
|
36
37
|
const phases = [];
|
|
@@ -39,6 +40,13 @@ const lifecycleSource = `
|
|
|
39
40
|
globalThis.phase = async phase => {
|
|
40
41
|
assert.equal(await readFile(lockPath, 'utf8'), lockContents);
|
|
41
42
|
phases.push(phase);
|
|
43
|
+
assert.equal(app.meta.completedReadiness.snapshot().application, 'starting');
|
|
44
|
+
if (phase === 'Started') {
|
|
45
|
+
assert.equal(app.meta.appReady, true);
|
|
46
|
+
assert.equal(app.meta.appStarted, true);
|
|
47
|
+
await Promise.resolve();
|
|
48
|
+
assert.equal(app.meta.completedReadiness.snapshot().application, 'starting');
|
|
49
|
+
}
|
|
42
50
|
if (phase === failingPhase) throw failure;
|
|
43
51
|
};
|
|
44
52
|
const config = {
|
|
@@ -62,6 +70,7 @@ const lifecycleSource = `
|
|
|
62
70
|
events.push({ event, error, phases: [...phases] });
|
|
63
71
|
},
|
|
64
72
|
};
|
|
73
|
+
app.meta.completedReadiness = createCompletedReadiness(app);
|
|
65
74
|
const start = new Start(app);
|
|
66
75
|
const order = ['Config', 'Freshness', 'Load', 'Start', 'Ready', 'Started'];
|
|
67
76
|
if (failingPhase) {
|
|
@@ -76,6 +85,7 @@ const lifecycleSource = `
|
|
|
76
85
|
assert.deepEqual(phases, order);
|
|
77
86
|
assert.equal(app.meta.appStartError, undefined);
|
|
78
87
|
}
|
|
88
|
+
assert.equal(app.meta.completedReadiness.snapshot().application, failingPhase ? 'failed' : 'ready');
|
|
79
89
|
const reachesStarted = !failingPhase || failingPhase === 'Started';
|
|
80
90
|
assert.equal(app.meta.appStarted, reachesStarted ? true : undefined);
|
|
81
91
|
const reachesReady = reachesStarted || failingPhase === 'Ready';
|
|
@@ -2,6 +2,10 @@ import type { VonaConfig, VonaConfigOptional } from '../../types/index.ts';
|
|
|
2
2
|
import type { VonaApplication } from '../core/application.ts';
|
|
3
3
|
|
|
4
4
|
import { EnumAppEvent } from '../../types/index.ts';
|
|
5
|
+
import {
|
|
6
|
+
beginCompletedApplication,
|
|
7
|
+
finishCompletedApplication,
|
|
8
|
+
} from '../core/completedReadiness.ts';
|
|
5
9
|
import { assertIsolatedTestConfig } from '../core/isolationGuard.ts';
|
|
6
10
|
import { registerTestClaimResourceInventory } from '../core/testClaim.ts';
|
|
7
11
|
import { assertFreshTestResources } from '../core/testFreshness.ts';
|
|
@@ -17,6 +21,8 @@ export class Start {
|
|
|
17
21
|
|
|
18
22
|
async start() {
|
|
19
23
|
const app = this.app;
|
|
24
|
+
const owner = app.meta.completedReadiness;
|
|
25
|
+
const attempt = beginCompletedApplication(owner);
|
|
20
26
|
try {
|
|
21
27
|
await this._start_appConfig();
|
|
22
28
|
await assertFreshTestResources(app);
|
|
@@ -24,7 +30,9 @@ export class Start {
|
|
|
24
30
|
await this._start_appStart();
|
|
25
31
|
await this._start_appReady();
|
|
26
32
|
await this._start_appStarted();
|
|
33
|
+
finishCompletedApplication(owner, attempt, true);
|
|
27
34
|
} catch (err) {
|
|
35
|
+
finishCompletedApplication(owner, attempt, false);
|
|
28
36
|
// record
|
|
29
37
|
app.meta.appStartError = err as Error;
|
|
30
38
|
// event: appReadyError
|