@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
|
@@ -5,7 +5,7 @@ import { compareHttpPathSpecificity, HttpPathDecodeError, matchHttpPath, normali
|
|
|
5
5
|
export const WEBSOCKET_SERVER = runtimeCapability('websocket.server');
|
|
6
6
|
export const websocketExtension = defineExecutionExtension({
|
|
7
7
|
kind: 'execution-extension',
|
|
8
|
-
abiVersion: '
|
|
8
|
+
abiVersion: '2',
|
|
9
9
|
name: 'loutre:websocket',
|
|
10
10
|
compile(definition, context) {
|
|
11
11
|
return {
|
|
@@ -101,24 +101,23 @@ export const websocket = Object.freeze({
|
|
|
101
101
|
serverCapability: WEBSOCKET_SERVER,
|
|
102
102
|
bindServer: bindWebSocketServer,
|
|
103
103
|
});
|
|
104
|
-
function compileRouteTree(tree, parentPath = '',
|
|
104
|
+
function compileRouteTree(tree, parentPath = '', prefix = '', routeNames = new Set()) {
|
|
105
105
|
return Object.entries(tree).flatMap(([name, node]) => {
|
|
106
|
+
if ('request' in node || 'responses' in node)
|
|
107
|
+
throw new TypeError('WebSocket v2ではhandshakeを使用してください。');
|
|
106
108
|
if (isBranch(node)) {
|
|
107
109
|
const branchPath = node.path ?? '';
|
|
108
110
|
if (branchPath !== '')
|
|
109
111
|
parseHttpPath(branchPath);
|
|
110
|
-
|
|
111
|
-
return compileRouteTree(node.routes, joinPath(parentPath, branchPath), { ...parentResponses, ...node.responses }, prefix ? `${prefix}.${name}` : name, routeNames);
|
|
112
|
+
return compileRouteTree(node.routes, joinPath(parentPath, branchPath), prefix ? `${prefix}.${name}` : name, routeNames);
|
|
112
113
|
}
|
|
113
114
|
parseHttpPath(node.path);
|
|
114
115
|
const path = joinPath(parentPath, node.path);
|
|
115
116
|
const segments = parseHttpPath(path);
|
|
116
|
-
assertExactPathParams(path, segments, node.
|
|
117
|
-
assertNoInheritedResponseCollision(name, parentResponses, node.responses ?? {});
|
|
117
|
+
assertExactPathParams(path, segments, node.handshake?.params);
|
|
118
118
|
const routeName = prefix ? `${prefix}.${name}` : name;
|
|
119
|
-
if (routeNames.has(routeName))
|
|
119
|
+
if (routeNames.has(routeName))
|
|
120
120
|
throw new TypeError(`Duplicate nested WebSocket route name: ${routeName}`);
|
|
121
|
-
}
|
|
122
121
|
routeNames.add(routeName);
|
|
123
122
|
return [
|
|
124
123
|
Object.freeze({
|
|
@@ -126,13 +125,9 @@ function compileRouteTree(tree, parentPath = '', parentResponses = {}, prefix =
|
|
|
126
125
|
path,
|
|
127
126
|
normalizedPath: normalizeHttpPath(segments),
|
|
128
127
|
segments,
|
|
129
|
-
...(node.
|
|
128
|
+
...(node.handshake === undefined
|
|
130
129
|
? {}
|
|
131
|
-
: {
|
|
132
|
-
responses: snapshotWebSocketResponses({
|
|
133
|
-
...parentResponses,
|
|
134
|
-
...node.responses,
|
|
135
|
-
}),
|
|
130
|
+
: { handshake: snapshotHandshake(node.handshake) }),
|
|
136
131
|
...(node.messages === undefined
|
|
137
132
|
? {}
|
|
138
133
|
: { messages: Object.freeze({ ...node.messages }) }),
|
|
@@ -152,27 +147,17 @@ function assertExactPathParams(path, segments, params) {
|
|
|
152
147
|
throw new TypeError(`WebSocket ${path} request params must exactly match path parameters.`);
|
|
153
148
|
}
|
|
154
149
|
}
|
|
155
|
-
function
|
|
156
|
-
for (const name of Object.keys(declared)) {
|
|
157
|
-
if (name in inherited) {
|
|
158
|
-
throw new TypeError(`Duplicate inherited WebSocket response ${name} at ${nodeName}.`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
function snapshotWebSocketRequest(request) {
|
|
150
|
+
function snapshotHandshake(handshake) {
|
|
163
151
|
return Object.freeze({
|
|
164
|
-
...
|
|
165
|
-
...(
|
|
152
|
+
...handshake,
|
|
153
|
+
...(handshake.params === undefined
|
|
154
|
+
? {}
|
|
155
|
+
: { params: Object.freeze({ ...handshake.params }) }),
|
|
156
|
+
...(handshake.protocols === undefined
|
|
166
157
|
? {}
|
|
167
|
-
: {
|
|
158
|
+
: { protocols: Object.freeze([...handshake.protocols]) }),
|
|
168
159
|
});
|
|
169
160
|
}
|
|
170
|
-
function snapshotWebSocketResponses(responses) {
|
|
171
|
-
return Object.freeze(Object.fromEntries(Object.entries(responses).map(([name, response]) => [
|
|
172
|
-
name,
|
|
173
|
-
Object.freeze({ ...response }),
|
|
174
|
-
])));
|
|
175
|
-
}
|
|
176
161
|
function isBranch(node) {
|
|
177
162
|
return 'routes' in node;
|
|
178
163
|
}
|
|
@@ -234,13 +219,28 @@ function createWebSocketRuntime(executions, driver, applicationRuntime) {
|
|
|
234
219
|
if (state !== 'running') {
|
|
235
220
|
return Response.json({ error: 'Service Unavailable' }, { status: 503 });
|
|
236
221
|
}
|
|
222
|
+
const offered = (request.headers.get('sec-websocket-protocol') ?? '')
|
|
223
|
+
.split(',')
|
|
224
|
+
.map((value) => value.trim())
|
|
225
|
+
.filter(Boolean);
|
|
226
|
+
const protocol = match.route.handshake?.protocols?.find((value) => offered.includes(value));
|
|
227
|
+
if (match.route.handshake?.protocols?.length && protocol === undefined) {
|
|
228
|
+
return new Response('WebSocket subprotocolが一致しません。', {
|
|
229
|
+
status: 400,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
237
232
|
const completePendingIngress = ingress.enter();
|
|
238
233
|
if (!completePendingIngress) {
|
|
239
234
|
return Response.json({ error: 'Service Unavailable' }, { status: 503 });
|
|
240
235
|
}
|
|
241
236
|
let lease;
|
|
242
237
|
try {
|
|
243
|
-
lease = applicationRuntime.beginExecution(
|
|
238
|
+
lease = applicationRuntime.beginExecution({
|
|
239
|
+
executionId: match.executionId,
|
|
240
|
+
executionKind: 'websocket.session',
|
|
241
|
+
graphNodeId: match.executionId,
|
|
242
|
+
name: `WS ${match.route.path}`,
|
|
243
|
+
});
|
|
244
244
|
}
|
|
245
245
|
catch (error) {
|
|
246
246
|
completePendingIngress();
|
|
@@ -248,14 +248,15 @@ function createWebSocketRuntime(executions, driver, applicationRuntime) {
|
|
|
248
248
|
}
|
|
249
249
|
let upgraded;
|
|
250
250
|
try {
|
|
251
|
-
upgraded = await driver.upgrade(request);
|
|
251
|
+
upgraded = await driver.upgrade(request, protocol === undefined ? {} : { protocol });
|
|
252
252
|
}
|
|
253
253
|
catch (error) {
|
|
254
|
+
lease.fail?.(error);
|
|
254
255
|
lease.complete();
|
|
255
256
|
completePendingIngress();
|
|
256
257
|
throw error;
|
|
257
258
|
}
|
|
258
|
-
const session = createSession(upgraded.connection, match.route, input, lease, handlers.get(match.executionId)?.[match.route.name]);
|
|
259
|
+
const session = createSession(upgraded.connection, request, applicationRuntime, match.route, input, lease, handlers.get(match.executionId)?.[match.route.name], lease.run);
|
|
259
260
|
sessions.add(session.active);
|
|
260
261
|
void session.completion.then(() => sessions.delete(session.active), () => sessions.delete(session.active));
|
|
261
262
|
completePendingIngress();
|
|
@@ -275,14 +276,19 @@ function createWebSocketRuntime(executions, driver, applicationRuntime) {
|
|
|
275
276
|
const results = await Promise.allSettled([...sessions].map(async (session) => {
|
|
276
277
|
const graceful = session
|
|
277
278
|
.close(1001, 'Going Away')
|
|
279
|
+
.then(() => session.completion)
|
|
278
280
|
.then(() => true)
|
|
279
281
|
.catch(() => false);
|
|
282
|
+
let timer;
|
|
280
283
|
const completed = gracefulTimeoutMs === 0
|
|
281
284
|
? false
|
|
282
285
|
: await Promise.race([
|
|
283
286
|
graceful,
|
|
284
|
-
|
|
287
|
+
new Promise((resolve) => {
|
|
288
|
+
timer = setTimeout(() => resolve(false), gracefulTimeoutMs);
|
|
289
|
+
}),
|
|
285
290
|
]);
|
|
291
|
+
clearTimeout(timer);
|
|
286
292
|
if (!completed)
|
|
287
293
|
await session.terminate();
|
|
288
294
|
await session.completion.catch(() => undefined);
|
|
@@ -312,19 +318,19 @@ function findRoute(routes, request) {
|
|
|
312
318
|
return undefined;
|
|
313
319
|
}
|
|
314
320
|
async function validateOpeningRequest(request, rawParams, route) {
|
|
315
|
-
const params = route.
|
|
316
|
-
? Object.fromEntries(await Promise.all(Object.entries(route.
|
|
321
|
+
const params = route.handshake?.params
|
|
322
|
+
? Object.fromEntries(await Promise.all(Object.entries(route.handshake.params).map(async ([name, schema]) => [
|
|
317
323
|
name,
|
|
318
324
|
await validateSchema(schema, rawParams[name]),
|
|
319
325
|
])))
|
|
320
326
|
: rawParams;
|
|
321
327
|
const url = new URL(request.url);
|
|
322
328
|
const rawQuery = decodeQuery(url.searchParams);
|
|
323
|
-
const query = route.
|
|
324
|
-
? await validateSchema(route.
|
|
329
|
+
const query = route.handshake?.query
|
|
330
|
+
? await validateSchema(route.handshake.query, rawQuery)
|
|
325
331
|
: rawQuery;
|
|
326
|
-
const headers = route.
|
|
327
|
-
? await validateSchema(route.
|
|
332
|
+
const headers = route.handshake?.headers
|
|
333
|
+
? await validateSchema(route.handshake.headers, Object.fromEntries(request.headers))
|
|
328
334
|
: request.headers;
|
|
329
335
|
return { params, query, headers };
|
|
330
336
|
}
|
|
@@ -341,7 +347,15 @@ function decodeQuery(searchParams) {
|
|
|
341
347
|
}
|
|
342
348
|
return query;
|
|
343
349
|
}
|
|
344
|
-
function createSession(connection, route, input, lease, handler) {
|
|
350
|
+
function createSession(connection, request, applicationRuntime, route, input, lease, handler, runInExecution) {
|
|
351
|
+
const session = Object.freeze({
|
|
352
|
+
id: `ws_${crypto.randomUUID()}`,
|
|
353
|
+
protocol: connection.protocol,
|
|
354
|
+
});
|
|
355
|
+
lease.annotate?.({
|
|
356
|
+
'websocket.session.id': session.id,
|
|
357
|
+
'websocket.protocol': session.protocol,
|
|
358
|
+
});
|
|
345
359
|
let state = 'open';
|
|
346
360
|
let transportFailed = false;
|
|
347
361
|
let sendTail = Promise.resolve();
|
|
@@ -352,6 +366,7 @@ function createSession(connection, route, input, lease, handler) {
|
|
|
352
366
|
return normalizeCloseInfo(info);
|
|
353
367
|
}, (error) => {
|
|
354
368
|
state = 'closed';
|
|
369
|
+
lease.fail?.(error);
|
|
355
370
|
lease.abort(error);
|
|
356
371
|
throw error;
|
|
357
372
|
});
|
|
@@ -392,6 +407,17 @@ function createSession(connection, route, input, lease, handler) {
|
|
|
392
407
|
};
|
|
393
408
|
const messages = decodeMessages(connection.messages, route.messages);
|
|
394
409
|
const context = {
|
|
410
|
+
request,
|
|
411
|
+
session,
|
|
412
|
+
protocol: session.protocol,
|
|
413
|
+
execution: {
|
|
414
|
+
signal: lease.signal,
|
|
415
|
+
annotate: (attributes) => lease.annotate?.(attributes),
|
|
416
|
+
beginOperation: (metadata) => {
|
|
417
|
+
const begin = () => applicationRuntime.beginOperation?.(metadata) ?? { complete() { } };
|
|
418
|
+
return lease.run ? lease.run(begin) : begin();
|
|
419
|
+
},
|
|
420
|
+
},
|
|
395
421
|
input: {
|
|
396
422
|
...input,
|
|
397
423
|
...(route.messages?.input === undefined ? {} : { messages }),
|
|
@@ -406,11 +432,13 @@ function createSession(connection, route, input, lease, handler) {
|
|
|
406
432
|
if (!handler) {
|
|
407
433
|
throw new Error(`LUTRE_WEBSOCKET_HANDLER_MISSING: ${route.name}`);
|
|
408
434
|
}
|
|
409
|
-
|
|
435
|
+
const invoke = () => handler(context);
|
|
436
|
+
await (runInExecution ? runInExecution(invoke) : invoke());
|
|
410
437
|
if (state === 'open')
|
|
411
438
|
await close(1000, '');
|
|
412
439
|
}
|
|
413
|
-
catch {
|
|
440
|
+
catch (error) {
|
|
441
|
+
lease.fail?.(error);
|
|
414
442
|
if (state === 'open')
|
|
415
443
|
await close(1011, '');
|
|
416
444
|
}
|
|
@@ -519,6 +547,3 @@ function webSocketGracefulShutdownBudget(timeoutMs) {
|
|
|
519
547
|
return 0;
|
|
520
548
|
return Math.min(5_000, Math.max(0, Math.floor(timeoutMs * 0.8)));
|
|
521
549
|
}
|
|
522
|
-
function delay(milliseconds) {
|
|
523
|
-
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
524
|
-
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { bindWebSocketServer, defineWebSocketContract, defineWebSocketImplementation, WEBSOCKET_SERVER, websocket, websocketExtension, } from './extension.js';
|
|
2
2
|
export { WebSocketConnectionNotOpenError, WebSocketMessageDecodeError, WebSocketMessageEncodeError, } from './errors.js';
|
|
3
|
-
export type { WebSocketBranchDefinition, WebSocketCloseInfo, WebSocketCodecKind, WebSocketConnectionDriver, WebSocketContract, WebSocketDataMessage, WebSocketExecutionDefinition, WebSocketExtensionRuntime, WebSocketHandlerContext, WebSocketHandlers, WebSocketHostApi, WebSocketImplementationDefinition, WebSocketIncomingMessage, WebSocketMessageCodec, WebSocketRouteDefinition, WebSocketRouteTree, WebSocketServerDriver, WebSocketUpgradeResult, } from './extension.js';
|
|
3
|
+
export type { WebSocketBranchDefinition, WebSocketHandshakeDefinition, WebSocketSession, WebSocketUpgradeOptions, WebSocketCloseInfo, WebSocketCodecKind, WebSocketConnectionDriver, WebSocketContract, WebSocketDataMessage, WebSocketExecutionDefinition, WebSocketExtensionRuntime, WebSocketHandlerContext, WebSocketHandlers, WebSocketHostApi, WebSocketImplementationDefinition, WebSocketIncomingMessage, WebSocketMessageCodec, WebSocketRouteDefinition, WebSocketRouteTree, WebSocketServerDriver, WebSocketUpgradeResult, } from './extension.js';
|
|
4
|
+
export { createEventWebSocketConnection, createWebSocketDriverChannel, } from './driver.js';
|
|
5
|
+
export type { EventWebSocket, WebSocketDriverChannel } from './driver.js';
|
package/dist/websocket/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { bindWebSocketServer, defineWebSocketContract, defineWebSocketImplementation, WEBSOCKET_SERVER, websocket, websocketExtension, } from './extension.js';
|
|
2
2
|
export { WebSocketConnectionNotOpenError, WebSocketMessageDecodeError, WebSocketMessageEncodeError, } from './errors.js';
|
|
3
|
+
export { createEventWebSocketConnection, createWebSocketDriverChannel, } from './driver.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loutrejs/loutre",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"types": "./dist/graph/index.d.ts",
|
|
21
21
|
"import": "./dist/graph/index.js"
|
|
22
22
|
},
|
|
23
|
+
"./devtools": {
|
|
24
|
+
"types": "./dist/devtools/index.d.ts",
|
|
25
|
+
"import": "./dist/devtools/index.js"
|
|
26
|
+
},
|
|
23
27
|
"./runtime": {
|
|
24
28
|
"types": "./dist/runtime/index.d.ts",
|
|
25
29
|
"import": "./dist/runtime/index.js"
|
|
@@ -44,10 +48,6 @@
|
|
|
44
48
|
"types": "./dist/adapters/bun.d.ts",
|
|
45
49
|
"import": "./dist/adapters/bun.js"
|
|
46
50
|
},
|
|
47
|
-
"./runtime/deno": {
|
|
48
|
-
"types": "./dist/adapters/deno.d.ts",
|
|
49
|
-
"import": "./dist/adapters/deno.js"
|
|
50
|
-
},
|
|
51
51
|
"./runtime/cloudflare-workers": {
|
|
52
52
|
"types": "./dist/adapters/cloudflare-workers.d.ts",
|
|
53
53
|
"import": "./dist/adapters/cloudflare-workers.js"
|
|
@@ -71,7 +71,8 @@
|
|
|
71
71
|
"provenance": true
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@standard-schema/spec": "^1.1.0"
|
|
74
|
+
"@standard-schema/spec": "^1.1.0",
|
|
75
|
+
"ts-pattern": "^5.9.0"
|
|
75
76
|
},
|
|
76
77
|
"repository": {
|
|
77
78
|
"type": "git",
|
package/dist/adapters/deno.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { type ApplicationDefinition, type BootstrapArguments, type KernelHostedApplication, type RequireApplicationExtension } from '../application/index.js';
|
|
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
|
-
export type DenoRuntimeOptions<TDefinition extends ApplicationDefinition> = {
|
|
6
|
-
readonly application: HttpApplication<TDefinition>;
|
|
7
|
-
readonly environment?: unknown;
|
|
8
|
-
readonly capabilities?: readonly RuntimeCapabilityBinding[];
|
|
9
|
-
readonly forceShutdownTimeoutMs?: number;
|
|
10
|
-
} & BootstrapArguments<TDefinition>;
|
|
11
|
-
export type DenoCreateOptions<TDefinition extends ApplicationDefinition> = DenoRuntimeOptions<TDefinition>;
|
|
12
|
-
export interface DenoServeOptions {
|
|
13
|
-
readonly port?: number;
|
|
14
|
-
readonly hostname?: string;
|
|
15
|
-
readonly shutdownHooks?: boolean;
|
|
16
|
-
}
|
|
17
|
-
export interface DenoBinding {
|
|
18
|
-
fetch(request: Request): Promise<Response>;
|
|
19
|
-
close(signal?: string): Promise<void>;
|
|
20
|
-
}
|
|
21
|
-
export interface DenoListenerHandle {
|
|
22
|
-
readonly port: number;
|
|
23
|
-
}
|
|
24
|
-
export type DenoRuntimeApplication<TDefinition extends ApplicationDefinition = ApplicationDefinition> = KernelHostedApplication<TDefinition> & {
|
|
25
|
-
serve(options?: DenoServeOptions): Promise<DenoListenerHandle>;
|
|
26
|
-
};
|
|
27
|
-
export declare const denoRuntime: {
|
|
28
|
-
readonly runtime: 'deno';
|
|
29
|
-
readonly capabilities: Set<string>;
|
|
30
|
-
readonly bind: typeof bind;
|
|
31
|
-
readonly create: typeof create;
|
|
32
|
-
};
|
|
33
|
-
declare function bind<const TDefinition extends ApplicationDefinition>(options: DenoRuntimeOptions<TDefinition>): DenoBinding;
|
|
34
|
-
declare function create<const TDefinition extends ApplicationDefinition>(options: DenoCreateOptions<TDefinition>): Promise<DenoRuntimeApplication<TDefinition>>;
|
|
35
|
-
export {};
|
package/dist/adapters/deno.js
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import { createKernelApplication, } from '../application/index.js';
|
|
2
|
-
import { bindApplicationCapability } from '../application/kernel-internal.js';
|
|
3
|
-
import { httpExecutionExtension } from '../http/index.js';
|
|
4
|
-
import { LOUTRE_VERSION, startStartupPresentation } from '../presentation.js';
|
|
5
|
-
import { assertRuntimeEngine } from '../runtime/engine.js';
|
|
6
|
-
import { serverUrl } from '../runtime/server-url.js';
|
|
7
|
-
import { canRetryOnNextPort, initialServerPort, } from '../runtime/server-port.js';
|
|
8
|
-
export const denoRuntime = {
|
|
9
|
-
runtime: 'deno',
|
|
10
|
-
capabilities: new Set([
|
|
11
|
-
'http.server',
|
|
12
|
-
'http.request.streaming',
|
|
13
|
-
'http.response.streaming',
|
|
14
|
-
'stream.readable',
|
|
15
|
-
'stream.writable',
|
|
16
|
-
'runtime.longLived',
|
|
17
|
-
'runtime.shutdownHook',
|
|
18
|
-
'env.runtime',
|
|
19
|
-
'crypto.random',
|
|
20
|
-
]),
|
|
21
|
-
bind,
|
|
22
|
-
create,
|
|
23
|
-
};
|
|
24
|
-
function bind(options) {
|
|
25
|
-
assertRuntimeEngine('deno');
|
|
26
|
-
if (options.application.model.extensions.get(httpExecutionExtension) ===
|
|
27
|
-
undefined) {
|
|
28
|
-
throw new Error('LUTRE_RUNTIME_HTTP_REQUIRED: denoRuntime.bind() requires the HTTP Execution Extension.');
|
|
29
|
-
}
|
|
30
|
-
const application = createKernelApplication({
|
|
31
|
-
...options,
|
|
32
|
-
application: options.application,
|
|
33
|
-
capabilities: [
|
|
34
|
-
bindApplicationCapability(options.application.model, 'http.server', {
|
|
35
|
-
runtime: 'deno',
|
|
36
|
-
}),
|
|
37
|
-
...(options.capabilities ?? []),
|
|
38
|
-
],
|
|
39
|
-
environment: 'environment' in options ? options.environment : denoEnvironment(),
|
|
40
|
-
});
|
|
41
|
-
let initialization;
|
|
42
|
-
return {
|
|
43
|
-
async fetch(request) {
|
|
44
|
-
initialization ??= application.init();
|
|
45
|
-
await initialization;
|
|
46
|
-
return application.http.fetch(request);
|
|
47
|
-
},
|
|
48
|
-
close: (signal) => application.close(signal),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
async function create(options) {
|
|
52
|
-
assertRuntimeEngine('deno');
|
|
53
|
-
const deno = denoGlobal();
|
|
54
|
-
const startedAt = performance.now();
|
|
55
|
-
const isTTY = deno?.stdout?.isTerminal?.() === true;
|
|
56
|
-
const presentation = startStartupPresentation({ version: LOUTRE_VERSION }, {
|
|
57
|
-
terminal: {
|
|
58
|
-
isTTY,
|
|
59
|
-
color: isTTY && deno?.env?.get?.('NO_COLOR') === undefined,
|
|
60
|
-
},
|
|
61
|
-
write: (value) => console.log(value),
|
|
62
|
-
});
|
|
63
|
-
if (options.application.model.extensions.get(httpExecutionExtension) ===
|
|
64
|
-
undefined) {
|
|
65
|
-
throw new Error('LUTRE_RUNTIME_HTTP_REQUIRED: denoRuntime.create() requires the HTTP Execution Extension.');
|
|
66
|
-
}
|
|
67
|
-
const hosted = createKernelApplication({
|
|
68
|
-
...options,
|
|
69
|
-
application: options.application,
|
|
70
|
-
capabilities: [
|
|
71
|
-
bindApplicationCapability(options.application.model, 'http.server', {
|
|
72
|
-
runtime: 'deno',
|
|
73
|
-
}),
|
|
74
|
-
...(options.capabilities ?? []),
|
|
75
|
-
],
|
|
76
|
-
environment: 'environment' in options ? options.environment : denoEnvironment(),
|
|
77
|
-
});
|
|
78
|
-
await hosted.init();
|
|
79
|
-
const application = hosted;
|
|
80
|
-
const http = hosted
|
|
81
|
-
.http;
|
|
82
|
-
const closeApplication = application.close.bind(application);
|
|
83
|
-
let server;
|
|
84
|
-
let removeShutdownHooks;
|
|
85
|
-
let serving = false;
|
|
86
|
-
let closed = false;
|
|
87
|
-
let closingPromise;
|
|
88
|
-
let serverClosingPromise;
|
|
89
|
-
const beginServerClose = () => {
|
|
90
|
-
if (serverClosingPromise)
|
|
91
|
-
return serverClosingPromise;
|
|
92
|
-
if (!server)
|
|
93
|
-
return Promise.resolve();
|
|
94
|
-
const closingServer = server;
|
|
95
|
-
serverClosingPromise = Promise.resolve()
|
|
96
|
-
.then(() => closingServer.shutdown())
|
|
97
|
-
.then(() => {
|
|
98
|
-
if (server === closingServer)
|
|
99
|
-
server = undefined;
|
|
100
|
-
}, (error) => {
|
|
101
|
-
serverClosingPromise = undefined;
|
|
102
|
-
throw error;
|
|
103
|
-
});
|
|
104
|
-
void serverClosingPromise.catch(() => undefined);
|
|
105
|
-
return serverClosingPromise;
|
|
106
|
-
};
|
|
107
|
-
const close = (signal) => {
|
|
108
|
-
if (closed)
|
|
109
|
-
return Promise.resolve();
|
|
110
|
-
if (closingPromise)
|
|
111
|
-
return closingPromise;
|
|
112
|
-
closingPromise = (async () => {
|
|
113
|
-
removeShutdownHooks?.();
|
|
114
|
-
removeShutdownHooks = undefined;
|
|
115
|
-
const errors = [];
|
|
116
|
-
const serverClosing = beginServerClose();
|
|
117
|
-
try {
|
|
118
|
-
await closeApplication(signal);
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
errors.push(error);
|
|
122
|
-
}
|
|
123
|
-
if (errors.length === 0) {
|
|
124
|
-
try {
|
|
125
|
-
await serverClosing;
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
errors.push(error);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (errors.length > 0) {
|
|
132
|
-
throw new AggregateError(errors, 'Deno runtime shutdown failed');
|
|
133
|
-
}
|
|
134
|
-
closed = true;
|
|
135
|
-
})().catch((error) => {
|
|
136
|
-
closingPromise = undefined;
|
|
137
|
-
throw error;
|
|
138
|
-
});
|
|
139
|
-
return closingPromise;
|
|
140
|
-
};
|
|
141
|
-
const serve = async (serveOptions = {}) => {
|
|
142
|
-
if (closed) {
|
|
143
|
-
throw new Error('LUTRE_APP_STOPPED: Application is stopped.');
|
|
144
|
-
}
|
|
145
|
-
if (serving) {
|
|
146
|
-
throw new Error('LUTRE_RUNTIME_ALREADY_SERVING: Deno runtime Application is already serving.');
|
|
147
|
-
}
|
|
148
|
-
if (!deno?.serve) {
|
|
149
|
-
await close().catch(() => undefined);
|
|
150
|
-
throw new Error('LUTRE_DENO_UNAVAILABLE: Deno.serve() is not available.');
|
|
151
|
-
}
|
|
152
|
-
serving = true;
|
|
153
|
-
try {
|
|
154
|
-
const requestedPort = serveOptions.port;
|
|
155
|
-
let port = initialServerPort(requestedPort);
|
|
156
|
-
while (true) {
|
|
157
|
-
try {
|
|
158
|
-
server = deno.serve({
|
|
159
|
-
port,
|
|
160
|
-
...(serveOptions.hostname === undefined
|
|
161
|
-
? {}
|
|
162
|
-
: { hostname: serveOptions.hostname }),
|
|
163
|
-
onListen: () => undefined,
|
|
164
|
-
}, createDenoFetchDriver(http));
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
catch (error) {
|
|
168
|
-
if (requestedPort !== undefined || !canRetryOnNextPort(error, port)) {
|
|
169
|
-
throw error;
|
|
170
|
-
}
|
|
171
|
-
port += 1;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
presentation.ready({
|
|
175
|
-
server: serverUrl(serveOptions.hostname, port),
|
|
176
|
-
runtime: `Deno ${deno.version?.deno ?? 'unknown'}`,
|
|
177
|
-
environment: deno.env?.get?.('DENO_ENV') ??
|
|
178
|
-
deno.env?.get?.('NODE_ENV') ??
|
|
179
|
-
'development',
|
|
180
|
-
startupDurationMs: performance.now() - startedAt,
|
|
181
|
-
});
|
|
182
|
-
removeShutdownHooks =
|
|
183
|
-
serveOptions.shutdownHooks === false
|
|
184
|
-
? undefined
|
|
185
|
-
: registerDenoShutdownHooks(deno, close);
|
|
186
|
-
return { port };
|
|
187
|
-
}
|
|
188
|
-
catch (error) {
|
|
189
|
-
await close().catch(() => undefined);
|
|
190
|
-
throw error;
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
Object.assign(application, { serve, close });
|
|
194
|
-
return application;
|
|
195
|
-
}
|
|
196
|
-
function registerDenoShutdownHooks(deno, close) {
|
|
197
|
-
if (!deno.addSignalListener || !deno.removeSignalListener) {
|
|
198
|
-
return undefined;
|
|
199
|
-
}
|
|
200
|
-
const handlers = new Map();
|
|
201
|
-
const remove = () => {
|
|
202
|
-
for (const [signal, handler] of handlers) {
|
|
203
|
-
deno.removeSignalListener?.(signal, handler);
|
|
204
|
-
}
|
|
205
|
-
handlers.clear();
|
|
206
|
-
};
|
|
207
|
-
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
208
|
-
const handler = () => {
|
|
209
|
-
remove();
|
|
210
|
-
void close(signal);
|
|
211
|
-
};
|
|
212
|
-
handlers.set(signal, handler);
|
|
213
|
-
deno.addSignalListener(signal, handler);
|
|
214
|
-
}
|
|
215
|
-
return remove;
|
|
216
|
-
}
|
|
217
|
-
function createDenoFetchDriver(application) {
|
|
218
|
-
let initialization;
|
|
219
|
-
return async (request) => {
|
|
220
|
-
initialization ??= application.initialize?.() ?? Promise.resolve();
|
|
221
|
-
await initialization;
|
|
222
|
-
return application.fetch(request);
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
function denoEnvironment() {
|
|
226
|
-
const deno = denoGlobal();
|
|
227
|
-
return deno?.env?.toObject ? deno.env.toObject() : undefined;
|
|
228
|
-
}
|
|
229
|
-
function denoGlobal() {
|
|
230
|
-
return globalThis.Deno;
|
|
231
|
-
}
|