@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.
Files changed (71) hide show
  1. package/dist/adapters/bun.d.ts +2 -4
  2. package/dist/adapters/bun.js +36 -28
  3. package/dist/adapters/cloudflare-workers.d.ts +2 -4
  4. package/dist/adapters/cloudflare-workers.js +6 -11
  5. package/dist/adapters/server-transports.d.ts +4 -0
  6. package/dist/adapters/server-transports.js +18 -0
  7. package/dist/adapters/websocket-native.d.ts +24 -0
  8. package/dist/adapters/websocket-native.js +150 -0
  9. package/dist/application/kernel.d.ts +2 -1
  10. package/dist/core/extension.d.ts +26 -3
  11. package/dist/core/extension.js +19 -0
  12. package/dist/core/index.d.ts +2 -1
  13. package/dist/core/instrumentation.d.ts +36 -0
  14. package/dist/core/instrumentation.js +1 -0
  15. package/dist/core/model/modules.js +13 -17
  16. package/dist/core/model.js +14 -4
  17. package/dist/core/module.d.ts +3 -3
  18. package/dist/devtools/capsule.d.ts +51 -0
  19. package/dist/devtools/capsule.js +247 -0
  20. package/dist/devtools/index.d.ts +11 -0
  21. package/dist/devtools/index.js +6 -0
  22. package/dist/devtools/inspector.d.ts +57 -0
  23. package/dist/devtools/inspector.js +1 -0
  24. package/dist/devtools/module.d.ts +18 -0
  25. package/dist/devtools/module.js +26 -0
  26. package/dist/devtools/observer.d.ts +77 -0
  27. package/dist/devtools/observer.js +10 -0
  28. package/dist/devtools/protocol.d.ts +1 -0
  29. package/dist/devtools/protocol.js +1 -0
  30. package/dist/devtools/provider-instrumentation.d.ts +14 -0
  31. package/dist/devtools/provider-instrumentation.js +117 -0
  32. package/dist/devtools/runtime.d.ts +20 -0
  33. package/dist/devtools/runtime.js +519 -0
  34. package/dist/devtools/values.d.ts +35 -0
  35. package/dist/devtools/values.js +172 -0
  36. package/dist/generated/version.d.ts +1 -1
  37. package/dist/generated/version.js +1 -1
  38. package/dist/graph/model.js +37 -40
  39. package/dist/http/auth.d.ts +2 -2
  40. package/dist/http/auth.js +9 -0
  41. package/dist/http/client.js +5 -5
  42. package/dist/http/extension.d.ts +37 -10
  43. package/dist/http/extension.js +249 -57
  44. package/dist/http/index.d.ts +3 -2
  45. package/dist/http/index.js +1 -1
  46. package/dist/http/openapi.d.ts +3 -4
  47. package/dist/http/openapi.js +45 -26
  48. package/dist/http/request-head.d.ts +6 -0
  49. package/dist/http/request-head.js +1 -0
  50. package/dist/http/route-validation.d.ts +9 -0
  51. package/dist/http/route-validation.js +18 -0
  52. package/dist/http/runtime/response.js +10 -10
  53. package/dist/message-port/extension.js +20 -4
  54. package/dist/runtime/di.d.ts +4 -0
  55. package/dist/runtime/di.js +42 -31
  56. package/dist/runtime/engine.d.ts +1 -1
  57. package/dist/runtime/engine.js +1 -2
  58. package/dist/runtime/kernel.d.ts +4 -2
  59. package/dist/runtime/kernel.js +96 -3
  60. package/dist/runtime/node-capabilities.d.ts +1 -1
  61. package/dist/runtime/node-capabilities.js +2 -1
  62. package/dist/tasks/extension.js +133 -118
  63. package/dist/websocket/driver.d.ts +30 -0
  64. package/dist/websocket/driver.js +96 -0
  65. package/dist/websocket/extension.d.ts +33 -12
  66. package/dist/websocket/extension.js +72 -47
  67. package/dist/websocket/index.d.ts +3 -1
  68. package/dist/websocket/index.js +1 -0
  69. package/package.json +7 -6
  70. package/dist/adapters/deno.d.ts +0 -35
  71. 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
- if (isStandardSchema(headers))
19
- return headers;
20
- if (isResponseHeadersWithDefaults(headers))
21
- return headers.schema;
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
- if (headers === undefined || isStandardSchema(headers))
26
- return undefined;
27
- if (isResponseHeadersWithDefaults(headers))
28
- return headers.defaults;
29
- return headers;
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, { route: definition, handler });
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 result = await route.handler({
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);
@@ -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
  }
@@ -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
- switch (provider.kind) {
135
- case 'value':
136
- return provider.useValue;
137
- case 'environment': {
138
- if (this.#environment.has(provider.provide)) {
139
- return this.#environment.get(provider.provide);
140
- }
141
- throw new DependencyResolutionError(`LUTRE_ENV_005: Environment ${provider.provide.name} requires a runtime Environment source before Application initialization.`);
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
- case 'arguments': {
144
- if (this.#arguments.has(provider.provide)) {
145
- return this.#arguments.get(provider.provide);
146
- }
147
- throw new DependencyResolutionError(`LUTRE_ARGS_005: Arguments ${provider.provide.name} requires runtime Arguments before Application initialization.`);
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
- case 'class':
150
- return this.#instantiate(provider.useClass, lineage);
151
- case 'factory': {
152
- const dependencies = provider.inject.map((token) => this.#resolve(token, tokenName(provider.provide), lineage));
153
- const value = provider.useFactory(...dependencies);
154
- if (isThenable(value)) {
155
- throw new DependencyResolutionError('LUTRE_DI_ASYNC_FACTORY: Async factory providers are not supported. Move asynchronous resource initialization to application lifecycle.');
156
- }
157
- return value;
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
- case 'conditional': {
160
- const input = this.#resolve(provider.select.contract, tokenName(provider.provide), lineage);
161
- const selected = input[provider.select.key];
162
- const implementation = provider.mapping[selected];
163
- if (!implementation) {
164
- throw new DependencyResolutionError(`No conditional Provider matches ${provider.select.key}=${String(selected)}`);
165
- }
166
- return this.#instantiate(implementation, lineage);
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') {
@@ -1,3 +1,3 @@
1
- export type RuntimeEngine = 'node' | 'bun' | 'deno' | 'cloudflare-workers' | 'aws-lambda' | 'electron' | 'unknown';
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;
@@ -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 'deno';
16
+ return 'unknown';
18
17
  if (globals.navigator?.userAgent === 'Cloudflare-Workers' ||
19
18
  globals.WebSocketPair !== undefined) {
20
19
  return 'cloudflare-workers';
@@ -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
  }
@@ -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
- return this.#executions.begin();
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/DenoでもNode.js専用moduleを評価するため、Capability metadataだけをruntime-neutral側へ分離する。 */
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/DenoでもNode.js専用moduleを評価するため、Capability metadataだけをruntime-neutral側へ分離する。 */
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',