@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/tasks/extension.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { collectInjectedDependencies, defineExecution, defineExecutionExtension, provide, runInInjectionContext, token, validateSchema, } from '../core/index.js';
|
|
2
2
|
import { isValidCronExpression, matchesCronTrigger } from './runtime/cron.js';
|
|
3
|
+
import { match } from 'ts-pattern';
|
|
3
4
|
const taskExecutionIdentityKey = Symbol.for('loutre.tasks.task-execution-identity');
|
|
4
5
|
const queueDriverToken = Symbol.for('loutre.tasks.queue-driver');
|
|
5
6
|
export const tasksExtension = defineExecutionExtension({
|
|
@@ -7,86 +8,78 @@ export const tasksExtension = defineExecutionExtension({
|
|
|
7
8
|
abiVersion: '1',
|
|
8
9
|
name: 'loutre:tasks',
|
|
9
10
|
compile(definition) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
taskName: definition.task.name,
|
|
77
|
-
}),
|
|
78
|
-
};
|
|
79
|
-
}
|
|
11
|
+
return match(definition)
|
|
12
|
+
.with({ type: 'task' }, (candidate) => ({
|
|
13
|
+
kind: 'execution',
|
|
14
|
+
id: `task.${candidate.name}`,
|
|
15
|
+
executionKind: 'task.invocation',
|
|
16
|
+
dependencies: collectInjectedDependencies({
|
|
17
|
+
kind: 'execution',
|
|
18
|
+
id: `task:${candidate.name}`,
|
|
19
|
+
name: candidate.name,
|
|
20
|
+
}, () => candidate.factory()),
|
|
21
|
+
capabilities: [],
|
|
22
|
+
compiled: Object.freeze({
|
|
23
|
+
type: 'task',
|
|
24
|
+
taskExecutionId: candidate[taskExecutionIdentityKey],
|
|
25
|
+
name: candidate.name,
|
|
26
|
+
factory: candidate.factory,
|
|
27
|
+
}),
|
|
28
|
+
}))
|
|
29
|
+
.with({ type: 'cron' }, (candidate) => ({
|
|
30
|
+
kind: 'execution',
|
|
31
|
+
id: `trigger.${candidate.name}`,
|
|
32
|
+
executionKind: 'trigger.cron',
|
|
33
|
+
dependencies: [],
|
|
34
|
+
capabilities: [],
|
|
35
|
+
compiled: Object.freeze({
|
|
36
|
+
type: 'cron',
|
|
37
|
+
name: candidate.name,
|
|
38
|
+
expression: candidate.expression,
|
|
39
|
+
timezone: candidate.timezone,
|
|
40
|
+
overlap: candidate.overlap,
|
|
41
|
+
taskExecutionId: candidate.task[taskExecutionIdentityKey],
|
|
42
|
+
taskName: candidate.task.name,
|
|
43
|
+
}),
|
|
44
|
+
}))
|
|
45
|
+
.with({ type: 'fixed-delay' }, (candidate) => ({
|
|
46
|
+
kind: 'execution',
|
|
47
|
+
id: `trigger.${candidate.name}`,
|
|
48
|
+
executionKind: 'trigger.fixed-delay',
|
|
49
|
+
dependencies: [],
|
|
50
|
+
capabilities: [],
|
|
51
|
+
compiled: Object.freeze({
|
|
52
|
+
type: 'fixed-delay',
|
|
53
|
+
name: candidate.name,
|
|
54
|
+
delay: candidate.delay,
|
|
55
|
+
immediate: candidate.immediate,
|
|
56
|
+
taskExecutionId: candidate.task[taskExecutionIdentityKey],
|
|
57
|
+
taskName: candidate.task.name,
|
|
58
|
+
}),
|
|
59
|
+
}))
|
|
60
|
+
.with({ type: 'queue-consumer' }, (candidate) => ({
|
|
61
|
+
kind: 'execution',
|
|
62
|
+
id: `trigger.${candidate.name}`,
|
|
63
|
+
executionKind: 'trigger.queue-consumer',
|
|
64
|
+
dependencies: [queueRuntimeToken(candidate.queue)],
|
|
65
|
+
capabilities: [],
|
|
66
|
+
compiled: Object.freeze({
|
|
67
|
+
type: 'queue-consumer',
|
|
68
|
+
name: candidate.name,
|
|
69
|
+
queueName: candidate.queue.name,
|
|
70
|
+
queuePayload: candidate.queue.payload,
|
|
71
|
+
queueDriver: queueRuntimeToken(candidate.queue),
|
|
72
|
+
taskExecutionId: candidate.task[taskExecutionIdentityKey],
|
|
73
|
+
taskName: candidate.task.name,
|
|
74
|
+
}),
|
|
75
|
+
}))
|
|
76
|
+
.exhaustive();
|
|
80
77
|
},
|
|
81
78
|
references(definition) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
case 'fixed-delay':
|
|
87
|
-
case 'queue-consumer':
|
|
88
|
-
return [definition.task];
|
|
89
|
-
}
|
|
79
|
+
return match(definition)
|
|
80
|
+
.with({ type: 'task' }, () => [])
|
|
81
|
+
.with({ type: 'cron' }, { type: 'fixed-delay' }, { type: 'queue-consumer' }, (candidate) => [candidate.task])
|
|
82
|
+
.exhaustive();
|
|
90
83
|
},
|
|
91
84
|
validate({ executions }) {
|
|
92
85
|
const taskExecutionIds = new Set(executions.flatMap((execution) => execution.compiled.type === 'task'
|
|
@@ -133,34 +126,33 @@ export const tasksExtension = defineExecutionExtension({
|
|
|
133
126
|
},
|
|
134
127
|
projectGraph: ({ execution }) => {
|
|
135
128
|
const compiled = execution.compiled;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
129
|
+
return match(compiled)
|
|
130
|
+
.with({ type: 'task' }, (candidate) => ({
|
|
131
|
+
type: 'task',
|
|
132
|
+
name: candidate.name,
|
|
133
|
+
}))
|
|
134
|
+
.with({ type: 'cron' }, (candidate) => ({
|
|
135
|
+
type: 'cron',
|
|
136
|
+
name: candidate.name,
|
|
137
|
+
expression: candidate.expression,
|
|
138
|
+
timezone: candidate.timezone,
|
|
139
|
+
overlap: candidate.overlap,
|
|
140
|
+
task: candidate.taskName,
|
|
141
|
+
}))
|
|
142
|
+
.with({ type: 'fixed-delay' }, (candidate) => ({
|
|
143
|
+
type: 'fixed-delay',
|
|
144
|
+
name: candidate.name,
|
|
145
|
+
delay: candidate.delay,
|
|
146
|
+
immediate: candidate.immediate,
|
|
147
|
+
task: candidate.taskName,
|
|
148
|
+
}))
|
|
149
|
+
.with({ type: 'queue-consumer' }, (candidate) => ({
|
|
150
|
+
type: 'queue-consumer',
|
|
151
|
+
name: candidate.name,
|
|
152
|
+
queue: candidate.queueName,
|
|
153
|
+
task: candidate.taskName,
|
|
154
|
+
}))
|
|
155
|
+
.exhaustive();
|
|
164
156
|
},
|
|
165
157
|
host: {
|
|
166
158
|
namespace: 'tasks',
|
|
@@ -238,6 +230,7 @@ export function consume(declaration) {
|
|
|
238
230
|
}
|
|
239
231
|
function createTasksRuntime(executions, applicationRuntime) {
|
|
240
232
|
const runtimes = new Map();
|
|
233
|
+
const runtimeExecutionIds = new Map();
|
|
241
234
|
const triggers = [];
|
|
242
235
|
for (const execution of executions) {
|
|
243
236
|
const compiled = execution.compiled;
|
|
@@ -254,6 +247,7 @@ function createTasksRuntime(executions, applicationRuntime) {
|
|
|
254
247
|
resolve: (dependency) => applicationRuntime.resolve(dependency, execution.id),
|
|
255
248
|
}, () => compiled.factory());
|
|
256
249
|
runtimes.set(compiled.taskExecutionId, runtime);
|
|
250
|
+
runtimeExecutionIds.set(compiled.taskExecutionId, execution.id);
|
|
257
251
|
}
|
|
258
252
|
let triggerHandles = [];
|
|
259
253
|
let triggerStartup;
|
|
@@ -261,7 +255,7 @@ function createTasksRuntime(executions, applicationRuntime) {
|
|
|
261
255
|
let triggersStarted = false;
|
|
262
256
|
let state = 'running';
|
|
263
257
|
const activeInvocations = new Set();
|
|
264
|
-
const
|
|
258
|
+
const invokeTask = (taskExecutionId, taskName, arguments_) => {
|
|
265
259
|
if (state !== 'running') {
|
|
266
260
|
return Promise.reject(new Error(`LUTRE_TASKS_${state.toUpperCase()}`));
|
|
267
261
|
}
|
|
@@ -270,18 +264,42 @@ function createTasksRuntime(executions, applicationRuntime) {
|
|
|
270
264
|
return Promise.reject(new Error(`LUTRE_TASK_NOT_REGISTERED: ${taskName}`));
|
|
271
265
|
}
|
|
272
266
|
const invocation = (async () => {
|
|
273
|
-
const
|
|
267
|
+
const executionId = runtimeExecutionIds.get(taskExecutionId) ?? taskName;
|
|
268
|
+
const hasInput = arguments_.length > 0;
|
|
269
|
+
const input = hasInput ? arguments_[0] : undefined;
|
|
270
|
+
const lease = applicationRuntime.beginExecution({
|
|
271
|
+
executionId,
|
|
272
|
+
executionKind: 'task.invocation',
|
|
273
|
+
graphNodeId: executionId,
|
|
274
|
+
name: taskName,
|
|
275
|
+
}, {
|
|
276
|
+
input,
|
|
277
|
+
invoke: (nextInput) => invokeTask(taskExecutionId, taskName, hasInput ? [nextInput] : []),
|
|
278
|
+
});
|
|
279
|
+
let result;
|
|
280
|
+
let hasResult = false;
|
|
274
281
|
try {
|
|
275
|
-
|
|
282
|
+
const invoke = () => Reflect.apply(runtime, undefined, arguments_);
|
|
283
|
+
result = await (lease.run ? lease.run(invoke) : invoke());
|
|
284
|
+
hasResult = true;
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
lease.fail?.(error);
|
|
289
|
+
throw error;
|
|
276
290
|
}
|
|
277
291
|
finally {
|
|
278
|
-
|
|
292
|
+
if (hasResult)
|
|
293
|
+
lease.complete(result);
|
|
294
|
+
else
|
|
295
|
+
lease.complete();
|
|
279
296
|
}
|
|
280
297
|
})();
|
|
281
298
|
activeInvocations.add(invocation);
|
|
282
299
|
void invocation.then(() => activeInvocations.delete(invocation), () => activeInvocations.delete(invocation));
|
|
283
300
|
return invocation;
|
|
284
301
|
};
|
|
302
|
+
const run = (taskExecutionId, taskName, ...arguments_) => invokeTask(taskExecutionId, taskName, arguments_);
|
|
285
303
|
const stopTriggers = () => {
|
|
286
304
|
if (triggerStop)
|
|
287
305
|
return triggerStop;
|
|
@@ -389,14 +407,11 @@ function createTasksRuntime(executions, applicationRuntime) {
|
|
|
389
407
|
};
|
|
390
408
|
}
|
|
391
409
|
async function startTrigger(trigger, run, applicationRuntime) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
case 'queue-consumer':
|
|
398
|
-
return startQueueTrigger(trigger, run, applicationRuntime);
|
|
399
|
-
}
|
|
410
|
+
return match(trigger)
|
|
411
|
+
.with({ type: 'cron' }, (candidate) => startCronTrigger(candidate, run))
|
|
412
|
+
.with({ type: 'fixed-delay' }, (candidate) => startFixedDelayTrigger(candidate, run))
|
|
413
|
+
.with({ type: 'queue-consumer' }, (candidate) => startQueueTrigger(candidate, run, applicationRuntime))
|
|
414
|
+
.exhaustive();
|
|
400
415
|
}
|
|
401
416
|
function startCronTrigger(trigger, run) {
|
|
402
417
|
let stopped = false;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { WebSocketCloseInfo, WebSocketConnectionDriver, WebSocketDataMessage } from './extension.js';
|
|
2
|
+
export interface WebSocketDriverChannel {
|
|
3
|
+
readonly connection: WebSocketConnectionDriver;
|
|
4
|
+
receive(message: WebSocketDataMessage): void;
|
|
5
|
+
finish(info: WebSocketCloseInfo): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createWebSocketDriverChannel(options: {
|
|
8
|
+
readonly protocol: string;
|
|
9
|
+
readonly send: WebSocketConnectionDriver['send'];
|
|
10
|
+
readonly close: WebSocketConnectionDriver['close'];
|
|
11
|
+
readonly terminate: WebSocketConnectionDriver['terminate'];
|
|
12
|
+
}): WebSocketDriverChannel;
|
|
13
|
+
export interface EventWebSocket {
|
|
14
|
+
readonly readyState: number;
|
|
15
|
+
binaryType: string;
|
|
16
|
+
send(data: string | Uint8Array): void;
|
|
17
|
+
close(code?: number, reason?: string): void;
|
|
18
|
+
addEventListener(type: 'open', listener: () => void, options?: {
|
|
19
|
+
once?: boolean;
|
|
20
|
+
}): void;
|
|
21
|
+
addEventListener(type: 'message', listener: (event: {
|
|
22
|
+
data: unknown;
|
|
23
|
+
}) => void): void;
|
|
24
|
+
addEventListener(type: 'close', listener: (event: WebSocketCloseInfo) => void): void;
|
|
25
|
+
addEventListener(type: 'error', listener: () => void): void;
|
|
26
|
+
}
|
|
27
|
+
export declare function createEventWebSocketConnection(socket: EventWebSocket, protocol: string, options?: {
|
|
28
|
+
readonly terminate?: () => void;
|
|
29
|
+
readonly run?: <T>(operation: () => T) => Promise<T>;
|
|
30
|
+
}): WebSocketConnectionDriver;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export function createWebSocketDriverChannel(options) {
|
|
2
|
+
const queue = [];
|
|
3
|
+
let done = false;
|
|
4
|
+
let wake;
|
|
5
|
+
let resolveClosed;
|
|
6
|
+
const closed = new Promise((resolve) => {
|
|
7
|
+
resolveClosed = resolve;
|
|
8
|
+
});
|
|
9
|
+
return {
|
|
10
|
+
connection: {
|
|
11
|
+
...options,
|
|
12
|
+
closed,
|
|
13
|
+
messages: {
|
|
14
|
+
async *[Symbol.asyncIterator]() {
|
|
15
|
+
while (true) {
|
|
16
|
+
if (queue.length > 0) {
|
|
17
|
+
yield queue.shift();
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (done)
|
|
21
|
+
return;
|
|
22
|
+
await new Promise((resolve) => {
|
|
23
|
+
wake = resolve;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
receive(message) {
|
|
30
|
+
if (done)
|
|
31
|
+
return;
|
|
32
|
+
queue.push(message);
|
|
33
|
+
wake?.();
|
|
34
|
+
wake = undefined;
|
|
35
|
+
},
|
|
36
|
+
finish(info) {
|
|
37
|
+
if (done)
|
|
38
|
+
return;
|
|
39
|
+
done = true;
|
|
40
|
+
queue.length = 0;
|
|
41
|
+
resolveClosed(info);
|
|
42
|
+
wake?.();
|
|
43
|
+
wake = undefined;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function createEventWebSocketConnection(socket, protocol, options = {}) {
|
|
48
|
+
const run = (operation) => options.run ? options.run(operation) : Promise.resolve().then(operation);
|
|
49
|
+
const opened = socket.readyState === 1
|
|
50
|
+
? Promise.resolve()
|
|
51
|
+
: new Promise((resolve) => socket.addEventListener('open', resolve, { once: true }));
|
|
52
|
+
socket.binaryType = 'arraybuffer';
|
|
53
|
+
const channel = createWebSocketDriverChannel({
|
|
54
|
+
protocol,
|
|
55
|
+
async send(message) {
|
|
56
|
+
await Promise.race([
|
|
57
|
+
opened,
|
|
58
|
+
channel.connection.closed.then(() => {
|
|
59
|
+
throw new Error('WebSocketは終了しました。');
|
|
60
|
+
}),
|
|
61
|
+
]);
|
|
62
|
+
await run(() => {
|
|
63
|
+
if (socket.readyState !== 1)
|
|
64
|
+
throw new Error('WebSocketはopenではありません。');
|
|
65
|
+
socket.send(message.data);
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
async close(code = 1000, reason = '') {
|
|
69
|
+
await run(() => socket.close(code, reason));
|
|
70
|
+
},
|
|
71
|
+
async terminate() {
|
|
72
|
+
try {
|
|
73
|
+
await run(() => options.terminate
|
|
74
|
+
? options.terminate()
|
|
75
|
+
: socket.close(1001, 'Going Away'));
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
channel.finish({ code: 1006, reason: '', wasClean: false });
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
socket.addEventListener('message', ({ data }) => {
|
|
83
|
+
if (typeof data === 'string')
|
|
84
|
+
channel.receive({ type: 'text', data });
|
|
85
|
+
else if (data instanceof ArrayBuffer)
|
|
86
|
+
channel.receive({ type: 'binary', data: new Uint8Array(data) });
|
|
87
|
+
else if (ArrayBuffer.isView(data))
|
|
88
|
+
channel.receive({
|
|
89
|
+
type: 'binary',
|
|
90
|
+
data: new Uint8Array(data.buffer, data.byteOffset, data.byteLength),
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
socket.addEventListener('close', (info) => channel.finish(info));
|
|
94
|
+
socket.addEventListener('error', () => channel.finish({ code: 1006, reason: '', wasClean: false }));
|
|
95
|
+
return channel.connection;
|
|
96
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type ExecutionDefinition, type ExecutionExtensionDrainContext, type RuntimeCapabilityBinding, type SchemaOutput, type StandardSchemaV1 } from '../core/index.js';
|
|
2
|
-
import type {
|
|
1
|
+
import { type ExecutionDefinition, type ExecutionContextView, type ExecutionExtensionDrainContext, type RuntimeCapabilityBinding, type SchemaOutput, type StandardSchemaV1 } from '../core/index.js';
|
|
2
|
+
import type { HttpRequestHeadDefinition } from '../http/request-head.js';
|
|
3
3
|
import { type HttpPathSegment } from '../http/index.js';
|
|
4
4
|
export type WebSocketDataMessage = {
|
|
5
5
|
readonly type: 'text';
|
|
@@ -14,6 +14,7 @@ export interface WebSocketCloseInfo {
|
|
|
14
14
|
readonly wasClean: boolean;
|
|
15
15
|
}
|
|
16
16
|
export interface WebSocketConnectionDriver {
|
|
17
|
+
readonly protocol: string;
|
|
17
18
|
readonly messages: AsyncIterable<WebSocketDataMessage>;
|
|
18
19
|
readonly closed: Promise<WebSocketCloseInfo>;
|
|
19
20
|
send(message: WebSocketDataMessage): Promise<void>;
|
|
@@ -24,9 +25,16 @@ export interface WebSocketUpgradeResult {
|
|
|
24
25
|
readonly response: Response;
|
|
25
26
|
readonly connection: WebSocketConnectionDriver;
|
|
26
27
|
}
|
|
28
|
+
export interface WebSocketUpgradeOptions {
|
|
29
|
+
readonly protocol?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface WebSocketSession {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly protocol: string;
|
|
34
|
+
}
|
|
27
35
|
export interface WebSocketServerDriver {
|
|
28
36
|
readonly runtime: string;
|
|
29
|
-
upgrade(request: Request): Promise<WebSocketUpgradeResult>;
|
|
37
|
+
upgrade(request: Request, options: WebSocketUpgradeOptions): Promise<WebSocketUpgradeResult>;
|
|
30
38
|
}
|
|
31
39
|
export declare const WEBSOCKET_SERVER: import("../core/extension.js").RuntimeCapability<WebSocketServerDriver>;
|
|
32
40
|
export type WebSocketCodecKind = 'json' | 'text' | 'binary';
|
|
@@ -45,13 +53,14 @@ export type WebSocketIncomingMessage<TValue> = {
|
|
|
45
53
|
};
|
|
46
54
|
export interface WebSocketBranchDefinition {
|
|
47
55
|
readonly path?: string;
|
|
48
|
-
readonly responses?: Readonly<Record<string, HttpExecutionResponseDefinition>>;
|
|
49
56
|
readonly routes: WebSocketRouteTree;
|
|
50
57
|
}
|
|
58
|
+
export interface WebSocketHandshakeDefinition extends HttpRequestHeadDefinition {
|
|
59
|
+
readonly protocols?: readonly string[];
|
|
60
|
+
}
|
|
51
61
|
export interface WebSocketRouteDefinition {
|
|
52
62
|
readonly path: string;
|
|
53
|
-
readonly
|
|
54
|
-
readonly responses?: Readonly<Record<string, HttpExecutionResponseDefinition>>;
|
|
63
|
+
readonly handshake?: WebSocketHandshakeDefinition;
|
|
55
64
|
readonly messages?: WebSocketMessageCodec;
|
|
56
65
|
}
|
|
57
66
|
export type WebSocketRouteTree = Readonly<Record<string, WebSocketRouteDefinition | WebSocketBranchDefinition>>;
|
|
@@ -67,24 +76,28 @@ type SendApi<TRoute extends WebSocketRouteDefinition> = TRoute['messages'] exten
|
|
|
67
76
|
} : {} : {};
|
|
68
77
|
type OpeningRequestInput<TRoute extends WebSocketRouteDefinition> = {
|
|
69
78
|
readonly params: TRoute extends {
|
|
70
|
-
readonly
|
|
79
|
+
readonly handshake: {
|
|
71
80
|
readonly params: infer TParams extends Readonly<Record<string, StandardSchemaV1>>;
|
|
72
81
|
};
|
|
73
82
|
} ? {
|
|
74
83
|
readonly [TName in keyof TParams]: SchemaOutput<TParams[TName]>;
|
|
75
84
|
} : Readonly<Record<string, string>>;
|
|
76
85
|
readonly query: TRoute extends {
|
|
77
|
-
readonly
|
|
86
|
+
readonly handshake: {
|
|
78
87
|
readonly query: infer TQuery extends StandardSchemaV1;
|
|
79
88
|
};
|
|
80
89
|
} ? SchemaOutput<TQuery> : Readonly<Record<string, string | string[]>>;
|
|
81
90
|
readonly headers: TRoute extends {
|
|
82
|
-
readonly
|
|
91
|
+
readonly handshake: {
|
|
83
92
|
readonly headers: infer THeaders extends StandardSchemaV1;
|
|
84
93
|
};
|
|
85
94
|
} ? SchemaOutput<THeaders> : Headers;
|
|
86
95
|
};
|
|
87
96
|
export type WebSocketHandlerContext<TRoute extends WebSocketRouteDefinition = WebSocketRouteDefinition> = {
|
|
97
|
+
readonly request: Request;
|
|
98
|
+
readonly session: WebSocketSession;
|
|
99
|
+
readonly protocol: string;
|
|
100
|
+
readonly execution: ExecutionContextView;
|
|
88
101
|
readonly input: OpeningRequestInput<TRoute> & IncomingApi<TRoute>;
|
|
89
102
|
readonly signal: AbortSignal;
|
|
90
103
|
readonly closed: Promise<WebSocketCloseInfo>;
|
|
@@ -107,8 +120,7 @@ interface CompiledWebSocketRoute {
|
|
|
107
120
|
readonly path: string;
|
|
108
121
|
readonly normalizedPath: string;
|
|
109
122
|
readonly segments: readonly HttpPathSegment[];
|
|
110
|
-
readonly
|
|
111
|
-
readonly responses: Readonly<Record<string, HttpExecutionResponseDefinition>>;
|
|
123
|
+
readonly handshake?: WebSocketHandshakeDefinition;
|
|
112
124
|
readonly messages?: WebSocketMessageCodec;
|
|
113
125
|
}
|
|
114
126
|
interface CompiledWebSocketExecution {
|
|
@@ -125,7 +137,16 @@ export interface WebSocketHostApi {
|
|
|
125
137
|
}
|
|
126
138
|
export declare const websocketExtension: import("../core/extension.js").ExecutionExtension<WebSocketImplementationDefinition<WebSocketContract<Readonly<Record<string, WebSocketBranchDefinition | WebSocketRouteDefinition>>>> & ExecutionDefinition<import("../core/extension.js").AnyExecutionExtension>, CompiledWebSocketExecution, "websocket", WebSocketHostApi, WebSocketExtensionRuntime>;
|
|
127
139
|
export type WebSocketExecutionDefinition<TContract extends WebSocketContract = WebSocketContract> = WebSocketImplementationDefinition<TContract> & ExecutionDefinition<typeof websocketExtension>;
|
|
128
|
-
|
|
140
|
+
type WebSocketV2Tree<TTree extends WebSocketRouteTree> = {
|
|
141
|
+
readonly [K in keyof TTree]: TTree[K] extends WebSocketBranchDefinition ? TTree[K] & {
|
|
142
|
+
readonly responses?: never;
|
|
143
|
+
readonly routes: WebSocketV2Tree<TTree[K]['routes']>;
|
|
144
|
+
} : TTree[K] & {
|
|
145
|
+
readonly request?: never;
|
|
146
|
+
readonly responses?: never;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
export declare function defineWebSocketContract<const TRoutes extends WebSocketRouteTree>(routes: TRoutes & WebSocketV2Tree<TRoutes>): WebSocketContract<TRoutes>;
|
|
129
150
|
export declare function defineWebSocketImplementation<const TContract extends WebSocketContract>(definition: {
|
|
130
151
|
readonly name?: string;
|
|
131
152
|
readonly contract: TContract;
|