@fluojs/runtime 2.0.1 → 3.0.0

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 (127) hide show
  1. package/README.ko.md +278 -20
  2. package/README.md +278 -20
  3. package/dist/adapters/internal-request-response-factory.d.ts +1 -1
  4. package/dist/adapters/internal-request-response-factory.d.ts.map +1 -1
  5. package/dist/adapters/internal-request-response-factory.js +1 -1
  6. package/dist/adapters/request-response-factory.d.ts +20 -1
  7. package/dist/adapters/request-response-factory.d.ts.map +1 -1
  8. package/dist/adapters/request-response-factory.js +75 -22
  9. package/dist/bootstrap.d.ts +3 -9
  10. package/dist/bootstrap.d.ts.map +1 -1
  11. package/dist/bootstrap.js +179 -75
  12. package/dist/devtools/contracts.d.ts +3 -135
  13. package/dist/devtools/contracts.d.ts.map +1 -1
  14. package/dist/devtools/index.d.ts +2 -3
  15. package/dist/devtools/index.d.ts.map +1 -1
  16. package/dist/devtools/index.js +1 -2
  17. package/dist/devtools/snapshot.d.ts +1 -1
  18. package/dist/devtools/snapshot.d.ts.map +1 -1
  19. package/dist/devtools/snapshot.js +18 -17
  20. package/dist/devtools/studio-request-observer.d.ts +14 -0
  21. package/dist/devtools/studio-request-observer.d.ts.map +1 -0
  22. package/dist/devtools/studio-request-observer.js +112 -0
  23. package/dist/devtools/studio-runtime-config.d.ts +25 -0
  24. package/dist/devtools/studio-runtime-config.d.ts.map +1 -0
  25. package/dist/devtools/studio-runtime-config.js +59 -0
  26. package/dist/devtools/studio-runtime.d.ts +12 -18
  27. package/dist/devtools/studio-runtime.d.ts.map +1 -1
  28. package/dist/devtools/studio-runtime.js +25 -169
  29. package/dist/errors.d.ts +26 -0
  30. package/dist/errors.d.ts.map +1 -1
  31. package/dist/errors.js +38 -0
  32. package/dist/health/health.d.ts +6 -2
  33. package/dist/health/health.d.ts.map +1 -1
  34. package/dist/health/health.js +3 -2
  35. package/dist/http-adapter-shared.d.ts +1 -1
  36. package/dist/http-adapter-shared.d.ts.map +1 -1
  37. package/dist/http-adapter-shared.js +1 -1
  38. package/dist/index.d.ts +6 -3
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +4 -1
  41. package/dist/internal/route-inspection-metadata.d.ts +30 -0
  42. package/dist/internal/route-inspection-metadata.d.ts.map +1 -0
  43. package/dist/internal/route-inspection-metadata.js +68 -0
  44. package/dist/internal.d.ts +6 -1
  45. package/dist/internal.d.ts.map +1 -1
  46. package/dist/internal.js +4 -1
  47. package/dist/module-definition.d.ts +10 -0
  48. package/dist/module-definition.d.ts.map +1 -0
  49. package/dist/module-definition.js +12 -0
  50. package/dist/module-graph.d.ts +41 -0
  51. package/dist/module-graph.d.ts.map +1 -1
  52. package/dist/module-graph.js +108 -13
  53. package/dist/multipart.d.ts +67 -3
  54. package/dist/multipart.d.ts.map +1 -1
  55. package/dist/multipart.js +665 -103
  56. package/dist/platform-component-registry.d.ts +9 -0
  57. package/dist/platform-component-registry.d.ts.map +1 -0
  58. package/dist/platform-component-registry.js +70 -0
  59. package/dist/platform-diagnostic-retention.d.ts +13 -0
  60. package/dist/platform-diagnostic-retention.d.ts.map +1 -0
  61. package/dist/platform-diagnostic-retention.js +43 -0
  62. package/dist/platform-shell-probe-reports.d.ts +8 -0
  63. package/dist/platform-shell-probe-reports.d.ts.map +1 -0
  64. package/dist/platform-shell-probe-reports.js +95 -0
  65. package/dist/platform-shell-probes.d.ts +16 -0
  66. package/dist/platform-shell-probes.d.ts.map +1 -0
  67. package/dist/platform-shell-probes.js +141 -0
  68. package/dist/platform-shell.d.ts +7 -13
  69. package/dist/platform-shell.d.ts.map +1 -1
  70. package/dist/platform-shell.js +58 -310
  71. package/dist/retryable-shutdown.d.ts +7 -0
  72. package/dist/retryable-shutdown.d.ts.map +1 -0
  73. package/dist/retryable-shutdown.js +20 -0
  74. package/dist/route-inspection.d.ts +56 -0
  75. package/dist/route-inspection.d.ts.map +1 -0
  76. package/dist/route-inspection.js +80 -0
  77. package/dist/tokens.d.ts +6 -4
  78. package/dist/tokens.d.ts.map +1 -1
  79. package/dist/tokens.js +5 -0
  80. package/dist/types.d.ts +53 -8
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/web.d.ts +22 -3
  83. package/dist/web.d.ts.map +1 -1
  84. package/dist/web.js +80 -34
  85. package/package.json +13 -26
  86. package/dist/internal-node.d.ts +0 -2
  87. package/dist/internal-node.d.ts.map +0 -1
  88. package/dist/internal-node.js +0 -1
  89. package/dist/logging/json-logger.d.ts +0 -8
  90. package/dist/logging/json-logger.d.ts.map +0 -1
  91. package/dist/logging/json-logger.js +0 -45
  92. package/dist/logging/logger.d.ts +0 -37
  93. package/dist/logging/logger.d.ts.map +0 -1
  94. package/dist/logging/logger.js +0 -103
  95. package/dist/node/internal-node-compression.d.ts +0 -22
  96. package/dist/node/internal-node-compression.d.ts.map +0 -1
  97. package/dist/node/internal-node-compression.js +0 -84
  98. package/dist/node/internal-node-request.d.ts +0 -162
  99. package/dist/node/internal-node-request.d.ts.map +0 -1
  100. package/dist/node/internal-node-request.js +0 -478
  101. package/dist/node/internal-node-response.d.ts +0 -28
  102. package/dist/node/internal-node-response.d.ts.map +0 -1
  103. package/dist/node/internal-node-response.js +0 -205
  104. package/dist/node/internal-node-shutdown.d.ts +0 -34
  105. package/dist/node/internal-node-shutdown.d.ts.map +0 -1
  106. package/dist/node/internal-node-shutdown.js +0 -83
  107. package/dist/node/internal-node.d.ts +0 -111
  108. package/dist/node/internal-node.d.ts.map +0 -1
  109. package/dist/node/internal-node.js +0 -283
  110. package/dist/node/node-compression.d.ts +0 -2
  111. package/dist/node/node-compression.d.ts.map +0 -1
  112. package/dist/node/node-compression.js +0 -1
  113. package/dist/node/node-request.d.ts +0 -2
  114. package/dist/node/node-request.d.ts.map +0 -1
  115. package/dist/node/node-request.js +0 -1
  116. package/dist/node/node-response.d.ts +0 -2
  117. package/dist/node/node-response.d.ts.map +0 -1
  118. package/dist/node/node-response.js +0 -1
  119. package/dist/node/node-shutdown.d.ts +0 -2
  120. package/dist/node/node-shutdown.d.ts.map +0 -1
  121. package/dist/node/node-shutdown.js +0 -1
  122. package/dist/node/node.d.ts +0 -2
  123. package/dist/node/node.d.ts.map +0 -1
  124. package/dist/node/node.js +0 -1
  125. package/dist/node.d.ts +0 -5
  126. package/dist/node.d.ts.map +0 -1
  127. package/dist/node.js +0 -3
@@ -1,113 +1,22 @@
1
1
  import { InvariantError } from '@fluojs/core';
2
- function isRegistration(value) {
3
- return typeof value === 'object' && value !== null && 'component' in value;
4
- }
5
- function normalizeRegistration(input) {
6
- if (isRegistration(input)) {
7
- return {
8
- component: input.component,
9
- dependencies: [...(input.dependencies ?? [])]
10
- };
11
- }
12
- return {
13
- component: input,
14
- dependencies: []
15
- };
16
- }
17
- function toRegisteredComponents(components) {
18
- return (components ?? []).map(component => normalizeRegistration(component));
19
- }
20
- function createUnknownFailureIssue(componentId, phase, error) {
21
- return {
22
- cause: error instanceof Error ? error.message : String(error),
23
- code: 'RUNTIME_PLATFORM_COMPONENT_FAILURE',
24
- componentId,
25
- fixHint: 'Inspect component implementation and ensure validate/start/ready/health/snapshot contracts are deterministic.',
26
- message: `Platform component failed during ${phase}.`,
27
- severity: 'error'
28
- };
29
- }
30
- function aggregateReadiness(reports) {
31
- const hasCriticalNotReady = reports.some(report => report.critical && report.status === 'not-ready');
32
- const hasNotReady = reports.some(report => report.status === 'not-ready');
33
- const hasDegraded = reports.some(report => report.status === 'degraded');
34
- const hasCritical = reports.some(report => report.critical);
35
- if (hasCriticalNotReady) {
36
- const reason = reports.find(report => report.critical && report.status === 'not-ready')?.reason;
37
- return {
38
- critical: hasCritical,
39
- reason: reason ?? 'One or more critical platform components are not ready.',
40
- status: 'not-ready'
41
- };
42
- }
43
- if (hasNotReady || hasDegraded) {
44
- const reason = reports.find(report => report.status !== 'ready')?.reason;
45
- return {
46
- critical: hasCritical,
47
- reason: reason ?? 'One or more platform components are degraded or not ready.',
48
- status: 'degraded'
49
- };
50
- }
51
- return {
52
- critical: hasCritical,
53
- status: 'ready'
54
- };
55
- }
56
- function aggregateHealth(reports) {
57
- const hasUnhealthy = reports.some(report => report.status === 'unhealthy');
58
- const hasDegraded = reports.some(report => report.status === 'degraded');
59
- if (hasUnhealthy) {
60
- const reason = reports.find(report => report.status === 'unhealthy')?.reason;
61
- return {
62
- reason: reason ?? 'One or more platform components are unhealthy.',
63
- status: 'unhealthy'
64
- };
65
- }
66
- if (hasDegraded) {
67
- const reason = reports.find(report => report.status === 'degraded')?.reason;
68
- return {
69
- reason: reason ?? 'One or more platform components are degraded.',
70
- status: 'degraded'
71
- };
72
- }
73
- return {
74
- status: 'healthy'
75
- };
76
- }
77
- function createEmptyShellSnapshot(diagnostics) {
78
- return {
79
- components: [],
80
- diagnostics,
81
- generatedAt: new Date().toISOString(),
82
- health: {
83
- status: 'healthy'
84
- },
85
- readiness: {
86
- critical: false,
87
- status: 'ready'
88
- }
89
- };
90
- }
91
- function normalizeSnapshot(snapshot, component, dependencies) {
92
- return {
93
- ...snapshot,
94
- dependencies: [...dependencies],
95
- id: component.id,
96
- kind: component.kind
97
- };
98
- }
2
+ import { PlatformLifecycleConflictError } from './errors.js';
3
+ import { assertValidPlatformComponentGraph, orderPlatformComponents, registerPlatformComponents } from './platform-component-registry.js';
4
+ import { createPlatformFailureIssue, createPlatformValidationFailureIssue, PlatformDiagnosticRetention } from './platform-diagnostic-retention.js';
5
+ import { PlatformShellProbeCollector } from './platform-shell-probes.js';
99
6
  /**
100
7
  * A runtime implementation of the {@link PlatformShell} that manages the lifecycle
101
8
  * of registered platform components, including dependency ordering and diagnostics.
102
9
  */
103
10
  export class RuntimePlatformShell {
104
11
  started = false;
105
- stopped = false;
106
12
  orderedComponents = [];
107
13
  rollbackPendingComponents = [];
108
- diagnostics = [];
14
+ diagnostics = new PlatformDiagnosticRetention();
15
+ probes;
16
+ activeLifecycleTransition;
109
17
  constructor(registeredComponents) {
110
18
  this.registeredComponents = registeredComponents;
19
+ this.probes = new PlatformShellProbeCollector(registeredComponents, this.diagnostics);
111
20
  }
112
21
 
113
22
  /**
@@ -117,31 +26,55 @@ export class RuntimePlatformShell {
117
26
  * @returns A new {@link RuntimePlatformShell} instance.
118
27
  */
119
28
  static fromInputs(components) {
120
- return new RuntimePlatformShell(toRegisteredComponents(components));
29
+ return new RuntimePlatformShell(registerPlatformComponents(components));
121
30
  }
122
31
  hasRegisteredComponents() {
123
32
  return this.registeredComponents.length > 0;
124
33
  }
125
- async start() {
34
+ start() {
35
+ return this.runLifecycleTransition('start', () => this.startComponents());
36
+ }
37
+ stop() {
38
+ return this.runLifecycleTransition('stop', () => this.stopComponents());
39
+ }
40
+ runLifecycleTransition(operation, run) {
41
+ const activeTransition = this.activeLifecycleTransition;
42
+ if (activeTransition) {
43
+ return Promise.reject(new PlatformLifecycleConflictError(activeTransition.operation, operation));
44
+ }
45
+ const transition = {
46
+ operation
47
+ };
48
+ this.activeLifecycleTransition = transition;
49
+ const promise = Promise.resolve(run());
50
+ const clearActiveTransition = () => {
51
+ if (this.activeLifecycleTransition === transition) {
52
+ this.activeLifecycleTransition = undefined;
53
+ }
54
+ };
55
+ void promise.then(clearActiveTransition, clearActiveTransition);
56
+ return promise;
57
+ }
58
+ async startComponents() {
126
59
  if (!this.hasRegisteredComponents() || this.started) {
127
60
  return;
128
61
  }
129
62
  if (this.rollbackPendingComponents.length > 0) {
130
- await this.stop();
63
+ await this.stopComponents();
131
64
  }
132
- this.validateIdentityAndDependencies();
65
+ assertValidPlatformComponentGraph(this.registeredComponents);
133
66
  const validationFailures = await this.validateComponents();
134
67
  if (validationFailures.length > 0) {
135
68
  throw new InvariantError(`Platform shell validation failed: ${validationFailures.map(issue => `${issue.componentId}:${issue.code}`).join(', ')}`);
136
69
  }
137
- this.orderedComponents = this.orderByDependency();
70
+ this.orderedComponents = orderPlatformComponents(this.registeredComponents);
138
71
  const startedComponents = [];
139
72
  for (const component of this.orderedComponents) {
140
73
  try {
141
74
  await component.component.start();
142
75
  startedComponents.push(component);
143
76
  } catch (error) {
144
- this.diagnostics.push(createUnknownFailureIssue(component.component.id, 'start', error));
77
+ this.diagnostics.append([createPlatformFailureIssue(component.component.id, 'start', error)]);
145
78
  const startFailure = new InvariantError(`Platform component "${component.component.id}" failed to start: ${error instanceof Error ? error.message : String(error)}`, {
146
79
  cause: error
147
80
  });
@@ -149,170 +82,32 @@ export class RuntimePlatformShell {
149
82
  await this.stopStartedComponents(startedComponents);
150
83
  this.rollbackPendingComponents = [];
151
84
  } catch (rollbackError) {
152
- this.rollbackPendingComponents = [...startedComponents];
153
- this.diagnostics.push(createUnknownFailureIssue(component.component.id, 'start-rollback', rollbackError));
85
+ this.diagnostics.append([createPlatformFailureIssue(component.component.id, 'start-rollback', rollbackError)]);
154
86
  }
155
87
  throw startFailure;
156
88
  }
157
89
  }
158
90
  this.started = true;
159
- this.stopped = false;
160
91
  this.rollbackPendingComponents = [];
161
92
  }
162
- async stop() {
93
+ async stopComponents() {
163
94
  const hasRollbackPending = this.rollbackPendingComponents.length > 0;
164
- if (!this.started && !hasRollbackPending || this.stopped) {
95
+ if (!this.started && !hasRollbackPending) {
165
96
  return;
166
97
  }
167
98
  const toStop = hasRollbackPending ? [...this.rollbackPendingComponents] : this.orderedComponents.length > 0 ? [...this.orderedComponents] : [...this.registeredComponents];
99
+ this.started = false;
168
100
  await this.stopStartedComponents(toStop);
169
101
  this.rollbackPendingComponents = [];
170
- this.started = false;
171
- this.stopped = true;
172
102
  }
173
- async ready() {
174
- if (!this.hasRegisteredComponents()) {
175
- return {
176
- critical: false,
177
- status: 'ready'
178
- };
179
- }
180
- const reports = [];
181
- for (const component of this.registeredComponents) {
182
- try {
183
- reports.push(await component.component.ready());
184
- } catch (error) {
185
- const issue = createUnknownFailureIssue(component.component.id, 'ready', error);
186
- this.diagnostics.push(issue);
187
- reports.push({
188
- critical: true,
189
- reason: issue.cause,
190
- status: 'not-ready'
191
- });
192
- }
193
- }
194
- return aggregateReadiness(reports);
103
+ ready() {
104
+ return this.probes.ready();
195
105
  }
196
- async health() {
197
- if (!this.hasRegisteredComponents()) {
198
- return {
199
- status: 'healthy'
200
- };
201
- }
202
- const reports = [];
203
- for (const component of this.registeredComponents) {
204
- try {
205
- reports.push(await component.component.health());
206
- } catch (error) {
207
- const issue = createUnknownFailureIssue(component.component.id, 'health', error);
208
- this.diagnostics.push(issue);
209
- reports.push({
210
- reason: issue.cause,
211
- status: 'unhealthy'
212
- });
213
- }
214
- }
215
- return aggregateHealth(reports);
216
- }
217
- async snapshot() {
218
- if (!this.hasRegisteredComponents()) {
219
- return createEmptyShellSnapshot([...this.diagnostics]);
220
- }
221
- const probeResults = await Promise.all(this.registeredComponents.map(registration => this.collectSnapshotProbe(registration)));
222
- const components = probeResults.map(result => result.snapshot);
223
- const readiness = aggregateReadiness(probeResults.map(result => result.readiness));
224
- const health = aggregateHealth(probeResults.map(result => result.health));
225
- const diagnostics = probeResults.flatMap(result => result.diagnostics);
226
- this.diagnostics.push(...diagnostics);
227
- return {
228
- components,
229
- diagnostics: [...this.diagnostics],
230
- generatedAt: new Date().toISOString(),
231
- health,
232
- readiness
233
- };
234
- }
235
- async collectSnapshotProbe(registration) {
236
- const [snapshot, readiness, health] = await Promise.all([this.collectComponentSnapshot(registration), this.collectComponentReadiness(registration), this.collectComponentHealth(registration)]);
237
- return {
238
- diagnostics: [...snapshot.diagnostics, ...readiness.diagnostics, ...health.diagnostics],
239
- health: health.health,
240
- readiness: readiness.readiness,
241
- snapshot: snapshot.snapshot
242
- };
106
+ health() {
107
+ return this.probes.health();
243
108
  }
244
- collectComponentSnapshot(registration) {
245
- try {
246
- const snapshot = registration.component.snapshot();
247
- return {
248
- diagnostics: [],
249
- snapshot: normalizeSnapshot(snapshot, registration.component, registration.dependencies)
250
- };
251
- } catch (error) {
252
- const issue = createUnknownFailureIssue(registration.component.id, 'snapshot', error);
253
- return {
254
- diagnostics: [issue],
255
- snapshot: {
256
- dependencies: [...registration.dependencies],
257
- details: {},
258
- health: {
259
- reason: issue.cause,
260
- status: 'unhealthy'
261
- },
262
- id: registration.component.id,
263
- kind: registration.component.kind,
264
- ownership: {
265
- externallyManaged: false,
266
- ownsResources: false
267
- },
268
- readiness: {
269
- critical: true,
270
- reason: issue.cause,
271
- status: 'not-ready'
272
- },
273
- state: 'failed',
274
- telemetry: {
275
- namespace: 'fluo.platform',
276
- tags: {}
277
- }
278
- }
279
- };
280
- }
281
- }
282
- async collectComponentReadiness(registration) {
283
- try {
284
- return {
285
- diagnostics: [],
286
- readiness: await registration.component.ready()
287
- };
288
- } catch (error) {
289
- const issue = createUnknownFailureIssue(registration.component.id, 'ready', error);
290
- return {
291
- diagnostics: [issue],
292
- readiness: {
293
- critical: true,
294
- reason: issue.cause,
295
- status: 'not-ready'
296
- }
297
- };
298
- }
299
- }
300
- async collectComponentHealth(registration) {
301
- try {
302
- return {
303
- diagnostics: [],
304
- health: await registration.component.health()
305
- };
306
- } catch (error) {
307
- const issue = createUnknownFailureIssue(registration.component.id, 'health', error);
308
- return {
309
- diagnostics: [issue],
310
- health: {
311
- reason: issue.cause,
312
- status: 'unhealthy'
313
- }
314
- };
315
- }
109
+ snapshot() {
110
+ return this.probes.snapshot();
316
111
  }
317
112
  async assertCriticalReadiness() {
318
113
  const readiness = await this.ready();
@@ -320,28 +115,6 @@ export class RuntimePlatformShell {
320
115
  throw new InvariantError(`Runtime platform shell is not ready: ${readiness.reason ?? 'critical platform component is unavailable.'}`);
321
116
  }
322
117
  }
323
- validateIdentityAndDependencies() {
324
- const ids = new Set();
325
- for (const registration of this.registeredComponents) {
326
- if (!registration.component.id || registration.component.id.trim().length === 0) {
327
- throw new InvariantError('Platform component id must be a non-empty string.');
328
- }
329
- if (ids.has(registration.component.id)) {
330
- throw new InvariantError(`Duplicate platform component id "${registration.component.id}" is not allowed.`);
331
- }
332
- ids.add(registration.component.id);
333
- }
334
- for (const registration of this.registeredComponents) {
335
- for (const dependency of registration.dependencies) {
336
- if (!ids.has(dependency)) {
337
- throw new InvariantError(`Platform component "${registration.component.id}" depends on unknown component "${dependency}".`);
338
- }
339
- if (dependency === registration.component.id) {
340
- throw new InvariantError(`Platform component "${registration.component.id}" cannot depend on itself.`);
341
- }
342
- }
343
- }
344
- }
345
118
  async validateComponents() {
346
119
  const failures = [];
347
120
  for (const registration of this.registeredComponents) {
@@ -349,61 +122,36 @@ export class RuntimePlatformShell {
349
122
  try {
350
123
  result = await registration.component.validate();
351
124
  } catch (error) {
352
- const issue = createUnknownFailureIssue(registration.component.id, 'validate', error);
353
- this.diagnostics.push(issue);
125
+ const issue = createPlatformFailureIssue(registration.component.id, 'validate', error);
126
+ this.diagnostics.append([issue]);
354
127
  failures.push(issue);
355
128
  continue;
356
129
  }
357
130
  if (result.warnings) {
358
- this.diagnostics.push(...result.warnings);
131
+ this.diagnostics.append(result.warnings);
359
132
  }
360
133
  if (!result.ok || result.issues.some(issue => issue.severity === 'error')) {
361
- this.diagnostics.push(...result.issues);
362
- failures.push(...result.issues);
134
+ const reportedIssues = result.issues.length > 0 ? result.issues : [createPlatformValidationFailureIssue(registration.component.id)];
135
+ this.diagnostics.append(reportedIssues);
136
+ failures.push(...reportedIssues);
363
137
  }
364
138
  }
365
139
  return failures;
366
140
  }
367
- orderByDependency() {
368
- const byId = new Map(this.registeredComponents.map(registration => [registration.component.id, registration]));
369
- const visiting = new Set();
370
- const visited = new Set();
371
- const ordered = [];
372
- const visit = id => {
373
- if (visited.has(id)) {
374
- return;
375
- }
376
- if (visiting.has(id)) {
377
- throw new InvariantError(`Platform component dependency cycle detected at "${id}".`);
378
- }
379
- visiting.add(id);
380
- const registration = byId.get(id);
381
- if (!registration) {
382
- throw new InvariantError(`Platform component "${id}" is missing from runtime registration.`);
383
- }
384
- for (const dependency of registration.dependencies) {
385
- visit(dependency);
386
- }
387
- visiting.delete(id);
388
- visited.add(id);
389
- ordered.push(registration);
390
- };
391
- for (const registration of this.registeredComponents) {
392
- visit(registration.component.id);
393
- }
394
- return ordered;
395
- }
396
141
  async stopStartedComponents(startedComponents) {
397
142
  const errors = [];
143
+ const pendingComponents = [];
398
144
  for (const component of [...startedComponents].reverse()) {
399
145
  try {
400
146
  await component.component.stop();
401
147
  } catch (error) {
402
148
  errors.push(error);
403
- this.diagnostics.push(createUnknownFailureIssue(component.component.id, 'stop', error));
149
+ pendingComponents.unshift(component);
150
+ this.diagnostics.append([createPlatformFailureIssue(component.component.id, 'stop', error)]);
404
151
  }
405
152
  }
406
153
  if (errors.length > 0) {
154
+ this.rollbackPendingComponents = pendingComponents;
407
155
  throw new AggregateError(errors, 'One or more platform components failed to stop cleanly.');
408
156
  }
409
157
  }
@@ -0,0 +1,7 @@
1
+ export type RetryableShutdownState<TPhase> = {
2
+ complete(phase: TPhase): void;
3
+ isComplete(phase: TPhase): boolean;
4
+ };
5
+ export declare function createLifecycleCloseError(errors: readonly unknown[]): Error;
6
+ export declare function createRetryableShutdownState<TPhase>(): RetryableShutdownState<TPhase>;
7
+ //# sourceMappingURL=retryable-shutdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retryable-shutdown.d.ts","sourceRoot":"","sources":["../src/retryable-shutdown.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,CAAC,MAAM,IAAI;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACpC,CAAC;AAMF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,KAAK,CAM3E;AAED,wBAAgB,4BAA4B,CAAC,MAAM,KAAK,sBAAsB,CAAC,MAAM,CAAC,CAWrF"}
@@ -0,0 +1,20 @@
1
+ function toError(error) {
2
+ return error instanceof Error ? error : new Error(String(error));
3
+ }
4
+ export function createLifecycleCloseError(errors) {
5
+ if (errors.length === 1) {
6
+ return toError(errors[0]);
7
+ }
8
+ return new AggregateError(errors, 'Application close failed for one or more shutdown steps.');
9
+ }
10
+ export function createRetryableShutdownState() {
11
+ const completedPhases = new Set();
12
+ return {
13
+ complete(phase) {
14
+ completedPhases.add(phase);
15
+ },
16
+ isComplete(phase) {
17
+ return completedPhases.has(phase);
18
+ }
19
+ };
20
+ }
@@ -0,0 +1,56 @@
1
+ import type { HandlerDescriptor, HttpMethod } from '@fluojs/http/portable';
2
+ import type { PlatformShellSnapshot } from './platform-contract.js';
3
+ /**
4
+ * Read-only, serializable description of one compiled HTTP handler.
5
+ *
6
+ * @remarks
7
+ * Values are projected from authoritative `HandlerDescriptor` fields after HTTP route
8
+ * compilation. `kind` is `http` unless a framework integration records a more specific
9
+ * runtime-neutral inspection marker. This contract never participates in matching,
10
+ * conflict detection, or dispatch.
11
+ */
12
+ export interface RuntimeRouteInspection {
13
+ readonly controller: string;
14
+ readonly handler: string;
15
+ readonly id: string;
16
+ readonly kind: string;
17
+ readonly method: HttpMethod;
18
+ readonly module?: string;
19
+ readonly params: readonly string[];
20
+ readonly path: string;
21
+ readonly version?: string;
22
+ }
23
+ /** Runtime platform snapshot extended with compiled route inspection data. */
24
+ export interface RuntimeInspectionSnapshot extends PlatformShellSnapshot {
25
+ readonly routes: readonly RuntimeRouteInspection[];
26
+ }
27
+ /**
28
+ * Creates the stable identifier shared by route inspection consumers.
29
+ *
30
+ * @param descriptor Compiled HTTP handler descriptor to identify.
31
+ * @returns Stable identifier containing method, effective path, controller, and handler.
32
+ */
33
+ export declare function createRuntimeRouteInspectionId(descriptor: HandlerDescriptor): string;
34
+ /**
35
+ * Projects one compiled HTTP descriptor into immutable machine-readable route data.
36
+ *
37
+ * @param descriptor Authoritative descriptor produced by the HTTP handler mapping.
38
+ * @returns Frozen route inspection entry detached from mutable descriptor collections.
39
+ */
40
+ export declare function createRuntimeRouteInspection(descriptor: HandlerDescriptor): RuntimeRouteInspection;
41
+ /**
42
+ * Projects compiled HTTP descriptors into an immutable route catalog for diagnostics.
43
+ *
44
+ * @param descriptors Authoritative descriptors produced by the HTTP handler mapping.
45
+ * @returns Frozen route entries in descriptor registration order.
46
+ */
47
+ export declare function createRuntimeRouteCatalog(descriptors: readonly HandlerDescriptor[]): readonly RuntimeRouteInspection[];
48
+ /**
49
+ * Adds immutable route inspection data to a runtime platform snapshot.
50
+ *
51
+ * @param snapshot Existing runtime-owned platform snapshot.
52
+ * @param descriptors Authoritative compiled HTTP descriptors.
53
+ * @returns A new frozen snapshot with a read-only `routes` collection.
54
+ */
55
+ export declare function createRuntimeInspectionSnapshot(snapshot: PlatformShellSnapshot, descriptors: readonly HandlerDescriptor[]): RuntimeInspectionSnapshot;
56
+ //# sourceMappingURL=route-inspection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-inspection.d.ts","sourceRoot":"","sources":["../src/route-inspection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAG3E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8EAA8E;AAC9E,MAAM,WAAW,yBAA0B,SAAQ,qBAAqB;IACtE,QAAQ,CAAC,MAAM,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACpD;AAMD;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAOpF;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,iBAAiB,GAAG,sBAAsB,CAkBlG;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACxC,SAAS,sBAAsB,EAAE,CAEnC;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,qBAAqB,EAC/B,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACxC,yBAAyB,CAK3B"}
@@ -0,0 +1,80 @@
1
+ import { getRuntimeRouteInspectionMetadata } from './internal/route-inspection-metadata.js';
2
+
3
+ /**
4
+ * Read-only, serializable description of one compiled HTTP handler.
5
+ *
6
+ * @remarks
7
+ * Values are projected from authoritative `HandlerDescriptor` fields after HTTP route
8
+ * compilation. `kind` is `http` unless a framework integration records a more specific
9
+ * runtime-neutral inspection marker. This contract never participates in matching,
10
+ * conflict detection, or dispatch.
11
+ */
12
+
13
+ /** Runtime platform snapshot extended with compiled route inspection data. */
14
+
15
+ function effectivePath(descriptor) {
16
+ return descriptor.metadata.effectivePath || descriptor.route.path;
17
+ }
18
+
19
+ /**
20
+ * Creates the stable identifier shared by route inspection consumers.
21
+ *
22
+ * @param descriptor Compiled HTTP handler descriptor to identify.
23
+ * @returns Stable identifier containing method, effective path, controller, and handler.
24
+ */
25
+ export function createRuntimeRouteInspectionId(descriptor) {
26
+ return [descriptor.route.method, effectivePath(descriptor), descriptor.controllerToken.name || '<anonymous-controller>', descriptor.methodName].join(' ');
27
+ }
28
+
29
+ /**
30
+ * Projects one compiled HTTP descriptor into immutable machine-readable route data.
31
+ *
32
+ * @param descriptor Authoritative descriptor produced by the HTTP handler mapping.
33
+ * @returns Frozen route inspection entry detached from mutable descriptor collections.
34
+ */
35
+ export function createRuntimeRouteInspection(descriptor) {
36
+ const route = {
37
+ controller: descriptor.controllerToken.name || '<anonymous-controller>',
38
+ handler: descriptor.methodName,
39
+ id: createRuntimeRouteInspectionId(descriptor),
40
+ kind: getRuntimeRouteInspectionMetadata(descriptor.controllerToken, descriptor.methodName)?.kind ?? 'http',
41
+ method: descriptor.route.method,
42
+ params: Object.freeze([...descriptor.metadata.pathParams]),
43
+ path: effectivePath(descriptor)
44
+ };
45
+ const moduleName = descriptor.metadata.moduleType?.name;
46
+ const version = descriptor.metadata.effectiveVersion ?? descriptor.route.version;
47
+ return Object.freeze({
48
+ ...route,
49
+ ...(moduleName ? {
50
+ module: moduleName
51
+ } : {}),
52
+ ...(version ? {
53
+ version
54
+ } : {})
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Projects compiled HTTP descriptors into an immutable route catalog for diagnostics.
60
+ *
61
+ * @param descriptors Authoritative descriptors produced by the HTTP handler mapping.
62
+ * @returns Frozen route entries in descriptor registration order.
63
+ */
64
+ export function createRuntimeRouteCatalog(descriptors) {
65
+ return Object.freeze(descriptors.map(descriptor => createRuntimeRouteInspection(descriptor)));
66
+ }
67
+
68
+ /**
69
+ * Adds immutable route inspection data to a runtime platform snapshot.
70
+ *
71
+ * @param snapshot Existing runtime-owned platform snapshot.
72
+ * @param descriptors Authoritative compiled HTTP descriptors.
73
+ * @returns A new frozen snapshot with a read-only `routes` collection.
74
+ */
75
+ export function createRuntimeInspectionSnapshot(snapshot, descriptors) {
76
+ return Object.freeze({
77
+ ...snapshot,
78
+ routes: createRuntimeRouteCatalog(descriptors)
79
+ });
80
+ }
package/dist/tokens.d.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  import type { Token } from '@fluojs/core';
2
2
  import type { Container } from '@fluojs/di';
3
- import type { HttpApplicationAdapter } from '@fluojs/http';
4
- import type { ApplicationLogger } from './types.js';
5
- import type { CompiledModule } from './types.js';
6
- import type { RuntimeCleanupRegistration } from './types.js';
3
+ import type { HttpApplicationAdapter } from '@fluojs/http/portable';
7
4
  import type { PlatformShell } from './platform-contract.js';
5
+ import type { ApplicationLogger, CompiledModule, RuntimeCleanupRegistration } from './types.js';
8
6
  /** Internal signal that resolves only after application bootstrap reaches runtime readiness. */
9
7
  export interface BootstrapReadySignal {
10
8
  /** Waits until every bootstrap hook and runtime readiness transition has completed. */
@@ -18,6 +16,10 @@ export declare const APPLICATION_LOGGER: Token<ApplicationLogger>;
18
16
  * Injection token for the runtime container.
19
17
  */
20
18
  export declare const RUNTIME_CONTAINER: Token<Container>;
19
+ /**
20
+ * Injection token for the effective provider tokens supplied to the application bootstrap.
21
+ */
22
+ export declare const BOOTSTRAP_PROVIDER_TOKENS: Token<ReadonlySet<Token>>;
21
23
  /**
22
24
  * Injection token for the compiled module list.
23
25
  */
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,gGAAgG;AAChG,MAAM,WAAW,oBAAoB;IACnC,uFAAuF;IACvF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,iBAAiB,CAAiD,CAAC;AAE1G;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAA+B,CAAC;AAE/E;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,SAAS,cAAc,EAAE,CAA8B,CAAC;AAE7F;;GAEG;AACH,eAAO,MAAM,wBAAwB,EAAE,KAAK,CAAC,sBAAsB,CAAsC,CAAC;AAE1G;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,aAAa,CAA4B,CAAC;AAE7E;;GAEG;AACH,eAAO,MAAM,4BAA4B,EAAE,KAAK,CAAC,0BAA0B,CAA0C,CAAC;AAEtH,mGAAmG;AACnG,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,oBAAoB,CAAoC,CAAC"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAEhG,gGAAgG;AAChG,MAAM,WAAW,oBAAoB;IACnC,uFAAuF;IACvF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,iBAAiB,CAAiD,CAAC;AAE1G;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,SAAS,CAA+B,CAAC;AAE/E;;GAEG;AACH,eAAO,MAAM,yBAAyB,EAAE,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAuC,CAAC;AAExG;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,SAAS,cAAc,EAAE,CAA8B,CAAC;AAE7F;;GAEG;AACH,eAAO,MAAM,wBAAwB,EAAE,KAAK,CAAC,sBAAsB,CAAsC,CAAC;AAE1G;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,aAAa,CAA4B,CAAC;AAE7E;;GAEG;AACH,eAAO,MAAM,4BAA4B,EAAE,KAAK,CAAC,0BAA0B,CAA0C,CAAC;AAEtH,mGAAmG;AACnG,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,oBAAoB,CAAoC,CAAC"}
package/dist/tokens.js CHANGED
@@ -10,6 +10,11 @@ export const APPLICATION_LOGGER = Symbol.for('fluo.runtime.application-logger');
10
10
  */
11
11
  export const RUNTIME_CONTAINER = Symbol('RUNTIME_CONTAINER');
12
12
 
13
+ /**
14
+ * Injection token for the effective provider tokens supplied to the application bootstrap.
15
+ */
16
+ export const BOOTSTRAP_PROVIDER_TOKENS = Symbol('BOOTSTRAP_PROVIDER_TOKENS');
17
+
13
18
  /**
14
19
  * Injection token for the compiled module list.
15
20
  */