@fluojs/runtime 1.0.0-beta.1 → 1.0.0-beta.10
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/README.ko.md +39 -3
- package/README.md +39 -3
- package/dist/adapters/request-response-factory.d.ts +9 -0
- package/dist/adapters/request-response-factory.d.ts.map +1 -1
- package/dist/adapters/request-response-factory.js +14 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +327 -60
- package/dist/health/diagnostics.d.ts +38 -0
- package/dist/health/diagnostics.d.ts.map +1 -1
- package/dist/health/diagnostics.js +48 -0
- package/dist/health/health.d.ts +21 -0
- package/dist/health/health.d.ts.map +1 -1
- package/dist/health/health.js +27 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/logging/json-logger.d.ts +5 -0
- package/dist/logging/json-logger.d.ts.map +1 -1
- package/dist/logging/json-logger.js +6 -0
- package/dist/logging/logger.d.ts +26 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +54 -5
- package/dist/module-graph.d.ts +16 -0
- package/dist/module-graph.d.ts.map +1 -1
- package/dist/module-graph.js +304 -8
- package/dist/node/internal-node-compression.d.ts +15 -0
- package/dist/node/internal-node-compression.d.ts.map +1 -1
- package/dist/node/internal-node-compression.js +16 -0
- package/dist/node/internal-node-request.d.ts +128 -0
- package/dist/node/internal-node-request.d.ts.map +1 -1
- package/dist/node/internal-node-request.js +321 -40
- package/dist/node/internal-node-response.d.ts +21 -1
- package/dist/node/internal-node-response.d.ts.map +1 -1
- package/dist/node/internal-node-response.js +42 -3
- package/dist/node/internal-node.d.ts +43 -6
- package/dist/node/internal-node.d.ts.map +1 -1
- package/dist/node/internal-node.js +65 -9
- package/dist/node/node-request.d.ts +1 -1
- package/dist/node/node-request.d.ts.map +1 -1
- package/dist/node/node-request.js +1 -1
- package/dist/platform-shell.d.ts +4 -0
- package/dist/platform-shell.d.ts.map +1 -1
- package/dist/platform-shell.js +72 -20
- package/dist/request-transaction.d.ts +28 -0
- package/dist/request-transaction.d.ts.map +1 -1
- package/dist/request-transaction.js +33 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/web.d.ts +9 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +207 -56
- package/package.json +6 -6
package/dist/bootstrap.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createDispatcher, createHandlerMapping } from '@fluojs/http';
|
|
|
6
6
|
import { DuplicateProviderError } from './errors.js';
|
|
7
7
|
import { createBootstrapTimingDiagnostics } from './health/diagnostics.js';
|
|
8
8
|
import { createConsoleApplicationLogger } from './logging/logger.js';
|
|
9
|
-
import { compileModuleGraph,
|
|
9
|
+
import { compileModuleGraph, providerToken } from './module-graph.js';
|
|
10
10
|
import { createRuntimePlatformShell } from './platform-shell.js';
|
|
11
11
|
import { APPLICATION_LOGGER, COMPILED_MODULES, HTTP_APPLICATION_ADAPTER, PLATFORM_SHELL, RUNTIME_CONTAINER } from './tokens.js';
|
|
12
12
|
const DEFAULT_MICROSERVICE_TOKEN = Symbol.for('fluo.microservices.service');
|
|
@@ -67,6 +67,7 @@ async function runCleanupCallbacks(cleanups) {
|
|
|
67
67
|
}
|
|
68
68
|
async function closeRuntimeResources(options) {
|
|
69
69
|
const errors = [];
|
|
70
|
+
resetReadinessState(options.modules);
|
|
70
71
|
errors.push(...(await runCleanupCallbacks(options.runtimeCleanup)));
|
|
71
72
|
try {
|
|
72
73
|
await runShutdownHooks(options.lifecycleInstances, options.signal);
|
|
@@ -91,6 +92,7 @@ async function closeRuntimeResources(options) {
|
|
|
91
92
|
}
|
|
92
93
|
async function runBootstrapFailureCleanup(options) {
|
|
93
94
|
const errors = [];
|
|
95
|
+
resetReadinessState(options.modules);
|
|
94
96
|
errors.push(...(await runCleanupCallbacks(options.runtimeCleanup)));
|
|
95
97
|
if (options.lifecycleInstances.length > 0) {
|
|
96
98
|
try {
|
|
@@ -138,6 +140,15 @@ function hasReadinessStateMethods(value) {
|
|
|
138
140
|
function isMicroserviceRuntime(value) {
|
|
139
141
|
return hasMethod(value, 'listen');
|
|
140
142
|
}
|
|
143
|
+
function hasMicroserviceRuntimeClose(value) {
|
|
144
|
+
return typeof value.close === 'function';
|
|
145
|
+
}
|
|
146
|
+
async function closeMicroserviceRuntime(runtime, signal) {
|
|
147
|
+
if (!hasMicroserviceRuntimeClose(runtime)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
await runtime.close(signal);
|
|
151
|
+
}
|
|
141
152
|
function resetReadinessState(modules) {
|
|
142
153
|
for (const compiledModule of modules) {
|
|
143
154
|
if (hasReadinessStateMethods(compiledModule.type)) {
|
|
@@ -156,44 +167,115 @@ function createDuplicateProviderMessage(token, moduleName, existingModuleName) {
|
|
|
156
167
|
const tokenLabel = typeof token === 'function' ? token.name || '<anonymous>' : String(token);
|
|
157
168
|
return `Duplicate provider token "${tokenLabel}" registered in module "${moduleName}". Previously registered in module "${existingModuleName}".`;
|
|
158
169
|
}
|
|
159
|
-
function
|
|
160
|
-
const
|
|
170
|
+
function createDuplicateRuntimeProviderMessage(token) {
|
|
171
|
+
const tokenLabel = typeof token === 'function' ? token.name || '<anonymous>' : String(token);
|
|
172
|
+
return `Duplicate runtime provider token "${tokenLabel}" registered.`;
|
|
173
|
+
}
|
|
174
|
+
function handleDuplicateProvider(token, moduleName, existingModuleName, policy, logger) {
|
|
175
|
+
const message = createDuplicateProviderMessage(token, moduleName, existingModuleName);
|
|
176
|
+
if (policy === 'throw') {
|
|
177
|
+
throw new DuplicateProviderError(message, {
|
|
178
|
+
module: moduleName,
|
|
179
|
+
token,
|
|
180
|
+
phase: 'provider registration',
|
|
181
|
+
hint: `Remove the duplicate registration from one of the modules, use container.override() for intentional replacements, or set duplicateProviderPolicy to 'warn' or 'ignore'.`
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (policy === 'warn') {
|
|
185
|
+
logger?.warn(message, 'BootstrapModule');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function handleDuplicateRuntimeProvider(token, policy, logger) {
|
|
189
|
+
const message = createDuplicateRuntimeProviderMessage(token);
|
|
190
|
+
if (policy === 'throw') {
|
|
191
|
+
throw new DuplicateProviderError(message, {
|
|
192
|
+
module: '<runtime>',
|
|
193
|
+
token,
|
|
194
|
+
phase: 'provider registration',
|
|
195
|
+
hint: `Remove the duplicate runtime provider registration, use container.override() after bootstrap for intentional replacements, or set duplicateProviderPolicy to 'warn' or 'ignore'.`
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (policy === 'warn') {
|
|
199
|
+
logger?.warn(message, 'BootstrapModule');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function selectEffectiveBootstrapProviders(modules, runtimeProviders, rootModule, policy, logger) {
|
|
203
|
+
const selectedRuntimeProviderIndexes = new Map();
|
|
204
|
+
const selectedModuleProviderIndexes = new Map();
|
|
205
|
+
const runtimeMultiTokens = new Set();
|
|
206
|
+
const runtimeSingleTokens = new Set();
|
|
207
|
+
const runtimeProviderEntries = [];
|
|
208
|
+
const moduleProviderEntries = [];
|
|
161
209
|
for (const runtimeProvider of runtimeProviders ?? []) {
|
|
162
210
|
const token = providerToken(runtimeProvider);
|
|
163
|
-
|
|
211
|
+
const multi = isMultiProvider(runtimeProvider);
|
|
212
|
+
const existingRuntimeProviderIndex = selectedRuntimeProviderIndexes.get(token);
|
|
213
|
+
if (multi) {
|
|
214
|
+
runtimeMultiTokens.add(token);
|
|
215
|
+
} else {
|
|
216
|
+
runtimeSingleTokens.add(token);
|
|
217
|
+
}
|
|
218
|
+
if (!multi && existingRuntimeProviderIndex !== undefined) {
|
|
219
|
+
handleDuplicateRuntimeProvider(token, policy, logger);
|
|
220
|
+
}
|
|
221
|
+
runtimeProviderEntries.push({
|
|
164
222
|
moduleName: '<runtime>',
|
|
165
223
|
provider: runtimeProvider,
|
|
166
224
|
source: 'runtime',
|
|
167
225
|
token
|
|
168
226
|
});
|
|
227
|
+
if (!multi) {
|
|
228
|
+
selectedRuntimeProviderIndexes.set(token, runtimeProviderEntries.length - 1);
|
|
229
|
+
}
|
|
169
230
|
}
|
|
170
231
|
for (const compiledModule of modules) {
|
|
171
232
|
for (const provider of compiledModule.definition.providers ?? []) {
|
|
172
233
|
const token = providerToken(provider);
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
module: compiledModule.type.name,
|
|
179
|
-
token,
|
|
180
|
-
phase: 'provider registration',
|
|
181
|
-
hint: `Remove the duplicate registration from one of the modules, use container.override() for intentional replacements, or set duplicateProviderPolicy to 'warn' or 'ignore'.`
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
if (policy === 'warn') {
|
|
185
|
-
logger?.warn(message, 'BootstrapModule');
|
|
186
|
-
}
|
|
234
|
+
const multi = isMultiProvider(provider);
|
|
235
|
+
const existingModuleProviderIndex = selectedModuleProviderIndexes.get(token);
|
|
236
|
+
const existing = existingModuleProviderIndex === undefined ? undefined : moduleProviderEntries[existingModuleProviderIndex];
|
|
237
|
+
if (!multi && existing && existing.source === 'module') {
|
|
238
|
+
handleDuplicateProvider(token, compiledModule.type.name, existing.moduleName, policy, logger);
|
|
187
239
|
}
|
|
188
|
-
|
|
240
|
+
const entry = {
|
|
189
241
|
moduleName: compiledModule.type.name,
|
|
242
|
+
moduleType: compiledModule.type,
|
|
190
243
|
provider,
|
|
191
244
|
source: 'module',
|
|
192
245
|
token
|
|
193
|
-
}
|
|
246
|
+
};
|
|
247
|
+
moduleProviderEntries.push(entry);
|
|
248
|
+
if (!multi) {
|
|
249
|
+
selectedModuleProviderIndexes.set(token, moduleProviderEntries.length - 1);
|
|
250
|
+
}
|
|
194
251
|
}
|
|
195
252
|
}
|
|
196
|
-
|
|
253
|
+
const selectedModuleProviderIndexesSet = new Set(selectedModuleProviderIndexes.values());
|
|
254
|
+
const selectedRuntimeProviderIndexesSet = new Set(selectedRuntimeProviderIndexes.values());
|
|
255
|
+
const moduleProviders = [];
|
|
256
|
+
const rootModuleProviders = [];
|
|
257
|
+
const effectiveRuntimeProviders = runtimeProviderEntries.filter((entry, index) => isMultiProvider(entry.provider) || selectedRuntimeProviderIndexesSet.has(index)).map(entry => entry.provider);
|
|
258
|
+
moduleProviderEntries.forEach((entry, index) => {
|
|
259
|
+
const multi = isMultiProvider(entry.provider);
|
|
260
|
+
if (entry.source !== 'module' || !multi && !selectedModuleProviderIndexesSet.has(index)) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (runtimeSingleTokens.has(entry.token) || runtimeMultiTokens.has(entry.token) && !multi) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
moduleProviders.push(entry.provider);
|
|
267
|
+
if (entry.moduleType === rootModule) {
|
|
268
|
+
rootModuleProviders.push(entry.provider);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
moduleProviders,
|
|
273
|
+
rootModuleProviders,
|
|
274
|
+
runtimeProviders: effectiveRuntimeProviders
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function isMultiProvider(provider) {
|
|
278
|
+
return typeof provider === 'object' && provider !== null && 'multi' in provider && provider.multi === true;
|
|
197
279
|
}
|
|
198
280
|
function registerControllers(container, modules) {
|
|
199
281
|
for (const compiledModule of modules) {
|
|
@@ -251,18 +333,18 @@ export function bootstrapModule(rootModule, options = {}) {
|
|
|
251
333
|
const container = new Container();
|
|
252
334
|
const policy = options.duplicateProviderPolicy ?? 'warn';
|
|
253
335
|
const runtimeProviders = options.providers ?? [];
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
container.register(...runtimeProviders);
|
|
336
|
+
const effectiveProviders = selectEffectiveBootstrapProviders(modules, runtimeProviders, rootModule, policy, options.logger);
|
|
337
|
+
if (effectiveProviders.runtimeProviders.length > 0) {
|
|
338
|
+
container.register(...effectiveProviders.runtimeProviders);
|
|
258
339
|
}
|
|
259
|
-
if (moduleProviders.length > 0) {
|
|
260
|
-
container.register(...moduleProviders);
|
|
340
|
+
if (effectiveProviders.moduleProviders.length > 0) {
|
|
341
|
+
container.register(...effectiveProviders.moduleProviders);
|
|
261
342
|
}
|
|
262
343
|
registerControllers(container, modules);
|
|
263
344
|
registerModuleMiddleware(container, modules);
|
|
264
345
|
return {
|
|
265
346
|
container,
|
|
347
|
+
effectiveProviders,
|
|
266
348
|
modules,
|
|
267
349
|
rootModule
|
|
268
350
|
};
|
|
@@ -275,9 +357,10 @@ class FluoApplication {
|
|
|
275
357
|
applicationState = 'bootstrapped';
|
|
276
358
|
closed = false;
|
|
277
359
|
closingPromise;
|
|
360
|
+
contextResolutionCache = new Map();
|
|
278
361
|
lifecycleInstances;
|
|
279
362
|
connectedMicroservices = [];
|
|
280
|
-
constructor(container, modules, rootModule, dispatcher, bootstrapTiming, adapter, hasHttpAdapter, platformShell, lifecycleInstances, logger, runtimeCleanup) {
|
|
363
|
+
constructor(container, modules, rootModule, dispatcher, bootstrapTiming, adapter, hasHttpAdapter, platformShell, lifecycleInstances, logger, runtimeCleanup, contextCacheableTokens) {
|
|
281
364
|
this.container = container;
|
|
282
365
|
this.modules = modules;
|
|
283
366
|
this.rootModule = rootModule;
|
|
@@ -288,13 +371,18 @@ class FluoApplication {
|
|
|
288
371
|
this.platformShell = platformShell;
|
|
289
372
|
this.logger = logger;
|
|
290
373
|
this.runtimeCleanup = runtimeCleanup;
|
|
374
|
+
this.contextCacheableTokens = contextCacheableTokens;
|
|
291
375
|
this.lifecycleInstances = lifecycleInstances;
|
|
376
|
+
installContextCacheInvalidation(this.container, this.contextResolutionCache, this.contextCacheableTokens);
|
|
292
377
|
}
|
|
293
378
|
get state() {
|
|
294
379
|
return this.applicationState;
|
|
295
380
|
}
|
|
296
381
|
async get(token) {
|
|
297
|
-
|
|
382
|
+
if (this.closed) {
|
|
383
|
+
return this.container.resolve(token);
|
|
384
|
+
}
|
|
385
|
+
return resolveContextToken(this.container, token, this.contextCacheableTokens, this.contextResolutionCache);
|
|
298
386
|
}
|
|
299
387
|
|
|
300
388
|
/**
|
|
@@ -312,12 +400,43 @@ class FluoApplication {
|
|
|
312
400
|
if (!isMicroserviceRuntime(runtime)) {
|
|
313
401
|
throw new InvariantError('Resolved microservice token does not implement listen().');
|
|
314
402
|
}
|
|
315
|
-
const microservice = new FluoMicroserviceApplication(this, this.logger, runtime);
|
|
403
|
+
const microservice = new FluoMicroserviceApplication(this, this.logger, runtime, false);
|
|
316
404
|
this.connectedMicroservices.push(microservice);
|
|
317
405
|
return microservice;
|
|
318
406
|
}
|
|
319
407
|
async startAllMicroservices() {
|
|
320
|
-
|
|
408
|
+
const startedMicroservices = [];
|
|
409
|
+
for (const microservice of this.connectedMicroservices) {
|
|
410
|
+
try {
|
|
411
|
+
await microservice.listen();
|
|
412
|
+
startedMicroservices.push(microservice);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
await this.rollbackStartedMicroservices(startedMicroservices);
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async rollbackStartedMicroservices(startedMicroservices) {
|
|
420
|
+
for (const microservice of [...startedMicroservices].reverse()) {
|
|
421
|
+
try {
|
|
422
|
+
await microservice.close('bootstrap-failed');
|
|
423
|
+
} catch (rollbackError) {
|
|
424
|
+
this.logger.error('Failed to roll back a started microservice after startup failure.', rollbackError, 'FluoApplication');
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async closeConnectedMicroservices(signal) {
|
|
429
|
+
const errors = [];
|
|
430
|
+
for (const microservice of [...this.connectedMicroservices].reverse()) {
|
|
431
|
+
try {
|
|
432
|
+
await microservice.close(signal);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
errors.push(error);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (errors.length > 0) {
|
|
438
|
+
throw createLifecycleCloseError(errors);
|
|
439
|
+
}
|
|
321
440
|
}
|
|
322
441
|
|
|
323
442
|
/**
|
|
@@ -359,13 +478,27 @@ class FluoApplication {
|
|
|
359
478
|
return;
|
|
360
479
|
}
|
|
361
480
|
this.closingPromise = (async () => {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
481
|
+
const errors = [];
|
|
482
|
+
try {
|
|
483
|
+
await this.closeConnectedMicroservices(signal);
|
|
484
|
+
} catch (error) {
|
|
485
|
+
errors.push(error);
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
await closeRuntimeResources({
|
|
489
|
+
adapter: this.adapter,
|
|
490
|
+
container: this.container,
|
|
491
|
+
lifecycleInstances: this.lifecycleInstances,
|
|
492
|
+
modules: this.modules,
|
|
493
|
+
runtimeCleanup: this.runtimeCleanup,
|
|
494
|
+
signal
|
|
495
|
+
});
|
|
496
|
+
} catch (error) {
|
|
497
|
+
errors.push(error);
|
|
498
|
+
}
|
|
499
|
+
if (errors.length > 0) {
|
|
500
|
+
throw createLifecycleCloseError(errors);
|
|
501
|
+
}
|
|
369
502
|
this.closed = true;
|
|
370
503
|
this.applicationState = 'closed';
|
|
371
504
|
})();
|
|
@@ -380,16 +513,22 @@ class FluoApplication {
|
|
|
380
513
|
class FluoApplicationContext {
|
|
381
514
|
closed = false;
|
|
382
515
|
closingPromise;
|
|
383
|
-
|
|
516
|
+
contextResolutionCache = new Map();
|
|
517
|
+
constructor(container, modules, rootModule, bootstrapTiming, lifecycleInstances, runtimeCleanup, contextCacheableTokens) {
|
|
384
518
|
this.container = container;
|
|
385
519
|
this.modules = modules;
|
|
386
520
|
this.rootModule = rootModule;
|
|
387
521
|
this.bootstrapTiming = bootstrapTiming;
|
|
388
522
|
this.lifecycleInstances = lifecycleInstances;
|
|
389
523
|
this.runtimeCleanup = runtimeCleanup;
|
|
524
|
+
this.contextCacheableTokens = contextCacheableTokens;
|
|
525
|
+
installContextCacheInvalidation(this.container, this.contextResolutionCache, this.contextCacheableTokens);
|
|
390
526
|
}
|
|
391
527
|
async get(token) {
|
|
392
|
-
|
|
528
|
+
if (this.closed) {
|
|
529
|
+
return this.container.resolve(token);
|
|
530
|
+
}
|
|
531
|
+
return resolveContextToken(this.container, token, this.contextCacheableTokens, this.contextResolutionCache);
|
|
393
532
|
}
|
|
394
533
|
async close(signal) {
|
|
395
534
|
if (this.closed) {
|
|
@@ -403,6 +542,7 @@ class FluoApplicationContext {
|
|
|
403
542
|
await closeRuntimeResources({
|
|
404
543
|
container: this.container,
|
|
405
544
|
lifecycleInstances: this.lifecycleInstances,
|
|
545
|
+
modules: this.modules,
|
|
406
546
|
runtimeCleanup: this.runtimeCleanup,
|
|
407
547
|
signal
|
|
408
548
|
});
|
|
@@ -420,10 +560,11 @@ class FluoMicroserviceApplication {
|
|
|
420
560
|
closed = false;
|
|
421
561
|
closingPromise;
|
|
422
562
|
microserviceState = 'bootstrapped';
|
|
423
|
-
constructor(context, logger, runtime) {
|
|
563
|
+
constructor(context, logger, runtime, closeContextOnClose) {
|
|
424
564
|
this.context = context;
|
|
425
565
|
this.logger = logger;
|
|
426
566
|
this.runtime = runtime;
|
|
567
|
+
this.closeContextOnClose = closeContextOnClose;
|
|
427
568
|
}
|
|
428
569
|
get container() {
|
|
429
570
|
return this.context.container;
|
|
@@ -472,7 +613,24 @@ class FluoMicroserviceApplication {
|
|
|
472
613
|
return;
|
|
473
614
|
}
|
|
474
615
|
this.closingPromise = (async () => {
|
|
475
|
-
|
|
616
|
+
if (this.closeContextOnClose) {
|
|
617
|
+
const errors = [];
|
|
618
|
+
try {
|
|
619
|
+
await closeMicroserviceRuntime(this.runtime, signal);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
errors.push(error);
|
|
622
|
+
}
|
|
623
|
+
try {
|
|
624
|
+
await this.context.close(signal);
|
|
625
|
+
} catch (error) {
|
|
626
|
+
errors.push(error);
|
|
627
|
+
}
|
|
628
|
+
if (errors.length > 0) {
|
|
629
|
+
throw createLifecycleCloseError(errors);
|
|
630
|
+
}
|
|
631
|
+
} else {
|
|
632
|
+
await closeMicroserviceRuntime(this.runtime, signal);
|
|
633
|
+
}
|
|
476
634
|
this.closed = true;
|
|
477
635
|
this.microserviceState = 'closed';
|
|
478
636
|
})();
|
|
@@ -488,22 +646,113 @@ class FluoMicroserviceApplication {
|
|
|
488
646
|
/**
|
|
489
647
|
* lifecycle hook이 있는 singleton provider 인스턴스를 미리 해석해 둔다.
|
|
490
648
|
*/
|
|
491
|
-
async function resolveLifecycleInstances(container, providers) {
|
|
492
|
-
const
|
|
649
|
+
async function resolveLifecycleInstances(container, providers, resolvedInstances = []) {
|
|
650
|
+
const lifecycleEntries = [];
|
|
493
651
|
const seen = new Set();
|
|
494
652
|
for (const provider of providers) {
|
|
495
|
-
const scope = providerScope(provider);
|
|
496
|
-
if (scope === 'request' || scope === 'transient') {
|
|
497
|
-
continue;
|
|
498
|
-
}
|
|
499
653
|
const token = providerToken(provider);
|
|
500
654
|
if (seen.has(token)) {
|
|
501
655
|
continue;
|
|
502
656
|
}
|
|
657
|
+
if (isHookBearingValueProvider(provider)) {
|
|
658
|
+
seen.add(token);
|
|
659
|
+
lifecycleEntries.push({
|
|
660
|
+
token,
|
|
661
|
+
useValue: provider.useValue
|
|
662
|
+
});
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (!isDirectSingletonContextProvider(provider)) {
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
503
668
|
seen.add(token);
|
|
504
|
-
|
|
669
|
+
lifecycleEntries.push({
|
|
670
|
+
token
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
const resolutionResults = await Promise.allSettled(lifecycleEntries.map(entry => entry.useValue ?? container.resolve(entry.token)));
|
|
674
|
+
let resolutionError;
|
|
675
|
+
let hasResolutionError = false;
|
|
676
|
+
for (const result of resolutionResults) {
|
|
677
|
+
if (result.status === 'fulfilled') {
|
|
678
|
+
resolvedInstances.push(result.value);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (!hasResolutionError) {
|
|
682
|
+
resolutionError = result.reason;
|
|
683
|
+
hasResolutionError = true;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (hasResolutionError) {
|
|
687
|
+
throw resolutionError;
|
|
505
688
|
}
|
|
506
|
-
return
|
|
689
|
+
return resolvedInstances;
|
|
690
|
+
}
|
|
691
|
+
function createContextCacheableTokenSet(effectiveProviders, runtimeTokens) {
|
|
692
|
+
const cacheableTokens = new Set(runtimeTokens);
|
|
693
|
+
for (const provider of effectiveProviders.runtimeProviders) {
|
|
694
|
+
if (!isDirectSingletonContextProvider(provider)) {
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
cacheableTokens.add(providerToken(provider));
|
|
698
|
+
}
|
|
699
|
+
for (const provider of effectiveProviders.rootModuleProviders) {
|
|
700
|
+
if (!isDirectSingletonContextProvider(provider)) {
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
cacheableTokens.add(providerToken(provider));
|
|
704
|
+
}
|
|
705
|
+
return cacheableTokens;
|
|
706
|
+
}
|
|
707
|
+
function isDirectSingletonContextProvider(provider) {
|
|
708
|
+
if (isMultiProvider(provider)) {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
if (typeof provider === 'function') {
|
|
712
|
+
return providerScope(provider) === 'singleton';
|
|
713
|
+
}
|
|
714
|
+
if ('useExisting' in provider || 'useValue' in provider) {
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
return providerScope(provider) === 'singleton';
|
|
718
|
+
}
|
|
719
|
+
function isHookBearingValueProvider(provider) {
|
|
720
|
+
return typeof provider === 'object' && provider !== null && 'useValue' in provider && hasLifecycleHook(provider.useValue);
|
|
721
|
+
}
|
|
722
|
+
function hasLifecycleHook(value) {
|
|
723
|
+
return isOnModuleInit(value) || isOnApplicationBootstrap(value) || isOnModuleDestroy(value) || isOnApplicationShutdown(value);
|
|
724
|
+
}
|
|
725
|
+
function installContextCacheInvalidation(container, cache, cacheableTokens) {
|
|
726
|
+
const cacheInvalidatingContainer = container;
|
|
727
|
+
const override = cacheInvalidatingContainer.override.bind(container);
|
|
728
|
+
cacheInvalidatingContainer.override = (...providers) => {
|
|
729
|
+
const result = override(...providers);
|
|
730
|
+
cache.clear();
|
|
731
|
+
for (const provider of providers) {
|
|
732
|
+
const token = providerToken(provider);
|
|
733
|
+
if (isDirectSingletonContextProvider(provider)) {
|
|
734
|
+
cacheableTokens.add(token);
|
|
735
|
+
} else {
|
|
736
|
+
cacheableTokens.delete(token);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return result;
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
async function resolveContextToken(container, token, cacheableTokens, cache) {
|
|
743
|
+
if (!cacheableTokens.has(token)) {
|
|
744
|
+
return container.resolve(token);
|
|
745
|
+
}
|
|
746
|
+
const cached = cache.get(token);
|
|
747
|
+
if (cached) {
|
|
748
|
+
return cached;
|
|
749
|
+
}
|
|
750
|
+
const resolution = container.resolve(token).catch(error => {
|
|
751
|
+
cache.delete(token);
|
|
752
|
+
throw error;
|
|
753
|
+
});
|
|
754
|
+
cache.set(token, resolution);
|
|
755
|
+
return resolution;
|
|
507
756
|
}
|
|
508
757
|
|
|
509
758
|
/**
|
|
@@ -600,9 +849,9 @@ function registerRuntimeApplicationContextTokens(bootstrapped, platformShell) {
|
|
|
600
849
|
useValue: platformShell
|
|
601
850
|
});
|
|
602
851
|
}
|
|
603
|
-
async function resolveBootstrapLifecycleInstances(bootstrapped,
|
|
604
|
-
const lifecycleProviders = [...runtimeProviders, ...bootstrapped.
|
|
605
|
-
return resolveLifecycleInstances(bootstrapped.container, lifecycleProviders);
|
|
852
|
+
async function resolveBootstrapLifecycleInstances(bootstrapped, resolvedInstances) {
|
|
853
|
+
const lifecycleProviders = [...bootstrapped.effectiveProviders.runtimeProviders, ...bootstrapped.effectiveProviders.moduleProviders];
|
|
854
|
+
return resolveLifecycleInstances(bootstrapped.container, lifecycleProviders, resolvedInstances);
|
|
606
855
|
}
|
|
607
856
|
async function runBootstrapLifecycle(modules, lifecycleInstances, logger, platformShell) {
|
|
608
857
|
resetReadinessState(modules);
|
|
@@ -618,15 +867,21 @@ function createFilterErrorHandler(filters) {
|
|
|
618
867
|
return async (error, request, response, requestId) => runExceptionFilters(filters, error, request, response, requestId);
|
|
619
868
|
}
|
|
620
869
|
function createRuntimeDispatcherOptions(bootstrapped, options, handlerMapping, errorHandler, logger) {
|
|
870
|
+
const converters = options.converters ?? [];
|
|
621
871
|
const dispatcherOptions = {
|
|
622
872
|
appMiddleware: options.middleware ?? [],
|
|
623
|
-
binder: new DefaultBinder(options.converters ?? []),
|
|
624
873
|
handlerMapping,
|
|
625
874
|
interceptors: options.interceptors ?? [],
|
|
626
875
|
logger,
|
|
627
876
|
observers: options.observers ?? [],
|
|
877
|
+
requestScope: {
|
|
878
|
+
converterDefinitions: converters
|
|
879
|
+
},
|
|
628
880
|
rootContainer: bootstrapped.container
|
|
629
881
|
};
|
|
882
|
+
if (converters.length > 0) {
|
|
883
|
+
dispatcherOptions.binder = new DefaultBinder(converters);
|
|
884
|
+
}
|
|
630
885
|
if (errorHandler) {
|
|
631
886
|
dispatcherOptions.onError = errorHandler;
|
|
632
887
|
}
|
|
@@ -654,6 +909,7 @@ export async function bootstrapApplication(options) {
|
|
|
654
909
|
const logger = options.logger ?? createConsoleApplicationLogger();
|
|
655
910
|
let lifecycleInstances = [];
|
|
656
911
|
let bootstrappedContainer;
|
|
912
|
+
let bootstrappedModules = [];
|
|
657
913
|
const hasHttpAdapter = options.adapter !== undefined;
|
|
658
914
|
const adapter = options.adapter ?? {
|
|
659
915
|
async close() {},
|
|
@@ -671,6 +927,7 @@ export async function bootstrapApplication(options) {
|
|
|
671
927
|
const bootstrapped = bootstrapModule(options.rootModule, {
|
|
672
928
|
duplicateProviderPolicy: options.duplicateProviderPolicy,
|
|
673
929
|
logger,
|
|
930
|
+
moduleGraphCache: options.moduleGraphCache,
|
|
674
931
|
providers: runtimeProviders,
|
|
675
932
|
validationTokens: [RUNTIME_CONTAINER, COMPILED_MODULES, HTTP_APPLICATION_ADAPTER]
|
|
676
933
|
});
|
|
@@ -689,8 +946,9 @@ export async function bootstrapApplication(options) {
|
|
|
689
946
|
});
|
|
690
947
|
}
|
|
691
948
|
bootstrappedContainer = bootstrapped.container;
|
|
949
|
+
bootstrappedModules = bootstrapped.modules;
|
|
692
950
|
const resolveLifecycleStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
693
|
-
lifecycleInstances = await resolveBootstrapLifecycleInstances(bootstrapped,
|
|
951
|
+
lifecycleInstances = await resolveBootstrapLifecycleInstances(bootstrapped, lifecycleInstances);
|
|
694
952
|
lifecycleInstances.push({
|
|
695
953
|
onModuleDestroy() {
|
|
696
954
|
return platformShell.stop();
|
|
@@ -719,13 +977,14 @@ export async function bootstrapApplication(options) {
|
|
|
719
977
|
});
|
|
720
978
|
}
|
|
721
979
|
const bootstrapTiming = timingEnabled ? createBootstrapTimingDiagnostics(timingPhases, runtimePerformance.now() - timingStart) : undefined;
|
|
722
|
-
return new FluoApplication(bootstrapped.container, bootstrapped.modules, options.rootModule, dispatcher, bootstrapTiming, adapter, hasHttpAdapter, platformShell, lifecycleInstances, logger, runtimeCleanup);
|
|
980
|
+
return new FluoApplication(bootstrapped.container, bootstrapped.modules, options.rootModule, dispatcher, bootstrapTiming, adapter, hasHttpAdapter, platformShell, lifecycleInstances, logger, runtimeCleanup, createContextCacheableTokenSet(bootstrapped.effectiveProviders, [RUNTIME_CONTAINER, COMPILED_MODULES, HTTP_APPLICATION_ADAPTER, PLATFORM_SHELL]));
|
|
723
981
|
} catch (error) {
|
|
724
982
|
logger.error('Failed to bootstrap the fluo application. Check the error below for what failed and how to fix it.', error, 'FluoFactory');
|
|
725
983
|
await runBootstrapFailureCleanup({
|
|
726
984
|
container: bootstrappedContainer,
|
|
727
985
|
lifecycleInstances,
|
|
728
986
|
logger,
|
|
987
|
+
modules: bootstrappedModules,
|
|
729
988
|
runtimeCleanup,
|
|
730
989
|
scope: 'application'
|
|
731
990
|
});
|
|
@@ -764,6 +1023,7 @@ export class FluoFactory {
|
|
|
764
1023
|
const logger = options.logger ?? createConsoleApplicationLogger();
|
|
765
1024
|
let lifecycleInstances = [];
|
|
766
1025
|
let bootstrappedContainer;
|
|
1026
|
+
let bootstrappedModules = [];
|
|
767
1027
|
const runtimeCleanup = [];
|
|
768
1028
|
const platformShell = createRuntimePlatformShell(options.platform?.components);
|
|
769
1029
|
const timingEnabled = options.diagnostics?.timing === true;
|
|
@@ -776,6 +1036,7 @@ export class FluoFactory {
|
|
|
776
1036
|
const bootstrapped = bootstrapModule(rootModule, {
|
|
777
1037
|
duplicateProviderPolicy: options.duplicateProviderPolicy,
|
|
778
1038
|
logger,
|
|
1039
|
+
moduleGraphCache: options.moduleGraphCache,
|
|
779
1040
|
providers: runtimeProviders,
|
|
780
1041
|
validationTokens: [RUNTIME_CONTAINER, COMPILED_MODULES]
|
|
781
1042
|
});
|
|
@@ -794,8 +1055,9 @@ export class FluoFactory {
|
|
|
794
1055
|
});
|
|
795
1056
|
}
|
|
796
1057
|
bootstrappedContainer = bootstrapped.container;
|
|
1058
|
+
bootstrappedModules = bootstrapped.modules;
|
|
797
1059
|
const resolveLifecycleStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
798
|
-
lifecycleInstances = await resolveBootstrapLifecycleInstances(bootstrapped,
|
|
1060
|
+
lifecycleInstances = await resolveBootstrapLifecycleInstances(bootstrapped, lifecycleInstances);
|
|
799
1061
|
lifecycleInstances.push({
|
|
800
1062
|
onModuleDestroy() {
|
|
801
1063
|
return platformShell.stop();
|
|
@@ -816,13 +1078,14 @@ export class FluoFactory {
|
|
|
816
1078
|
});
|
|
817
1079
|
}
|
|
818
1080
|
const bootstrapTiming = timingEnabled ? createBootstrapTimingDiagnostics(timingPhases, runtimePerformance.now() - timingStart) : undefined;
|
|
819
|
-
return new FluoApplicationContext(bootstrapped.container, bootstrapped.modules, rootModule, bootstrapTiming, lifecycleInstances, runtimeCleanup);
|
|
1081
|
+
return new FluoApplicationContext(bootstrapped.container, bootstrapped.modules, rootModule, bootstrapTiming, lifecycleInstances, runtimeCleanup, createContextCacheableTokenSet(bootstrapped.effectiveProviders, [RUNTIME_CONTAINER, COMPILED_MODULES, PLATFORM_SHELL]));
|
|
820
1082
|
} catch (error) {
|
|
821
1083
|
logger.error('Failed to bootstrap application context. Check the error below for what failed and how to fix it.', error, 'FluoFactory');
|
|
822
1084
|
await runBootstrapFailureCleanup({
|
|
823
1085
|
container: bootstrappedContainer,
|
|
824
1086
|
lifecycleInstances,
|
|
825
1087
|
logger,
|
|
1088
|
+
modules: bootstrappedModules,
|
|
826
1089
|
runtimeCleanup,
|
|
827
1090
|
scope: 'application context'
|
|
828
1091
|
});
|
|
@@ -848,9 +1111,13 @@ export class FluoFactory {
|
|
|
848
1111
|
if (!isMicroserviceRuntime(runtime)) {
|
|
849
1112
|
throw new InvariantError('Resolved microservice token does not implement listen().');
|
|
850
1113
|
}
|
|
851
|
-
return new FluoMicroserviceApplication(context, logger, runtime);
|
|
1114
|
+
return new FluoMicroserviceApplication(context, logger, runtime, true);
|
|
852
1115
|
} catch (error) {
|
|
853
|
-
|
|
1116
|
+
try {
|
|
1117
|
+
await context.close('bootstrap-failed');
|
|
1118
|
+
} catch (cleanupError) {
|
|
1119
|
+
logger.error('Failed to clean up after microservice bootstrap failure.', cleanupError, 'FluoFactory');
|
|
1120
|
+
}
|
|
854
1121
|
logger.error('Failed to bootstrap microservice context. Check the error below for what failed and how to fix it.', error, 'FluoFactory');
|
|
855
1122
|
throw error;
|
|
856
1123
|
}
|