@fluojs/runtime 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.ko.md +182 -0
- package/README.md +182 -0
- package/dist/abort.d.ts +19 -0
- package/dist/abort.d.ts.map +1 -0
- package/dist/abort.js +39 -0
- package/dist/adapters/internal-http-adapter.d.ts +2 -0
- package/dist/adapters/internal-http-adapter.d.ts.map +1 -0
- package/dist/adapters/internal-http-adapter.js +1 -0
- package/dist/adapters/internal-request-response-factory.d.ts +2 -0
- package/dist/adapters/internal-request-response-factory.d.ts.map +1 -0
- package/dist/adapters/internal-request-response-factory.js +1 -0
- package/dist/adapters/request-response-factory.d.ts +17 -0
- package/dist/adapters/request-response-factory.d.ts.map +1 -0
- package/dist/adapters/request-response-factory.js +27 -0
- package/dist/bootstrap.d.ts +73 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +870 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +88 -0
- package/dist/health/diagnostics.d.ts +56 -0
- package/dist/health/diagnostics.d.ts.map +1 -0
- package/dist/health/diagnostics.js +155 -0
- package/dist/health/health.d.ts +18 -0
- package/dist/health/health.d.ts.map +1 -0
- package/dist/health/health.js +82 -0
- package/dist/http-adapter-shared.d.ts +88 -0
- package/dist/http-adapter-shared.d.ts.map +1 -0
- package/dist/http-adapter-shared.js +199 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/internal-http-adapter.d.ts +2 -0
- package/dist/internal-http-adapter.d.ts.map +1 -0
- package/dist/internal-http-adapter.js +1 -0
- package/dist/internal-node.d.ts +2 -0
- package/dist/internal-node.d.ts.map +1 -0
- package/dist/internal-node.js +1 -0
- package/dist/internal-request-response-factory.d.ts +2 -0
- package/dist/internal-request-response-factory.d.ts.map +1 -0
- package/dist/internal-request-response-factory.js +1 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +1 -0
- package/dist/logging/json-logger.d.ts +3 -0
- package/dist/logging/json-logger.d.ts.map +1 -0
- package/dist/logging/json-logger.js +39 -0
- package/dist/logging/logger.d.ts +3 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +36 -0
- package/dist/module-graph.d.ts +26 -0
- package/dist/module-graph.d.ts.map +1 -0
- package/dist/module-graph.js +248 -0
- package/dist/multipart.d.ts +45 -0
- package/dist/multipart.d.ts.map +1 -0
- package/dist/multipart.js +195 -0
- package/dist/node/internal-node-compression.d.ts +7 -0
- package/dist/node/internal-node-compression.d.ts.map +1 -0
- package/dist/node/internal-node-compression.js +68 -0
- package/dist/node/internal-node-request.d.ts +34 -0
- package/dist/node/internal-node-request.d.ts.map +1 -0
- package/dist/node/internal-node-request.js +195 -0
- package/dist/node/internal-node-response.d.ts +8 -0
- package/dist/node/internal-node-response.d.ts.map +1 -0
- package/dist/node/internal-node-response.js +166 -0
- package/dist/node/internal-node-shutdown.d.ts +34 -0
- package/dist/node/internal-node-shutdown.d.ts.map +1 -0
- package/dist/node/internal-node-shutdown.js +83 -0
- package/dist/node/internal-node.d.ts +80 -0
- package/dist/node/internal-node.d.ts.map +1 -0
- package/dist/node/internal-node.js +209 -0
- package/dist/node/node-compression.d.ts +2 -0
- package/dist/node/node-compression.d.ts.map +1 -0
- package/dist/node/node-compression.js +1 -0
- package/dist/node/node-request.d.ts +2 -0
- package/dist/node/node-request.d.ts.map +1 -0
- package/dist/node/node-request.js +1 -0
- package/dist/node/node-response.d.ts +2 -0
- package/dist/node/node-response.d.ts.map +1 -0
- package/dist/node/node-response.js +1 -0
- package/dist/node/node-shutdown.d.ts +2 -0
- package/dist/node/node-shutdown.d.ts.map +1 -0
- package/dist/node/node-shutdown.js +1 -0
- package/dist/node/node.d.ts +2 -0
- package/dist/node/node.d.ts.map +1 -0
- package/dist/node/node.js +1 -0
- package/dist/node.d.ts +5 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +3 -0
- package/dist/platform-contract.d.ts +140 -0
- package/dist/platform-contract.d.ts.map +1 -0
- package/dist/platform-contract.js +1 -0
- package/dist/platform-shell.d.ts +45 -0
- package/dist/platform-shell.d.ts.map +1 -0
- package/dist/platform-shell.js +368 -0
- package/dist/request-transaction.d.ts +17 -0
- package/dist/request-transaction.d.ts.map +1 -0
- package/dist/request-transaction.js +39 -0
- package/dist/tokens.d.ts +27 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +24 -0
- package/dist/types.d.ts +161 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/web.d.ts +58 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +431 -0
- package/package.json +86 -0
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
import { Container } from '@fluojs/di';
|
|
2
|
+
import { DefaultBinder } from '@fluojs/http/internal';
|
|
3
|
+
import { InvariantError } from '@fluojs/core';
|
|
4
|
+
import { defineModuleMetadata, getClassDiMetadata } from '@fluojs/core/internal';
|
|
5
|
+
import { createDispatcher, createHandlerMapping } from '@fluojs/http';
|
|
6
|
+
import { DuplicateProviderError } from './errors.js';
|
|
7
|
+
import { createBootstrapTimingDiagnostics } from './health/diagnostics.js';
|
|
8
|
+
import { createConsoleApplicationLogger } from './logging/logger.js';
|
|
9
|
+
import { compileModuleGraph, createRuntimeTokenSet, providerToken } from './module-graph.js';
|
|
10
|
+
import { createRuntimePlatformShell } from './platform-shell.js';
|
|
11
|
+
import { APPLICATION_LOGGER, COMPILED_MODULES, HTTP_APPLICATION_ADAPTER, PLATFORM_SHELL, RUNTIME_CONTAINER } from './tokens.js';
|
|
12
|
+
const DEFAULT_MICROSERVICE_TOKEN = Symbol.for('fluo.microservices.service');
|
|
13
|
+
const runtimePerformance = globalThis.performance;
|
|
14
|
+
async function runExceptionFilters(filters, error, request, response, requestId) {
|
|
15
|
+
for (const filter of filters) {
|
|
16
|
+
const handled = await filter.catch(error, {
|
|
17
|
+
request,
|
|
18
|
+
response,
|
|
19
|
+
requestId
|
|
20
|
+
});
|
|
21
|
+
if (handled) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
function providerScope(provider) {
|
|
28
|
+
if (typeof provider === 'function') {
|
|
29
|
+
return getClassDiMetadata(provider)?.scope ?? 'singleton';
|
|
30
|
+
}
|
|
31
|
+
if ('useValue' in provider) {
|
|
32
|
+
return 'singleton';
|
|
33
|
+
}
|
|
34
|
+
if ('useClass' in provider) {
|
|
35
|
+
return provider.scope ?? getClassDiMetadata(provider.useClass)?.scope ?? 'singleton';
|
|
36
|
+
}
|
|
37
|
+
if ('useFactory' in provider) {
|
|
38
|
+
return provider.scope ?? (provider.resolverClass ? getClassDiMetadata(provider.resolverClass)?.scope : undefined) ?? 'singleton';
|
|
39
|
+
}
|
|
40
|
+
return 'singleton';
|
|
41
|
+
}
|
|
42
|
+
async function disposeContainer(container) {
|
|
43
|
+
if (!hasMethod(container, 'dispose')) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
await container.dispose();
|
|
47
|
+
}
|
|
48
|
+
function toError(error) {
|
|
49
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
50
|
+
}
|
|
51
|
+
function createLifecycleCloseError(errors) {
|
|
52
|
+
if (errors.length === 1) {
|
|
53
|
+
return toError(errors[0]);
|
|
54
|
+
}
|
|
55
|
+
return new AggregateError(errors, 'Application close failed for one or more shutdown steps.');
|
|
56
|
+
}
|
|
57
|
+
async function runCleanupCallbacks(cleanups) {
|
|
58
|
+
const errors = [];
|
|
59
|
+
for (const cleanup of cleanups) {
|
|
60
|
+
try {
|
|
61
|
+
cleanup();
|
|
62
|
+
} catch (error) {
|
|
63
|
+
errors.push(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return errors;
|
|
67
|
+
}
|
|
68
|
+
async function closeRuntimeResources(options) {
|
|
69
|
+
const errors = [];
|
|
70
|
+
errors.push(...(await runCleanupCallbacks(options.runtimeCleanup)));
|
|
71
|
+
try {
|
|
72
|
+
await runShutdownHooks(options.lifecycleInstances, options.signal);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
errors.push(error);
|
|
75
|
+
}
|
|
76
|
+
if (options.adapter) {
|
|
77
|
+
try {
|
|
78
|
+
await options.adapter.close(options.signal);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
errors.push(error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await disposeContainer(options.container);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
errors.push(error);
|
|
87
|
+
}
|
|
88
|
+
if (errors.length > 0) {
|
|
89
|
+
throw createLifecycleCloseError(errors);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function runBootstrapFailureCleanup(options) {
|
|
93
|
+
const errors = [];
|
|
94
|
+
errors.push(...(await runCleanupCallbacks(options.runtimeCleanup)));
|
|
95
|
+
if (options.lifecycleInstances.length > 0) {
|
|
96
|
+
try {
|
|
97
|
+
await runShutdownHooks(options.lifecycleInstances, 'bootstrap-failed');
|
|
98
|
+
} catch (error) {
|
|
99
|
+
errors.push(error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (options.container) {
|
|
103
|
+
try {
|
|
104
|
+
await disposeContainer(options.container);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
errors.push(error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const error of errors) {
|
|
110
|
+
options.logger.error(`Failed to clean up after ${options.scope} bootstrap failure.`, error, 'FluoFactory');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function hasMethod(value, methodName) {
|
|
114
|
+
if (typeof value !== 'object' || value === null) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return typeof value[methodName] === 'function';
|
|
118
|
+
}
|
|
119
|
+
function isOnModuleInit(value) {
|
|
120
|
+
return hasMethod(value, 'onModuleInit');
|
|
121
|
+
}
|
|
122
|
+
function isOnApplicationBootstrap(value) {
|
|
123
|
+
return hasMethod(value, 'onApplicationBootstrap');
|
|
124
|
+
}
|
|
125
|
+
function isOnModuleDestroy(value) {
|
|
126
|
+
return hasMethod(value, 'onModuleDestroy');
|
|
127
|
+
}
|
|
128
|
+
function isOnApplicationShutdown(value) {
|
|
129
|
+
return hasMethod(value, 'onApplicationShutdown');
|
|
130
|
+
}
|
|
131
|
+
function hasReadinessStateMethods(value) {
|
|
132
|
+
if (typeof value !== 'function') {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const readinessAware = value;
|
|
136
|
+
return typeof readinessAware.markReady === 'function' && typeof readinessAware.markStarting === 'function';
|
|
137
|
+
}
|
|
138
|
+
function isMicroserviceRuntime(value) {
|
|
139
|
+
return hasMethod(value, 'listen');
|
|
140
|
+
}
|
|
141
|
+
function resetReadinessState(modules) {
|
|
142
|
+
for (const compiledModule of modules) {
|
|
143
|
+
if (hasReadinessStateMethods(compiledModule.type)) {
|
|
144
|
+
compiledModule.type.markStarting();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function markReadinessState(modules) {
|
|
149
|
+
for (const compiledModule of modules) {
|
|
150
|
+
if (hasReadinessStateMethods(compiledModule.type)) {
|
|
151
|
+
compiledModule.type.markReady();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function createDuplicateProviderMessage(token, moduleName, existingModuleName) {
|
|
156
|
+
const tokenLabel = typeof token === 'function' ? token.name || '<anonymous>' : String(token);
|
|
157
|
+
return `Duplicate provider token "${tokenLabel}" registered in module "${moduleName}". Previously registered in module "${existingModuleName}".`;
|
|
158
|
+
}
|
|
159
|
+
function collectProvidersForContainer(modules, runtimeProviders, policy, logger) {
|
|
160
|
+
const selectedProviders = new Map();
|
|
161
|
+
for (const runtimeProvider of runtimeProviders ?? []) {
|
|
162
|
+
const token = providerToken(runtimeProvider);
|
|
163
|
+
selectedProviders.set(token, {
|
|
164
|
+
moduleName: '<runtime>',
|
|
165
|
+
provider: runtimeProvider,
|
|
166
|
+
source: 'runtime',
|
|
167
|
+
token
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
for (const compiledModule of modules) {
|
|
171
|
+
for (const provider of compiledModule.definition.providers ?? []) {
|
|
172
|
+
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
|
+
}
|
|
187
|
+
}
|
|
188
|
+
selectedProviders.set(token, {
|
|
189
|
+
moduleName: compiledModule.type.name,
|
|
190
|
+
provider,
|
|
191
|
+
source: 'module',
|
|
192
|
+
token
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return [...selectedProviders.values()].map(entry => entry.provider);
|
|
197
|
+
}
|
|
198
|
+
function registerControllers(container, modules) {
|
|
199
|
+
for (const compiledModule of modules) {
|
|
200
|
+
for (const controller of compiledModule.definition.controllers ?? []) {
|
|
201
|
+
container.register(controller);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function registerMiddlewareToken(container, middlewareToken) {
|
|
206
|
+
if (container.has(middlewareToken)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
container.register(middlewareToken);
|
|
210
|
+
}
|
|
211
|
+
function registerModuleMiddleware(container, modules) {
|
|
212
|
+
for (const compiledModule of modules) {
|
|
213
|
+
for (const middleware of compiledModule.definition.middleware ?? []) {
|
|
214
|
+
if (typeof middleware === 'object' && middleware !== null && 'middleware' in middleware && 'routes' in middleware) {
|
|
215
|
+
const middlewareToken = middleware.middleware;
|
|
216
|
+
if (typeof middlewareToken === 'function') {
|
|
217
|
+
registerMiddlewareToken(container, middlewareToken);
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (typeof middleware === 'function') {
|
|
222
|
+
registerMiddlewareToken(container, middleware);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Associates module metadata with a module type.
|
|
230
|
+
*
|
|
231
|
+
* @param moduleType Module class that should receive runtime module metadata.
|
|
232
|
+
* @param definition Module definition contract (`imports`, `providers`, `controllers`, `exports`, etc.).
|
|
233
|
+
* @returns The same `moduleType` reference for fluent helper composition.
|
|
234
|
+
*/
|
|
235
|
+
export function defineModule(moduleType, definition) {
|
|
236
|
+
defineModuleMetadata(moduleType, definition);
|
|
237
|
+
return moduleType;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Bootstraps the module graph and returns the root container baseline.
|
|
242
|
+
*
|
|
243
|
+
* @param rootModule Root module type used as the module-graph entrypoint.
|
|
244
|
+
* @param options Bootstrap-module options such as runtime providers and duplicate-provider policy.
|
|
245
|
+
* @returns The compiled module graph and initialized DI container baseline.
|
|
246
|
+
* @throws {DuplicateProviderError} When duplicate module provider tokens are detected and policy is `throw`.
|
|
247
|
+
* @throws {Error} When module-graph compilation or provider registration fails.
|
|
248
|
+
*/
|
|
249
|
+
export function bootstrapModule(rootModule, options = {}) {
|
|
250
|
+
const modules = compileModuleGraph(rootModule, options);
|
|
251
|
+
const container = new Container();
|
|
252
|
+
const policy = options.duplicateProviderPolicy ?? 'warn';
|
|
253
|
+
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);
|
|
258
|
+
}
|
|
259
|
+
if (moduleProviders.length > 0) {
|
|
260
|
+
container.register(...moduleProviders);
|
|
261
|
+
}
|
|
262
|
+
registerControllers(container, modules);
|
|
263
|
+
registerModuleMiddleware(container, modules);
|
|
264
|
+
return {
|
|
265
|
+
container,
|
|
266
|
+
modules,
|
|
267
|
+
rootModule
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 애플리케이션 라이프사이클과 상태 전이를 담당하는 최소 런타임 셸이다.
|
|
273
|
+
*/
|
|
274
|
+
class FluoApplication {
|
|
275
|
+
applicationState = 'bootstrapped';
|
|
276
|
+
closed = false;
|
|
277
|
+
closingPromise;
|
|
278
|
+
lifecycleInstances;
|
|
279
|
+
connectedMicroservices = [];
|
|
280
|
+
constructor(container, modules, rootModule, dispatcher, bootstrapTiming, adapter, hasHttpAdapter, platformShell, lifecycleInstances, logger, runtimeCleanup) {
|
|
281
|
+
this.container = container;
|
|
282
|
+
this.modules = modules;
|
|
283
|
+
this.rootModule = rootModule;
|
|
284
|
+
this.dispatcher = dispatcher;
|
|
285
|
+
this.bootstrapTiming = bootstrapTiming;
|
|
286
|
+
this.adapter = adapter;
|
|
287
|
+
this.hasHttpAdapter = hasHttpAdapter;
|
|
288
|
+
this.platformShell = platformShell;
|
|
289
|
+
this.logger = logger;
|
|
290
|
+
this.runtimeCleanup = runtimeCleanup;
|
|
291
|
+
this.lifecycleInstances = lifecycleInstances;
|
|
292
|
+
}
|
|
293
|
+
get state() {
|
|
294
|
+
return this.applicationState;
|
|
295
|
+
}
|
|
296
|
+
async get(token) {
|
|
297
|
+
return this.container.resolve(token);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 애플리케이션이 더 이상 닫힌 상태가 아닌지 확인한다.
|
|
302
|
+
*/
|
|
303
|
+
async ready() {
|
|
304
|
+
if (this.applicationState === 'closed') {
|
|
305
|
+
throw new InvariantError('Application cannot become ready after it has been closed.');
|
|
306
|
+
}
|
|
307
|
+
await this.platformShell.assertCriticalReadiness();
|
|
308
|
+
}
|
|
309
|
+
async connectMicroservice(options = {}) {
|
|
310
|
+
const microserviceToken = options.microserviceToken ?? DEFAULT_MICROSERVICE_TOKEN;
|
|
311
|
+
const runtime = await this.container.resolve(microserviceToken);
|
|
312
|
+
if (!isMicroserviceRuntime(runtime)) {
|
|
313
|
+
throw new InvariantError('Resolved microservice token does not implement listen().');
|
|
314
|
+
}
|
|
315
|
+
const microservice = new FluoMicroserviceApplication(this, this.logger, runtime);
|
|
316
|
+
this.connectedMicroservices.push(microservice);
|
|
317
|
+
return microservice;
|
|
318
|
+
}
|
|
319
|
+
async startAllMicroservices() {
|
|
320
|
+
await Promise.all(this.connectedMicroservices.map(async microservice => microservice.listen()));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 준비 검사를 통과한 뒤 어댑터에 바인딩을 위임하고 상태를 `ready`로 전이한다.
|
|
325
|
+
*/
|
|
326
|
+
async listen() {
|
|
327
|
+
if (this.applicationState === 'closed') {
|
|
328
|
+
throw new InvariantError('Application cannot listen after it has been closed.');
|
|
329
|
+
}
|
|
330
|
+
if (this.applicationState === 'ready') {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (!this.hasHttpAdapter) {
|
|
334
|
+
throw new InvariantError('Application cannot listen without an HTTP adapter. Provide options.adapter for HTTP startup, or use createApplicationContext() for adapterless DI-only bootstrap.');
|
|
335
|
+
}
|
|
336
|
+
await this.ready();
|
|
337
|
+
try {
|
|
338
|
+
await this.adapter.listen(this.dispatcher);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
this.logger.error('Failed to start the HTTP adapter.', error, 'FluoApplication');
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
343
|
+
this.applicationState = 'ready';
|
|
344
|
+
this.logger.log('fluo application successfully started.', 'FluoApplication');
|
|
345
|
+
}
|
|
346
|
+
dispatch = async (...args) => {
|
|
347
|
+
await this.dispatcher.dispatch(...args);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* 어댑터 종료와 shutdown hook 실행을 담당하는 명시적 종료 경로다.
|
|
352
|
+
*/
|
|
353
|
+
async close(signal) {
|
|
354
|
+
if (this.closed) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (this.closingPromise) {
|
|
358
|
+
await this.closingPromise;
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
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
|
+
});
|
|
369
|
+
this.closed = true;
|
|
370
|
+
this.applicationState = 'closed';
|
|
371
|
+
})();
|
|
372
|
+
try {
|
|
373
|
+
await this.closingPromise;
|
|
374
|
+
} catch (error) {
|
|
375
|
+
this.closingPromise = undefined;
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
class FluoApplicationContext {
|
|
381
|
+
closed = false;
|
|
382
|
+
closingPromise;
|
|
383
|
+
constructor(container, modules, rootModule, bootstrapTiming, lifecycleInstances, runtimeCleanup) {
|
|
384
|
+
this.container = container;
|
|
385
|
+
this.modules = modules;
|
|
386
|
+
this.rootModule = rootModule;
|
|
387
|
+
this.bootstrapTiming = bootstrapTiming;
|
|
388
|
+
this.lifecycleInstances = lifecycleInstances;
|
|
389
|
+
this.runtimeCleanup = runtimeCleanup;
|
|
390
|
+
}
|
|
391
|
+
async get(token) {
|
|
392
|
+
return this.container.resolve(token);
|
|
393
|
+
}
|
|
394
|
+
async close(signal) {
|
|
395
|
+
if (this.closed) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (this.closingPromise) {
|
|
399
|
+
await this.closingPromise;
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
this.closingPromise = (async () => {
|
|
403
|
+
await closeRuntimeResources({
|
|
404
|
+
container: this.container,
|
|
405
|
+
lifecycleInstances: this.lifecycleInstances,
|
|
406
|
+
runtimeCleanup: this.runtimeCleanup,
|
|
407
|
+
signal
|
|
408
|
+
});
|
|
409
|
+
this.closed = true;
|
|
410
|
+
})();
|
|
411
|
+
try {
|
|
412
|
+
await this.closingPromise;
|
|
413
|
+
} catch (error) {
|
|
414
|
+
this.closingPromise = undefined;
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
class FluoMicroserviceApplication {
|
|
420
|
+
closed = false;
|
|
421
|
+
closingPromise;
|
|
422
|
+
microserviceState = 'bootstrapped';
|
|
423
|
+
constructor(context, logger, runtime) {
|
|
424
|
+
this.context = context;
|
|
425
|
+
this.logger = logger;
|
|
426
|
+
this.runtime = runtime;
|
|
427
|
+
}
|
|
428
|
+
get container() {
|
|
429
|
+
return this.context.container;
|
|
430
|
+
}
|
|
431
|
+
get modules() {
|
|
432
|
+
return this.context.modules;
|
|
433
|
+
}
|
|
434
|
+
get rootModule() {
|
|
435
|
+
return this.context.rootModule;
|
|
436
|
+
}
|
|
437
|
+
get state() {
|
|
438
|
+
return this.microserviceState;
|
|
439
|
+
}
|
|
440
|
+
async get(token) {
|
|
441
|
+
return this.context.get(token);
|
|
442
|
+
}
|
|
443
|
+
async listen() {
|
|
444
|
+
if (this.microserviceState === 'closed') {
|
|
445
|
+
throw new InvariantError('Microservice cannot listen after it has been closed.');
|
|
446
|
+
}
|
|
447
|
+
if (this.microserviceState === 'ready') {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
await this.runtime.listen();
|
|
451
|
+
this.microserviceState = 'ready';
|
|
452
|
+
this.logger.log('fluo microservice successfully started.', 'FluoFactory');
|
|
453
|
+
}
|
|
454
|
+
async send(pattern, payload, signal) {
|
|
455
|
+
if (!this.runtime.send) {
|
|
456
|
+
throw new InvariantError('Resolved microservice runtime does not implement send().');
|
|
457
|
+
}
|
|
458
|
+
return await this.runtime.send(pattern, payload, signal);
|
|
459
|
+
}
|
|
460
|
+
async emit(pattern, payload) {
|
|
461
|
+
if (!this.runtime.emit) {
|
|
462
|
+
throw new InvariantError('Resolved microservice runtime does not implement emit().');
|
|
463
|
+
}
|
|
464
|
+
await this.runtime.emit(pattern, payload);
|
|
465
|
+
}
|
|
466
|
+
async close(signal) {
|
|
467
|
+
if (this.closed) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (this.closingPromise) {
|
|
471
|
+
await this.closingPromise;
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
this.closingPromise = (async () => {
|
|
475
|
+
await this.context.close(signal);
|
|
476
|
+
this.closed = true;
|
|
477
|
+
this.microserviceState = 'closed';
|
|
478
|
+
})();
|
|
479
|
+
try {
|
|
480
|
+
await this.closingPromise;
|
|
481
|
+
} catch (error) {
|
|
482
|
+
this.closingPromise = undefined;
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* lifecycle hook이 있는 singleton provider 인스턴스를 미리 해석해 둔다.
|
|
490
|
+
*/
|
|
491
|
+
async function resolveLifecycleInstances(container, providers) {
|
|
492
|
+
const instances = [];
|
|
493
|
+
const seen = new Set();
|
|
494
|
+
for (const provider of providers) {
|
|
495
|
+
const scope = providerScope(provider);
|
|
496
|
+
if (scope === 'request' || scope === 'transient') {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const token = providerToken(provider);
|
|
500
|
+
if (seen.has(token)) {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
seen.add(token);
|
|
504
|
+
instances.push(await container.resolve(token));
|
|
505
|
+
}
|
|
506
|
+
return instances;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* 부트스트랩 단계의 hook을 고정된 순서로 실행한다.
|
|
511
|
+
*/
|
|
512
|
+
async function runBootstrapHooks(instances) {
|
|
513
|
+
for (const instance of instances) {
|
|
514
|
+
if (isOnModuleInit(instance)) {
|
|
515
|
+
await instance.onModuleInit();
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
for (const instance of instances) {
|
|
519
|
+
if (isOnApplicationBootstrap(instance)) {
|
|
520
|
+
await instance.onApplicationBootstrap();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* 종료 단계의 hook을 역순으로 실행해 이미 시작한 리소스를 정리한다.
|
|
527
|
+
*/
|
|
528
|
+
async function runShutdownHooks(instances, signal) {
|
|
529
|
+
for (const instance of [...instances].reverse()) {
|
|
530
|
+
if (isOnModuleDestroy(instance)) {
|
|
531
|
+
await instance.onModuleDestroy();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
for (const instance of [...instances].reverse()) {
|
|
535
|
+
if (isOnApplicationShutdown(instance)) {
|
|
536
|
+
await instance.onApplicationShutdown(signal);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function createHandlerSources(modules) {
|
|
541
|
+
return modules.flatMap(compiledModule => (compiledModule.definition.controllers ?? []).map(controllerToken => ({
|
|
542
|
+
controllerToken,
|
|
543
|
+
moduleMiddleware: compiledModule.definition.middleware ?? [],
|
|
544
|
+
moduleType: compiledModule.type
|
|
545
|
+
})));
|
|
546
|
+
}
|
|
547
|
+
function logCompiledModules(logger, modules) {
|
|
548
|
+
for (const compiledModule of modules) {
|
|
549
|
+
logger.log(`${compiledModule.type.name} dependencies initialized`, 'InstanceLoader');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function logRouteMappings(logger, descriptors) {
|
|
553
|
+
const byController = new Map();
|
|
554
|
+
for (const descriptor of descriptors) {
|
|
555
|
+
const key = descriptor.controllerToken.name;
|
|
556
|
+
const current = byController.get(key);
|
|
557
|
+
if (current) {
|
|
558
|
+
current.descriptors.push(descriptor);
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
byController.set(key, {
|
|
562
|
+
controllerPath: descriptor.metadata.controllerPath || '/',
|
|
563
|
+
descriptors: [descriptor]
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
for (const [controllerName, value] of byController) {
|
|
567
|
+
logger.log(`${controllerName} {${value.controllerPath}}`, 'RoutesResolver');
|
|
568
|
+
for (const descriptor of value.descriptors) {
|
|
569
|
+
logger.log(`Mapped {${descriptor.route.path}, ${descriptor.route.method}} route`, 'RouterExplorer');
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function createRuntimeProviders(options, logger) {
|
|
574
|
+
return [...(options.providers ?? []), {
|
|
575
|
+
provide: APPLICATION_LOGGER,
|
|
576
|
+
useValue: logger
|
|
577
|
+
}];
|
|
578
|
+
}
|
|
579
|
+
function registerRuntimeBootstrapTokens(bootstrapped, adapter, platformShell) {
|
|
580
|
+
registerRuntimeContextTokens(bootstrapped, {
|
|
581
|
+
provide: HTTP_APPLICATION_ADAPTER,
|
|
582
|
+
useValue: adapter
|
|
583
|
+
}, {
|
|
584
|
+
provide: PLATFORM_SHELL,
|
|
585
|
+
useValue: platformShell
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
function registerRuntimeContextTokens(bootstrapped, ...providers) {
|
|
589
|
+
bootstrapped.container.register(...providers, {
|
|
590
|
+
provide: RUNTIME_CONTAINER,
|
|
591
|
+
useValue: bootstrapped.container
|
|
592
|
+
}, {
|
|
593
|
+
provide: COMPILED_MODULES,
|
|
594
|
+
useValue: bootstrapped.modules
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
function registerRuntimeApplicationContextTokens(bootstrapped, platformShell) {
|
|
598
|
+
registerRuntimeContextTokens(bootstrapped, {
|
|
599
|
+
provide: PLATFORM_SHELL,
|
|
600
|
+
useValue: platformShell
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
async function resolveBootstrapLifecycleInstances(bootstrapped, runtimeProviders) {
|
|
604
|
+
const lifecycleProviders = [...runtimeProviders, ...bootstrapped.modules.flatMap(compiledModule => compiledModule.definition.providers ?? [])];
|
|
605
|
+
return resolveLifecycleInstances(bootstrapped.container, lifecycleProviders);
|
|
606
|
+
}
|
|
607
|
+
async function runBootstrapLifecycle(modules, lifecycleInstances, logger, platformShell) {
|
|
608
|
+
resetReadinessState(modules);
|
|
609
|
+
await runBootstrapHooks(lifecycleInstances);
|
|
610
|
+
await platformShell.start();
|
|
611
|
+
markReadinessState(modules);
|
|
612
|
+
logCompiledModules(logger, modules);
|
|
613
|
+
}
|
|
614
|
+
function createFilterErrorHandler(filters) {
|
|
615
|
+
if (!filters || filters.length === 0) {
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
return async (error, request, response, requestId) => runExceptionFilters(filters, error, request, response, requestId);
|
|
619
|
+
}
|
|
620
|
+
function createRuntimeDispatcherOptions(bootstrapped, options, handlerMapping, errorHandler, logger) {
|
|
621
|
+
const dispatcherOptions = {
|
|
622
|
+
appMiddleware: options.middleware ?? [],
|
|
623
|
+
binder: new DefaultBinder(options.converters ?? []),
|
|
624
|
+
handlerMapping,
|
|
625
|
+
interceptors: options.interceptors ?? [],
|
|
626
|
+
logger,
|
|
627
|
+
observers: options.observers ?? [],
|
|
628
|
+
rootContainer: bootstrapped.container
|
|
629
|
+
};
|
|
630
|
+
if (errorHandler) {
|
|
631
|
+
dispatcherOptions.onError = errorHandler;
|
|
632
|
+
}
|
|
633
|
+
return dispatcherOptions;
|
|
634
|
+
}
|
|
635
|
+
function createRuntimeDispatcher(bootstrapped, options, logger) {
|
|
636
|
+
const handlerMapping = createHandlerMapping(createHandlerSources(bootstrapped.modules), {
|
|
637
|
+
versioning: options.versioning
|
|
638
|
+
});
|
|
639
|
+
logRouteMappings(logger, handlerMapping.descriptors);
|
|
640
|
+
const errorHandler = createFilterErrorHandler(options.filters);
|
|
641
|
+
const dispatcherOptions = createRuntimeDispatcherOptions(bootstrapped, options, handlerMapping, errorHandler, logger);
|
|
642
|
+
return createDispatcher(dispatcherOptions);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Creates the runtime application shell by composing bootstrap-level providers,
|
|
647
|
+
* module bootstrap, and lifecycle hook execution.
|
|
648
|
+
*
|
|
649
|
+
* @param options Runtime bootstrap contract including root module, adapter, and global runtime hooks.
|
|
650
|
+
* @returns A fully bootstrapped `Application` shell ready for `ready()`/`listen()`.
|
|
651
|
+
* @throws {Error} Propagates module-graph, lifecycle, or runtime initialization failures.
|
|
652
|
+
*/
|
|
653
|
+
export async function bootstrapApplication(options) {
|
|
654
|
+
const logger = options.logger ?? createConsoleApplicationLogger();
|
|
655
|
+
let lifecycleInstances = [];
|
|
656
|
+
let bootstrappedContainer;
|
|
657
|
+
const hasHttpAdapter = options.adapter !== undefined;
|
|
658
|
+
const adapter = options.adapter ?? {
|
|
659
|
+
async close() {},
|
|
660
|
+
async listen() {}
|
|
661
|
+
};
|
|
662
|
+
const runtimeCleanup = [];
|
|
663
|
+
const platformShell = createRuntimePlatformShell(options.platform?.components);
|
|
664
|
+
const timingEnabled = options.diagnostics?.timing === true;
|
|
665
|
+
const timingStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
666
|
+
const timingPhases = [];
|
|
667
|
+
try {
|
|
668
|
+
logger.log('Starting fluo application...', 'FluoFactory');
|
|
669
|
+
const runtimeProviders = createRuntimeProviders(options, logger);
|
|
670
|
+
const moduleBootstrapStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
671
|
+
const bootstrapped = bootstrapModule(options.rootModule, {
|
|
672
|
+
duplicateProviderPolicy: options.duplicateProviderPolicy,
|
|
673
|
+
logger,
|
|
674
|
+
providers: runtimeProviders,
|
|
675
|
+
validationTokens: [RUNTIME_CONTAINER, COMPILED_MODULES, HTTP_APPLICATION_ADAPTER]
|
|
676
|
+
});
|
|
677
|
+
if (timingEnabled) {
|
|
678
|
+
timingPhases.push({
|
|
679
|
+
durationMs: runtimePerformance.now() - moduleBootstrapStart,
|
|
680
|
+
name: 'bootstrap_module'
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
const registerTokensStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
684
|
+
registerRuntimeBootstrapTokens(bootstrapped, adapter, platformShell);
|
|
685
|
+
if (timingEnabled) {
|
|
686
|
+
timingPhases.push({
|
|
687
|
+
durationMs: runtimePerformance.now() - registerTokensStart,
|
|
688
|
+
name: 'register_runtime_tokens'
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
bootstrappedContainer = bootstrapped.container;
|
|
692
|
+
const resolveLifecycleStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
693
|
+
lifecycleInstances = await resolveBootstrapLifecycleInstances(bootstrapped, runtimeProviders);
|
|
694
|
+
lifecycleInstances.push({
|
|
695
|
+
onModuleDestroy() {
|
|
696
|
+
return platformShell.stop();
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
if (timingEnabled) {
|
|
700
|
+
timingPhases.push({
|
|
701
|
+
durationMs: runtimePerformance.now() - resolveLifecycleStart,
|
|
702
|
+
name: 'resolve_lifecycle_instances'
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
const lifecycleStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
706
|
+
await runBootstrapLifecycle(bootstrapped.modules, lifecycleInstances, logger, platformShell);
|
|
707
|
+
if (timingEnabled) {
|
|
708
|
+
timingPhases.push({
|
|
709
|
+
durationMs: runtimePerformance.now() - lifecycleStart,
|
|
710
|
+
name: 'run_bootstrap_lifecycle'
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const dispatcherStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
714
|
+
const dispatcher = createRuntimeDispatcher(bootstrapped, options, logger);
|
|
715
|
+
if (timingEnabled) {
|
|
716
|
+
timingPhases.push({
|
|
717
|
+
durationMs: runtimePerformance.now() - dispatcherStart,
|
|
718
|
+
name: 'create_dispatcher'
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
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);
|
|
723
|
+
} catch (error) {
|
|
724
|
+
logger.error('Failed to bootstrap the fluo application. Check the error below for what failed and how to fix it.', error, 'FluoFactory');
|
|
725
|
+
await runBootstrapFailureCleanup({
|
|
726
|
+
container: bootstrappedContainer,
|
|
727
|
+
lifecycleInstances,
|
|
728
|
+
logger,
|
|
729
|
+
runtimeCleanup,
|
|
730
|
+
scope: 'application'
|
|
731
|
+
});
|
|
732
|
+
throw error;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Canonical runtime bootstrap facade for HTTP, context-only, and microservice startup.
|
|
738
|
+
*/
|
|
739
|
+
export class FluoFactory {
|
|
740
|
+
/**
|
|
741
|
+
* Creates a full HTTP-capable application from the root module.
|
|
742
|
+
*
|
|
743
|
+
* @param rootModule Root module type used as the application composition entrypoint.
|
|
744
|
+
* @param options Optional HTTP-runtime bootstrap options.
|
|
745
|
+
* @returns A bootstrapped HTTP-capable `Application`.
|
|
746
|
+
* @throws {Error} Propagates bootstrap failures from `bootstrapApplication(...)`.
|
|
747
|
+
*/
|
|
748
|
+
static async create(rootModule, options = {}) {
|
|
749
|
+
return bootstrapApplication({
|
|
750
|
+
...options,
|
|
751
|
+
rootModule
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Creates an application context without attaching an HTTP runtime.
|
|
757
|
+
*
|
|
758
|
+
* @param rootModule Root module type used as the context composition entrypoint.
|
|
759
|
+
* @param options Optional context bootstrap options.
|
|
760
|
+
* @returns A bootstrapped `ApplicationContext` that exposes DI and lifecycle control.
|
|
761
|
+
* @throws {Error} Propagates module-graph, lifecycle, and context bootstrap failures.
|
|
762
|
+
*/
|
|
763
|
+
static async createApplicationContext(rootModule, options = {}) {
|
|
764
|
+
const logger = options.logger ?? createConsoleApplicationLogger();
|
|
765
|
+
let lifecycleInstances = [];
|
|
766
|
+
let bootstrappedContainer;
|
|
767
|
+
const runtimeCleanup = [];
|
|
768
|
+
const platformShell = createRuntimePlatformShell(options.platform?.components);
|
|
769
|
+
const timingEnabled = options.diagnostics?.timing === true;
|
|
770
|
+
const timingStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
771
|
+
const timingPhases = [];
|
|
772
|
+
try {
|
|
773
|
+
logger.log('Starting fluo application context...', 'FluoFactory');
|
|
774
|
+
const runtimeProviders = createRuntimeProviders(options, logger);
|
|
775
|
+
const moduleBootstrapStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
776
|
+
const bootstrapped = bootstrapModule(rootModule, {
|
|
777
|
+
duplicateProviderPolicy: options.duplicateProviderPolicy,
|
|
778
|
+
logger,
|
|
779
|
+
providers: runtimeProviders,
|
|
780
|
+
validationTokens: [RUNTIME_CONTAINER, COMPILED_MODULES]
|
|
781
|
+
});
|
|
782
|
+
if (timingEnabled) {
|
|
783
|
+
timingPhases.push({
|
|
784
|
+
durationMs: runtimePerformance.now() - moduleBootstrapStart,
|
|
785
|
+
name: 'bootstrap_module'
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
const registerTokensStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
789
|
+
registerRuntimeApplicationContextTokens(bootstrapped, platformShell);
|
|
790
|
+
if (timingEnabled) {
|
|
791
|
+
timingPhases.push({
|
|
792
|
+
durationMs: runtimePerformance.now() - registerTokensStart,
|
|
793
|
+
name: 'register_runtime_tokens'
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
bootstrappedContainer = bootstrapped.container;
|
|
797
|
+
const resolveLifecycleStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
798
|
+
lifecycleInstances = await resolveBootstrapLifecycleInstances(bootstrapped, runtimeProviders);
|
|
799
|
+
lifecycleInstances.push({
|
|
800
|
+
onModuleDestroy() {
|
|
801
|
+
return platformShell.stop();
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
if (timingEnabled) {
|
|
805
|
+
timingPhases.push({
|
|
806
|
+
durationMs: runtimePerformance.now() - resolveLifecycleStart,
|
|
807
|
+
name: 'resolve_lifecycle_instances'
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
const lifecycleStart = timingEnabled ? runtimePerformance.now() : 0;
|
|
811
|
+
await runBootstrapLifecycle(bootstrapped.modules, lifecycleInstances, logger, platformShell);
|
|
812
|
+
if (timingEnabled) {
|
|
813
|
+
timingPhases.push({
|
|
814
|
+
durationMs: runtimePerformance.now() - lifecycleStart,
|
|
815
|
+
name: 'run_bootstrap_lifecycle'
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
const bootstrapTiming = timingEnabled ? createBootstrapTimingDiagnostics(timingPhases, runtimePerformance.now() - timingStart) : undefined;
|
|
819
|
+
return new FluoApplicationContext(bootstrapped.container, bootstrapped.modules, rootModule, bootstrapTiming, lifecycleInstances, runtimeCleanup);
|
|
820
|
+
} catch (error) {
|
|
821
|
+
logger.error('Failed to bootstrap application context. Check the error below for what failed and how to fix it.', error, 'FluoFactory');
|
|
822
|
+
await runBootstrapFailureCleanup({
|
|
823
|
+
container: bootstrappedContainer,
|
|
824
|
+
lifecycleInstances,
|
|
825
|
+
logger,
|
|
826
|
+
runtimeCleanup,
|
|
827
|
+
scope: 'application context'
|
|
828
|
+
});
|
|
829
|
+
throw error;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Creates a microservice application from the configured runtime token.
|
|
835
|
+
*
|
|
836
|
+
* @param rootModule Root module type used as the microservice composition entrypoint.
|
|
837
|
+
* @param options Optional microservice bootstrap options, including `microserviceToken` overrides.
|
|
838
|
+
* @returns A bootstrapped `MicroserviceApplication` wrapper around the resolved runtime transport.
|
|
839
|
+
* @throws {InvariantError} When the resolved runtime token does not implement `listen()`.
|
|
840
|
+
* @throws {Error} Propagates application-context bootstrap or runtime-resolution failures.
|
|
841
|
+
*/
|
|
842
|
+
static async createMicroservice(rootModule, options = {}) {
|
|
843
|
+
const logger = options.logger ?? createConsoleApplicationLogger();
|
|
844
|
+
const microserviceToken = options.microserviceToken ?? DEFAULT_MICROSERVICE_TOKEN;
|
|
845
|
+
const context = await FluoFactory.createApplicationContext(rootModule, options);
|
|
846
|
+
try {
|
|
847
|
+
const runtime = await context.get(microserviceToken);
|
|
848
|
+
if (!isMicroserviceRuntime(runtime)) {
|
|
849
|
+
throw new InvariantError('Resolved microservice token does not implement listen().');
|
|
850
|
+
}
|
|
851
|
+
return new FluoMicroserviceApplication(context, logger, runtime);
|
|
852
|
+
} catch (error) {
|
|
853
|
+
await context.close('bootstrap-failed');
|
|
854
|
+
logger.error('Failed to bootstrap microservice context. Check the error below for what failed and how to fix it.', error, 'FluoFactory');
|
|
855
|
+
throw error;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Lower-camel-case compatibility alias that matches the documented runtime entrypoint.
|
|
862
|
+
*
|
|
863
|
+
* @example
|
|
864
|
+
* ```ts
|
|
865
|
+
* import { fluoFactory } from '@fluojs/runtime';
|
|
866
|
+
*
|
|
867
|
+
* const app = await fluoFactory.create(AppModule);
|
|
868
|
+
* ```
|
|
869
|
+
*/
|
|
870
|
+
export const fluoFactory = FluoFactory;
|