@loutrejs/loutre 0.7.0 → 0.8.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/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/core/extension.d.ts +13 -1
- package/dist/core/extension.js +19 -0
- package/dist/core/index.d.ts +1 -1
- 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.js +13 -11
- package/dist/devtools/values.js +10 -13
- 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 +117 -39
- 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/runtime/di.js +40 -39
- package/dist/runtime/engine.d.ts +1 -1
- package/dist/runtime/engine.js +1 -2
- package/dist/runtime/node-capabilities.d.ts +1 -1
- package/dist/runtime/node-capabilities.js +2 -1
- package/dist/tasks/extension.js +103 -114
- 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 +60 -44
- package/dist/websocket/index.d.ts +3 -1
- package/dist/websocket/index.js +1 -0
- package/package.json +3 -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
|
+
}
|
package/dist/core/extension.d.ts
CHANGED
|
@@ -104,11 +104,23 @@ export interface ExecutionDefinition<TExtension extends AnyExecutionExtension =
|
|
|
104
104
|
readonly extension: TExtension;
|
|
105
105
|
readonly [executionDefinitionBrand]: true;
|
|
106
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
|
+
}
|
|
107
119
|
export declare function defineExecution<const TExtension extends ExecutionExtension, const TDefinition extends object>(extension: TExtension, definition: TDefinition): Readonly<TDefinition & ExecutionDefinition<TExtension>>;
|
|
108
120
|
export declare function isExecutionDefinition(value: unknown): value is ExecutionDefinition;
|
|
109
121
|
export type ExtensionOfDefinition<TDefinition> = TDefinition extends {
|
|
110
122
|
readonly extension: infer TExtension extends AnyExecutionExtension;
|
|
111
|
-
} ? TExtension : never;
|
|
123
|
+
} ? TExtension : TDefinition extends ExecutionGroup<infer TExecutions> ? ExecutionGroup extends TDefinition ? AnyExecutionExtension : ExtensionOfDefinition<TExecutions[number]> : never;
|
|
112
124
|
export type AnyExecutionExtension = ExecutionExtension<any, any, any, any, any>;
|
|
113
125
|
export type HostApiOfExtension<TExtension> = TExtension extends {
|
|
114
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,7 @@ 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, ExecutionOperationLease, 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
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';
|
|
@@ -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)) {
|
package/dist/core/module.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EnvClass } from './env.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ExecutionDeclaration, ExtensionOfDefinition, HostApiOfExtension } from './extension.js';
|
|
3
3
|
import type { ModuleLifecycle } from './lifecycle.js';
|
|
4
4
|
import { type ProviderDeclaration } from './provider.js';
|
|
5
5
|
export interface ModuleDefinition {
|
|
@@ -8,7 +8,7 @@ export interface ModuleDefinition {
|
|
|
8
8
|
readonly imports?: readonly ModuleInstance[];
|
|
9
9
|
readonly environment?: readonly EnvClass[];
|
|
10
10
|
readonly providers?: readonly ProviderDeclaration[];
|
|
11
|
-
readonly executions?: readonly
|
|
11
|
+
readonly executions?: readonly ExecutionDeclaration[];
|
|
12
12
|
readonly exports?: readonly unknown[];
|
|
13
13
|
readonly lifecycle?: ModuleLifecycle;
|
|
14
14
|
}
|
|
@@ -16,7 +16,7 @@ export declare const moduleTypeInfo: unique symbol;
|
|
|
16
16
|
export interface ModuleTypeInfo<TDefinition extends ModuleDefinition = ModuleDefinition> {
|
|
17
17
|
readonly extensions: ExtensionsOfModuleDefinition<TDefinition>;
|
|
18
18
|
}
|
|
19
|
-
type DirectExtensions<TDefinition extends ModuleDefinition> = TDefinition['executions'] extends readonly
|
|
19
|
+
type DirectExtensions<TDefinition extends ModuleDefinition> = TDefinition['executions'] extends readonly ExecutionDeclaration[] ? ExtensionOfDefinition<TDefinition['executions'][number]> : never;
|
|
20
20
|
type ImportedExtensions<TDefinition extends ModuleDefinition> = TDefinition['imports'] extends readonly ModuleInstance[] ? ModuleExtensions<TDefinition['imports'][number]> : never;
|
|
21
21
|
export type ExtensionsOfModuleDefinition<TDefinition extends ModuleDefinition> = DirectExtensions<TDefinition> | ImportedExtensions<TDefinition>;
|
|
22
22
|
export type ModuleExtensions<TModule> = TModule extends ModuleInstance<infer TDefinition> ? ExtensionsOfModuleDefinition<TDefinition> : never;
|
package/dist/devtools/capsule.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { serializeDevtoolsError } from './observer.js';
|
|
2
2
|
import { previewDevtoolsValue } from './values.js';
|
|
3
|
+
import { match } from 'ts-pattern';
|
|
3
4
|
const defaultCapsuleTtlMs = 5 * 60_000;
|
|
4
5
|
const defaultMaxCapsules = 1_000;
|
|
5
6
|
export class ReplayCapsuleStore {
|
|
@@ -151,17 +152,18 @@ export class ReplayCapsuleStore {
|
|
|
151
152
|
return { mode: 'snapshot', value: undefined };
|
|
152
153
|
}
|
|
153
154
|
#restore(captured) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
155
|
+
return match(captured)
|
|
156
|
+
.with({ mode: 'snapshot' }, (value) => structuredClone(value.value))
|
|
157
|
+
.with({ mode: 'reference' }, (value) => {
|
|
158
|
+
if (this.#references.has(value.handleId)) {
|
|
159
|
+
return this.#references.get(value.handleId);
|
|
160
|
+
}
|
|
161
|
+
throw new Error(`LUTRE_DEVTOOLS_REFERENCE_UNAVAILABLE: ${value.handleId}`);
|
|
162
|
+
})
|
|
163
|
+
.with({ mode: 'unavailable' }, (value) => {
|
|
164
|
+
throw new Error(`LUTRE_DEVTOOLS_CAPTURE_UNAVAILABLE: ${value.reason}`);
|
|
165
|
+
})
|
|
166
|
+
.exhaustive();
|
|
165
167
|
}
|
|
166
168
|
#purgeExpired() {
|
|
167
169
|
const now = Date.now();
|
package/dist/devtools/values.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { match } from 'ts-pattern';
|
|
1
2
|
const defaultMaxValueBytes = 64 * 1024;
|
|
2
3
|
const defaultMaxDepth = 8;
|
|
3
4
|
export function previewDevtoolsValue(value, options = {}) {
|
|
@@ -139,19 +140,15 @@ function shouldRedact(key, path, redactions) {
|
|
|
139
140
|
return redactions.has(normalizedKey) || redactions.has(normalizedPath);
|
|
140
141
|
}
|
|
141
142
|
function valueType(value) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return 'map';
|
|
152
|
-
if (value instanceof Set)
|
|
153
|
-
return 'set';
|
|
154
|
-
return typeof value === 'object' ? 'object' : typeof value;
|
|
143
|
+
return match(value)
|
|
144
|
+
.with(null, () => 'null')
|
|
145
|
+
.when(Array.isArray, () => 'array')
|
|
146
|
+
.when((candidate) => candidate instanceof Date, () => 'date')
|
|
147
|
+
.when((candidate) => candidate instanceof Uint8Array, () => 'bytes')
|
|
148
|
+
.when((candidate) => candidate instanceof Map, () => 'map')
|
|
149
|
+
.when((candidate) => candidate instanceof Set, () => 'set')
|
|
150
|
+
.when((candidate) => typeof candidate === 'object', () => 'object')
|
|
151
|
+
.otherwise((candidate) => typeof candidate);
|
|
155
152
|
}
|
|
156
153
|
function safeValueType(value) {
|
|
157
154
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LOUTRE_VERSION = "0.
|
|
1
|
+
export declare const LOUTRE_VERSION = "0.8.1";
|