@fluojs/runtime 1.0.0-beta.1 → 1.0.0-beta.11

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 (52) hide show
  1. package/README.ko.md +53 -11
  2. package/README.md +53 -11
  3. package/dist/adapters/request-response-factory.d.ts +9 -0
  4. package/dist/adapters/request-response-factory.d.ts.map +1 -1
  5. package/dist/adapters/request-response-factory.js +14 -0
  6. package/dist/bootstrap.d.ts.map +1 -1
  7. package/dist/bootstrap.js +327 -60
  8. package/dist/health/diagnostics.d.ts +38 -0
  9. package/dist/health/diagnostics.d.ts.map +1 -1
  10. package/dist/health/diagnostics.js +48 -0
  11. package/dist/health/health.d.ts +34 -0
  12. package/dist/health/health.d.ts.map +1 -1
  13. package/dist/health/health.js +54 -4
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/logging/json-logger.d.ts +5 -0
  18. package/dist/logging/json-logger.d.ts.map +1 -1
  19. package/dist/logging/json-logger.js +6 -0
  20. package/dist/logging/logger.d.ts +35 -1
  21. package/dist/logging/logger.d.ts.map +1 -1
  22. package/dist/logging/logger.js +69 -5
  23. package/dist/module-graph.d.ts +16 -0
  24. package/dist/module-graph.d.ts.map +1 -1
  25. package/dist/module-graph.js +304 -8
  26. package/dist/node/internal-node-compression.d.ts +15 -0
  27. package/dist/node/internal-node-compression.d.ts.map +1 -1
  28. package/dist/node/internal-node-compression.js +16 -0
  29. package/dist/node/internal-node-request.d.ts +128 -0
  30. package/dist/node/internal-node-request.d.ts.map +1 -1
  31. package/dist/node/internal-node-request.js +321 -40
  32. package/dist/node/internal-node-response.d.ts +21 -1
  33. package/dist/node/internal-node-response.d.ts.map +1 -1
  34. package/dist/node/internal-node-response.js +42 -3
  35. package/dist/node/internal-node.d.ts +43 -6
  36. package/dist/node/internal-node.d.ts.map +1 -1
  37. package/dist/node/internal-node.js +65 -9
  38. package/dist/node/node-request.d.ts +1 -1
  39. package/dist/node/node-request.d.ts.map +1 -1
  40. package/dist/node/node-request.js +1 -1
  41. package/dist/platform-shell.d.ts +4 -0
  42. package/dist/platform-shell.d.ts.map +1 -1
  43. package/dist/platform-shell.js +72 -20
  44. package/dist/request-transaction.d.ts +28 -0
  45. package/dist/request-transaction.d.ts.map +1 -1
  46. package/dist/request-transaction.js +33 -0
  47. package/dist/types.d.ts +29 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/web.d.ts +9 -1
  50. package/dist/web.d.ts.map +1 -1
  51. package/dist/web.js +207 -56
  52. 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, createRuntimeTokenSet, providerToken } from './module-graph.js';
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 collectProvidersForContainer(modules, runtimeProviders, policy, logger) {
160
- const selectedProviders = new Map();
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
- selectedProviders.set(token, {
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 existing = selectedProviders.get(token);
174
- if (existing && existing.source === 'module') {
175
- const message = createDuplicateProviderMessage(token, compiledModule.type.name, existing.moduleName);
176
- if (policy === 'throw') {
177
- throw new DuplicateProviderError(message, {
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
- selectedProviders.set(token, {
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
- return [...selectedProviders.values()].map(entry => entry.provider);
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 runtimeProviderTokens = createRuntimeTokenSet(runtimeProviders);
255
- const moduleProviders = collectProvidersForContainer(modules, runtimeProviders, policy, options.logger).filter(provider => !runtimeProviderTokens.has(providerToken(provider)));
256
- if (runtimeProviders.length > 0) {
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
- return this.container.resolve(token);
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
- await Promise.all(this.connectedMicroservices.map(async microservice => microservice.listen()));
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
- await closeRuntimeResources({
363
- adapter: this.adapter,
364
- container: this.container,
365
- lifecycleInstances: this.lifecycleInstances,
366
- runtimeCleanup: this.runtimeCleanup,
367
- signal
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
- constructor(container, modules, rootModule, bootstrapTiming, lifecycleInstances, runtimeCleanup) {
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
- return this.container.resolve(token);
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
- await this.context.close(signal);
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 instances = [];
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
- instances.push(await container.resolve(token));
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 instances;
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, runtimeProviders) {
604
- const lifecycleProviders = [...runtimeProviders, ...bootstrapped.modules.flatMap(compiledModule => compiledModule.definition.providers ?? [])];
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, runtimeProviders);
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, runtimeProviders);
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
- await context.close('bootstrap-failed');
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
  }