@loutrejs/loutre 0.9.2 → 0.10.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.
Files changed (145) hide show
  1. package/dist/adapters/aws-lambda.d.ts +1 -1
  2. package/dist/adapters/aws-lambda.js +8 -2
  3. package/dist/adapters/bun.d.ts +3 -2
  4. package/dist/adapters/bun.js +12 -6
  5. package/dist/adapters/cloudflare-workers.d.ts +1 -1
  6. package/dist/adapters/cloudflare-workers.js +2 -1
  7. package/dist/adapters/electron.d.ts +1 -1
  8. package/dist/adapters/electron.js +8 -51
  9. package/dist/adapters/server-transports.js +2 -2
  10. package/dist/adapters/websocket-native.js +4 -1
  11. package/dist/application/definition.d.ts +4 -7
  12. package/dist/application/definition.js +3 -1
  13. package/dist/application/kernel.d.ts +0 -2
  14. package/dist/application/kernel.js +1 -3
  15. package/dist/core/args.d.ts +1 -0
  16. package/dist/core/args.js +2 -1
  17. package/dist/core/env.d.ts +1 -0
  18. package/dist/core/env.js +2 -1
  19. package/dist/core/extension.d.ts +23 -13
  20. package/dist/core/extension.js +3 -1
  21. package/dist/core/generic-layer.d.ts +9 -2
  22. package/dist/core/generic-layer.js +2 -1
  23. package/dist/core/identity.d.ts +18 -0
  24. package/dist/core/identity.js +5 -0
  25. package/dist/core/index.d.ts +13 -5
  26. package/dist/core/index.js +7 -1
  27. package/dist/core/injection.d.ts +2 -1
  28. package/dist/core/instrumentation.d.ts +7 -5
  29. package/dist/core/lifecycle.d.ts +4 -0
  30. package/dist/core/logger-identity.d.ts +4 -0
  31. package/dist/core/logger-identity.js +9 -0
  32. package/dist/core/logger-internal.d.ts +5 -0
  33. package/dist/core/logger-internal.js +27 -0
  34. package/dist/core/logger-module.d.ts +5 -0
  35. package/dist/core/logger-module.js +8 -0
  36. package/dist/core/logger.d.ts +42 -0
  37. package/dist/core/logger.js +221 -0
  38. package/dist/core/model/context.d.ts +6 -3
  39. package/dist/core/model/context.js +17 -17
  40. package/dist/core/model/identity.d.ts +19 -0
  41. package/dist/core/model/identity.js +8 -0
  42. package/dist/core/model/lifecycle.d.ts +3 -2
  43. package/dist/core/model/lifecycle.js +7 -3
  44. package/dist/core/model/logger.d.ts +3 -0
  45. package/dist/core/model/logger.js +59 -0
  46. package/dist/core/model/modules.d.ts +4 -1
  47. package/dist/core/model/modules.js +6 -4
  48. package/dist/core/model/providers.d.ts +1 -1
  49. package/dist/core/model/providers.js +8 -8
  50. package/dist/core/model/types.d.ts +25 -18
  51. package/dist/core/model/validation.js +3 -2
  52. package/dist/core/model/visibility.js +7 -2
  53. package/dist/core/model.js +15 -7
  54. package/dist/core/module.d.ts +7 -7
  55. package/dist/core/module.js +0 -3
  56. package/dist/core/runtime-input.d.ts +2 -1
  57. package/dist/core/source-location.d.ts +14 -2
  58. package/dist/core/source-location.js +8 -3
  59. package/dist/core/token-internal.js +3 -1
  60. package/dist/core/token.d.ts +4 -2
  61. package/dist/core/token.js +3 -0
  62. package/dist/core/units.d.ts +22 -0
  63. package/dist/core/units.js +14 -0
  64. package/dist/devtools/capsule.d.ts +17 -13
  65. package/dist/devtools/capsule.js +14 -10
  66. package/dist/devtools/decoder.d.ts +15 -0
  67. package/dist/devtools/decoder.js +179 -0
  68. package/dist/devtools/identity-internal.d.ts +6 -0
  69. package/dist/devtools/identity-internal.js +5 -0
  70. package/dist/devtools/identity.d.ts +21 -0
  71. package/dist/devtools/identity.js +7 -0
  72. package/dist/devtools/index.d.ts +3 -0
  73. package/dist/devtools/index.js +2 -0
  74. package/dist/devtools/inspector.d.ts +18 -16
  75. package/dist/devtools/observer.d.ts +32 -28
  76. package/dist/devtools/runtime.js +34 -30
  77. package/dist/devtools/values.js +3 -5
  78. package/dist/generated/version.d.ts +1 -1
  79. package/dist/generated/version.js +1 -1
  80. package/dist/graph/model.d.ts +25 -14
  81. package/dist/http/auth.d.ts +4 -2
  82. package/dist/http/client.d.ts +6 -3
  83. package/dist/http/client.js +8 -9
  84. package/dist/http/cors-internal.d.ts +7 -5
  85. package/dist/http/cors-internal.js +8 -6
  86. package/dist/http/extension.d.ts +60 -21
  87. package/dist/http/extension.js +91 -20
  88. package/dist/http/identity.d.ts +20 -0
  89. package/dist/http/identity.js +10 -0
  90. package/dist/http/index.d.ts +6 -2
  91. package/dist/http/index.js +3 -1
  92. package/dist/http/openapi-identity.d.ts +9 -0
  93. package/dist/http/openapi-identity.js +4 -0
  94. package/dist/http/openapi.d.ts +1 -1
  95. package/dist/http/openapi.js +10 -7
  96. package/dist/http/path.d.ts +8 -5
  97. package/dist/http/route-validation.js +2 -1
  98. package/dist/http/runtime/request.d.ts +3 -2
  99. package/dist/http/runtime/request.js +1 -4
  100. package/dist/http/websocket-upgrade.d.ts +2 -0
  101. package/dist/http/websocket-upgrade.js +4 -0
  102. package/dist/internal/brand.d.ts +8 -0
  103. package/dist/internal/brand.js +1 -0
  104. package/dist/message-port/extension.d.ts +4 -2
  105. package/dist/message-port/extension.js +15 -8
  106. package/dist/message-port/identity.d.ts +9 -0
  107. package/dist/message-port/identity.js +16 -0
  108. package/dist/message-port/index.d.ts +2 -0
  109. package/dist/message-port/index.js +1 -0
  110. package/dist/message-port/transport.d.ts +4 -2
  111. package/dist/message-port/transport.js +6 -1
  112. package/dist/runtime/async-iterator-lifecycle.d.ts +2 -1
  113. package/dist/runtime/async-iterator-lifecycle.js +2 -1
  114. package/dist/runtime/capability.d.ts +6 -2
  115. package/dist/runtime/capability.js +3 -1
  116. package/dist/runtime/di.d.ts +1 -3
  117. package/dist/runtime/di.js +62 -35
  118. package/dist/runtime/error.d.ts +3 -1
  119. package/dist/runtime/index.d.ts +6 -2
  120. package/dist/runtime/index.js +3 -1
  121. package/dist/runtime/kernel.d.ts +0 -2
  122. package/dist/runtime/kernel.js +68 -30
  123. package/dist/runtime/logger-source.d.ts +4 -0
  124. package/dist/runtime/logger-source.js +47 -0
  125. package/dist/runtime/logger.d.ts +2 -38
  126. package/dist/runtime/logger.js +1 -209
  127. package/dist/runtime/node-capabilities.d.ts +1 -1
  128. package/dist/runtime/node-capabilities.js +2 -1
  129. package/dist/runtime/server-port.d.ts +5 -1
  130. package/dist/runtime/server-port.js +2 -1
  131. package/dist/runtime/shutdown-hooks.d.ts +1 -1
  132. package/dist/runtime/shutdown-hooks.js +5 -4
  133. package/dist/tasks/extension.d.ts +18 -16
  134. package/dist/tasks/extension.js +28 -23
  135. package/dist/tasks/identity.d.ts +9 -0
  136. package/dist/tasks/identity.js +4 -0
  137. package/dist/tasks/index.d.ts +1 -0
  138. package/dist/tasks/runtime/cron.d.ts +6 -2
  139. package/dist/tasks/runtime/cron.js +9 -0
  140. package/dist/websocket/extension.d.ts +19 -9
  141. package/dist/websocket/extension.js +103 -30
  142. package/dist/websocket/identity.d.ts +9 -0
  143. package/dist/websocket/identity.js +4 -0
  144. package/dist/websocket/index.d.ts +1 -0
  145. package/package.json +1 -1
@@ -2,3 +2,5 @@ export { defineMessagePortContract, defineMessagePortImplementation, messagePort
2
2
  export { attachMessagePort } from './transport.js';
3
3
  export type { MessagePortContext, MessagePortContract, MessagePortExecutionDefinition, MessagePortExtensionRuntime, MessagePortHandlers, MessagePortHostApi, MessagePortImplementationData, MessagePortResponseDefinition, MessagePortResult, MessagePortRouteDefinition, MessagePortServerStreamResponseDefinition, } from './extension.js';
4
4
  export type { MessagePortHost, MessagePortLike } from './transport.js';
5
+ export { decodeMessagePortRequest, messagePortProcedureName, } from './identity.js';
6
+ export type { MessagePortRequestId, MessagePortProcedureName, } from './identity.js';
@@ -1,2 +1,3 @@
1
1
  export { defineMessagePortContract, defineMessagePortImplementation, messagePort, messagePortExtension, } from './extension.js';
2
2
  export { attachMessagePort } from './transport.js';
3
+ export { decodeMessagePortRequest, messagePortProcedureName, } from './identity.js';
@@ -1,5 +1,6 @@
1
+ import { type MessagePortProcedureName } from './identity.js';
1
2
  export interface MessagePortHost {
2
- invoke(method: string, input?: unknown): Promise<{
3
+ invoke(method: MessagePortProcedureName, input?: unknown): Promise<{
3
4
  readonly response: string;
4
5
  readonly value: unknown;
5
6
  }>;
@@ -7,5 +8,6 @@ export interface MessagePortHost {
7
8
  export type MessagePortLike = {
8
9
  postMessage(value: unknown): void;
9
10
  start?(): void;
10
- } & Pick<EventTarget, 'addEventListener'>;
11
+ addEventListener(type: 'message', listener: (event: unknown) => void): void;
12
+ };
11
13
  export declare function attachMessagePort(host: MessagePortHost, port: MessagePortLike): void;
@@ -1,6 +1,11 @@
1
+ import { decodeMessagePortRequest, } from './identity.js';
1
2
  export function attachMessagePort(host, port) {
2
3
  port.addEventListener('message', async (event) => {
3
- const request = event.data;
4
+ if (typeof event !== 'object' || event === null || !('data' in event))
5
+ return;
6
+ const request = decodeMessagePortRequest(event.data);
7
+ if (!request)
8
+ return;
4
9
  try {
5
10
  const result = await host.invoke(request.procedure, request.input);
6
11
  if (isAsyncIterable(result.value)) {
@@ -1,3 +1,4 @@
1
+ import { type EpochMs } from '../core/units.js';
1
2
  export declare class AsyncIteratorCleanupDeadlineError extends Error {
2
3
  constructor(message: string);
3
4
  }
@@ -8,5 +9,5 @@ export declare class AsyncIteratorLifecycle<T> {
8
9
  next(): Promise<IteratorResult<T>>;
9
10
  return(reason?: unknown): Promise<IteratorResult<T>>;
10
11
  throw(error?: unknown): Promise<IteratorResult<T>>;
11
- drain(reason: unknown, deadline: () => number | undefined, timeoutMessage: string): Promise<void>;
12
+ drain(reason: unknown, deadline: () => EpochMs | undefined, timeoutMessage: string): Promise<void>;
12
13
  }
@@ -1,3 +1,4 @@
1
+ import { epochNow } from '../core/units.js';
1
2
  const cleanupYieldInterval = 32;
2
3
  export class AsyncIteratorCleanupDeadlineError extends Error {
3
4
  constructor(message) {
@@ -56,7 +57,7 @@ export class AsyncIteratorLifecycle {
56
57
  let continuationSteps = 0;
57
58
  while (!this.#done) {
58
59
  const currentDeadline = deadline();
59
- if (currentDeadline !== undefined && Date.now() >= currentDeadline) {
60
+ if (currentDeadline !== undefined && epochNow() >= currentDeadline) {
60
61
  throw new AsyncIteratorCleanupDeadlineError(timeoutMessage);
61
62
  }
62
63
  await this.next();
@@ -1,4 +1,8 @@
1
- export type RuntimeCapabilityId = string;
1
+ import type { Brand } from '../internal/brand.js';
2
+ export type RuntimeSupportFeature = Brand<string, 'runtime.support-feature'>;
3
+ export declare const runtimeSupportFeature: (value: string) => RuntimeSupportFeature;
4
+ export declare const runtimeSupportFeatures: (values: Iterable<string>) => ReadonlySet<RuntimeSupportFeature>;
5
+ export type RuntimeCapabilityId = RuntimeSupportFeature;
2
6
  export interface RuntimeSupportProfile {
3
7
  readonly runtime: string;
4
8
  readonly capabilities: ReadonlySet<RuntimeCapabilityId>;
@@ -9,7 +13,7 @@ export interface RuntimeSupportCheck {
9
13
  readonly supported: readonly RuntimeCapabilityId[];
10
14
  readonly missing: readonly RuntimeCapabilityId[];
11
15
  }
12
- export declare function checkRuntimeSupport(required: Iterable<RuntimeCapabilityId>, runtime: RuntimeSupportProfile): RuntimeSupportCheck;
16
+ export declare function checkRuntimeSupport(required: Iterable<string>, runtime: RuntimeSupportProfile): RuntimeSupportCheck;
13
17
  export declare class MissingRuntimeSupportError extends Error {
14
18
  readonly check: RuntimeSupportCheck;
15
19
  constructor(check: RuntimeSupportCheck);
@@ -1,5 +1,7 @@
1
+ export const runtimeSupportFeature = (value) => value;
2
+ export const runtimeSupportFeatures = (values) => new Set([...values].map(runtimeSupportFeature));
1
3
  export function checkRuntimeSupport(required, runtime) {
2
- const requirements = [...new Set(required)];
4
+ const requirements = [...runtimeSupportFeatures(required)];
3
5
  const missing = requirements.filter((capability) => !runtime.capabilities.has(capability));
4
6
  return {
5
7
  ok: missing.length === 0,
@@ -1,5 +1,4 @@
1
1
  import { type ArgsClass, type EnvClass, type DependencyResolutionContext, type ProviderDescriptor, type TokenLike } from '../core/index.js';
2
- import { Logger } from './logger.js';
3
2
  export declare class DependencyResolutionError extends Error {
4
3
  constructor(message: string);
5
4
  }
@@ -7,7 +6,6 @@ export interface ContainerHooks {
7
6
  created?(provider: ProviderDescriptor, value: unknown): void;
8
7
  }
9
8
  export interface ContainerOptions {
10
- readonly logger?: Logger;
11
9
  readonly dependencies?: ReadonlyMap<TokenLike, readonly TokenLike[]>;
12
10
  readonly hooks?: ContainerHooks;
13
11
  readonly environment?: ReadonlyMap<EnvClass, object>;
@@ -15,7 +13,7 @@ export interface ContainerOptions {
15
13
  }
16
14
  export declare class Container {
17
15
  #private;
18
- constructor(providers: readonly ProviderDescriptor[], options?: Logger | ContainerOptions);
16
+ constructor(providers: readonly ProviderDescriptor[], options?: ContainerOptions);
19
17
  bindEnvironment(environment: EnvClass, value: object): void;
20
18
  bindArguments(argumentsContract: ArgsClass, value: object): void;
21
19
  resolve<T>(token: TokenLike<T>, context?: string | DependencyResolutionContext): T;
@@ -1,5 +1,7 @@
1
1
  import { isArgsClass, isEnvClass, runInInjectionContext, tokenName, } from '../core/index.js';
2
- import { Logger } from './logger.js';
2
+ import { LoggerToken } from './logger.js';
3
+ import { defaultLoggerProviders, isLogger, isLoggerToken, } from '../core/logger-internal.js';
4
+ import { bindLoggerSource } from './logger-source.js';
3
5
  import { match } from 'ts-pattern';
4
6
  export class DependencyResolutionError extends Error {
5
7
  constructor(message) {
@@ -10,27 +12,39 @@ export class DependencyResolutionError extends Error {
10
12
  export class Container {
11
13
  #providers = new Map();
12
14
  #applicationCache = new Map();
13
- #logger;
15
+ #loggerToken;
14
16
  #dependencies;
15
17
  #hooks;
16
18
  #environment = new Map();
17
19
  #arguments = new Map();
18
20
  constructor(providers, options = {}) {
19
- this.#logger =
20
- options instanceof Logger ? options : (options.logger ?? new Logger());
21
- this.#dependencies =
22
- options instanceof Logger ? undefined : options.dependencies;
23
- this.#hooks = options instanceof Logger ? {} : (options.hooks ?? {});
24
- if (!(options instanceof Logger)) {
25
- for (const [environment, value] of options.environment ?? []) {
26
- this.#environment.set(environment, value);
21
+ if (isLogger(options) || 'logger' in options) {
22
+ throw new DependencyResolutionError('LUTRE_LOGGER_CONFIGURATION: Register provide(LoggerToken).useClass(LoggerService) in providers.');
23
+ }
24
+ const configured = providers.find((provider) => isLoggerToken(provider.provide));
25
+ this.#loggerToken =
26
+ configured && isLoggerToken(configured.provide)
27
+ ? configured.provide
28
+ : LoggerToken;
29
+ this.#dependencies = options.dependencies;
30
+ this.#hooks = options.hooks ?? {};
31
+ for (const [environment, value] of options.environment ?? []) {
32
+ this.#environment.set(environment, value);
33
+ }
34
+ for (const [argumentsContract, value] of options.arguments ?? []) {
35
+ this.#arguments.set(argumentsContract, value);
36
+ }
37
+ const declarations = configured
38
+ ? providers
39
+ : [...providers, ...defaultLoggerProviders()];
40
+ for (const provider of declarations) {
41
+ if (isLoggerToken(provider.provide) && provider.scope !== 'application') {
42
+ throw new DependencyResolutionError('LUTRE_LOGGER_SCOPE: LoggerToken must have application scope.');
27
43
  }
28
- for (const [argumentsContract, value] of options.arguments ?? []) {
29
- this.#arguments.set(argumentsContract, value);
30
- }
31
- }
32
- for (const provider of providers) {
33
- const existing = this.#providers.get(provider.provide);
44
+ const key = isLoggerToken(provider.provide)
45
+ ? this.#loggerToken
46
+ : provider.provide;
47
+ const existing = this.#providers.get(key);
34
48
  if (existing) {
35
49
  if (existing.kind === provider.kind &&
36
50
  isRuntimeInputProvider(provider)) {
@@ -46,7 +60,7 @@ export class Container {
46
60
  }
47
61
  throw new DependencyResolutionError(`LUTRE_DI_DUPLICATE: Duplicate provider for ${tokenName(provider.provide)}`);
48
62
  }
49
- this.#providers.set(provider.provide, provider);
63
+ this.#providers.set(key, provider);
50
64
  }
51
65
  }
52
66
  bindEnvironment(environment, value) {
@@ -74,7 +88,8 @@ export class Container {
74
88
  return this.#resolve(token, resolution?.source, [], resolution?.consumer);
75
89
  }
76
90
  get(token) {
77
- const provider = this.#providers.get(token);
91
+ const requested = isLoggerToken(token) ? this.#loggerToken : token;
92
+ const provider = this.#providers.get(requested);
78
93
  if (!provider) {
79
94
  if (isEnvClass(token)) {
80
95
  throw new DependencyResolutionError(`LUTRE_ENV_002: ${token.name} is not declared by any Module.environment.`);
@@ -99,24 +114,23 @@ export class Container {
99
114
  }
100
115
  throw new DependencyResolutionError(`LUTRE_ARGS_005: Arguments ${provider.provide.name} requires runtime Arguments before Application initialization.`);
101
116
  }
102
- if (this.#applicationCache.has(token)) {
103
- return this.#applicationCache.get(token);
117
+ if (this.#applicationCache.has(requested)) {
118
+ const value = this.#applicationCache.get(requested);
119
+ return (isLoggerToken(requested) ? requireLogger(value) : value);
104
120
  }
105
121
  throw new DependencyResolutionError(`LUTRE_DI_NOT_INITIALIZED: ${tokenName(token)} has not been constructed by Application initialization.`);
106
122
  }
107
123
  #resolve(token, source, lineage = [], inquirer) {
108
- const cycleStart = lineage.indexOf(token);
124
+ const requested = isLoggerToken(token) ? this.#loggerToken : token;
125
+ const cycleStart = lineage.indexOf(requested);
109
126
  if (cycleStart >= 0) {
110
- const cycle = [...lineage.slice(cycleStart), token]
127
+ const cycle = [...lineage.slice(cycleStart), requested]
111
128
  .map(tokenName)
112
129
  .join(' -> ');
113
130
  throw new DependencyResolutionError(`LUTRE_DI_CYCLE: Circular dependency detected: ${cycle}`);
114
131
  }
115
- const provider = this.#providers.get(token);
132
+ const provider = this.#providers.get(requested);
116
133
  if (!provider) {
117
- if (token === Logger) {
118
- return this.#logger.child(source === undefined ? {} : { source });
119
- }
120
134
  if (isEnvClass(token)) {
121
135
  throw new DependencyResolutionError(`LUTRE_ENV_002: ${token.name} is injected by ${source ?? 'Application'} but is not declared by any Module.environment.`);
122
136
  }
@@ -125,16 +139,22 @@ export class Container {
125
139
  }
126
140
  throw new DependencyResolutionError(`LUTRE_DI_UNRESOLVED: ${source ?? 'Application'} requires ${tokenName(token)}, but no provider is declared for ${tokenName(token)}.`);
127
141
  }
128
- const nextLineage = [...lineage, token];
129
- if (provider.scope !== 'application') {
130
- return this.#create(provider, nextLineage, inquirer);
142
+ const nextLineage = [...lineage, requested];
143
+ let value;
144
+ if (provider.scope === 'application' &&
145
+ this.#applicationCache.has(requested)) {
146
+ value = this.#applicationCache.get(requested);
131
147
  }
132
- if (this.#applicationCache.has(token)) {
133
- return this.#applicationCache.get(token);
148
+ else {
149
+ value = this.#create(provider, nextLineage, inquirer);
150
+ if (provider.scope === 'application')
151
+ this.#applicationCache.set(requested, value);
134
152
  }
135
- const instance = this.#create(provider, nextLineage, inquirer);
136
- this.#applicationCache.set(token, instance);
137
- return instance;
153
+ if (isLoggerToken(requested)) {
154
+ const root = requireLogger(value);
155
+ return (source === undefined ? root : bindLoggerSource(root, source));
156
+ }
157
+ return value;
138
158
  }
139
159
  #create(provider, lineage, inquirer) {
140
160
  try {
@@ -212,6 +232,12 @@ export class Container {
212
232
  function isRuntimeInputProvider(provider) {
213
233
  return provider.kind === 'environment' || provider.kind === 'arguments';
214
234
  }
235
+ function requireLogger(value) {
236
+ if (!isLogger(value)) {
237
+ throw new DependencyResolutionError('LUTRE_LOGGER_VALUE: The root Logger Provider must return a LoggerService instance.');
238
+ }
239
+ return value;
240
+ }
215
241
  function isThenable(value) {
216
242
  return (((typeof value === 'object' && value !== null) ||
217
243
  typeof value === 'function') &&
@@ -219,7 +245,8 @@ function isThenable(value) {
219
245
  typeof value.then === 'function');
220
246
  }
221
247
  export function assertDeclaredDependency(consumer, token, dependencies) {
222
- if (!dependencies.includes(token)) {
248
+ if (!dependencies.includes(token) &&
249
+ !(isLoggerToken(token) && dependencies.some(isLoggerToken))) {
223
250
  throw new DependencyResolutionError(`LUTRE_DI_UNDECLARED_DEPENDENCY: ${consumer} requested ${tokenName(token)}, but it is not declared in the canonical Application Model. Declare dependencies unconditionally; construction metadata and runtime values must not add dependencies.`);
224
251
  }
225
252
  }
@@ -1,6 +1,8 @@
1
+ import type { Brand } from '../internal/brand.js';
2
+ export type ApplicationErrorId = Brand<string, 'runtime.application-error-id'>;
1
3
  export interface NormalizedApplicationError {
2
4
  readonly code: 'INTERNAL_ERROR';
3
- readonly errorId: string;
5
+ readonly errorId: ApplicationErrorId;
4
6
  readonly message: string;
5
7
  readonly stack?: string;
6
8
  readonly cause?: unknown;
@@ -3,10 +3,10 @@ export type { RuntimeCapabilityId, RuntimeSupportCheck, RuntimeSupportProfile, }
3
3
  export { Container, DependencyResolutionError } from './di.js';
4
4
  export type { ContainerOptions } from './di.js';
5
5
  export { normalizeUnknownError } from './error.js';
6
- export type { NormalizedApplicationError } from './error.js';
6
+ export type { NormalizedApplicationError, ApplicationErrorId } from './error.js';
7
7
  export { assertRuntimeEngine, assertSupportedBunVersion, detectRuntimeEngine, } from './engine.js';
8
8
  export type { RuntimeEngine } from './engine.js';
9
- export { ConsoleLoggerBackend, JsonConsoleLoggerBackend, Logger, SilentLogger, } from './logger.js';
9
+ export { ConsoleLoggerBackend, JsonConsoleLoggerBackend, LoggerService, LoggerToken, SilentLogger, } from './logger.js';
10
10
  export type { ConsoleLoggerBackendOptions, LoggerBackend, LogLevel, LogRecord, } from './logger.js';
11
11
  export { ApplicationKernelRuntime } from './kernel.js';
12
12
  export type { ApplicationKernelRuntimeOptions } from './kernel.js';
@@ -17,3 +17,7 @@ export { ProcessShutdownHooks } from './shutdown-hooks.js';
17
17
  export type { ProcessShutdownHooksOptions, ProcessSignalSource, ShutdownSignal, } from './shutdown-hooks.js';
18
18
  export { ApplicationShutdownError, RuntimeShutdown } from './shutdown.js';
19
19
  export type { RuntimeShutdownOptions } from './shutdown.js';
20
+ export { runtimeSupportFeature, runtimeSupportFeatures } from './capability.js';
21
+ export type { RuntimeSupportFeature } from './capability.js';
22
+ export { boundPort } from './server-port.js';
23
+ export type { RequestedPort, BoundPort } from './server-port.js';
@@ -2,10 +2,12 @@ export { checkRuntimeSupport, MissingRuntimeSupportError, } from './capability.j
2
2
  export { Container, DependencyResolutionError } from './di.js';
3
3
  export { normalizeUnknownError } from './error.js';
4
4
  export { assertRuntimeEngine, assertSupportedBunVersion, detectRuntimeEngine, } from './engine.js';
5
- export { ConsoleLoggerBackend, JsonConsoleLoggerBackend, Logger, SilentLogger, } from './logger.js';
5
+ export { ConsoleLoggerBackend, JsonConsoleLoggerBackend, LoggerService, LoggerToken, SilentLogger, } from './logger.js';
6
6
  export { ApplicationKernelRuntime } from './kernel.js';
7
7
  export { nodeRuntimeSupport } from './node-capabilities.js';
8
8
  export { canRetryOnNextPort, initialServerPort } from './server-port.js';
9
9
  export { serverUrl } from './server-url.js';
10
10
  export { ProcessShutdownHooks } from './shutdown-hooks.js';
11
11
  export { ApplicationShutdownError, RuntimeShutdown } from './shutdown.js';
12
+ export { runtimeSupportFeature, runtimeSupportFeatures } from './capability.js';
13
+ export { boundPort } from './server-port.js';
@@ -1,9 +1,7 @@
1
1
  import { RuntimeCapabilityRegistry, type ApplicationModel, type ExecutionExtension, type ExecutionExtensionRuntime, type ExecutionKernelRuntime, type ExecutionRuntimeBinding, 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
- import { Logger } from './logger.js';
4
3
  export interface ApplicationKernelRuntimeOptions {
5
4
  readonly capabilities?: readonly RuntimeCapabilityBinding[];
6
- readonly logger?: Logger;
7
5
  readonly environmentSource?: unknown;
8
6
  readonly argumentsSource?: unknown;
9
7
  readonly forceShutdownTimeoutMs?: number;
@@ -1,8 +1,10 @@
1
+ import { milliseconds } from '../core/units.js';
1
2
  import { ApplicationShutdownError } from './shutdown.js';
2
3
  import { assertValidApplicationModel, loadArgs, loadEnv, RuntimeCapabilityRegistry, tokenName, } from '../core/index.js';
3
4
  import { Container, assertDeclaredDependency } from './di.js';
4
5
  import { ExecutionTracker } from './execution-tracker.js';
5
- import { Logger } from './logger.js';
6
+ import { LoggerToken } from './logger.js';
7
+ import { loggerLifecycleOwner } from './logger-source.js';
6
8
  const defaultForceShutdownTimeoutMs = 5_000;
7
9
  export class ApplicationKernelRuntime {
8
10
  model;
@@ -10,6 +12,9 @@ export class ApplicationKernelRuntime {
10
12
  capabilities;
11
13
  #extensionRuntimes = new Map();
12
14
  #providerInstances = new Map();
15
+ #createdInstances = [];
16
+ #trackedInstances = new Set();
17
+ #initializedInstanceCount = 0;
13
18
  #providerNodes = new Map();
14
19
  #executionBindings = new Map();
15
20
  #initializedModuleIds = new Set();
@@ -23,6 +28,9 @@ export class ApplicationKernelRuntime {
23
28
  #initialization;
24
29
  #shutdown;
25
30
  constructor(model, options = {}) {
31
+ if ('logger' in options) {
32
+ throw new TypeError('LUTRE_LOGGER_CONFIGURATION: Register provide(LoggerToken).useClass(LoggerService) in Module.providers; runtime Logger overrides are not supported.');
33
+ }
26
34
  this.model = assertValidApplicationModel(model);
27
35
  for (const node of this.model.nodes) {
28
36
  if (node.kind === 'provider')
@@ -32,21 +40,19 @@ export class ApplicationKernelRuntime {
32
40
  this.#environmentSource = options.environmentSource;
33
41
  this.#argumentsSource =
34
42
  'argumentsSource' in options ? options.argumentsSource : Object.freeze({});
35
- this.#forceShutdownTimeoutMs =
36
- options.forceShutdownTimeoutMs ?? defaultForceShutdownTimeoutMs;
43
+ this.#forceShutdownTimeoutMs = milliseconds(options.forceShutdownTimeoutMs ?? defaultForceShutdownTimeoutMs);
37
44
  this.#instrumentation = options.instrumentation;
38
45
  if (!Number.isFinite(this.#forceShutdownTimeoutMs) ||
39
46
  this.#forceShutdownTimeoutMs < 0) {
40
47
  throw new TypeError('LUTRE_FORCE_SHUTDOWN_TIMEOUT: forceShutdownTimeoutMs must be a non-negative finite number.');
41
48
  }
42
49
  this.container = new Container(model.providers, {
43
- logger: options.logger ?? new Logger(),
44
50
  dependencies: new Map([...this.#providerNodes.values()].map((node) => [
45
51
  node.token,
46
52
  node.dependencies,
47
53
  ])),
48
54
  hooks: {
49
- created: (provider, value) => this.#notifyProviderCreated(provider, value),
55
+ created: (provider, value) => this.#onProviderCreated(provider, value),
50
56
  },
51
57
  });
52
58
  for (const { extension, executions } of this.model.extensions) {
@@ -88,6 +94,8 @@ export class ApplicationKernelRuntime {
88
94
  try {
89
95
  this.#validateCapabilities();
90
96
  await this.#bindRuntimeInputs();
97
+ this.container.resolve(LoggerToken);
98
+ await this.#initializeConstructedProviders();
91
99
  await this.#initializeProviders();
92
100
  for (const modelExtension of this.model.extensions) {
93
101
  const runtime = await modelExtension.extension.createRuntime({
@@ -141,8 +149,6 @@ export class ApplicationKernelRuntime {
141
149
  }
142
150
  async #initializeProviders() {
143
151
  for (const module of moduleNodes(this.model)) {
144
- const instances = [];
145
- this.#providerInstances.set(module.id, instances);
146
152
  for (const providerNode of providerNodesOfModule(this.model, module.id)) {
147
153
  const provider = providerNode.provider;
148
154
  if (provider.scope !== 'application' ||
@@ -150,13 +156,15 @@ export class ApplicationKernelRuntime {
150
156
  provider.kind === 'arguments') {
151
157
  continue;
152
158
  }
153
- const instance = this.container.resolve(provider.provide);
154
- instances.push(instance);
155
- await callLifecycle(instance, 'onModuleInit');
159
+ this.container.resolve(provider.provide);
160
+ await this.#initializeConstructedProviders();
156
161
  }
157
162
  this.#initializedModuleIds.add(module.id);
158
163
  await this.#runHook(lifecycleHookOf(this.model, module.id, 'onModuleInit'));
159
164
  }
165
+ for (const instance of this.#providerInstances.get('application') ?? []) {
166
+ await callLifecycle(instance, 'onApplicationBootstrap');
167
+ }
160
168
  for (const module of moduleNodes(this.model)) {
161
169
  for (const instance of this.#providerInstances.get(module.id) ?? []) {
162
170
  await callLifecycle(instance, 'onApplicationBootstrap');
@@ -164,6 +172,12 @@ export class ApplicationKernelRuntime {
164
172
  await this.#runHook(lifecycleHookOf(this.model, module.id, 'onApplicationBootstrap'));
165
173
  }
166
174
  }
175
+ async #initializeConstructedProviders() {
176
+ while (this.#initializedInstanceCount < this.#createdInstances.length) {
177
+ const instance = this.#createdInstances[this.#initializedInstanceCount++];
178
+ await callLifecycle(instance, 'onModuleInit');
179
+ }
180
+ }
167
181
  executionBindings(extension) {
168
182
  const bindings = this.#executionBindings.get(extension.identity);
169
183
  if (!bindings) {
@@ -211,7 +225,18 @@ export class ApplicationKernelRuntime {
211
225
  beginOperation(metadata, invocation) {
212
226
  return instrumentationLease(safely(() => this.#instrumentation?.beginOperation?.(metadata, invocation)));
213
227
  }
214
- #notifyProviderCreated(provider, value) {
228
+ #onProviderCreated(provider, value) {
229
+ const node = this.#providerNodes.get(provider);
230
+ const owner = loggerLifecycleOwner(value);
231
+ if (node &&
232
+ provider.scope === 'application' &&
233
+ !this.#trackedInstances.has(owner)) {
234
+ this.#trackedInstances.add(owner);
235
+ this.#createdInstances.push(owner);
236
+ const instances = this.#providerInstances.get(node.moduleId) ?? [];
237
+ instances.push(owner);
238
+ this.#providerInstances.set(node.moduleId, instances);
239
+ }
215
240
  const instrumentation = this.#instrumentation;
216
241
  if (!instrumentation?.providerCreated)
217
242
  return;
@@ -305,6 +330,7 @@ export class ApplicationKernelRuntime {
305
330
  }
306
331
  this.#extensionDrainOperations.clear();
307
332
  await this.#cleanupProviders(signal, errors);
333
+ await this.#disposeProviders(errors);
308
334
  this.#state = 'stopped';
309
335
  await closeInstrumentation(this.#instrumentation);
310
336
  if (errors.length > 0) {
@@ -338,23 +364,28 @@ export class ApplicationKernelRuntime {
338
364
  }
339
365
  async #cleanupProviders(signal, errors) {
340
366
  const modules = moduleNodes(this.model).toReversed();
341
- for (const module of modules) {
342
- for (const instance of (this.#providerInstances.get(module.id) ?? []).toReversed()) {
343
- await collectError(() => callLifecycle(instance, 'onModuleDestroy'), errors);
367
+ for (const phase of [
368
+ 'onModuleDestroy',
369
+ 'beforeApplicationShutdown',
370
+ 'onApplicationShutdown',
371
+ ]) {
372
+ for (const module of modules) {
373
+ for (const instance of (this.#providerInstances.get(module.id) ?? []).toReversed()) {
374
+ await collectError(() => callLifecycle(instance, phase, phase === 'onModuleDestroy' ? undefined : signal), errors);
375
+ }
376
+ await collectError(() => this.#runCleanupHook(module.id, phase), errors);
344
377
  }
345
- await collectError(() => this.#runCleanupHook(module.id, 'onModuleDestroy'), errors);
346
- }
347
- for (const module of modules) {
348
- for (const instance of (this.#providerInstances.get(module.id) ?? []).toReversed()) {
349
- await collectError(() => callLifecycle(instance, 'beforeApplicationShutdown', signal), errors);
378
+ for (const instance of (this.#providerInstances.get('application') ?? []).toReversed()) {
379
+ await collectError(() => callLifecycle(instance, phase, phase === 'onModuleDestroy' ? undefined : signal), errors);
350
380
  }
351
- await collectError(() => this.#runCleanupHook(module.id, 'beforeApplicationShutdown'), errors);
352
381
  }
353
- for (const module of modules) {
354
- for (const instance of (this.#providerInstances.get(module.id) ?? []).toReversed()) {
355
- await collectError(() => callLifecycle(instance, 'onApplicationShutdown', signal), errors);
356
- }
357
- await collectError(() => this.#runCleanupHook(module.id, 'onApplicationShutdown'), errors);
382
+ }
383
+ async #disposeProviders(errors) {
384
+ for (const module of moduleNodes(this.model).toReversed()) {
385
+ await collectError(() => this.#runCleanupHook(module.id, 'onApplicationDispose'), errors);
386
+ }
387
+ for (const instance of this.#createdInstances.toReversed()) {
388
+ await collectError(() => callLifecycle(instance, 'onApplicationDispose'), errors);
358
389
  }
359
390
  }
360
391
  async #rollbackInitialization() {
@@ -369,14 +400,21 @@ export class ApplicationKernelRuntime {
369
400
  await collectError(() => callLifecycle(instance, 'onModuleDestroy'), errors);
370
401
  }
371
402
  }
403
+ for (const instance of (this.#providerInstances.get('application') ?? []).toReversed()) {
404
+ await collectError(() => callLifecycle(instance, 'onModuleDestroy'), errors);
405
+ }
406
+ await this.#disposeProviders(errors);
372
407
  await closeInstrumentation(this.#instrumentation);
373
408
  return errors;
374
409
  }
375
- async #runHook(hook) {
376
- if (!hook)
410
+ async #runHook(node) {
411
+ if (!node)
377
412
  return;
378
- const dependencies = hook.inject.map((token) => this.container.resolve(token));
379
- await hook.run(...dependencies);
413
+ const dependencies = node.hook.inject.map((token) => this.container.resolve(token, {
414
+ source: node.id,
415
+ consumer: { id: node.id, name: node.phase, kind: 'lifecycle' },
416
+ }));
417
+ await node.hook.run(...dependencies);
380
418
  }
381
419
  async #runCleanupHook(moduleId, phase) {
382
420
  if (!this.#initializedModuleIds.has(moduleId))
@@ -393,7 +431,7 @@ function providerNodesOfModule(model, moduleId) {
393
431
  function lifecycleHookOf(model, moduleId, phase) {
394
432
  return model.nodes.find((node) => node.kind === 'lifecycle' &&
395
433
  node.moduleId === moduleId &&
396
- node.phase === phase)?.hook;
434
+ node.phase === phase);
397
435
  }
398
436
  function instrumentationLease(scope) {
399
437
  return {
@@ -0,0 +1,4 @@
1
+ import type { LoggerService } from './logger.js';
2
+ export declare function loggerLifecycleOwner(value: LoggerService): LoggerService;
3
+ export declare function loggerLifecycleOwner(value: unknown): unknown;
4
+ export declare function bindLoggerSource(logger: LoggerService, source: string): LoggerService;
@@ -0,0 +1,47 @@
1
+ import { isLogger } from '../core/logger-internal.js';
2
+ import { loggerInstanceBrand, loggerSourceRoot, } from '../core/logger-identity.js';
3
+ export function loggerLifecycleOwner(value) {
4
+ if (!isLogger(value) || !(loggerSourceRoot in value))
5
+ return value;
6
+ let owner = value;
7
+ const visited = new Set();
8
+ while (loggerSourceRoot in owner && !visited.has(owner)) {
9
+ visited.add(owner);
10
+ const root = owner[loggerSourceRoot];
11
+ if (!isLogger(root))
12
+ break;
13
+ owner = root;
14
+ }
15
+ return owner;
16
+ }
17
+ export function bindLoggerSource(logger, source) {
18
+ const context = { ...logger.context, source };
19
+ const bind = (level) => (message, fields = {}) => logger[level](message, { source, ...fields });
20
+ const methods = {
21
+ debug: bind('debug'),
22
+ info: bind('info'),
23
+ warn: bind('warn'),
24
+ error: bind('error'),
25
+ };
26
+ const reference = {
27
+ [loggerInstanceBrand]: true,
28
+ [loggerSourceRoot]: loggerLifecycleOwner(logger),
29
+ backend: logger.backend,
30
+ context,
31
+ ...methods,
32
+ onApplicationDispose: () => logger.onApplicationDispose(),
33
+ };
34
+ Object.setPrototypeOf(reference, Reflect.getPrototypeOf(logger));
35
+ // 凍結した元の実体を対象にすると、出力メソッドとcontextを委譲用に置き換えられない。
36
+ return new Proxy(reference, {
37
+ get(target, property) {
38
+ if (Object.hasOwn(target, property))
39
+ return Reflect.get(target, property);
40
+ // private fieldを持つ実装のreceiverをproxyへ置き換えない。
41
+ const value = Reflect.get(logger, property, logger);
42
+ return typeof value === 'function' && property !== 'constructor'
43
+ ? value.bind(logger)
44
+ : value;
45
+ },
46
+ });
47
+ }