@loutrejs/loutre 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/bun.d.ts +2 -4
- package/dist/adapters/bun.js +36 -28
- package/dist/adapters/cloudflare-workers.d.ts +2 -4
- package/dist/adapters/cloudflare-workers.js +6 -11
- package/dist/adapters/server-transports.d.ts +4 -0
- package/dist/adapters/server-transports.js +18 -0
- package/dist/adapters/websocket-native.d.ts +24 -0
- package/dist/adapters/websocket-native.js +150 -0
- package/dist/application/kernel.d.ts +2 -1
- package/dist/core/extension.d.ts +26 -3
- package/dist/core/extension.js +19 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/instrumentation.d.ts +36 -0
- package/dist/core/instrumentation.js +1 -0
- package/dist/core/model/modules.js +13 -17
- package/dist/core/model.js +14 -4
- package/dist/core/module.d.ts +3 -3
- package/dist/devtools/capsule.d.ts +51 -0
- package/dist/devtools/capsule.js +247 -0
- package/dist/devtools/index.d.ts +11 -0
- package/dist/devtools/index.js +6 -0
- package/dist/devtools/inspector.d.ts +57 -0
- package/dist/devtools/inspector.js +1 -0
- package/dist/devtools/module.d.ts +18 -0
- package/dist/devtools/module.js +26 -0
- package/dist/devtools/observer.d.ts +77 -0
- package/dist/devtools/observer.js +10 -0
- package/dist/devtools/protocol.d.ts +1 -0
- package/dist/devtools/protocol.js +1 -0
- package/dist/devtools/provider-instrumentation.d.ts +14 -0
- package/dist/devtools/provider-instrumentation.js +117 -0
- package/dist/devtools/runtime.d.ts +20 -0
- package/dist/devtools/runtime.js +519 -0
- package/dist/devtools/values.d.ts +35 -0
- package/dist/devtools/values.js +172 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/graph/model.js +37 -40
- package/dist/http/auth.d.ts +2 -2
- package/dist/http/auth.js +9 -0
- package/dist/http/client.js +5 -5
- package/dist/http/extension.d.ts +37 -10
- package/dist/http/extension.js +249 -57
- package/dist/http/index.d.ts +3 -2
- package/dist/http/index.js +1 -1
- package/dist/http/openapi.d.ts +3 -4
- package/dist/http/openapi.js +45 -26
- package/dist/http/request-head.d.ts +6 -0
- package/dist/http/request-head.js +1 -0
- package/dist/http/route-validation.d.ts +9 -0
- package/dist/http/route-validation.js +18 -0
- package/dist/http/runtime/response.js +10 -10
- package/dist/message-port/extension.js +20 -4
- package/dist/runtime/di.d.ts +4 -0
- package/dist/runtime/di.js +42 -31
- package/dist/runtime/engine.d.ts +1 -1
- package/dist/runtime/engine.js +1 -2
- package/dist/runtime/kernel.d.ts +4 -2
- package/dist/runtime/kernel.js +96 -3
- package/dist/runtime/node-capabilities.d.ts +1 -1
- package/dist/runtime/node-capabilities.js +2 -1
- package/dist/tasks/extension.js +133 -118
- package/dist/websocket/driver.d.ts +30 -0
- package/dist/websocket/driver.js +96 -0
- package/dist/websocket/extension.d.ts +33 -12
- package/dist/websocket/extension.js +72 -47
- package/dist/websocket/index.d.ts +3 -1
- package/dist/websocket/index.js +1 -0
- package/package.json +7 -6
- package/dist/adapters/deno.d.ts +0 -35
- package/dist/adapters/deno.js +0 -231
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validateSchema } from '../../core/index.js';
|
|
2
|
+
import { match } from 'ts-pattern';
|
|
2
3
|
export async function validateResponseHeaders(schema, headers) {
|
|
3
4
|
if (!schema) {
|
|
4
5
|
if (headers !== undefined) {
|
|
@@ -15,18 +16,17 @@ export async function validateResponseHeaders(schema, headers) {
|
|
|
15
16
|
return validated;
|
|
16
17
|
}
|
|
17
18
|
export function responseHeadersSchema(headers) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return undefined;
|
|
19
|
+
return match(headers)
|
|
20
|
+
.when(isStandardSchema, (schema) => schema)
|
|
21
|
+
.when(isResponseHeadersWithDefaults, (definition) => definition.schema)
|
|
22
|
+
.otherwise(() => undefined);
|
|
23
23
|
}
|
|
24
24
|
export function responseHeadersDefaults(headers) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
return match(headers)
|
|
26
|
+
.with(undefined, () => undefined)
|
|
27
|
+
.when(isStandardSchema, () => undefined)
|
|
28
|
+
.when(isResponseHeadersWithDefaults, (definition) => definition.defaults)
|
|
29
|
+
.otherwise((definition) => definition);
|
|
30
30
|
}
|
|
31
31
|
export function isStandardSchema(value) {
|
|
32
32
|
return typeof value === 'object' && value !== null && '~standard' in value;
|
|
@@ -97,8 +97,13 @@ function createMessagePortRuntime(executions, applicationRuntime) {
|
|
|
97
97
|
}, () => execution.compiled.factory());
|
|
98
98
|
for (const [method, definition] of Object.entries(execution.compiled.routes)) {
|
|
99
99
|
const handler = handlers[method];
|
|
100
|
-
if (handler)
|
|
101
|
-
routes.set(method, {
|
|
100
|
+
if (handler) {
|
|
101
|
+
routes.set(method, {
|
|
102
|
+
executionId: execution.id,
|
|
103
|
+
route: definition,
|
|
104
|
+
handler,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
return {
|
|
@@ -114,7 +119,12 @@ function createMessagePortRuntime(executions, applicationRuntime) {
|
|
|
114
119
|
}
|
|
115
120
|
let lease;
|
|
116
121
|
try {
|
|
117
|
-
lease = applicationRuntime.beginExecution(
|
|
122
|
+
lease = applicationRuntime.beginExecution({
|
|
123
|
+
executionId: route.executionId,
|
|
124
|
+
executionKind: 'message-port.invocation',
|
|
125
|
+
graphNodeId: route.executionId,
|
|
126
|
+
name: method,
|
|
127
|
+
});
|
|
118
128
|
}
|
|
119
129
|
catch (error) {
|
|
120
130
|
completePendingIngress();
|
|
@@ -133,11 +143,12 @@ function createMessagePortRuntime(executions, applicationRuntime) {
|
|
|
133
143
|
value: result,
|
|
134
144
|
}),
|
|
135
145
|
]));
|
|
136
|
-
const
|
|
146
|
+
const invoke = () => route.handler({
|
|
137
147
|
input: value,
|
|
138
148
|
response,
|
|
139
149
|
signal: lease.signal,
|
|
140
150
|
});
|
|
151
|
+
const result = await (lease.run ? lease.run(invoke) : invoke());
|
|
141
152
|
const schema = route.route.responses[result.response];
|
|
142
153
|
if (!schema) {
|
|
143
154
|
throw new Error(`LUTRE_MESSAGE_PORT_RESPONSE_UNDECLARED: ${result.response}`);
|
|
@@ -155,6 +166,10 @@ function createMessagePortRuntime(executions, applicationRuntime) {
|
|
|
155
166
|
value: await validateSchema(schema, result.value),
|
|
156
167
|
};
|
|
157
168
|
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
lease.fail?.(error);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
158
173
|
finally {
|
|
159
174
|
if (!executionOwnedByStream)
|
|
160
175
|
lease.complete();
|
|
@@ -290,6 +305,7 @@ function createLeasedMessagePortStream(schema, source, lease, activeStreams) {
|
|
|
290
305
|
else
|
|
291
306
|
lease.signal.addEventListener('abort', onAbort, { once: true });
|
|
292
307
|
const closeAfterError = async (error) => {
|
|
308
|
+
lease.fail?.(error);
|
|
293
309
|
let cleanupError;
|
|
294
310
|
try {
|
|
295
311
|
await startIteratorCleanup(error);
|
package/dist/runtime/di.d.ts
CHANGED
|
@@ -3,8 +3,12 @@ import { Logger } from './logger.js';
|
|
|
3
3
|
export declare class DependencyResolutionError extends Error {
|
|
4
4
|
constructor(message: string);
|
|
5
5
|
}
|
|
6
|
+
export interface ContainerHooks {
|
|
7
|
+
created?(provider: ProviderDescriptor, value: unknown): void;
|
|
8
|
+
}
|
|
6
9
|
export interface ContainerOptions {
|
|
7
10
|
readonly logger?: Logger;
|
|
11
|
+
readonly hooks?: ContainerHooks;
|
|
8
12
|
readonly environment?: ReadonlyMap<EnvClass, object>;
|
|
9
13
|
readonly arguments?: ReadonlyMap<ArgsClass, object>;
|
|
10
14
|
}
|
package/dist/runtime/di.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isArgsClass, isEnvClass, runInInjectionContext, tokenName, } from '../core/index.js';
|
|
2
2
|
import { Logger } from './logger.js';
|
|
3
|
+
import { match } from 'ts-pattern';
|
|
3
4
|
export class DependencyResolutionError extends Error {
|
|
4
5
|
constructor(message) {
|
|
5
6
|
super(message);
|
|
@@ -10,11 +11,13 @@ export class Container {
|
|
|
10
11
|
#providers = new Map();
|
|
11
12
|
#applicationCache = new Map();
|
|
12
13
|
#logger;
|
|
14
|
+
#hooks;
|
|
13
15
|
#environment = new Map();
|
|
14
16
|
#arguments = new Map();
|
|
15
17
|
constructor(providers, options = {}) {
|
|
16
18
|
this.#logger =
|
|
17
19
|
options instanceof Logger ? options : (options.logger ?? new Logger());
|
|
20
|
+
this.#hooks = options instanceof Logger ? {} : (options.hooks ?? {});
|
|
18
21
|
if (!(options instanceof Logger)) {
|
|
19
22
|
for (const [environment, value] of options.environment ?? []) {
|
|
20
23
|
this.#environment.set(environment, value);
|
|
@@ -131,41 +134,49 @@ export class Container {
|
|
|
131
134
|
}
|
|
132
135
|
#create(provider, lineage) {
|
|
133
136
|
try {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
137
|
+
return match(provider)
|
|
138
|
+
.with({ kind: 'value' }, (candidate) => {
|
|
139
|
+
this.#hooks.created?.(candidate, candidate.useValue);
|
|
140
|
+
return candidate.useValue;
|
|
141
|
+
})
|
|
142
|
+
.with({ kind: 'environment' }, (candidate) => {
|
|
143
|
+
if (this.#environment.has(candidate.provide)) {
|
|
144
|
+
return this.#environment.get(candidate.provide);
|
|
142
145
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
throw new DependencyResolutionError(`LUTRE_ENV_005: Environment ${candidate.provide.name} requires a runtime Environment source before Application initialization.`);
|
|
147
|
+
})
|
|
148
|
+
.with({ kind: 'arguments' }, (candidate) => {
|
|
149
|
+
if (this.#arguments.has(candidate.provide)) {
|
|
150
|
+
return this.#arguments.get(candidate.provide);
|
|
148
151
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
152
|
+
throw new DependencyResolutionError(`LUTRE_ARGS_005: Arguments ${candidate.provide.name} requires runtime Arguments before Application initialization.`);
|
|
153
|
+
})
|
|
154
|
+
.with({ kind: 'class' }, (candidate) => {
|
|
155
|
+
const value = this.#instantiate(candidate.useClass, lineage);
|
|
156
|
+
this.#hooks.created?.(candidate, value);
|
|
157
|
+
return value;
|
|
158
|
+
})
|
|
159
|
+
.with({ kind: 'factory' }, (candidate) => {
|
|
160
|
+
const dependencies = candidate.inject.map((token) => this.#resolve(token, tokenName(candidate.provide), lineage));
|
|
161
|
+
const value = candidate.useFactory(...dependencies);
|
|
162
|
+
if (isThenable(value)) {
|
|
163
|
+
throw new DependencyResolutionError('LUTRE_DI_ASYNC_FACTORY: Async factory providers are not supported. Move asynchronous resource initialization to application lifecycle.');
|
|
158
164
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
this.#hooks.created?.(candidate, value);
|
|
166
|
+
return value;
|
|
167
|
+
})
|
|
168
|
+
.with({ kind: 'conditional' }, (candidate) => {
|
|
169
|
+
const input = this.#resolve(candidate.select.contract, tokenName(candidate.provide), lineage);
|
|
170
|
+
const selected = input[candidate.select.key];
|
|
171
|
+
const implementation = candidate.mapping[selected];
|
|
172
|
+
if (!implementation) {
|
|
173
|
+
throw new DependencyResolutionError(`No conditional Provider matches ${candidate.select.key}=${String(selected)}`);
|
|
167
174
|
}
|
|
168
|
-
|
|
175
|
+
const value = this.#instantiate(implementation, lineage);
|
|
176
|
+
this.#hooks.created?.(candidate, value);
|
|
177
|
+
return value;
|
|
178
|
+
})
|
|
179
|
+
.exhaustive();
|
|
169
180
|
}
|
|
170
181
|
catch (error) {
|
|
171
182
|
if (provider.scope === 'application') {
|
package/dist/runtime/engine.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type RuntimeEngine = 'node' | 'bun' | '
|
|
1
|
+
export type RuntimeEngine = 'node' | 'bun' | 'cloudflare-workers' | 'aws-lambda' | 'electron' | 'unknown';
|
|
2
2
|
export declare function detectRuntimeEngine(): RuntimeEngine;
|
|
3
3
|
export declare function assertRuntimeEngine(expected: Exclude<RuntimeEngine, 'unknown'>): void;
|
package/dist/runtime/engine.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const runtimeLabels = {
|
|
2
2
|
node: 'Node.js',
|
|
3
3
|
bun: 'Bun',
|
|
4
|
-
deno: 'Deno',
|
|
5
4
|
'cloudflare-workers': 'Cloudflare Workers',
|
|
6
5
|
'aws-lambda': 'AWS Lambda',
|
|
7
6
|
electron: 'Electron',
|
|
@@ -14,7 +13,7 @@ export function detectRuntimeEngine() {
|
|
|
14
13
|
return 'bun';
|
|
15
14
|
}
|
|
16
15
|
if (globals.Deno !== undefined)
|
|
17
|
-
return '
|
|
16
|
+
return 'unknown';
|
|
18
17
|
if (globals.navigator?.userAgent === 'Cloudflare-Workers' ||
|
|
19
18
|
globals.WebSocketPair !== undefined) {
|
|
20
19
|
return 'cloudflare-workers';
|
package/dist/runtime/kernel.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RuntimeCapabilityRegistry, type ApplicationModel, type ExecutionExtension, type ExecutionExtensionRuntime, type ExecutionKernelRuntime, type ExecutionLease, type RuntimeCapabilityBinding, type TokenLike } from '../core/index.js';
|
|
1
|
+
import { RuntimeCapabilityRegistry, type ApplicationModel, type ExecutionExtension, type ExecutionExtensionRuntime, type ExecutionKernelRuntime, type ExecutionLease, type ExecutionOperationLease, type RuntimeCapabilityBinding, type RuntimeExecutionMetadata, type RuntimeInstrumentation, type RuntimeInvocationRegistration, type RuntimeOperationMetadata, type TokenLike } from '../core/index.js';
|
|
2
2
|
import { Container } from './di.js';
|
|
3
3
|
import { Logger } from './logger.js';
|
|
4
4
|
export interface ApplicationKernelRuntimeOptions {
|
|
@@ -7,6 +7,7 @@ export interface ApplicationKernelRuntimeOptions {
|
|
|
7
7
|
readonly environmentSource?: unknown;
|
|
8
8
|
readonly argumentsSource?: unknown;
|
|
9
9
|
readonly forceShutdownTimeoutMs?: number;
|
|
10
|
+
readonly instrumentation?: RuntimeInstrumentation;
|
|
10
11
|
}
|
|
11
12
|
export declare class ApplicationKernelRuntime implements ExecutionKernelRuntime {
|
|
12
13
|
#private;
|
|
@@ -18,6 +19,7 @@ export declare class ApplicationKernelRuntime implements ExecutionKernelRuntime
|
|
|
18
19
|
resolve<TValue>(token: TokenLike<TValue>, source?: string): TValue;
|
|
19
20
|
get<TValue>(token: TokenLike<TValue>): TValue;
|
|
20
21
|
extensionRuntime<TRuntime extends ExecutionExtensionRuntime>(extension: ExecutionExtension<any, any, any, any, TRuntime>): TRuntime;
|
|
21
|
-
beginExecution(): ExecutionLease;
|
|
22
|
+
beginExecution(metadata?: RuntimeExecutionMetadata, invocation?: RuntimeInvocationRegistration): ExecutionLease;
|
|
23
|
+
beginOperation(metadata: RuntimeOperationMetadata, invocation?: RuntimeInvocationRegistration): ExecutionOperationLease;
|
|
22
24
|
shutdown(signal?: string): Promise<void>;
|
|
23
25
|
}
|
package/dist/runtime/kernel.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertValidApplicationModel, loadArgs, loadEnv, RuntimeCapabilityRegistry, } from '../core/index.js';
|
|
1
|
+
import { assertValidApplicationModel, loadArgs, loadEnv, RuntimeCapabilityRegistry, tokenName, } from '../core/index.js';
|
|
2
2
|
import { Container } from './di.js';
|
|
3
3
|
import { ExecutionTracker } from './execution-tracker.js';
|
|
4
4
|
import { Logger } from './logger.js';
|
|
@@ -9,29 +9,39 @@ export class ApplicationKernelRuntime {
|
|
|
9
9
|
capabilities;
|
|
10
10
|
#extensionRuntimes = new Map();
|
|
11
11
|
#providerInstances = new Map();
|
|
12
|
+
#providerNodes = new Map();
|
|
12
13
|
#initializedModuleIds = new Set();
|
|
13
14
|
#executions = new ExecutionTracker();
|
|
14
15
|
#extensionDrainOperations = new Map();
|
|
15
16
|
#environmentSource;
|
|
16
17
|
#argumentsSource;
|
|
17
18
|
#forceShutdownTimeoutMs;
|
|
19
|
+
#instrumentation;
|
|
18
20
|
#state = 'created';
|
|
19
21
|
#initialization;
|
|
20
22
|
#shutdown;
|
|
21
23
|
constructor(model, options = {}) {
|
|
22
24
|
this.model = assertValidApplicationModel(model);
|
|
25
|
+
for (const node of this.model.nodes) {
|
|
26
|
+
if (node.kind === 'provider')
|
|
27
|
+
this.#providerNodes.set(node.provider, node);
|
|
28
|
+
}
|
|
23
29
|
this.capabilities = new RuntimeCapabilityRegistry(options.capabilities ?? []);
|
|
24
30
|
this.#environmentSource = options.environmentSource;
|
|
25
31
|
this.#argumentsSource =
|
|
26
32
|
'argumentsSource' in options ? options.argumentsSource : Object.freeze({});
|
|
27
33
|
this.#forceShutdownTimeoutMs =
|
|
28
34
|
options.forceShutdownTimeoutMs ?? defaultForceShutdownTimeoutMs;
|
|
35
|
+
this.#instrumentation = options.instrumentation;
|
|
29
36
|
if (!Number.isFinite(this.#forceShutdownTimeoutMs) ||
|
|
30
37
|
this.#forceShutdownTimeoutMs < 0) {
|
|
31
38
|
throw new TypeError('LUTRE_FORCE_SHUTDOWN_TIMEOUT: forceShutdownTimeoutMs must be a non-negative finite number.');
|
|
32
39
|
}
|
|
33
40
|
this.container = new Container(model.providers, {
|
|
34
41
|
logger: options.logger ?? new Logger(),
|
|
42
|
+
hooks: {
|
|
43
|
+
created: (provider, value) => this.#notifyProviderCreated(provider, value),
|
|
44
|
+
},
|
|
35
45
|
});
|
|
36
46
|
}
|
|
37
47
|
initialize() {
|
|
@@ -149,10 +159,48 @@ export class ApplicationKernelRuntime {
|
|
|
149
159
|
}
|
|
150
160
|
return runtime;
|
|
151
161
|
}
|
|
152
|
-
beginExecution() {
|
|
162
|
+
beginExecution(metadata, invocation) {
|
|
153
163
|
if (this.#state !== 'running')
|
|
154
164
|
throw applicationStateError(this.#state);
|
|
155
|
-
|
|
165
|
+
const tracked = this.#executions.begin();
|
|
166
|
+
if (metadata === undefined)
|
|
167
|
+
return tracked;
|
|
168
|
+
const scope = safely(() => this.#instrumentation?.beginExecution?.(metadata, invocation));
|
|
169
|
+
if (!scope)
|
|
170
|
+
return tracked;
|
|
171
|
+
const instrumented = instrumentationLease(scope);
|
|
172
|
+
return {
|
|
173
|
+
signal: tracked.signal,
|
|
174
|
+
run: instrumented.run,
|
|
175
|
+
abort(reason) {
|
|
176
|
+
callInstrumentation(() => scope.abort?.(reason));
|
|
177
|
+
tracked.abort(reason);
|
|
178
|
+
},
|
|
179
|
+
fail: instrumented.fail,
|
|
180
|
+
annotate: instrumented.annotate,
|
|
181
|
+
complete(...result) {
|
|
182
|
+
instrumented.complete(...result);
|
|
183
|
+
tracked.complete();
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
beginOperation(metadata, invocation) {
|
|
188
|
+
return instrumentationLease(safely(() => this.#instrumentation?.beginOperation?.(metadata, invocation)));
|
|
189
|
+
}
|
|
190
|
+
#notifyProviderCreated(provider, value) {
|
|
191
|
+
const instrumentation = this.#instrumentation;
|
|
192
|
+
if (!instrumentation?.providerCreated)
|
|
193
|
+
return;
|
|
194
|
+
const providerNode = this.#providerNodes.get(provider);
|
|
195
|
+
if (!providerNode)
|
|
196
|
+
return;
|
|
197
|
+
callInstrumentation(() => instrumentation.providerCreated({
|
|
198
|
+
providerId: providerNode.id,
|
|
199
|
+
graphNodeId: providerNode.id,
|
|
200
|
+
name: tokenName(providerNode.token),
|
|
201
|
+
scope: provider.scope,
|
|
202
|
+
providerKind: provider.kind,
|
|
203
|
+
}, value));
|
|
156
204
|
}
|
|
157
205
|
shutdown(signal) {
|
|
158
206
|
if (this.#state === 'stopped')
|
|
@@ -171,6 +219,7 @@ export class ApplicationKernelRuntime {
|
|
|
171
219
|
await this.#initialization.catch(() => undefined);
|
|
172
220
|
if (this.#state === 'created') {
|
|
173
221
|
this.#state = 'stopped';
|
|
222
|
+
await closeInstrumentation(this.#instrumentation);
|
|
174
223
|
return;
|
|
175
224
|
}
|
|
176
225
|
if (this.#state === 'stopped')
|
|
@@ -225,6 +274,7 @@ export class ApplicationKernelRuntime {
|
|
|
225
274
|
this.#extensionDrainOperations.clear();
|
|
226
275
|
await this.#cleanupProviders(signal, errors);
|
|
227
276
|
this.#state = 'stopped';
|
|
277
|
+
await closeInstrumentation(this.#instrumentation);
|
|
228
278
|
if (errors.length > 0) {
|
|
229
279
|
throw new AggregateError(errors, 'Application shutdown failed.');
|
|
230
280
|
}
|
|
@@ -281,6 +331,7 @@ export class ApplicationKernelRuntime {
|
|
|
281
331
|
await collectError(() => callLifecycle(instance, 'onModuleDestroy'), errors);
|
|
282
332
|
}
|
|
283
333
|
}
|
|
334
|
+
await closeInstrumentation(this.#instrumentation);
|
|
284
335
|
return errors;
|
|
285
336
|
}
|
|
286
337
|
async #runHook(hook) {
|
|
@@ -306,6 +357,48 @@ function lifecycleHookOf(model, moduleId, phase) {
|
|
|
306
357
|
node.moduleId === moduleId &&
|
|
307
358
|
node.phase === phase)?.hook;
|
|
308
359
|
}
|
|
360
|
+
function instrumentationLease(scope) {
|
|
361
|
+
return {
|
|
362
|
+
run(operation) {
|
|
363
|
+
if (!scope?.run)
|
|
364
|
+
return operation();
|
|
365
|
+
return scope.run(operation);
|
|
366
|
+
},
|
|
367
|
+
fail(reason) {
|
|
368
|
+
callInstrumentation(() => scope?.fail?.(reason));
|
|
369
|
+
},
|
|
370
|
+
annotate(attributes) {
|
|
371
|
+
callInstrumentation(() => scope?.annotate?.(attributes));
|
|
372
|
+
},
|
|
373
|
+
complete(...result) {
|
|
374
|
+
callInstrumentation(() => scope?.complete(...result));
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function safely(operation) {
|
|
379
|
+
try {
|
|
380
|
+
return operation();
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function callInstrumentation(operation) {
|
|
387
|
+
try {
|
|
388
|
+
operation();
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
// instrumentation障害をApplication semanticsへ伝播させてはならない。
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async function closeInstrumentation(instrumentation) {
|
|
395
|
+
try {
|
|
396
|
+
await instrumentation?.close?.();
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
// instrumentationのcleanup失敗をshutdown失敗として扱わない。
|
|
400
|
+
}
|
|
401
|
+
}
|
|
309
402
|
async function withTimeout(operation, timeoutMs, createTimeoutError) {
|
|
310
403
|
let timer;
|
|
311
404
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** CLIが`@loutrejs/node`へ依存するとBun
|
|
1
|
+
/** CLIが`@loutrejs/node`へ依存するとBunでもNode.js専用moduleを評価するため、Capability metadataだけをruntime-neutral側へ分離する。 */
|
|
2
2
|
export declare const nodeRuntimeSupport: {
|
|
3
3
|
readonly runtime: 'node';
|
|
4
4
|
readonly capabilities: Set<string>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
/** CLIが`@loutrejs/node`へ依存するとBun
|
|
1
|
+
/** CLIが`@loutrejs/node`へ依存するとBunでもNode.js専用moduleを評価するため、Capability metadataだけをruntime-neutral側へ分離する。 */
|
|
2
2
|
export const nodeRuntimeSupport = {
|
|
3
3
|
runtime: 'node',
|
|
4
4
|
capabilities: new Set([
|
|
5
5
|
'http.server',
|
|
6
|
+
'websocket.server',
|
|
6
7
|
'http.request.streaming',
|
|
7
8
|
'http.response.streaming',
|
|
8
9
|
'stream.readable',
|