@loutrejs/loutre 0.9.2 → 0.10.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 (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
@@ -1,7 +1,9 @@
1
+ import { moduleName } from '../identity.js';
1
2
  import { collectInjectedDependencies } from '../injection.js';
2
3
  import { diagnostic } from '../diagnostic.js';
3
4
  import { normalizeProvider } from '../provider.js';
4
5
  import { tokenName } from '../token.js';
6
+ import { findProviderNode } from './context.js';
5
7
  import { appendLifecycleNodes } from './lifecycle.js';
6
8
  import { snapshotProvider } from './snapshot.js';
7
9
  import { getSourceLocation } from '../source-location.js';
@@ -12,10 +14,10 @@ export function collectModuleDeclarations(context) {
12
14
  for (const module of modules) {
13
15
  const moduleId = moduleIds.get(module);
14
16
  if (module.definition.name) {
15
- if (moduleNames.has(module.definition.name)) {
17
+ if (moduleNames.has(moduleName(module.definition.name))) {
16
18
  diagnostics.push(diagnostic('LUTRE_MODULE_NAME_COLLISION', `Module name ${module.definition.name} is already registered.`, moduleId));
17
19
  }
18
- moduleNames.add(module.definition.name);
20
+ moduleNames.add(moduleName(module.definition.name));
19
21
  }
20
22
  const moduleSource = getSourceLocation(module) ?? getSourceLocation(module.template);
21
23
  nodes.push({
@@ -41,7 +43,7 @@ export function collectModuleDeclarations(context) {
41
43
  ? getSourceLocation(normalized.useClass)
42
44
  : getSourceLocation(declaration);
43
45
  const providerId = context.nextProviderId();
44
- const existingProvider = providerNodes.get(provider.provide);
46
+ const existingProvider = findProviderNode(context, provider.provide);
45
47
  if (existingProvider?.provider.kind === 'environment' &&
46
48
  provider.kind === 'environment') {
47
49
  edges.push({ from: moduleId, to: existingProvider.id, kind: 'owns' });
@@ -78,7 +80,7 @@ export function collectModuleDeclarations(context) {
78
80
  appendLifecycleNodes(module, moduleId, nodes, edges);
79
81
  }
80
82
  }
81
- function collectProviderDependencies(provider) {
83
+ export function collectProviderDependencies(provider) {
82
84
  return match(provider)
83
85
  .with({ kind: 'class' }, (candidate) => Object.freeze(collectInjectedDependencies(candidate.provide, () => Reflect.construct(candidate.useClass, []))))
84
86
  .with({ kind: 'factory' }, (candidate) => Object.freeze([...candidate.inject]))
@@ -1,4 +1,4 @@
1
- import type { ModelBuildContext } from './context.js';
1
+ import { type ModelBuildContext } from './context.js';
2
2
  export declare function resolveModuleExports(context: ModelBuildContext): void;
3
3
  export declare function compileArgumentsProvider(context: ModelBuildContext): void;
4
4
  export declare function resolveProviderDependencies(context: ModelBuildContext): void;
@@ -1,17 +1,19 @@
1
+ import { isLoggerToken } from '../logger-internal.js';
1
2
  import { diagnostic } from '../diagnostic.js';
2
3
  import { argumentsProvider } from '../provider.js';
3
4
  import { isFrameworkProvidedToken } from '../token-internal.js';
4
5
  import { tokenName } from '../token.js';
6
+ import { findProviderNode } from './context.js';
5
7
  import { snapshotProvider } from './snapshot.js';
6
8
  import { isTokenVisible, moduleDeclaresToken } from './visibility.js';
7
9
  export function resolveModuleExports(context) {
8
- const { modules, moduleIds, providerNodes, edges, diagnostics } = context;
10
+ const { modules, moduleIds, edges, diagnostics } = context;
9
11
  for (const module of modules) {
10
12
  const moduleId = moduleIds.get(module);
11
13
  for (const exported of module.definition.exports ?? []) {
12
14
  if (moduleDeclaresToken(module, exported))
13
15
  continue;
14
- const provider = providerNodes.get(exported);
16
+ const provider = findProviderNode(context, exported);
15
17
  const providerModule = provider
16
18
  ? modules.find((candidate) => moduleIds.get(candidate) === provider.moduleId)
17
19
  : undefined;
@@ -49,17 +51,15 @@ export function compileArgumentsProvider(context) {
49
51
  export function resolveProviderDependencies(context) {
50
52
  const { providerNodes, modules, moduleIds, edges, diagnostics } = context;
51
53
  for (const provider of providerNodes.values()) {
52
- if (provider.moduleId === 'application')
53
- continue;
54
54
  const sourceModule = modules.find((candidate) => moduleIds.get(candidate) === provider.moduleId);
55
- if (!sourceModule)
56
- continue;
57
55
  for (const dependency of provider.dependencies) {
58
- const target = providerNodes.get(dependency);
56
+ const target = findProviderNode(context, dependency);
59
57
  if (target) {
60
58
  edges.push({ from: provider.id, to: target.id, kind: 'injects' });
61
59
  const targetModule = modules.find((candidate) => moduleIds.get(candidate) === target.moduleId);
62
- if (targetModule &&
60
+ if (sourceModule &&
61
+ !isLoggerToken(dependency) &&
62
+ targetModule &&
63
63
  target.moduleId !== provider.moduleId &&
64
64
  !moduleDeclaresToken(sourceModule, dependency) &&
65
65
  !isTokenVisible(context, sourceModule, targetModule, dependency)) {
@@ -1,32 +1,33 @@
1
+ import type { ModuleId, ProviderNodeId, ExecutionNodeId, LifecycleNodeId, CapabilityNodeId, ExtensionNodeId } from './identity.js';
1
2
  import type { ArgsClass } from '../args.js';
2
3
  import type { AnyExecutionExtension, ExecutionExtension, RuntimeCapability } from '../extension.js';
3
4
  import type { LifecycleHook } from '../lifecycle.js';
4
- import type { ModuleInstance, ModuleTemplate } from '../module.js';
5
+ import type { ModuleInstance } from '../module.js';
5
6
  import type { ProviderDescriptor } from '../provider.js';
6
7
  import type { Diagnostic } from '../diagnostic.js';
7
8
  import type { TokenLike } from '../token.js';
8
9
  import type { SourceLocation } from '../source-location.js';
9
10
  export interface ModuleModelNode {
10
11
  readonly kind: 'module';
11
- readonly id: string;
12
+ readonly id: ModuleId;
12
13
  readonly name?: string;
13
14
  readonly description?: string;
14
15
  readonly source?: SourceLocation;
15
16
  }
16
17
  export interface ProviderModelNode {
17
18
  readonly kind: 'provider';
18
- readonly id: string;
19
+ readonly id: ProviderNodeId;
19
20
  readonly token: TokenLike;
20
21
  readonly provider: ProviderDescriptor;
21
- readonly moduleId: string;
22
+ readonly moduleId: ModuleId | 'application';
22
23
  readonly dependencies: readonly TokenLike[];
23
24
  readonly source?: SourceLocation;
24
25
  }
25
26
  export interface ExecutionModelNode<TCompiled = unknown> {
26
27
  readonly kind: 'execution';
27
- readonly id: string;
28
+ readonly id: ExecutionNodeId;
28
29
  readonly executionKind: string;
29
- readonly moduleId: string;
30
+ readonly moduleId: ModuleId;
30
31
  readonly dependencies: readonly TokenLike[];
31
32
  readonly capabilities: readonly RuntimeCapability[];
32
33
  readonly compiled: TCompiled;
@@ -34,25 +35,30 @@ export interface ExecutionModelNode<TCompiled = unknown> {
34
35
  }
35
36
  export interface LifecycleModelNode {
36
37
  readonly kind: 'lifecycle';
37
- readonly id: string;
38
- readonly moduleId: string;
38
+ readonly id: LifecycleNodeId;
39
+ readonly moduleId: ModuleId;
39
40
  readonly phase: string;
40
41
  readonly hook: LifecycleHook<any>;
41
42
  readonly source?: SourceLocation;
42
43
  }
43
- export interface FrameworkModelNode {
44
+ export type FrameworkModelNode = {
44
45
  readonly kind: 'framework';
45
- readonly id: string;
46
- readonly frameworkKind: 'runtime-capability' | 'execution-extension';
47
46
  readonly name: string;
48
47
  readonly source?: SourceLocation;
49
- }
48
+ } & ({
49
+ readonly frameworkKind: 'runtime-capability';
50
+ readonly id: CapabilityNodeId;
51
+ } | {
52
+ readonly frameworkKind: 'execution-extension';
53
+ readonly id: ExtensionNodeId;
54
+ });
50
55
  export type ApplicationModelNode = ModuleModelNode | ProviderModelNode | ExecutionModelNode | LifecycleModelNode | FrameworkModelNode;
51
- export interface ApplicationModelEdge {
52
- readonly from: string;
53
- readonly to: string;
54
- readonly kind: 'owns' | 'imports' | 'exports' | 'injects' | 'references' | 'requires' | 'starts' | 'wraps';
55
- }
56
+ type Edge<Kind, From, To> = {
57
+ readonly kind: Kind;
58
+ readonly from: From;
59
+ readonly to: To;
60
+ };
61
+ export type ApplicationModelEdge = Edge<'owns', ModuleId, ProviderNodeId | ExecutionNodeId | LifecycleNodeId> | Edge<'imports', ModuleId, ModuleId> | Edge<'exports', ModuleId, ProviderNodeId> | Edge<'injects', ProviderNodeId | ExecutionNodeId | LifecycleNodeId, ProviderNodeId> | Edge<'references', ExecutionNodeId, ExecutionNodeId> | Edge<'requires', ExecutionNodeId, CapabilityNodeId> | Edge<'starts', ExtensionNodeId, ExecutionNodeId> | Edge<'wraps', ApplicationModelNode['id'], ApplicationModelNode['id']>;
56
62
  export type CompiledOf<TExtension extends AnyExecutionExtension> = TExtension extends ExecutionExtension<any, infer TCompiled, any, any, any> ? TCompiled : never;
57
63
  export interface ApplicationModelExtension<TExtension extends AnyExecutionExtension = AnyExecutionExtension> {
58
64
  readonly extension: TExtension;
@@ -73,6 +79,7 @@ export interface ApplicationModel {
73
79
  readonly diagnostics: readonly Diagnostic[];
74
80
  }
75
81
  export interface ApplicationModelBuildOptions {
76
- readonly modules: readonly (ModuleInstance | ModuleTemplate<void>)[];
82
+ readonly modules: readonly ModuleInstance[];
77
83
  readonly arguments?: ArgsClass;
78
84
  }
85
+ export {};
@@ -1,3 +1,4 @@
1
+ import { hostNamespace } from '../identity.js';
1
2
  import { diagnostic } from '../diagnostic.js';
2
3
  export function validateHostNamespaces(extensions, diagnostics) {
3
4
  const reserved = new Set([
@@ -20,12 +21,12 @@ export function validateHostNamespaces(extensions, diagnostics) {
20
21
  diagnostics.push(diagnostic('LUTRE_HOST_NAMESPACE_RESERVED', `Host namespace ${namespace} is reserved by the Application or runtime adapter API.`, `host.${namespace}`));
21
22
  continue;
22
23
  }
23
- const owner = owners.get(namespace);
24
+ const owner = owners.get(hostNamespace(namespace));
24
25
  if (owner && owner.identity !== extension.identity) {
25
26
  diagnostics.push(diagnostic('LUTRE_HOST_NAMESPACE_COLLISION', `Host namespace ${namespace} is contributed by both ${owner.name} and ${extension.name}.`, `host.${namespace}`));
26
27
  continue;
27
28
  }
28
- owners.set(namespace, extension);
29
+ owners.set(hostNamespace(namespace), extension);
29
30
  }
30
31
  }
31
32
  export function validateNodeIds(nodes, diagnostics) {
@@ -1,13 +1,18 @@
1
+ import { isLoggerToken } from '../logger-internal.js';
1
2
  import { normalizeProvider } from '../provider.js';
2
3
  export function moduleDeclaresToken(module, token) {
3
- return (module.definition.providers ?? []).some((declaration) => normalizeProvider(declaration).provide === token);
4
+ return (module.definition.providers ?? []).some((declaration) => {
5
+ const provided = normalizeProvider(declaration).provide;
6
+ return (provided === token || (isLoggerToken(provided) && isLoggerToken(token)));
7
+ });
4
8
  }
5
9
  export function isTokenVisible(context, source, target, token, visited = new Set()) {
6
10
  if (visited.has(source))
7
11
  return false;
8
12
  visited.add(source);
9
13
  for (const imported of context.imports.get(source) ?? []) {
10
- if (!imported.definition.exports?.includes(token))
14
+ if (!imported.definition.exports?.some((exported) => exported === token ||
15
+ (isLoggerToken(exported) && isLoggerToken(token))))
11
16
  continue;
12
17
  if (imported === target)
13
18
  return true;
@@ -1,3 +1,4 @@
1
+ import { executionNodeId, capabilityNodeId, extensionNodeId, } from './model/identity.js';
1
2
  import { diagnostic, isErrorDiagnostic } from './diagnostic.js';
2
3
  import { isExecutionDefinition, flattenExecutions, } from './extension.js';
3
4
  import { isFrameworkProvidedToken } from './token-internal.js';
@@ -8,6 +9,9 @@ import { collectModuleDeclarations } from './model/modules.js';
8
9
  import { compileArgumentsProvider, resolveModuleExports, resolveProviderDependencies, } from './model/providers.js';
9
10
  import { snapshotExecutionExtension } from './model/snapshot.js';
10
11
  import { validateHostNamespaces, validateNodeIds } from './model/validation.js';
12
+ import { compileApplicationLogger, validateApplicationLogger, } from './model/logger.js';
13
+ import { findProviderNode } from './model/context.js';
14
+ import { isLoggerToken } from './logger-internal.js';
11
15
  import { getSourceLocation } from './source-location.js';
12
16
  import { isTokenVisible, moduleDeclaresToken } from './model/visibility.js';
13
17
  class ApplicationModelExtensionRegistry {
@@ -47,11 +51,12 @@ export function assertValidApplicationModel(model) {
47
51
  export function buildApplicationModel(options) {
48
52
  const context = createModelBuildContext(options);
49
53
  collectModuleDeclarations(context);
50
- resolveModuleExports(context);
51
54
  compileArgumentsProvider(context);
55
+ compileApplicationLogger(context);
56
+ resolveModuleExports(context);
52
57
  resolveProviderDependencies(context);
53
58
  resolveLifecycleDependencies(context);
54
- const { modules, moduleIds, nodes, edges, diagnostics, providers, executions, providerNodes, } = context;
59
+ const { modules, moduleIds, nodes, edges, diagnostics, providers, executions, } = context;
55
60
  const extensionExecutions = new Map();
56
61
  const extensionNames = new Map();
57
62
  const extensionIdentities = new Map();
@@ -216,14 +221,15 @@ export function buildApplicationModel(options) {
216
221
  diagnostics.push(diagnostic('LUTRE_EXTENSION_COMPILE', describeError(error), path));
217
222
  continue;
218
223
  }
219
- if (executionIds.has(contribution.id)) {
224
+ if (executionIds.has(executionNodeId(contribution.id))) {
220
225
  diagnostics.push(diagnostic('LUTRE_EXECUTION_ID_COLLISION', `Execution id ${contribution.id} is already registered.`, path));
221
226
  continue;
222
227
  }
223
- executionIds.add(contribution.id);
228
+ executionIds.add(executionNodeId(contribution.id));
224
229
  const executionSource = getSourceLocation(value);
225
230
  const execution = Object.freeze({
226
231
  ...contribution,
232
+ id: executionNodeId(contribution.id),
227
233
  dependencies: Object.freeze([...contribution.dependencies]),
228
234
  capabilities: Object.freeze([...contribution.capabilities]),
229
235
  moduleId,
@@ -240,11 +246,12 @@ export function buildApplicationModel(options) {
240
246
  grouped.executions.push(execution);
241
247
  extensionExecutions.set(extension.identity, grouped);
242
248
  for (const dependency of execution.dependencies) {
243
- const provider = providerNodes.get(dependency);
249
+ const provider = findProviderNode(context, dependency);
244
250
  if (provider) {
245
251
  edges.push({ from: execution.id, to: provider.id, kind: 'injects' });
246
252
  const providerModule = modules.find((candidate) => moduleIds.get(candidate) === provider.moduleId);
247
253
  if (providerModule &&
254
+ !isLoggerToken(dependency) &&
248
255
  provider.moduleId !== moduleId &&
249
256
  !moduleDeclaresToken(module, dependency) &&
250
257
  !isTokenVisible(context, module, providerModule, dependency)) {
@@ -256,7 +263,7 @@ export function buildApplicationModel(options) {
256
263
  }
257
264
  }
258
265
  for (const capability of execution.capabilities) {
259
- const capabilityId = `capability:${capability.id}`;
266
+ const capabilityId = capabilityNodeId(capability.id);
260
267
  if (!capabilityNodeIds.has(capabilityId)) {
261
268
  capabilityNodeIds.add(capabilityId);
262
269
  nodes.push({
@@ -312,7 +319,7 @@ export function buildApplicationModel(options) {
312
319
  edges.push({ from: from.id, to: to.id, kind: 'references' });
313
320
  }
314
321
  const extensionGroups = [...extensionExecutions.values()].map(({ extension, executions: ownedExecutions }) => {
315
- const extensionId = `extension:${extension.name}`;
322
+ const extensionId = extensionNodeId(extension.name);
316
323
  nodes.push({
317
324
  kind: 'framework',
318
325
  id: extensionId,
@@ -336,6 +343,7 @@ export function buildApplicationModel(options) {
336
343
  });
337
344
  });
338
345
  const extensions = new ApplicationModelExtensionRegistry(extensionGroups);
346
+ validateApplicationLogger(context);
339
347
  validateHostNamespaces(extensionGroups, diagnostics);
340
348
  validateNodeIds(nodes, diagnostics);
341
349
  return Object.freeze({
@@ -1,3 +1,4 @@
1
+ import type { Descriptor } from '../internal/brand.js';
1
2
  import type { EnvClass } from './env.js';
2
3
  import type { ExecutionDeclaration, ExtensionOfDefinition, HostApiOfExtension } from './extension.js';
3
4
  import type { ModuleLifecycle } from './lifecycle.js';
@@ -5,7 +6,7 @@ import { type ProviderDeclaration } from './provider.js';
5
6
  export interface ModuleDefinition {
6
7
  readonly name?: string;
7
8
  readonly description?: string;
8
- readonly imports?: readonly ModuleInput[];
9
+ readonly imports?: readonly ModuleInstance[];
9
10
  readonly environment?: readonly EnvClass[];
10
11
  readonly providers?: readonly ProviderDeclaration[];
11
12
  readonly executions?: readonly ExecutionDeclaration[];
@@ -17,18 +18,18 @@ export interface ModuleTypeInfo<TDefinition extends ModuleDefinition = ModuleDef
17
18
  readonly extensions: ExtensionsOfModuleDefinition<TDefinition>;
18
19
  }
19
20
  type DirectExtensions<TDefinition extends ModuleDefinition> = TDefinition['executions'] extends readonly ExecutionDeclaration[] ? ExtensionOfDefinition<TDefinition['executions'][number]> : never;
20
- type ImportedExtensions<TDefinition extends ModuleDefinition> = TDefinition['imports'] extends readonly ModuleInput[] ? ModuleExtensions<TDefinition['imports'][number]> : never;
21
+ type ImportedExtensions<TDefinition extends ModuleDefinition> = TDefinition['imports'] extends readonly ModuleInstance[] ? ModuleExtensions<TDefinition['imports'][number]> : never;
21
22
  export type ExtensionsOfModuleDefinition<TDefinition extends ModuleDefinition> = DirectExtensions<TDefinition> | ImportedExtensions<TDefinition>;
22
- export type ModuleExtensions<TModule> = TModule extends ModuleInstance<infer TDefinition> ? ExtensionsOfModuleDefinition<TDefinition> : TModule extends ModuleTemplate<void, infer TDefinition> ? ExtensionsOfModuleDefinition<TDefinition> : never;
23
+ export type ModuleExtensions<TModule> = TModule extends ModuleInstance<infer TDefinition> ? ExtensionsOfModuleDefinition<TDefinition> : never;
23
24
  export type ModuleHostApis<TModule> = HostApiOfExtension<ModuleExtensions<TModule>>;
24
- export interface ModuleInstance<TDefinition extends ModuleDefinition = ModuleDefinition> {
25
+ export interface ModuleInstance<TDefinition extends ModuleDefinition = ModuleDefinition> extends Descriptor<'module-instance'> {
25
26
  readonly kind: 'module-instance';
26
27
  readonly template: AnyModuleTemplate;
27
28
  readonly args: unknown;
28
29
  readonly definition: TDefinition;
29
30
  readonly [moduleTypeInfo]?: ModuleTypeInfo<TDefinition>;
30
31
  }
31
- export interface AnyModuleTemplate {
32
+ export interface AnyModuleTemplate extends Descriptor<'module-template'> {
32
33
  (args?: any): ModuleInstance<any>;
33
34
  readonly kind: 'module-template';
34
35
  readonly instantiate: (args: any) => ModuleInstance<any>;
@@ -40,11 +41,10 @@ export type ModuleTemplate<Args, TDefinition extends ModuleDefinition = ModuleDe
40
41
  (args: Args): ModuleInstance<TDefinition>;
41
42
  }) & {
42
43
  readonly kind: 'module-template';
44
+ readonly '~loutre.descriptor': 'module-template';
43
45
  readonly instantiate: (args: Args) => ModuleInstance<TDefinition>;
44
46
  readonly [moduleTypeInfo]?: ModuleTypeInfo<TDefinition>;
45
47
  };
46
- export type ModuleInput = ModuleInstance | ModuleTemplate<void>;
47
48
  export declare function defineModule<const TDefinition extends ModuleDefinition>(definition: TDefinition): ModuleInstance<TDefinition>;
48
49
  export declare function defineModule<Args = void, const TDefinition extends ModuleDefinition = ModuleDefinition>(factory: (args: Args) => TDefinition): ModuleTemplate<Args, TDefinition>;
49
- export declare function asModuleInstance(module: ModuleInput): ModuleInstance;
50
50
  export {};
@@ -31,6 +31,3 @@ function createModuleTemplate(factory) {
31
31
  });
32
32
  return template;
33
33
  }
34
- export function asModuleInstance(module) {
35
- return module.kind === 'module-instance' ? module : module();
36
- }
@@ -1,8 +1,9 @@
1
+ import type { Descriptor } from '../internal/brand.js';
1
2
  import type { TokenLike } from './token.js';
2
3
  export type RuntimeInputContract = TokenLike<object> & {
3
4
  readonly name: string;
4
5
  };
5
- export interface RuntimeInputKey<TValue = unknown> {
6
+ export interface RuntimeInputKey<TValue = unknown> extends Descriptor<'runtime-input-key'> {
6
7
  readonly kind: 'runtime-input-key';
7
8
  readonly source: 'environment' | 'arguments';
8
9
  readonly contract: RuntimeInputContract;
@@ -1,8 +1,20 @@
1
- export interface SourceLocation {
1
+ import type { Brand } from '../internal/brand.js';
2
+ export type SourceFileReference = Brand<string, 'source.file-reference'>;
3
+ export type SourceLine = Brand<number, 'source.line'>;
4
+ export type SourceColumn = Brand<number, 'source.column'>;
5
+ export declare const sourceFileReference: (value: string) => SourceFileReference;
6
+ export declare const sourceLine: (value: number) => SourceLine;
7
+ export declare const sourceColumn: (value: number) => SourceColumn;
8
+ export interface SourceLocationInput {
2
9
  readonly file: string;
3
10
  readonly line?: number;
4
11
  readonly column?: number;
5
12
  }
6
- export declare function registerSourceLocation<T>(value: T, source: SourceLocation): T;
13
+ export interface SourceLocation {
14
+ readonly file: SourceFileReference;
15
+ readonly line?: SourceLine;
16
+ readonly column?: SourceColumn;
17
+ }
18
+ export declare function registerSourceLocation<T>(value: T, source: SourceLocationInput): T;
7
19
  export declare function getSourceLocation(value: unknown): SourceLocation | undefined;
8
20
  export declare function inheritSourceLocation<T>(target: T, source: unknown): T;
@@ -1,3 +1,6 @@
1
+ export const sourceFileReference = (value) => value;
2
+ export const sourceLine = (value) => value;
3
+ export const sourceColumn = (value) => value;
1
4
  const sourceMetadata = new WeakMap();
2
5
  function asObject(value) {
3
6
  return (typeof value === 'object' && value !== null) ||
@@ -7,9 +10,11 @@ function asObject(value) {
7
10
  }
8
11
  function snapshotSourceLocation(source) {
9
12
  return Object.freeze({
10
- file: source.file,
11
- ...(source.line === undefined ? {} : { line: source.line }),
12
- ...(source.column === undefined ? {} : { column: source.column }),
13
+ file: sourceFileReference(source.file),
14
+ ...(source.line === undefined ? {} : { line: sourceLine(source.line) }),
15
+ ...(source.column === undefined
16
+ ? {}
17
+ : { column: sourceColumn(source.column) }),
13
18
  });
14
19
  }
15
20
  export function registerSourceLocation(value, source) {
@@ -1,4 +1,6 @@
1
1
  export const frameworkProvidedTokenBrand = Symbol.for('loutre.framework-provided-token');
2
2
  export function isFrameworkProvidedToken(token) {
3
- return (token[frameworkProvidedTokenBrand] === true);
3
+ return (Object.hasOwn(token, frameworkProvidedTokenBrand) &&
4
+ frameworkProvidedTokenBrand in token &&
5
+ token[frameworkProvidedTokenBrand] === true);
4
6
  }
@@ -1,11 +1,13 @@
1
+ import type { Descriptor } from '../internal/brand.js';
2
+ import type { TokenKey } from './identity.js';
1
3
  export interface TokenOptions {
2
4
  readonly description?: string;
3
5
  }
4
- export interface Token<T> {
6
+ export interface Token<T> extends Descriptor<'token'> {
5
7
  readonly kind: 'token';
6
8
  readonly id: string;
7
9
  readonly description?: string;
8
- readonly key: symbol;
10
+ readonly key: TokenKey;
9
11
  readonly '~type'?: T;
10
12
  }
11
13
  export type Class<T = unknown> = new (...args: any[]) => T;
@@ -1,3 +1,4 @@
1
+ import { isLoggerTokenIdentity } from './logger-identity.js';
1
2
  export function token(id, options = {}) {
2
3
  return Object.freeze({
3
4
  kind: 'token',
@@ -9,5 +10,7 @@ export function token(id, options = {}) {
9
10
  });
10
11
  }
11
12
  export function tokenName(value) {
13
+ if (isLoggerTokenIdentity(value))
14
+ return 'LoggerToken';
12
15
  return typeof value === 'function' ? value.name : value.id;
13
16
  }
@@ -0,0 +1,22 @@
1
+ import type { Brand } from '../internal/brand.js';
2
+ export type DurationMs = Brand<number, 'duration.ms'>;
3
+ export type DurationSeconds = Brand<number, 'duration.seconds'>;
4
+ export type EpochMs = Brand<number, 'time.epoch-ms'>;
5
+ export type MonotonicMs = Brand<number, 'time.monotonic-ms'>;
6
+ export type ByteLength = Brand<number, 'bytes.length'>;
7
+ export type ByteLimit = Brand<number, 'bytes.limit'>;
8
+ export type CaptureDepthLimit = Brand<number, 'capture.depth-limit'>;
9
+ export declare const milliseconds: (value: number) => DurationMs;
10
+ export declare const seconds: (value: number) => DurationSeconds;
11
+ export declare const epochMilliseconds: (value: number) => EpochMs;
12
+ export declare const monotonicMilliseconds: (value: number) => MonotonicMs;
13
+ export declare const byteLimit: (value: number) => ByteLimit;
14
+ export declare const captureDepthLimit: (value: number) => CaptureDepthLimit;
15
+ export declare const utf8ByteLength: (value: string) => ByteLength;
16
+ export declare const epochNow: () => EpochMs;
17
+ export declare const monotonicNow: () => MonotonicMs;
18
+ export declare const elapsed: (start: MonotonicMs, end: MonotonicMs) => DurationMs;
19
+ export declare const expiresAt: (now: EpochMs, ttl: DurationMs) => EpochMs;
20
+ export declare const epochFromMonotonic: (value: MonotonicMs) => EpochMs;
21
+ export declare const secondsToMilliseconds: (value: DurationSeconds) => DurationMs;
22
+ export declare const epochDifference: (start: EpochMs, end: EpochMs) => DurationMs;
@@ -0,0 +1,14 @@
1
+ export const milliseconds = (value) => value;
2
+ export const seconds = (value) => value;
3
+ export const epochMilliseconds = (value) => value;
4
+ export const monotonicMilliseconds = (value) => value;
5
+ export const byteLimit = (value) => value;
6
+ export const captureDepthLimit = (value) => value;
7
+ export const utf8ByteLength = (value) => new TextEncoder().encode(value).byteLength;
8
+ export const epochNow = () => epochMilliseconds(Date.now());
9
+ export const monotonicNow = () => monotonicMilliseconds(performance.now());
10
+ export const elapsed = (start, end) => milliseconds(end - start);
11
+ export const expiresAt = (now, ttl) => epochMilliseconds(now + ttl);
12
+ export const epochFromMonotonic = (value) => epochMilliseconds(performance.timeOrigin + value);
13
+ export const secondsToMilliseconds = (value) => milliseconds(value * 1_000);
14
+ export const epochDifference = (start, end) => milliseconds(end - start);
@@ -1,3 +1,7 @@
1
+ import type { RunId, TraceId, SpanId, CapsuleId, CaptureHandleId } from './identity.js';
2
+ import type { ModelNodeId } from '../core/model/identity.js';
3
+ import type { RuntimeExecutionId } from '../core/identity.js';
4
+ import type { EpochMs } from '../core/units.js';
1
5
  import type { RuntimeExecutionMetadata } from '../core/instrumentation.js';
2
6
  import type { DevtoolsModuleOptions } from './module.js';
3
7
  import type { ReplayCapsuleRequest, ReplayExecutionContext, RuntimeInvocationResult } from './inspector.js';
@@ -11,23 +15,23 @@ export type CapturedValue = {
11
15
  readonly value: unknown;
12
16
  } | {
13
17
  readonly mode: 'reference';
14
- readonly handleId: string;
18
+ readonly handleId: CaptureHandleId;
15
19
  } | {
16
20
  readonly mode: 'unavailable';
17
21
  readonly reason: string;
18
22
  };
19
23
  export interface ExecutionReplayCapsulePreview {
20
- readonly id: string;
24
+ readonly id: CapsuleId;
21
25
  readonly kind: 'execution';
22
- readonly runId: string;
23
- readonly traceId: string;
24
- readonly spanId: string;
25
- readonly createdAt: number;
26
- readonly expiresAt: number;
26
+ readonly runId: RunId;
27
+ readonly traceId: TraceId;
28
+ readonly spanId: SpanId;
29
+ readonly createdAt: EpochMs;
30
+ readonly expiresAt: EpochMs;
27
31
  readonly execution: {
28
- readonly executionId: string;
32
+ readonly executionId: RuntimeExecutionId;
29
33
  readonly executionKind: string;
30
- readonly graphNodeId?: string;
34
+ readonly graphNodeId?: ModelNodeId;
31
35
  readonly name: string;
32
36
  };
33
37
  readonly input: DevtoolsValuePreview;
@@ -42,10 +46,10 @@ export interface ReplayCapsuleStoreOptions {
42
46
  }
43
47
  export declare class ReplayCapsuleStore {
44
48
  #private;
45
- constructor(runId: string, options?: ReplayCapsuleStoreOptions);
46
- createExecution(traceId: string, spanId: string, observation: RuntimeExecutionMetadata, registration: ReplayRegistration): ExecutionReplayCapsulePreview;
47
- preview(capsuleId: string): ExecutionReplayCapsulePreview | undefined;
49
+ constructor(runId: RunId, options?: ReplayCapsuleStoreOptions);
50
+ createExecution(traceId: TraceId, spanId: SpanId, observation: RuntimeExecutionMetadata, registration: ReplayRegistration): ExecutionReplayCapsulePreview;
51
+ preview(capsuleId: CapsuleId): ExecutionReplayCapsulePreview | undefined;
48
52
  replay(request: ReplayCapsuleRequest): Promise<RuntimeInvocationResult>;
49
- discard(capsuleId: string): void;
53
+ discard(capsuleId: CapsuleId): void;
50
54
  clear(): void;
51
55
  }
@@ -1,3 +1,10 @@
1
+ function capsuleLimit(value) {
2
+ if (!Number.isInteger(value) || value < 1)
3
+ throw new TypeError('maxCapsules must be a positive integer.');
4
+ return value;
5
+ }
6
+ import { createCapsuleId, createCaptureHandleId } from './identity-internal.js';
7
+ import { milliseconds, epochNow, expiresAt } from '../core/units.js';
1
8
  import { serializeDevtoolsError } from './observer.js';
2
9
  import { previewDevtoolsValue } from './values.js';
3
10
  import { match } from 'ts-pattern';
@@ -13,21 +20,18 @@ export class ReplayCapsuleStore {
13
20
  #references = new Map();
14
21
  constructor(runId, options = {}) {
15
22
  this.#runId = runId;
16
- this.#ttlMs = options.ttlMs ?? defaultCapsuleTtlMs;
17
- this.#maxCapsules = options.maxCapsules ?? defaultMaxCapsules;
23
+ this.#ttlMs = milliseconds(options.ttlMs ?? defaultCapsuleTtlMs);
24
+ this.#maxCapsules = capsuleLimit(options.maxCapsules ?? defaultMaxCapsules);
18
25
  this.#capture = options.capture;
19
26
  this.#redact = options.redact ?? [];
20
27
  if (!Number.isFinite(this.#ttlMs) || this.#ttlMs <= 0) {
21
28
  throw new TypeError('capsuleTtlMs must be a positive finite number.');
22
29
  }
23
- if (!Number.isInteger(this.#maxCapsules) || this.#maxCapsules < 1) {
24
- throw new TypeError('maxCapsules must be a positive integer.');
25
- }
26
30
  }
27
31
  createExecution(traceId, spanId, observation, registration) {
28
32
  this.#purgeExpired();
29
- const id = `capsule_${crypto.randomUUID()}`;
30
- const createdAt = Date.now();
33
+ const id = createCapsuleId();
34
+ const createdAt = epochNow();
31
35
  const captured = this.#captureValue(registration.input);
32
36
  const preview = {
33
37
  id,
@@ -36,7 +40,7 @@ export class ReplayCapsuleStore {
36
40
  traceId,
37
41
  spanId,
38
42
  createdAt,
39
- expiresAt: createdAt + this.#ttlMs,
43
+ expiresAt: expiresAt(createdAt, this.#ttlMs),
40
44
  execution: {
41
45
  executionId: observation.executionId,
42
46
  executionKind: observation.executionKind,
@@ -145,7 +149,7 @@ export class ReplayCapsuleStore {
145
149
  // cloneできない値を失うより、同一process内だけで有効な参照として保持する。
146
150
  }
147
151
  if (value !== undefined) {
148
- const handleId = `ref_${crypto.randomUUID()}`;
152
+ const handleId = createCaptureHandleId();
149
153
  this.#references.set(handleId, value);
150
154
  return { mode: 'reference', handleId };
151
155
  }
@@ -166,7 +170,7 @@ export class ReplayCapsuleStore {
166
170
  .exhaustive();
167
171
  }
168
172
  #purgeExpired() {
169
- const now = Date.now();
173
+ const now = epochNow();
170
174
  for (const [id, capsule] of this.#capsules) {
171
175
  if (capsule.preview.expiresAt > now)
172
176
  continue;
@@ -0,0 +1,15 @@
1
+ import type { RuntimeEvent } from './observer.js';
2
+ import type { ReplayCapsuleRequest, ProviderPlaygroundRequest, ProviderMethodInvocationRequest } from './inspector.js';
3
+ export declare const isDevtoolsRecord: (value: unknown) => value is Record<string, unknown>;
4
+ export declare function decodeRuntimeEvent(value: unknown): RuntimeEvent | undefined;
5
+ export type InspectorCommand = {
6
+ readonly type: 'command.replay-capsule';
7
+ readonly request: ReplayCapsuleRequest;
8
+ } | {
9
+ readonly type: 'command.provider-playground';
10
+ readonly request: ProviderPlaygroundRequest;
11
+ } | {
12
+ readonly type: 'command.invoke-provider-method';
13
+ readonly request: ProviderMethodInvocationRequest;
14
+ };
15
+ export declare function decodeInspectorCommand(value: unknown): InspectorCommand | undefined;