@loutrejs/loutre 0.8.2 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/adapters/bun.d.ts +1 -0
- package/dist/adapters/bun.js +40 -75
- package/dist/application/definition.d.ts +4 -4
- package/dist/application/kernel.js +1 -1
- package/dist/core/extension.d.ts +7 -3
- package/dist/core/generic-layer.d.ts +2 -2
- package/dist/core/generic-layer.js +1 -1
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +1 -1
- package/dist/core/injection.d.ts +11 -1
- package/dist/core/injection.js +7 -3
- package/dist/core/model/context.d.ts +2 -1
- package/dist/core/model/context.js +23 -12
- package/dist/core/model/lifecycle.js +1 -1
- package/dist/core/model/modules.js +6 -6
- package/dist/core/model/providers.js +2 -2
- package/dist/core/model/visibility.d.ts +3 -2
- package/dist/core/model/visibility.js +3 -5
- package/dist/core/model.js +1 -1
- package/dist/core/module.d.ts +6 -4
- package/dist/core/module.js +6 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/http/extension.js +7 -6
- package/dist/message-port/extension.js +3 -3
- package/dist/runtime/di.d.ts +4 -2
- package/dist/runtime/di.js +33 -14
- package/dist/runtime/engine.d.ts +1 -0
- package/dist/runtime/engine.js +15 -1
- package/dist/runtime/index.d.ts +5 -1
- package/dist/runtime/index.js +3 -1
- package/dist/runtime/kernel.d.ts +2 -2
- package/dist/runtime/kernel.js +47 -9
- package/dist/runtime/shutdown-hooks.d.ts +16 -0
- package/dist/runtime/shutdown-hooks.js +69 -0
- package/dist/runtime/shutdown.d.ts +20 -0
- package/dist/runtime/shutdown.js +71 -0
- package/dist/tasks/extension.js +11 -10
- package/dist/websocket/extension.js +5 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -43,11 +43,11 @@ const HealthHttp = http.implementation({
|
|
|
43
43
|
}),
|
|
44
44
|
})
|
|
45
45
|
|
|
46
|
-
const AppModule = defineModule(
|
|
46
|
+
const AppModule = defineModule({
|
|
47
47
|
executions: [HealthHttp],
|
|
48
|
-
})
|
|
48
|
+
})
|
|
49
49
|
|
|
50
|
-
export default defineApplication({ modules: [AppModule
|
|
50
|
+
export default defineApplication({ modules: [AppModule] })
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
## Kernel bootstrap
|
package/dist/adapters/bun.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type BunCreateOptions<TDefinition extends ApplicationDefinition> = {
|
|
|
4
4
|
readonly application: TDefinition;
|
|
5
5
|
readonly environment?: unknown;
|
|
6
6
|
readonly capabilities?: readonly RuntimeCapabilityBinding[];
|
|
7
|
+
readonly shutdownHooks?: boolean;
|
|
7
8
|
readonly forceShutdownTimeoutMs?: number;
|
|
8
9
|
} & BootstrapArguments<TDefinition>;
|
|
9
10
|
export interface BunServeOptions {
|
package/dist/adapters/bun.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { RuntimeShutdown } from '../runtime/shutdown.js';
|
|
2
|
+
import { ProcessShutdownHooks } from '../runtime/shutdown-hooks.js';
|
|
3
|
+
import { Logger } from '../runtime/logger.js';
|
|
1
4
|
import { createBunWebSocketDriver, } from './websocket-native.js';
|
|
2
5
|
import { serverTransportBindings, dispatchServerRequest, } from './server-transports.js';
|
|
3
6
|
import { createKernelApplication, } from '../application/index.js';
|
|
@@ -44,67 +47,41 @@ async function create(options) {
|
|
|
44
47
|
const application = hosted;
|
|
45
48
|
const closeApplication = application.close.bind(application);
|
|
46
49
|
let server;
|
|
47
|
-
|
|
50
|
+
const logger = options.application.logger ?? new Logger();
|
|
48
51
|
let serving = false;
|
|
49
|
-
let
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
let serverClosingPromise;
|
|
53
|
+
const beginServerClose = () => {
|
|
54
|
+
if (serverClosingPromise)
|
|
55
|
+
return serverClosingPromise;
|
|
56
|
+
if (!server)
|
|
53
57
|
return Promise.resolve();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const errors = [];
|
|
60
|
-
try {
|
|
61
|
-
await closeApplication(signal);
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
errors.push(error);
|
|
65
|
-
}
|
|
66
|
-
if (server && errors.length === 0) {
|
|
67
|
-
let timer;
|
|
68
|
-
try {
|
|
69
|
-
// Bun 1.3のserver側WebSocket close後はstopが未完了になるため、Kernel drain後も無期限には待たない。
|
|
70
|
-
// https://github.com/oven-sh/bun/issues/36223
|
|
71
|
-
await Promise.race([
|
|
72
|
-
Promise.resolve(server.stop(false)),
|
|
73
|
-
new Promise((resolve, reject) => {
|
|
74
|
-
timer = setTimeout(() => {
|
|
75
|
-
try {
|
|
76
|
-
const forced = server.stop(true);
|
|
77
|
-
server.unref?.();
|
|
78
|
-
void Promise.resolve(forced).then(resolve, reject);
|
|
79
|
-
resolve();
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
reject(error);
|
|
83
|
-
}
|
|
84
|
-
}, options.forceShutdownTimeoutMs ?? 5_000);
|
|
85
|
-
}),
|
|
86
|
-
]);
|
|
87
|
-
server = undefined;
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
errors.push(error);
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
clearTimeout(timer);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (errors.length > 0) {
|
|
97
|
-
throw new AggregateError(errors, 'Bun runtime shutdown failed');
|
|
98
|
-
}
|
|
99
|
-
closed = true;
|
|
58
|
+
const closingServer = server;
|
|
59
|
+
serverClosingPromise = (async () => {
|
|
60
|
+
await closingServer.stop(false);
|
|
61
|
+
if (server === closingServer)
|
|
62
|
+
server = undefined;
|
|
100
63
|
})().catch((error) => {
|
|
101
|
-
|
|
64
|
+
serverClosingPromise = undefined;
|
|
102
65
|
throw error;
|
|
103
66
|
});
|
|
104
|
-
|
|
67
|
+
void serverClosingPromise.catch(() => undefined);
|
|
68
|
+
return serverClosingPromise;
|
|
105
69
|
};
|
|
70
|
+
const shutdown = new RuntimeShutdown({
|
|
71
|
+
message: 'Bun runtime shutdown failed',
|
|
72
|
+
beginClose: () => {
|
|
73
|
+
shutdownHooks.dispose();
|
|
74
|
+
return beginServerClose();
|
|
75
|
+
},
|
|
76
|
+
closeApplication,
|
|
77
|
+
closeResources: async (safeCleanupBoundaryReached, serverClosing) => {
|
|
78
|
+
if (safeCleanupBoundaryReached)
|
|
79
|
+
await serverClosing;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const close = shutdown.close.bind(shutdown);
|
|
106
83
|
const serve = async (serveOptions = {}) => {
|
|
107
|
-
if (
|
|
84
|
+
if (shutdown.started) {
|
|
108
85
|
throw new Error('LUTRE_APP_STOPPED: Application is stopped.');
|
|
109
86
|
}
|
|
110
87
|
if (serving) {
|
|
@@ -115,6 +92,9 @@ async function create(options) {
|
|
|
115
92
|
throw new Error('LUTRE_BUN_UNAVAILABLE: Bun.serve() is not available.');
|
|
116
93
|
}
|
|
117
94
|
serving = true;
|
|
95
|
+
if (serveOptions.shutdownHooks !== undefined) {
|
|
96
|
+
shutdownHooks.setEnabled(serveOptions.shutdownHooks);
|
|
97
|
+
}
|
|
118
98
|
try {
|
|
119
99
|
const requestedPort = serveOptions.port;
|
|
120
100
|
let port = initialServerPort(requestedPort);
|
|
@@ -144,10 +124,6 @@ async function create(options) {
|
|
|
144
124
|
environment: environment.NODE_ENV ?? 'development',
|
|
145
125
|
startupDurationMs: performance.now() - startedAt,
|
|
146
126
|
});
|
|
147
|
-
removeShutdownHooks =
|
|
148
|
-
serveOptions.shutdownHooks === false
|
|
149
|
-
? undefined
|
|
150
|
-
: registerBunShutdownHooks(close);
|
|
151
127
|
return { port };
|
|
152
128
|
}
|
|
153
129
|
catch (error) {
|
|
@@ -155,26 +131,15 @@ async function create(options) {
|
|
|
155
131
|
throw error;
|
|
156
132
|
}
|
|
157
133
|
};
|
|
134
|
+
const shutdownHooks = new ProcessShutdownHooks({
|
|
135
|
+
source: process,
|
|
136
|
+
close,
|
|
137
|
+
onError: (error, signal) => logger.error('Bun runtime shutdown failed', { error, signal }),
|
|
138
|
+
});
|
|
158
139
|
Object.assign(application, { serve, close });
|
|
140
|
+
shutdownHooks.setEnabled(options.shutdownHooks !== false);
|
|
159
141
|
return application;
|
|
160
142
|
}
|
|
161
|
-
function registerBunShutdownHooks(close) {
|
|
162
|
-
const handlers = new Map();
|
|
163
|
-
const remove = () => {
|
|
164
|
-
for (const [signal, handler] of handlers)
|
|
165
|
-
process.off(signal, handler);
|
|
166
|
-
handlers.clear();
|
|
167
|
-
};
|
|
168
|
-
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
169
|
-
const handler = () => {
|
|
170
|
-
remove();
|
|
171
|
-
void close(signal);
|
|
172
|
-
};
|
|
173
|
-
handlers.set(signal, handler);
|
|
174
|
-
process.once(signal, handler);
|
|
175
|
-
}
|
|
176
|
-
return remove;
|
|
177
|
-
}
|
|
178
143
|
function bunGlobal() {
|
|
179
144
|
return globalThis.Bun;
|
|
180
145
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/// <reference lib="esnext.disposable" preserve="true" />
|
|
2
|
-
import type { ArgsClass, ApplicationModel, ExecutionExtension, HostApiOfExtension, ModuleExtensions,
|
|
2
|
+
import type { ArgsClass, ApplicationModel, ExecutionExtension, HostApiOfExtension, ModuleExtensions, ModuleInput, SchemaInput } from '../core/index.js';
|
|
3
3
|
import type { Logger } from '../runtime/index.js';
|
|
4
|
-
export interface ApplicationDefinitionOptions<TModules extends readonly
|
|
4
|
+
export interface ApplicationDefinitionOptions<TModules extends readonly ModuleInput[], TArguments extends ArgsClass | undefined> {
|
|
5
5
|
readonly modules: TModules;
|
|
6
6
|
readonly arguments?: TArguments;
|
|
7
7
|
readonly logger?: Logger;
|
|
8
8
|
}
|
|
9
|
-
export interface ApplicationDefinition<TModules extends readonly
|
|
9
|
+
export interface ApplicationDefinition<TModules extends readonly ModuleInput[] = readonly ModuleInput[], TArguments extends ArgsClass | undefined = ArgsClass | undefined> {
|
|
10
10
|
readonly kind: 'application-definition';
|
|
11
11
|
readonly model: ApplicationModel;
|
|
12
12
|
readonly modules: TModules;
|
|
13
13
|
readonly arguments: TArguments;
|
|
14
14
|
readonly logger?: Logger;
|
|
15
15
|
}
|
|
16
|
-
export declare function defineApplication<const TModules extends readonly
|
|
16
|
+
export declare function defineApplication<const TModules extends readonly ModuleInput[], const TArguments extends ArgsClass | undefined = undefined>(options: ApplicationDefinitionOptions<TModules, TArguments>): ApplicationDefinition<TModules, TArguments>;
|
|
17
17
|
export type ApplicationArgumentsInput<TDefinition extends ApplicationDefinition> = TDefinition['arguments'] extends ArgsClass<infer TSchema> ? SchemaInput<TSchema> : never;
|
|
18
18
|
export type BootstrapArguments<TDefinition extends ApplicationDefinition> = TDefinition['arguments'] extends ArgsClass<infer TSchema> ? {} extends SchemaInput<TSchema> ? {
|
|
19
19
|
readonly arguments?: SchemaInput<TSchema>;
|
|
@@ -25,7 +25,7 @@ export function createKernelApplication(options) {
|
|
|
25
25
|
if (!host)
|
|
26
26
|
continue;
|
|
27
27
|
application[host.namespace] = host.create({
|
|
28
|
-
executions: modelExtension.
|
|
28
|
+
executions: runtime.executionBindings(modelExtension.extension),
|
|
29
29
|
runtime: runtime.extensionRuntime(modelExtension.extension),
|
|
30
30
|
applicationRuntime: runtime,
|
|
31
31
|
});
|
package/dist/core/extension.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DependencyResolver } from './injection.js';
|
|
1
2
|
import type { Diagnostic } from './diagnostic.js';
|
|
2
3
|
import type { RuntimeExecutionMetadata, RuntimeInvocationRegistration, RuntimeOperationMetadata } from './instrumentation.js';
|
|
3
4
|
import type { TokenLike } from './token.js';
|
|
@@ -43,7 +44,6 @@ export interface ExecutionOperationLease {
|
|
|
43
44
|
export interface ExecutionKernelRuntime {
|
|
44
45
|
beginExecution(metadata?: RuntimeExecutionMetadata, invocation?: RuntimeInvocationRegistration): ExecutionLease;
|
|
45
46
|
beginOperation?(metadata: RuntimeOperationMetadata, invocation?: RuntimeInvocationRegistration): ExecutionOperationLease;
|
|
46
|
-
resolve<TValue>(token: TokenLike<TValue>, source?: string): TValue;
|
|
47
47
|
}
|
|
48
48
|
export interface ExecutionExtensionDrainContext {
|
|
49
49
|
readonly timeoutMs: number;
|
|
@@ -64,6 +64,10 @@ export interface ExecutionContribution<TCompiled = unknown> {
|
|
|
64
64
|
readonly capabilities: readonly RuntimeCapability[];
|
|
65
65
|
readonly compiled: TCompiled;
|
|
66
66
|
}
|
|
67
|
+
export interface ExecutionRuntimeBinding<TCompiled = unknown> {
|
|
68
|
+
readonly execution: ExecutionContribution<TCompiled>;
|
|
69
|
+
readonly resolve: DependencyResolver;
|
|
70
|
+
}
|
|
67
71
|
export interface ExecutionExtensionValidationContext<TCompiled = unknown> {
|
|
68
72
|
readonly executions: readonly ExecutionContribution<TCompiled>[];
|
|
69
73
|
}
|
|
@@ -71,12 +75,12 @@ export interface ExecutionProjectionContext<TCompiled = unknown> {
|
|
|
71
75
|
readonly execution: ExecutionContribution<TCompiled>;
|
|
72
76
|
}
|
|
73
77
|
export interface ExecutionExtensionRuntimeContext<TCompiled = unknown> {
|
|
74
|
-
readonly executions: readonly
|
|
78
|
+
readonly executions: readonly ExecutionRuntimeBinding<TCompiled>[];
|
|
75
79
|
readonly capabilities: RuntimeCapabilityBindings;
|
|
76
80
|
readonly applicationRuntime: ExecutionKernelRuntime;
|
|
77
81
|
}
|
|
78
82
|
export interface HostExtensionContext<TCompiled = unknown, TRuntime extends ExecutionExtensionRuntime = ExecutionExtensionRuntime> {
|
|
79
|
-
readonly executions: readonly
|
|
83
|
+
readonly executions: readonly ExecutionRuntimeBinding<TCompiled>[];
|
|
80
84
|
readonly runtime: TRuntime;
|
|
81
85
|
readonly applicationRuntime: ExecutionKernelRuntime;
|
|
82
86
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RuntimeCapability } from './extension.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type DependencyResolver } from './injection.js';
|
|
3
3
|
import type { Type } from './type.js';
|
|
4
4
|
export interface GenericLayerContext<TState extends object = {}> {
|
|
5
5
|
readonly state: Readonly<TState>;
|
|
@@ -21,6 +21,6 @@ export declare function defineLayer<TContribution extends object = {}, TContext
|
|
|
21
21
|
export declare function composeLayers<TContext extends object, TOutcome>(options: {
|
|
22
22
|
readonly context: TContext;
|
|
23
23
|
readonly layers: readonly GenericLayer<any, any, TOutcome>[];
|
|
24
|
-
readonly resolve:
|
|
24
|
+
readonly resolve: DependencyResolver;
|
|
25
25
|
readonly terminal: (context: TContext & GenericLayerContext<Record<string, unknown>>) => Promise<TOutcome>;
|
|
26
26
|
}): Promise<TOutcome>;
|
|
@@ -15,7 +15,7 @@ export function composeLayers(options) {
|
|
|
15
15
|
id: `layer:${index}:${layer.name}`,
|
|
16
16
|
name: layer.name,
|
|
17
17
|
},
|
|
18
|
-
resolve: (token) => options.resolve(token, layer.name),
|
|
18
|
+
resolve: (token, consumer) => options.resolve(token, { source: layer.name, consumer }),
|
|
19
19
|
}, () => layer.factory()));
|
|
20
20
|
const dispatch = async (index, state) => {
|
|
21
21
|
const context = Object.assign(Object.create(options.context), {
|
package/dist/core/index.d.ts
CHANGED
|
@@ -7,18 +7,18 @@ export type { EnvClass, EnvKey, EnvSchema } from './env.js';
|
|
|
7
7
|
export { defineError, DomainError } from './error.js';
|
|
8
8
|
export type { ErrorDefinition } from './error.js';
|
|
9
9
|
export { bindRuntimeCapability, defineExecution, defineExecutionExtension, executionDefinitionBrand, isExecutionDefinition, runtimeCapability, RuntimeCapabilityRegistry, } from './extension.js';
|
|
10
|
-
export type { AnyExecutionExtension, ExecutionCompileContext, ExecutionContribution, ExecutionDefinition, ExecutionDeclaration, ExecutionGroup, ExecutionContextView, ExecutionExtension, ExecutionExtensionDrainContext, ExecutionExtensionRuntime, ExecutionExtensionRuntimeContext, ExecutionExtensionValidationContext, ExecutionKernelRuntime, ExecutionLease, ExecutionProjectionContext, ExecutionOperationLease, ExtensionOfDefinition, HostApiOfExtension, HostExtension, HostExtensionContext, RuntimeCapability, RuntimeCapabilityBinding, RuntimeCapabilityBindings, RuntimeCapabilityValue, } from './extension.js';
|
|
10
|
+
export type { AnyExecutionExtension, ExecutionCompileContext, ExecutionContribution, ExecutionRuntimeBinding, ExecutionDefinition, ExecutionDeclaration, ExecutionGroup, ExecutionContextView, ExecutionExtension, ExecutionExtensionDrainContext, ExecutionExtensionRuntime, ExecutionExtensionRuntimeContext, ExecutionExtensionValidationContext, ExecutionKernelRuntime, ExecutionLease, ExecutionProjectionContext, ExecutionOperationLease, ExtensionOfDefinition, HostApiOfExtension, HostExtension, HostExtensionContext, RuntimeCapability, RuntimeCapabilityBinding, RuntimeCapabilityBindings, RuntimeCapabilityValue, } from './extension.js';
|
|
11
11
|
export type { RuntimeExecutionMetadata, RuntimeInstrumentation, RuntimeInstrumentationScope, RuntimeInvocationRegistration, RuntimeProviderMetadata, RuntimeOperationMetadata, } from './instrumentation.js';
|
|
12
12
|
export { composeLayers, defineLayer } from './generic-layer.js';
|
|
13
13
|
export type { GenericLayer, GenericLayerContext, GenericLayerNext, } from './generic-layer.js';
|
|
14
|
-
export { collectInjectedDependencies, inject, InjectionContextError, runInInjectionContext, } from './injection.js';
|
|
15
|
-
export type { DependencyConsumer, DependencyConsumerDescriptor, InjectionContext, } from './injection.js';
|
|
14
|
+
export { collectInjectedDependencies, inject, injectInquirer, InjectionContextError, runInInjectionContext, } from './injection.js';
|
|
15
|
+
export type { DependencyConsumer, DependencyConsumerDescriptor, DependencyResolutionContext, DependencyResolver, InjectionContext, } from './injection.js';
|
|
16
16
|
export { hook } from './lifecycle.js';
|
|
17
17
|
export type { BeforeApplicationShutdown, LifecycleHook, ModuleLifecycle, OnApplicationBootstrap, OnApplicationShutdown, OnModuleDestroy, OnModuleInit, } from './lifecycle.js';
|
|
18
18
|
export { ApplicationModelError, assertValidApplicationModel, buildApplicationModel, } from './model.js';
|
|
19
19
|
export type { ApplicationModel, ApplicationModelBuildOptions, ApplicationModelEdge, ApplicationModelExtension, ApplicationModelExtensions, ApplicationModelNode, CompiledOf, ExecutionModelNode, FrameworkModelNode, LifecycleModelNode, ModuleModelNode, ProviderModelNode, } from './model.js';
|
|
20
20
|
export { asModuleInstance, defineModule, moduleTypeInfo } from './module.js';
|
|
21
|
-
export type { AnyModuleTemplate, ExtensionsOfModuleDefinition, ModuleDefinition, ModuleExtensions, ModuleHostApis, ModuleInstance, ModuleTemplate, ModuleTypeInfo, } from './module.js';
|
|
21
|
+
export type { AnyModuleTemplate, ExtensionsOfModuleDefinition, ModuleDefinition, ModuleExtensions, ModuleHostApis, ModuleInstance, ModuleInput, ModuleTemplate, ModuleTypeInfo, } from './module.js';
|
|
22
22
|
export { argumentsProvider, environmentProvider, normalizeProvider, provide, } from './provider.js';
|
|
23
23
|
export type { ArgumentsProvider, ClassProvider, ConditionalProvider, EnvironmentProvider, FactoryDefinition, FactoryProvider, ProviderDeclaration, ProviderDescriptor, ProviderScopeOptions, Scope, ValueProvider, } from './provider.js';
|
|
24
24
|
export type { RuntimeInputContract, RuntimeInputKey } from './runtime-input.js';
|
package/dist/core/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { defineEnv, isEnvClass, loadEnv } from './env.js';
|
|
|
4
4
|
export { defineError, DomainError } from './error.js';
|
|
5
5
|
export { bindRuntimeCapability, defineExecution, defineExecutionExtension, executionDefinitionBrand, isExecutionDefinition, runtimeCapability, RuntimeCapabilityRegistry, } from './extension.js';
|
|
6
6
|
export { composeLayers, defineLayer } from './generic-layer.js';
|
|
7
|
-
export { collectInjectedDependencies, inject, InjectionContextError, runInInjectionContext, } from './injection.js';
|
|
7
|
+
export { collectInjectedDependencies, inject, injectInquirer, InjectionContextError, runInInjectionContext, } from './injection.js';
|
|
8
8
|
export { hook } from './lifecycle.js';
|
|
9
9
|
export { ApplicationModelError, assertValidApplicationModel, buildApplicationModel, } from './model.js';
|
|
10
10
|
export { asModuleInstance, defineModule, moduleTypeInfo } from './module.js';
|
package/dist/core/injection.d.ts
CHANGED
|
@@ -5,9 +5,17 @@ export interface DependencyConsumerDescriptor {
|
|
|
5
5
|
readonly kind?: string;
|
|
6
6
|
}
|
|
7
7
|
export type DependencyConsumer = TokenLike | DependencyConsumerDescriptor;
|
|
8
|
+
export interface DependencyResolutionContext {
|
|
9
|
+
readonly source?: string;
|
|
10
|
+
readonly consumer?: DependencyConsumer;
|
|
11
|
+
}
|
|
12
|
+
export interface DependencyResolver {
|
|
13
|
+
<TValue>(token: TokenLike<TValue>, context?: string | DependencyResolutionContext): TValue;
|
|
14
|
+
}
|
|
8
15
|
export interface InjectionContext {
|
|
9
16
|
readonly consumer: DependencyConsumer;
|
|
10
|
-
readonly
|
|
17
|
+
readonly inquirer?: DependencyConsumer;
|
|
18
|
+
readonly resolve?: <T>(token: TokenLike<T>, consumer: DependencyConsumer) => T;
|
|
11
19
|
readonly record?: (consumer: DependencyConsumer, dependency: TokenLike) => void;
|
|
12
20
|
}
|
|
13
21
|
export declare function runInInjectionContext<T>(context: InjectionContext, run: () => T): T;
|
|
@@ -16,4 +24,6 @@ export declare class InjectionContextError extends Error {
|
|
|
16
24
|
constructor(token: TokenLike);
|
|
17
25
|
}
|
|
18
26
|
export declare function inject<TToken extends TokenLike>(token: TToken): TokenValue<TToken>;
|
|
27
|
+
/** Modelに現れない依存は解決できないため、依存宣言の分岐ではなく構築metadataの取得に使う。 */
|
|
28
|
+
export declare function injectInquirer(): DependencyConsumer | undefined;
|
|
19
29
|
export declare function collectInjectedDependencies(consumer: DependencyConsumer, construct: () => unknown): readonly TokenLike[];
|
package/dist/core/injection.js
CHANGED
|
@@ -22,16 +22,20 @@ export function runInInjectionContext(context, run) {
|
|
|
22
22
|
export class InjectionContextError extends Error {
|
|
23
23
|
code = 'LUTRE_DI_CONTEXT';
|
|
24
24
|
constructor(token) {
|
|
25
|
-
super(`inject(${typeof token === 'function' ? token.name : token.id})
|
|
25
|
+
super(`inject(${typeof token === 'function' ? token.name : token.id}) requires a Loutre injection context with a dependency resolver.`);
|
|
26
26
|
this.name = 'InjectionContextError';
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
export function inject(token) {
|
|
30
30
|
const context = currentInjectionContext();
|
|
31
|
-
if (!context)
|
|
31
|
+
if (!context?.resolve)
|
|
32
32
|
throw new InjectionContextError(token);
|
|
33
33
|
context.record?.(context.consumer, token);
|
|
34
|
-
return context.resolve(token);
|
|
34
|
+
return context.resolve(token, context.consumer);
|
|
35
|
+
}
|
|
36
|
+
/** Modelに現れない依存は解決できないため、依存宣言の分岐ではなく構築metadataの取得に使う。 */
|
|
37
|
+
export function injectInquirer() {
|
|
38
|
+
return currentInjectionContext()?.inquirer;
|
|
35
39
|
}
|
|
36
40
|
function dependencyProbeTarget() { }
|
|
37
41
|
class DependencyProbeBoundary extends Error {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { Diagnostic } from '../diagnostic.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type ModuleInstance } from '../module.js';
|
|
3
3
|
import type { ProviderDescriptor } from '../provider.js';
|
|
4
4
|
import type { TokenLike } from '../token.js';
|
|
5
5
|
import type { ApplicationModelBuildOptions, ApplicationModelEdge, ApplicationModelNode, ExecutionModelNode, ProviderModelNode } from './types.js';
|
|
6
6
|
export interface ModelBuildContext {
|
|
7
7
|
readonly options: ApplicationModelBuildOptions;
|
|
8
8
|
readonly modules: readonly ModuleInstance[];
|
|
9
|
+
readonly imports: ReadonlyMap<ModuleInstance, readonly ModuleInstance[]>;
|
|
9
10
|
readonly moduleIds: ReadonlyMap<ModuleInstance, string>;
|
|
10
11
|
readonly nodes: ApplicationModelNode[];
|
|
11
12
|
readonly edges: ApplicationModelEdge[];
|
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
import { asModuleInstance } from '../module.js';
|
|
1
|
+
import { asModuleInstance, } from '../module.js';
|
|
2
2
|
function collectModules(roots) {
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
const modules = [];
|
|
4
|
+
const imports = new Map();
|
|
5
|
+
const instances = new Map();
|
|
6
|
+
const normalize = (input) => {
|
|
7
|
+
const existing = instances.get(input);
|
|
8
|
+
if (existing)
|
|
9
|
+
return existing;
|
|
10
|
+
const instance = asModuleInstance(input);
|
|
11
|
+
instances.set(input, instance);
|
|
12
|
+
return instance;
|
|
13
|
+
};
|
|
14
|
+
const visit = (input) => {
|
|
15
|
+
const module = normalize(input);
|
|
16
|
+
if (imports.has(module))
|
|
8
17
|
return;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
const imported = (module.definition.imports ?? []).map(normalize);
|
|
19
|
+
imports.set(module, imported);
|
|
20
|
+
for (const child of imported)
|
|
21
|
+
visit(child);
|
|
22
|
+
modules.push(module);
|
|
13
23
|
};
|
|
14
24
|
for (const root of roots)
|
|
15
25
|
visit(root);
|
|
16
|
-
return
|
|
26
|
+
return { modules, imports };
|
|
17
27
|
}
|
|
18
28
|
export function createModelBuildContext(options) {
|
|
19
|
-
const modules = collectModules(options.modules);
|
|
29
|
+
const { modules, imports } = collectModules(options.modules);
|
|
20
30
|
const moduleIds = new Map(modules.map((module, index) => [module, `module:${index + 1}`]));
|
|
21
31
|
let providerOrdinal = 0;
|
|
22
32
|
return {
|
|
23
33
|
options,
|
|
24
34
|
modules,
|
|
35
|
+
imports,
|
|
25
36
|
moduleIds,
|
|
26
37
|
nodes: [],
|
|
27
38
|
edges: [],
|
|
@@ -32,7 +32,7 @@ export function resolveLifecycleDependencies(context) {
|
|
|
32
32
|
providerModule &&
|
|
33
33
|
provider.moduleId !== lifecycle.moduleId &&
|
|
34
34
|
!moduleDeclaresToken(sourceModule, dependency) &&
|
|
35
|
-
!isTokenVisible(sourceModule, providerModule, dependency)) {
|
|
35
|
+
!isTokenVisible(context, sourceModule, providerModule, dependency)) {
|
|
36
36
|
diagnostics.push(diagnostic('LUTRE_MODULE_VISIBILITY', `Lifecycle ${lifecycle.phase} depends on private ${tokenName(dependency)} from another Module.`, lifecycle.id));
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { collectInjectedDependencies } from '../injection.js';
|
|
2
2
|
import { diagnostic } from '../diagnostic.js';
|
|
3
|
-
import { asModuleInstance } from '../module.js';
|
|
4
3
|
import { normalizeProvider } from '../provider.js';
|
|
5
4
|
import { tokenName } from '../token.js';
|
|
6
5
|
import { appendLifecycleNodes } from './lifecycle.js';
|
|
@@ -18,7 +17,7 @@ export function collectModuleDeclarations(context) {
|
|
|
18
17
|
}
|
|
19
18
|
moduleNames.add(module.definition.name);
|
|
20
19
|
}
|
|
21
|
-
const moduleSource = getSourceLocation(module.template);
|
|
20
|
+
const moduleSource = getSourceLocation(module) ?? getSourceLocation(module.template);
|
|
22
21
|
nodes.push({
|
|
23
22
|
kind: 'module',
|
|
24
23
|
id: moduleId,
|
|
@@ -30,8 +29,8 @@ export function collectModuleDeclarations(context) {
|
|
|
30
29
|
? {}
|
|
31
30
|
: { description: module.definition.description }),
|
|
32
31
|
});
|
|
33
|
-
for (const imported of
|
|
34
|
-
const target = moduleIds.get(
|
|
32
|
+
for (const imported of context.imports.get(module) ?? []) {
|
|
33
|
+
const target = moduleIds.get(imported);
|
|
35
34
|
if (target)
|
|
36
35
|
edges.push({ from: moduleId, to: target, kind: 'imports' });
|
|
37
36
|
}
|
|
@@ -84,8 +83,9 @@ function collectProviderDependencies(provider) {
|
|
|
84
83
|
.with({ kind: 'class' }, (candidate) => Object.freeze(collectInjectedDependencies(candidate.provide, () => Reflect.construct(candidate.useClass, []))))
|
|
85
84
|
.with({ kind: 'factory' }, (candidate) => Object.freeze([...candidate.inject]))
|
|
86
85
|
.with({ kind: 'conditional' }, (candidate) => {
|
|
87
|
-
const dependencies = new Set();
|
|
88
|
-
for (const
|
|
86
|
+
const dependencies = new Set([candidate.select.contract]);
|
|
87
|
+
for (const key of Reflect.ownKeys(candidate.mapping)) {
|
|
88
|
+
const implementation = candidate.mapping[key];
|
|
89
89
|
for (const dependency of collectInjectedDependencies(candidate.provide, () => Reflect.construct(implementation, []))) {
|
|
90
90
|
dependencies.add(dependency);
|
|
91
91
|
}
|
|
@@ -17,7 +17,7 @@ export function resolveModuleExports(context) {
|
|
|
17
17
|
: undefined;
|
|
18
18
|
if (provider &&
|
|
19
19
|
providerModule &&
|
|
20
|
-
isTokenVisible(module, providerModule, exported)) {
|
|
20
|
+
isTokenVisible(context, module, providerModule, exported)) {
|
|
21
21
|
edges.push({ from: moduleId, to: provider.id, kind: 'exports' });
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
@@ -62,7 +62,7 @@ export function resolveProviderDependencies(context) {
|
|
|
62
62
|
if (targetModule &&
|
|
63
63
|
target.moduleId !== provider.moduleId &&
|
|
64
64
|
!moduleDeclaresToken(sourceModule, dependency) &&
|
|
65
|
-
!isTokenVisible(sourceModule, targetModule, dependency)) {
|
|
65
|
+
!isTokenVisible(context, sourceModule, targetModule, dependency)) {
|
|
66
66
|
diagnostics.push(diagnostic('LUTRE_MODULE_VISIBILITY', `Provider ${tokenName(provider.token)} depends on private ${tokenName(dependency)} from another Module.`, provider.id));
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ModuleInstance } from '../module.js';
|
|
2
|
+
import type { ModelBuildContext } from './context.js';
|
|
2
3
|
import type { TokenLike } from '../token.js';
|
|
3
4
|
export declare function moduleDeclaresToken(module: ModuleInstance, token: TokenLike): boolean;
|
|
4
|
-
export declare function isTokenVisible(source: ModuleInstance, target: ModuleInstance, token: TokenLike, visited?: Set<ModuleInstance<import("../module.js").ModuleDefinition>>): boolean;
|
|
5
|
+
export declare function isTokenVisible(context: ModelBuildContext, source: ModuleInstance, target: ModuleInstance, token: TokenLike, visited?: Set<ModuleInstance<import("../module.js").ModuleDefinition>>): boolean;
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import { asModuleInstance } from '../module.js';
|
|
2
1
|
import { normalizeProvider } from '../provider.js';
|
|
3
2
|
export function moduleDeclaresToken(module, token) {
|
|
4
3
|
return (module.definition.providers ?? []).some((declaration) => normalizeProvider(declaration).provide === token);
|
|
5
4
|
}
|
|
6
|
-
export function isTokenVisible(source, target, token, visited = new Set()) {
|
|
5
|
+
export function isTokenVisible(context, source, target, token, visited = new Set()) {
|
|
7
6
|
if (visited.has(source))
|
|
8
7
|
return false;
|
|
9
8
|
visited.add(source);
|
|
10
|
-
for (const
|
|
11
|
-
const imported = asModuleInstance(importedValue);
|
|
9
|
+
for (const imported of context.imports.get(source) ?? []) {
|
|
12
10
|
if (!imported.definition.exports?.includes(token))
|
|
13
11
|
continue;
|
|
14
12
|
if (imported === target)
|
|
15
13
|
return true;
|
|
16
|
-
if (isTokenVisible(imported, target, token, new Set(visited)))
|
|
14
|
+
if (isTokenVisible(context, imported, target, token, new Set(visited)))
|
|
17
15
|
return true;
|
|
18
16
|
}
|
|
19
17
|
return false;
|
package/dist/core/model.js
CHANGED
|
@@ -247,7 +247,7 @@ export function buildApplicationModel(options) {
|
|
|
247
247
|
if (providerModule &&
|
|
248
248
|
provider.moduleId !== moduleId &&
|
|
249
249
|
!moduleDeclaresToken(module, dependency) &&
|
|
250
|
-
!isTokenVisible(module, providerModule, dependency)) {
|
|
250
|
+
!isTokenVisible(context, module, providerModule, dependency)) {
|
|
251
251
|
diagnostics.push(diagnostic('LUTRE_MODULE_VISIBILITY', `Execution ${execution.id} depends on private ${tokenName(dependency)} from another Module.`, execution.id));
|
|
252
252
|
}
|
|
253
253
|
}
|
package/dist/core/module.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { type ProviderDeclaration } from './provider.js';
|
|
|
5
5
|
export interface ModuleDefinition {
|
|
6
6
|
readonly name?: string;
|
|
7
7
|
readonly description?: string;
|
|
8
|
-
readonly imports?: readonly
|
|
8
|
+
readonly imports?: readonly ModuleInput[];
|
|
9
9
|
readonly environment?: readonly EnvClass[];
|
|
10
10
|
readonly providers?: readonly ProviderDeclaration[];
|
|
11
11
|
readonly executions?: readonly ExecutionDeclaration[];
|
|
@@ -17,9 +17,9 @@ export interface ModuleTypeInfo<TDefinition extends ModuleDefinition = ModuleDef
|
|
|
17
17
|
readonly extensions: ExtensionsOfModuleDefinition<TDefinition>;
|
|
18
18
|
}
|
|
19
19
|
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
|
|
20
|
+
type ImportedExtensions<TDefinition extends ModuleDefinition> = TDefinition['imports'] extends readonly ModuleInput[] ? ModuleExtensions<TDefinition['imports'][number]> : never;
|
|
21
21
|
export type ExtensionsOfModuleDefinition<TDefinition extends ModuleDefinition> = DirectExtensions<TDefinition> | ImportedExtensions<TDefinition>;
|
|
22
|
-
export type ModuleExtensions<TModule> = TModule extends ModuleInstance<infer TDefinition> ? ExtensionsOfModuleDefinition<TDefinition> : never;
|
|
22
|
+
export type ModuleExtensions<TModule> = TModule extends ModuleInstance<infer TDefinition> ? ExtensionsOfModuleDefinition<TDefinition> : TModule extends ModuleTemplate<void, infer TDefinition> ? ExtensionsOfModuleDefinition<TDefinition> : never;
|
|
23
23
|
export type ModuleHostApis<TModule> = HostApiOfExtension<ModuleExtensions<TModule>>;
|
|
24
24
|
export interface ModuleInstance<TDefinition extends ModuleDefinition = ModuleDefinition> {
|
|
25
25
|
readonly kind: 'module-instance';
|
|
@@ -43,6 +43,8 @@ export type ModuleTemplate<Args, TDefinition extends ModuleDefinition = ModuleDe
|
|
|
43
43
|
readonly instantiate: (args: Args) => ModuleInstance<TDefinition>;
|
|
44
44
|
readonly [moduleTypeInfo]?: ModuleTypeInfo<TDefinition>;
|
|
45
45
|
};
|
|
46
|
+
export type ModuleInput = ModuleInstance | ModuleTemplate<void>;
|
|
47
|
+
export declare function defineModule<const TDefinition extends ModuleDefinition>(definition: TDefinition): ModuleInstance<TDefinition>;
|
|
46
48
|
export declare function defineModule<Args = void, const TDefinition extends ModuleDefinition = ModuleDefinition>(factory: (args: Args) => TDefinition): ModuleTemplate<Args, TDefinition>;
|
|
47
|
-
export declare function asModuleInstance(module:
|
|
49
|
+
export declare function asModuleInstance(module: ModuleInput): ModuleInstance;
|
|
48
50
|
export {};
|
package/dist/core/module.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { environmentProvider } from './provider.js';
|
|
2
2
|
export const moduleTypeInfo = Symbol('loutre.module-type-info');
|
|
3
|
-
export function defineModule(
|
|
3
|
+
export function defineModule(definition) {
|
|
4
|
+
const factory = typeof definition === 'function' ? definition : () => definition;
|
|
5
|
+
const template = createModuleTemplate(factory);
|
|
6
|
+
return typeof definition === 'function' ? template : template();
|
|
7
|
+
}
|
|
8
|
+
function createModuleTemplate(factory) {
|
|
4
9
|
const instantiate = (args) => {
|
|
5
10
|
const declared = factory(args);
|
|
6
11
|
const environment = [...new Set(declared.environment ?? [])];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LOUTRE_VERSION = "0.
|
|
1
|
+
export declare const LOUTRE_VERSION = "0.9.1";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// package manifestとのずれを避けるため、scripts/generate-version.mjsがbuild前に更新する。
|
|
2
|
-
export const LOUTRE_VERSION = '0.
|
|
2
|
+
export const LOUTRE_VERSION = '0.9.1';
|