@loutrejs/loutre 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/bun.d.ts +2 -4
- package/dist/adapters/bun.js +36 -28
- package/dist/adapters/cloudflare-workers.d.ts +2 -4
- package/dist/adapters/cloudflare-workers.js +6 -11
- package/dist/adapters/server-transports.d.ts +4 -0
- package/dist/adapters/server-transports.js +18 -0
- package/dist/adapters/websocket-native.d.ts +24 -0
- package/dist/adapters/websocket-native.js +150 -0
- package/dist/application/kernel.d.ts +2 -1
- package/dist/core/extension.d.ts +26 -3
- package/dist/core/extension.js +19 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/instrumentation.d.ts +36 -0
- package/dist/core/instrumentation.js +1 -0
- package/dist/core/model/modules.js +13 -17
- package/dist/core/model.js +14 -4
- package/dist/core/module.d.ts +3 -3
- package/dist/devtools/capsule.d.ts +51 -0
- package/dist/devtools/capsule.js +247 -0
- package/dist/devtools/index.d.ts +11 -0
- package/dist/devtools/index.js +6 -0
- package/dist/devtools/inspector.d.ts +57 -0
- package/dist/devtools/inspector.js +1 -0
- package/dist/devtools/module.d.ts +18 -0
- package/dist/devtools/module.js +26 -0
- package/dist/devtools/observer.d.ts +77 -0
- package/dist/devtools/observer.js +10 -0
- package/dist/devtools/protocol.d.ts +1 -0
- package/dist/devtools/protocol.js +1 -0
- package/dist/devtools/provider-instrumentation.d.ts +14 -0
- package/dist/devtools/provider-instrumentation.js +117 -0
- package/dist/devtools/runtime.d.ts +20 -0
- package/dist/devtools/runtime.js +519 -0
- package/dist/devtools/values.d.ts +35 -0
- package/dist/devtools/values.js +172 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/graph/model.js +37 -40
- package/dist/http/auth.d.ts +2 -2
- package/dist/http/auth.js +9 -0
- package/dist/http/client.js +5 -5
- package/dist/http/extension.d.ts +37 -10
- package/dist/http/extension.js +249 -57
- package/dist/http/index.d.ts +3 -2
- package/dist/http/index.js +1 -1
- package/dist/http/openapi.d.ts +3 -4
- package/dist/http/openapi.js +45 -26
- package/dist/http/request-head.d.ts +6 -0
- package/dist/http/request-head.js +1 -0
- package/dist/http/route-validation.d.ts +9 -0
- package/dist/http/route-validation.js +18 -0
- package/dist/http/runtime/response.js +10 -10
- package/dist/message-port/extension.js +20 -4
- package/dist/runtime/di.d.ts +4 -0
- package/dist/runtime/di.js +42 -31
- package/dist/runtime/engine.d.ts +1 -1
- package/dist/runtime/engine.js +1 -2
- package/dist/runtime/kernel.d.ts +4 -2
- package/dist/runtime/kernel.js +96 -3
- package/dist/runtime/node-capabilities.d.ts +1 -1
- package/dist/runtime/node-capabilities.js +2 -1
- package/dist/tasks/extension.js +133 -118
- package/dist/websocket/driver.d.ts +30 -0
- package/dist/websocket/driver.js +96 -0
- package/dist/websocket/extension.d.ts +33 -12
- package/dist/websocket/extension.js +72 -47
- package/dist/websocket/index.d.ts +3 -1
- package/dist/websocket/index.js +1 -0
- package/package.json +7 -6
- package/dist/adapters/deno.d.ts +0 -35
- package/dist/adapters/deno.js +0 -231
package/dist/adapters/bun.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { type ApplicationDefinition, type BootstrapArguments, type KernelHostedApplication
|
|
1
|
+
import { type ApplicationDefinition, type BootstrapArguments, type KernelHostedApplication } from '../application/index.js';
|
|
2
2
|
import type { RuntimeCapabilityBinding } from '../core/index.js';
|
|
3
|
-
import { httpExecutionExtension } from '../http/index.js';
|
|
4
|
-
type HttpApplication<TDefinition extends ApplicationDefinition> = RequireApplicationExtension<TDefinition, typeof httpExecutionExtension>;
|
|
5
3
|
export type BunCreateOptions<TDefinition extends ApplicationDefinition> = {
|
|
6
|
-
readonly application:
|
|
4
|
+
readonly application: TDefinition;
|
|
7
5
|
readonly environment?: unknown;
|
|
8
6
|
readonly capabilities?: readonly RuntimeCapabilityBinding[];
|
|
9
7
|
readonly forceShutdownTimeoutMs?: number;
|
package/dist/adapters/bun.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createBunWebSocketDriver, } from './websocket-native.js';
|
|
2
|
+
import { serverTransportBindings, dispatchServerRequest, } from './server-transports.js';
|
|
1
3
|
import { createKernelApplication, } from '../application/index.js';
|
|
2
|
-
import { bindApplicationCapability } from '../application/kernel-internal.js';
|
|
3
|
-
import { httpExecutionExtension } from '../http/index.js';
|
|
4
4
|
import { LOUTRE_VERSION, detectPresentationTerminal, startStartupPresentation, } from '../presentation.js';
|
|
5
5
|
import { assertRuntimeEngine } from '../runtime/engine.js';
|
|
6
6
|
import { serverUrl } from '../runtime/server-url.js';
|
|
@@ -9,6 +9,7 @@ export const bunRuntime = {
|
|
|
9
9
|
runtime: 'bun',
|
|
10
10
|
capabilities: new Set([
|
|
11
11
|
'http.server',
|
|
12
|
+
'websocket.server',
|
|
12
13
|
'http.request.streaming',
|
|
13
14
|
'http.response.streaming',
|
|
14
15
|
'stream.readable',
|
|
@@ -29,25 +30,18 @@ async function create(options) {
|
|
|
29
30
|
terminal: detectPresentationTerminal(process.stdout, environment),
|
|
30
31
|
write: (value) => console.log(value),
|
|
31
32
|
});
|
|
32
|
-
|
|
33
|
-
undefined) {
|
|
34
|
-
throw new Error('LUTRE_RUNTIME_HTTP_REQUIRED: bunRuntime.create() requires the HTTP Execution Extension.');
|
|
35
|
-
}
|
|
33
|
+
const websocketDriver = createBunWebSocketDriver();
|
|
36
34
|
const hosted = createKernelApplication({
|
|
37
35
|
...options,
|
|
38
36
|
application: options.application,
|
|
39
37
|
capabilities: [
|
|
40
|
-
|
|
41
|
-
runtime: 'bun',
|
|
42
|
-
}),
|
|
38
|
+
...serverTransportBindings(options.application.model, 'bun', websocketDriver.driver),
|
|
43
39
|
...(options.capabilities ?? []),
|
|
44
40
|
],
|
|
45
41
|
environment: 'environment' in options ? options.environment : environment,
|
|
46
42
|
});
|
|
47
43
|
await hosted.init();
|
|
48
44
|
const application = hosted;
|
|
49
|
-
const http = hosted
|
|
50
|
-
.http;
|
|
51
45
|
const closeApplication = application.close.bind(application);
|
|
52
46
|
let server;
|
|
53
47
|
let removeShutdownHooks;
|
|
@@ -63,20 +57,41 @@ async function create(options) {
|
|
|
63
57
|
removeShutdownHooks?.();
|
|
64
58
|
removeShutdownHooks = undefined;
|
|
65
59
|
const errors = [];
|
|
66
|
-
|
|
60
|
+
try {
|
|
61
|
+
await closeApplication(signal);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
errors.push(error);
|
|
65
|
+
}
|
|
66
|
+
if (server && errors.length === 0) {
|
|
67
|
+
let timer;
|
|
67
68
|
try {
|
|
68
|
-
|
|
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
|
+
]);
|
|
69
87
|
server = undefined;
|
|
70
88
|
}
|
|
71
89
|
catch (error) {
|
|
72
90
|
errors.push(error);
|
|
73
91
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
errors.push(error);
|
|
92
|
+
finally {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
}
|
|
80
95
|
}
|
|
81
96
|
if (errors.length > 0) {
|
|
82
97
|
throw new AggregateError(errors, 'Bun runtime shutdown failed');
|
|
@@ -110,7 +125,8 @@ async function create(options) {
|
|
|
110
125
|
...(serveOptions.hostname === undefined
|
|
111
126
|
? {}
|
|
112
127
|
: { hostname: serveOptions.hostname }),
|
|
113
|
-
fetch:
|
|
128
|
+
fetch: (request, upgradeServer) => websocketDriver.fetch(hosted, request, upgradeServer, dispatchServerRequest),
|
|
129
|
+
websocket: websocketDriver.websocket,
|
|
114
130
|
});
|
|
115
131
|
break;
|
|
116
132
|
}
|
|
@@ -158,14 +174,6 @@ function registerBunShutdownHooks(close) {
|
|
|
158
174
|
}
|
|
159
175
|
return remove;
|
|
160
176
|
}
|
|
161
|
-
function createBunFetchDriver(application) {
|
|
162
|
-
let initialization;
|
|
163
|
-
return async (request) => {
|
|
164
|
-
initialization ??= application.initialize?.() ?? Promise.resolve();
|
|
165
|
-
await initialization;
|
|
166
|
-
return application.fetch(request);
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
177
|
function bunGlobal() {
|
|
170
178
|
return globalThis.Bun;
|
|
171
179
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { type ApplicationDefinition, type BootstrapArguments
|
|
1
|
+
import { type ApplicationDefinition, type BootstrapArguments } from '../application/index.js';
|
|
2
2
|
import type { RuntimeCapabilityBinding } from '../core/index.js';
|
|
3
|
-
import { httpExecutionExtension } from '../http/index.js';
|
|
4
|
-
type HttpApplication<TDefinition extends ApplicationDefinition> = RequireApplicationExtension<TDefinition, typeof httpExecutionExtension>;
|
|
5
3
|
export type CloudflareWorkersBindOptions<TDefinition extends ApplicationDefinition> = {
|
|
6
|
-
readonly application:
|
|
4
|
+
readonly application: TDefinition;
|
|
7
5
|
readonly capabilities?: readonly RuntimeCapabilityBinding[];
|
|
8
6
|
} & BootstrapArguments<TDefinition>;
|
|
9
7
|
export interface CloudflareWorkersBinding {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { createCloudflareWebSocketDriver } from './websocket-native.js';
|
|
2
|
+
import { serverTransportBindings, dispatchServerRequest, } from './server-transports.js';
|
|
1
3
|
import { createKernelApplication, } from '../application/index.js';
|
|
2
|
-
import { bindApplicationCapability } from '../application/kernel-internal.js';
|
|
3
|
-
import { httpExecutionExtension } from '../http/index.js';
|
|
4
4
|
import { assertRuntimeEngine } from '../runtime/engine.js';
|
|
5
5
|
export const cloudflareWorkersRuntime = {
|
|
6
6
|
runtime: 'cloudflare-workers',
|
|
7
7
|
compatibilityDateMinimum: '2026-08-04',
|
|
8
8
|
capabilities: new Set([
|
|
9
9
|
'http.server',
|
|
10
|
+
'websocket.server',
|
|
10
11
|
'http.request.streaming',
|
|
11
12
|
'http.response.streaming',
|
|
12
13
|
'stream.readable',
|
|
@@ -19,10 +20,6 @@ export const cloudflareWorkersRuntime = {
|
|
|
19
20
|
};
|
|
20
21
|
function bind(options) {
|
|
21
22
|
assertRuntimeEngine('cloudflare-workers');
|
|
22
|
-
if (options.application.model.extensions.get(httpExecutionExtension) ===
|
|
23
|
-
undefined) {
|
|
24
|
-
throw new Error('LUTRE_RUNTIME_HTTP_REQUIRED: cloudflareWorkersRuntime.bind() requires the HTTP Execution Extension.');
|
|
25
|
-
}
|
|
26
23
|
let application;
|
|
27
24
|
let initialization;
|
|
28
25
|
const resolve = async (environment) => {
|
|
@@ -30,20 +27,18 @@ function bind(options) {
|
|
|
30
27
|
...options,
|
|
31
28
|
application: options.application,
|
|
32
29
|
capabilities: [
|
|
33
|
-
|
|
34
|
-
runtime: 'cloudflare-workers',
|
|
35
|
-
}),
|
|
30
|
+
...serverTransportBindings(options.application.model, 'cloudflare-workers', createCloudflareWebSocketDriver()),
|
|
36
31
|
...(options.capabilities ?? []),
|
|
37
32
|
],
|
|
38
33
|
environment,
|
|
39
34
|
});
|
|
40
35
|
initialization ??= application.init();
|
|
41
36
|
await initialization;
|
|
42
|
-
return application
|
|
37
|
+
return application;
|
|
43
38
|
};
|
|
44
39
|
return {
|
|
45
40
|
async fetch(request, environment) {
|
|
46
|
-
return (await resolve(environment)
|
|
41
|
+
return dispatchServerRequest(await resolve(environment), request);
|
|
47
42
|
},
|
|
48
43
|
async close(signal) {
|
|
49
44
|
await application?.close(signal);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ApplicationModel, RuntimeCapabilityBinding } from '../core/index.js';
|
|
2
|
+
import { type WebSocketServerDriver } from '../websocket/index.js';
|
|
3
|
+
export declare function serverTransportBindings(model: ApplicationModel, runtime: string, websocket: WebSocketServerDriver): RuntimeCapabilityBinding[];
|
|
4
|
+
export declare function dispatchServerRequest(application: object, request: Request): Promise<Response>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { bindHttpServer } from '../http/index.js';
|
|
2
|
+
import { bindWebSocketServer, } from '../websocket/index.js';
|
|
3
|
+
export function serverTransportBindings(model, runtime, websocket) {
|
|
4
|
+
const requires = (id) => model.executions.some((execution) => execution.capabilities.some((capability) => capability.id === id));
|
|
5
|
+
return [
|
|
6
|
+
...(requires('http.server') ? [bindHttpServer({ runtime })] : []),
|
|
7
|
+
...(requires('websocket.server') ? [bindWebSocketServer(websocket)] : []),
|
|
8
|
+
];
|
|
9
|
+
}
|
|
10
|
+
export function dispatchServerRequest(application, request) {
|
|
11
|
+
const transports = application;
|
|
12
|
+
if (request.headers.get('upgrade')?.toLowerCase() === 'websocket') {
|
|
13
|
+
return (transports.websocket?.upgrade(request) ??
|
|
14
|
+
Promise.resolve(new Response('Not Found', { status: 404 })));
|
|
15
|
+
}
|
|
16
|
+
return (transports.http?.fetch(request) ??
|
|
17
|
+
Promise.resolve(new Response('Not Found', { status: 404 })));
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type WebSocketServerDriver, type WebSocketDriverChannel } from '../websocket/index.js';
|
|
2
|
+
export declare function createCloudflareWebSocketDriver(): WebSocketServerDriver;
|
|
3
|
+
interface BunSocket {
|
|
4
|
+
readonly data: WebSocketDriverChannel;
|
|
5
|
+
send(message: string | Uint8Array): number;
|
|
6
|
+
close(code?: number, reason?: string): void;
|
|
7
|
+
terminate(): void;
|
|
8
|
+
}
|
|
9
|
+
export interface BunUpgradeServer {
|
|
10
|
+
upgrade(request: Request, options: {
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
data: WebSocketDriverChannel;
|
|
13
|
+
}): boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function createBunWebSocketDriver(): {
|
|
16
|
+
driver: WebSocketServerDriver;
|
|
17
|
+
fetch(application: object, request: Request, server: BunUpgradeServer, dispatch: (application: object, request: Request) => Promise<Response>): Promise<Response | undefined>;
|
|
18
|
+
websocket: {
|
|
19
|
+
open(socket: BunSocket): void;
|
|
20
|
+
message(socket: BunSocket, message: string | Uint8Array): void;
|
|
21
|
+
close(socket: BunSocket, code: number, reason: string): void;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createEventWebSocketConnection, createWebSocketDriverChannel, } from '../websocket/index.js';
|
|
2
|
+
export function createCloudflareWebSocketDriver() {
|
|
3
|
+
return {
|
|
4
|
+
runtime: 'cloudflare-workers',
|
|
5
|
+
async upgrade(_request, { protocol }) {
|
|
6
|
+
const Pair = globalThis.WebSocketPair;
|
|
7
|
+
const pair = new Pair();
|
|
8
|
+
pair[1].accept();
|
|
9
|
+
// WorkersのI/Oはopening requestへ所属するため、別requestからのdrainをそのcontextへ戻す。
|
|
10
|
+
const io = createOwnedWebSocketIo();
|
|
11
|
+
const connection = createEventWebSocketConnection(pair[1], protocol ?? '', { run: io.run });
|
|
12
|
+
void connection.closed.then(io.stop);
|
|
13
|
+
const response = new Response(null, {
|
|
14
|
+
status: 101,
|
|
15
|
+
webSocket: pair[0],
|
|
16
|
+
headers: protocol ? { 'sec-websocket-protocol': protocol } : {},
|
|
17
|
+
});
|
|
18
|
+
return { response, connection };
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function createBunWebSocketDriver() {
|
|
23
|
+
const requests = new WeakMap();
|
|
24
|
+
const upgraded = new WeakSet();
|
|
25
|
+
const driver = {
|
|
26
|
+
runtime: 'bun',
|
|
27
|
+
async upgrade(request, { protocol }) {
|
|
28
|
+
const native = requests.get(request);
|
|
29
|
+
const server = native?.server;
|
|
30
|
+
if (!server)
|
|
31
|
+
throw new Error('Bunのupgrade serverが見つかりません。');
|
|
32
|
+
let socket;
|
|
33
|
+
let open;
|
|
34
|
+
const opened = new Promise((resolve) => {
|
|
35
|
+
open = resolve;
|
|
36
|
+
});
|
|
37
|
+
const channel = createWebSocketDriverChannel({
|
|
38
|
+
protocol: protocol ?? '',
|
|
39
|
+
async send(message) {
|
|
40
|
+
await opened;
|
|
41
|
+
socket.send(message.data);
|
|
42
|
+
},
|
|
43
|
+
async close(code = 1000, reason = '') {
|
|
44
|
+
await opened;
|
|
45
|
+
socket.close(code, reason);
|
|
46
|
+
},
|
|
47
|
+
terminate() {
|
|
48
|
+
socket?.terminate();
|
|
49
|
+
channel.finish({ code: 1006, reason: '', wasClean: false });
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
pending.set(channel, (value) => {
|
|
53
|
+
socket = value;
|
|
54
|
+
open();
|
|
55
|
+
});
|
|
56
|
+
if (!server.upgrade(request, {
|
|
57
|
+
headers: protocol ? { 'sec-websocket-protocol': protocol } : {},
|
|
58
|
+
data: channel,
|
|
59
|
+
})) {
|
|
60
|
+
pending.delete(channel);
|
|
61
|
+
throw new Error('Bun WebSocket upgradeに失敗しました。');
|
|
62
|
+
}
|
|
63
|
+
upgraded.add(request);
|
|
64
|
+
native.accepted();
|
|
65
|
+
return { response: new Response(), connection: channel.connection };
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const pending = new WeakMap();
|
|
69
|
+
return {
|
|
70
|
+
driver,
|
|
71
|
+
async fetch(application, request, server, dispatch) {
|
|
72
|
+
let accept;
|
|
73
|
+
const accepted = new Promise((resolve) => {
|
|
74
|
+
accept = () => resolve(undefined);
|
|
75
|
+
});
|
|
76
|
+
requests.set(request, { server, accepted: accept });
|
|
77
|
+
try {
|
|
78
|
+
const dispatched = dispatch(application, request).then((response) => upgraded.has(request) ? undefined : response);
|
|
79
|
+
return await Promise.race([dispatched, accepted]);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
requests.delete(request);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
websocket: {
|
|
86
|
+
open(socket) {
|
|
87
|
+
pending.get(socket.data)?.(socket);
|
|
88
|
+
pending.delete(socket.data);
|
|
89
|
+
},
|
|
90
|
+
message(socket, message) {
|
|
91
|
+
socket.data.receive(typeof message === 'string'
|
|
92
|
+
? { type: 'text', data: message }
|
|
93
|
+
: { type: 'binary', data: new Uint8Array(message) });
|
|
94
|
+
},
|
|
95
|
+
close(socket, code, reason) {
|
|
96
|
+
socket.data.finish({ code, reason, wasClean: code !== 1006 });
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function createOwnedWebSocketIo() {
|
|
102
|
+
const queue = [];
|
|
103
|
+
let active = true;
|
|
104
|
+
let wake;
|
|
105
|
+
const stopped = new Error('WebSocketは終了しました。');
|
|
106
|
+
const pump = async () => {
|
|
107
|
+
while (true) {
|
|
108
|
+
if (!active)
|
|
109
|
+
return;
|
|
110
|
+
const task = queue.shift();
|
|
111
|
+
if (task)
|
|
112
|
+
task.run();
|
|
113
|
+
else
|
|
114
|
+
await new Promise((resolve) => {
|
|
115
|
+
wake = resolve;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
void pump();
|
|
120
|
+
return {
|
|
121
|
+
run(operation) {
|
|
122
|
+
if (!active)
|
|
123
|
+
return Promise.reject(stopped);
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
queue.push({
|
|
126
|
+
run() {
|
|
127
|
+
try {
|
|
128
|
+
resolve(operation());
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
reject(error);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
cancel() {
|
|
135
|
+
reject(stopped);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
wake?.();
|
|
139
|
+
wake = undefined;
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
stop() {
|
|
143
|
+
active = false;
|
|
144
|
+
for (const task of queue.splice(0))
|
|
145
|
+
task.cancel();
|
|
146
|
+
wake?.();
|
|
147
|
+
wake = undefined;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference lib="esnext.disposable" preserve="true" />
|
|
2
|
-
import type { RuntimeCapabilityBinding, TokenLike, TokenValue } from '../core/index.js';
|
|
2
|
+
import type { RuntimeCapabilityBinding, RuntimeInstrumentation, TokenLike, TokenValue } from '../core/index.js';
|
|
3
3
|
import { type ApplicationModelGraphIR } from '../graph/index.js';
|
|
4
4
|
import { type Logger } from '../runtime/index.js';
|
|
5
5
|
import type { ApplicationDefinition, ApplicationExtensionHostApis, BootstrapArguments } from './definition.js';
|
|
@@ -16,6 +16,7 @@ export type KernelApplicationOptions<TDefinition extends ApplicationDefinition>
|
|
|
16
16
|
readonly environment?: unknown;
|
|
17
17
|
readonly logger?: Logger;
|
|
18
18
|
readonly forceShutdownTimeoutMs?: number;
|
|
19
|
+
readonly instrumentation?: RuntimeInstrumentation;
|
|
19
20
|
} & BootstrapArguments<TDefinition>;
|
|
20
21
|
export declare function createKernelApplication<const TDefinition extends ApplicationDefinition>(options: KernelApplicationOptions<TDefinition>): KernelHostedApplication<TDefinition>;
|
|
21
22
|
export declare function bootstrapApplication<const TDefinition extends ApplicationDefinition>(options: KernelApplicationOptions<TDefinition>): Promise<KernelHostedApplication<TDefinition>>;
|
package/dist/core/extension.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Diagnostic } from './diagnostic.js';
|
|
2
|
+
import type { RuntimeExecutionMetadata, RuntimeInvocationRegistration, RuntimeOperationMetadata } from './instrumentation.js';
|
|
2
3
|
import type { TokenLike } from './token.js';
|
|
3
4
|
declare const runtimeCapabilityValue: unique symbol;
|
|
4
5
|
declare const executionExtensionTypeInfo: unique symbol;
|
|
@@ -27,11 +28,21 @@ export declare class RuntimeCapabilityRegistry implements RuntimeCapabilityBindi
|
|
|
27
28
|
}
|
|
28
29
|
export interface ExecutionLease {
|
|
29
30
|
readonly signal: AbortSignal;
|
|
31
|
+
run?<T>(operation: () => T): T;
|
|
30
32
|
abort(reason?: unknown): void;
|
|
31
|
-
|
|
33
|
+
fail?(reason: unknown): void;
|
|
34
|
+
annotate?(attributes: Readonly<Record<string, unknown>>): void;
|
|
35
|
+
complete(...result: [] | [unknown]): void;
|
|
36
|
+
}
|
|
37
|
+
export interface ExecutionOperationLease {
|
|
38
|
+
run?<T>(operation: () => T): T;
|
|
39
|
+
fail?(reason: unknown): void;
|
|
40
|
+
annotate?(attributes: Readonly<Record<string, unknown>>): void;
|
|
41
|
+
complete(...result: [] | [unknown]): void;
|
|
32
42
|
}
|
|
33
43
|
export interface ExecutionKernelRuntime {
|
|
34
|
-
beginExecution(): ExecutionLease;
|
|
44
|
+
beginExecution(metadata?: RuntimeExecutionMetadata, invocation?: RuntimeInvocationRegistration): ExecutionLease;
|
|
45
|
+
beginOperation?(metadata: RuntimeOperationMetadata, invocation?: RuntimeInvocationRegistration): ExecutionOperationLease;
|
|
35
46
|
resolve<TValue>(token: TokenLike<TValue>, source?: string): TValue;
|
|
36
47
|
}
|
|
37
48
|
export interface ExecutionExtensionDrainContext {
|
|
@@ -93,11 +104,23 @@ export interface ExecutionDefinition<TExtension extends AnyExecutionExtension =
|
|
|
93
104
|
readonly extension: TExtension;
|
|
94
105
|
readonly [executionDefinitionBrand]: true;
|
|
95
106
|
}
|
|
107
|
+
export interface ExecutionGroup<TExecutions extends readonly ExecutionDeclaration[] = readonly ExecutionDeclaration[]> {
|
|
108
|
+
readonly kind: 'execution-group';
|
|
109
|
+
readonly name?: string;
|
|
110
|
+
readonly executions: TExecutions;
|
|
111
|
+
}
|
|
112
|
+
export type ExecutionDeclaration = ExecutionDefinition | ExecutionGroup;
|
|
113
|
+
export declare function flattenExecutions(declarations: readonly unknown[]): unknown[];
|
|
114
|
+
export interface ExecutionContextView {
|
|
115
|
+
readonly signal: AbortSignal;
|
|
116
|
+
annotate(attributes: Readonly<Record<string, unknown>>): void;
|
|
117
|
+
beginOperation(metadata: RuntimeOperationMetadata): ExecutionOperationLease;
|
|
118
|
+
}
|
|
96
119
|
export declare function defineExecution<const TExtension extends ExecutionExtension, const TDefinition extends object>(extension: TExtension, definition: TDefinition): Readonly<TDefinition & ExecutionDefinition<TExtension>>;
|
|
97
120
|
export declare function isExecutionDefinition(value: unknown): value is ExecutionDefinition;
|
|
98
121
|
export type ExtensionOfDefinition<TDefinition> = TDefinition extends {
|
|
99
122
|
readonly extension: infer TExtension extends AnyExecutionExtension;
|
|
100
|
-
} ? TExtension : never;
|
|
123
|
+
} ? TExtension : TDefinition extends ExecutionGroup<infer TExecutions> ? ExecutionGroup extends TDefinition ? AnyExecutionExtension : ExtensionOfDefinition<TExecutions[number]> : never;
|
|
101
124
|
export type AnyExecutionExtension = ExecutionExtension<any, any, any, any, any>;
|
|
102
125
|
export type HostApiOfExtension<TExtension> = TExtension extends {
|
|
103
126
|
readonly host?: infer THost;
|
package/dist/core/extension.js
CHANGED
|
@@ -56,6 +56,25 @@ export function defineExecutionExtension(extension) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
export const executionDefinitionBrand = Symbol.for('loutre.execution-definition');
|
|
59
|
+
export function flattenExecutions(declarations) {
|
|
60
|
+
const active = new Set();
|
|
61
|
+
const visit = (values) => values.flatMap((value) => {
|
|
62
|
+
if (typeof value !== 'object' ||
|
|
63
|
+
value === null ||
|
|
64
|
+
!('kind' in value) ||
|
|
65
|
+
value.kind !== 'execution-group')
|
|
66
|
+
return [value];
|
|
67
|
+
if (active.has(value))
|
|
68
|
+
throw new TypeError('ExecutionGroupに循環参照があります。');
|
|
69
|
+
if (!('executions' in value) || !Array.isArray(value.executions))
|
|
70
|
+
throw new TypeError('ExecutionGroup.executionsは配列である必要があります。');
|
|
71
|
+
active.add(value);
|
|
72
|
+
const executions = visit(value.executions);
|
|
73
|
+
active.delete(value);
|
|
74
|
+
return executions;
|
|
75
|
+
});
|
|
76
|
+
return visit(declarations);
|
|
77
|
+
}
|
|
59
78
|
export function defineExecution(extension, definition) {
|
|
60
79
|
return Object.freeze({
|
|
61
80
|
...definition,
|
package/dist/core/index.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ 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, ExecutionExtension, ExecutionExtensionDrainContext, ExecutionExtensionRuntime, ExecutionExtensionRuntimeContext, ExecutionExtensionValidationContext, ExecutionKernelRuntime, ExecutionLease, ExecutionProjectionContext, ExtensionOfDefinition, HostApiOfExtension, HostExtension, HostExtensionContext, RuntimeCapability, RuntimeCapabilityBinding, RuntimeCapabilityBindings, RuntimeCapabilityValue, } 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';
|
|
11
|
+
export type { RuntimeExecutionMetadata, RuntimeInstrumentation, RuntimeInstrumentationScope, RuntimeInvocationRegistration, RuntimeProviderMetadata, RuntimeOperationMetadata, } from './instrumentation.js';
|
|
11
12
|
export { composeLayers, defineLayer } from './generic-layer.js';
|
|
12
13
|
export type { GenericLayer, GenericLayerContext, GenericLayerNext, } from './generic-layer.js';
|
|
13
14
|
export { collectInjectedDependencies, inject, InjectionContextError, runInInjectionContext, } from './injection.js';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Scope } from './provider.js';
|
|
2
|
+
export interface RuntimeInvocationRegistration {
|
|
3
|
+
readonly input: unknown;
|
|
4
|
+
invoke(input: unknown): unknown | Promise<unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface RuntimeExecutionMetadata {
|
|
7
|
+
readonly executionId: string;
|
|
8
|
+
readonly executionKind: string;
|
|
9
|
+
readonly name?: string;
|
|
10
|
+
readonly graphNodeId?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RuntimeOperationMetadata {
|
|
13
|
+
readonly kind: string;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly graphNodeId?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface RuntimeProviderMetadata {
|
|
18
|
+
readonly providerId: string;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly graphNodeId: string;
|
|
21
|
+
readonly scope: Scope;
|
|
22
|
+
readonly providerKind: 'value' | 'class' | 'factory' | 'conditional' | 'environment' | 'arguments';
|
|
23
|
+
}
|
|
24
|
+
export interface RuntimeInstrumentationScope {
|
|
25
|
+
run?<T>(operation: () => T): T;
|
|
26
|
+
abort?(reason?: unknown): void;
|
|
27
|
+
fail?(reason: unknown): void;
|
|
28
|
+
annotate?(attributes: Readonly<Record<string, unknown>>): void;
|
|
29
|
+
complete(...result: [] | [unknown]): void;
|
|
30
|
+
}
|
|
31
|
+
export interface RuntimeInstrumentation {
|
|
32
|
+
beginExecution?(metadata: RuntimeExecutionMetadata, invocation?: RuntimeInvocationRegistration): RuntimeInstrumentationScope | undefined;
|
|
33
|
+
beginOperation?(metadata: RuntimeOperationMetadata, invocation?: RuntimeInvocationRegistration): RuntimeInstrumentationScope | undefined;
|
|
34
|
+
providerCreated?(metadata: RuntimeProviderMetadata, value: unknown): void;
|
|
35
|
+
close?(): void | Promise<void>;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -6,6 +6,7 @@ import { tokenName } from '../token.js';
|
|
|
6
6
|
import { appendLifecycleNodes } from './lifecycle.js';
|
|
7
7
|
import { snapshotProvider } from './snapshot.js';
|
|
8
8
|
import { getSourceLocation } from '../source-location.js';
|
|
9
|
+
import { match } from 'ts-pattern';
|
|
9
10
|
export function collectModuleDeclarations(context) {
|
|
10
11
|
const { modules, moduleIds, nodes, edges, diagnostics, providers, providerNodes, } = context;
|
|
11
12
|
const moduleNames = new Set();
|
|
@@ -79,23 +80,18 @@ export function collectModuleDeclarations(context) {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
function collectProviderDependencies(provider) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
for (const dependency of collectInjectedDependencies(provider.provide, () => Reflect.construct(implementation, []))) {
|
|
91
|
-
dependencies.add(dependency);
|
|
92
|
-
}
|
|
83
|
+
return match(provider)
|
|
84
|
+
.with({ kind: 'class' }, (candidate) => Object.freeze(collectInjectedDependencies(candidate.provide, () => Reflect.construct(candidate.useClass, []))))
|
|
85
|
+
.with({ kind: 'factory' }, (candidate) => Object.freeze([...candidate.inject]))
|
|
86
|
+
.with({ kind: 'conditional' }, (candidate) => {
|
|
87
|
+
const dependencies = new Set();
|
|
88
|
+
for (const implementation of Object.values(candidate.mapping)) {
|
|
89
|
+
for (const dependency of collectInjectedDependencies(candidate.provide, () => Reflect.construct(implementation, []))) {
|
|
90
|
+
dependencies.add(dependency);
|
|
93
91
|
}
|
|
94
|
-
return Object.freeze([...dependencies]);
|
|
95
92
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
93
|
+
return Object.freeze([...dependencies]);
|
|
94
|
+
})
|
|
95
|
+
.with({ kind: 'value' }, { kind: 'environment' }, { kind: 'arguments' }, () => Object.freeze([]))
|
|
96
|
+
.exhaustive();
|
|
101
97
|
}
|
package/dist/core/model.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { diagnostic, isErrorDiagnostic } from './diagnostic.js';
|
|
2
|
-
import { isExecutionDefinition, } from './extension.js';
|
|
2
|
+
import { isExecutionDefinition, flattenExecutions, } from './extension.js';
|
|
3
3
|
import { isFrameworkProvidedToken } from './token-internal.js';
|
|
4
4
|
import { tokenName } from './token.js';
|
|
5
5
|
import { createModelBuildContext } from './model/context.js';
|
|
@@ -84,9 +84,19 @@ export function buildApplicationModel(options) {
|
|
|
84
84
|
return result;
|
|
85
85
|
}
|
|
86
86
|
};
|
|
87
|
+
const moduleExecutions = new Map();
|
|
88
|
+
for (const module of modules) {
|
|
89
|
+
try {
|
|
90
|
+
moduleExecutions.set(module, flattenExecutions(module.definition.executions ?? []));
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
diagnostics.push(diagnostic('LUTRE_EXECUTION_GROUP_INVALID', String(error), moduleIds.get(module)));
|
|
94
|
+
moduleExecutions.set(module, []);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
87
97
|
const rootDefinitionOwners = new Map();
|
|
88
98
|
for (const module of modules) {
|
|
89
|
-
for (const value of module
|
|
99
|
+
for (const value of moduleExecutions.get(module) ?? []) {
|
|
90
100
|
if (!isExecutionDefinition(value))
|
|
91
101
|
continue;
|
|
92
102
|
const owners = rootDefinitionOwners.get(value) ?? new Set();
|
|
@@ -96,7 +106,7 @@ export function buildApplicationModel(options) {
|
|
|
96
106
|
}
|
|
97
107
|
const reachableDefinitionModules = new Map();
|
|
98
108
|
for (const module of modules) {
|
|
99
|
-
const queue = (module
|
|
109
|
+
const queue = (moduleExecutions.get(module) ?? []).filter(isExecutionDefinition);
|
|
100
110
|
const seen = new Set();
|
|
101
111
|
let queueIndex = 0;
|
|
102
112
|
while (queueIndex < queue.length) {
|
|
@@ -147,7 +157,7 @@ export function buildApplicationModel(options) {
|
|
|
147
157
|
}
|
|
148
158
|
for (const module of modules) {
|
|
149
159
|
const moduleId = moduleIds.get(module);
|
|
150
|
-
const rootDefinitions = module
|
|
160
|
+
const rootDefinitions = moduleExecutions.get(module) ?? [];
|
|
151
161
|
const rootDefinitionIndexes = new Map();
|
|
152
162
|
for (const [definitionIndex, value] of rootDefinitions.entries()) {
|
|
153
163
|
if (isExecutionDefinition(value) && !rootDefinitionIndexes.has(value)) {
|