@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/http/openapi.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { supportsJsonSchema, assertValidApplicationModel, } from '../core/index.js';
|
|
2
|
-
import { httpExecutionExtension, } from './extension.js';
|
|
2
|
+
import { httpExecutionExtension, isHttpContract, } from './extension.js';
|
|
3
|
+
import { findHttpRouteConflicts } from './route-validation.js';
|
|
4
|
+
import { match } from 'ts-pattern';
|
|
3
5
|
const FIXED_METHODS = new Set([
|
|
4
6
|
'get',
|
|
5
7
|
'put',
|
|
@@ -12,24 +14,11 @@ const FIXED_METHODS = new Set([
|
|
|
12
14
|
'query',
|
|
13
15
|
]);
|
|
14
16
|
export function generateOpenApi(source, options) {
|
|
15
|
-
const model = 'model' in source ? source.model : source;
|
|
16
|
-
assertValidApplicationModel(model);
|
|
17
17
|
const registry = new SchemaRegistry();
|
|
18
18
|
const paths = {};
|
|
19
19
|
const operationIds = new Set();
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
for (const route of execution.compiled.routes) {
|
|
23
|
-
const target = {
|
|
24
|
-
procedure: route.name,
|
|
25
|
-
definition: {
|
|
26
|
-
...route.definition,
|
|
27
|
-
method: route.method,
|
|
28
|
-
path: route.path,
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
attachOperation(paths, target.definition, createOperation(target, registry, operationIds, options.operationId));
|
|
32
|
-
}
|
|
20
|
+
for (const target of httpOperationTargets(source)) {
|
|
21
|
+
attachOperation(paths, target.definition, createOperation(target, registry, operationIds, options.operationId));
|
|
33
22
|
}
|
|
34
23
|
const schemas = registry.components();
|
|
35
24
|
return {
|
|
@@ -40,6 +29,37 @@ export function generateOpenApi(source, options) {
|
|
|
40
29
|
...(Object.keys(schemas).length === 0 ? {} : { components: { schemas } }),
|
|
41
30
|
};
|
|
42
31
|
}
|
|
32
|
+
function httpOperationTargets(source) {
|
|
33
|
+
if (source.kind === 'http-contract') {
|
|
34
|
+
if (!isHttpContract(source)) {
|
|
35
|
+
throw openApiError('LUTRE_OPENAPI_SOURCE_001', 'HttpContract source must be created by http.contract().');
|
|
36
|
+
}
|
|
37
|
+
const routes = Object.entries(source.routes).map(([procedure, definition]) => ({ procedure, definition }));
|
|
38
|
+
const [conflict] = findHttpRouteConflicts(routes.map(({ definition }) => definition));
|
|
39
|
+
if (conflict) {
|
|
40
|
+
throw openApiError('LUTRE_OPENAPI_OPERATION_001', `Duplicate OpenAPI operation: ${conflict.route.method.toUpperCase()} ${conflict.route.path} conflicts with ${conflict.existing.method.toUpperCase()} ${conflict.existing.path}`);
|
|
41
|
+
}
|
|
42
|
+
return routes.map(({ procedure, definition }) => ({
|
|
43
|
+
procedure,
|
|
44
|
+
definition: {
|
|
45
|
+
...definition,
|
|
46
|
+
method: definition.method.toUpperCase(),
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
assertValidApplicationModel(source);
|
|
51
|
+
const http = source.extensions.get(httpExecutionExtension);
|
|
52
|
+
return (http?.executions ?? []).flatMap((execution) => execution.compiled.routes
|
|
53
|
+
.filter((route) => !route.raw)
|
|
54
|
+
.map((route) => ({
|
|
55
|
+
procedure: route.name,
|
|
56
|
+
definition: {
|
|
57
|
+
...route.definition,
|
|
58
|
+
method: route.method,
|
|
59
|
+
path: route.path,
|
|
60
|
+
},
|
|
61
|
+
})));
|
|
62
|
+
}
|
|
43
63
|
function createOperation(target, registry, operationIds, resolveOperationId) {
|
|
44
64
|
const definition = target.definition;
|
|
45
65
|
const operationId = resolveOperationId?.({
|
|
@@ -244,18 +264,17 @@ function mergeResponseHeaders(entries, registry, target) {
|
|
|
244
264
|
]));
|
|
245
265
|
}
|
|
246
266
|
function responseHeadersSchema(headers) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return undefined;
|
|
267
|
+
return match(headers)
|
|
268
|
+
.when(isStandardSchema, (schema) => schema)
|
|
269
|
+
.when(isResponseHeadersWithDefaults, (definition) => definition.schema)
|
|
270
|
+
.otherwise(() => undefined);
|
|
252
271
|
}
|
|
253
272
|
function responseHeadersDefaults(headers) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
273
|
+
return match(headers)
|
|
274
|
+
.with(undefined, () => undefined)
|
|
275
|
+
.when(isStandardSchema, () => undefined)
|
|
276
|
+
.when(isResponseHeadersWithDefaults, (definition) => definition.defaults)
|
|
277
|
+
.otherwise((definition) => definition);
|
|
259
278
|
}
|
|
260
279
|
function isStandardSchema(value) {
|
|
261
280
|
return typeof value === 'object' && value !== null && '~standard' in value;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface HttpRouteDescriptor {
|
|
2
|
+
readonly method: string;
|
|
3
|
+
readonly path: string;
|
|
4
|
+
}
|
|
5
|
+
export interface HttpRouteConflict<TRoute extends HttpRouteDescriptor> {
|
|
6
|
+
readonly existing: TRoute;
|
|
7
|
+
readonly route: TRoute;
|
|
8
|
+
}
|
|
9
|
+
export declare function findHttpRouteConflicts<TRoute extends HttpRouteDescriptor>(routes: readonly TRoute[]): readonly HttpRouteConflict<TRoute>[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createHttpDispatchKey, parseHttpPath } from './path.js';
|
|
2
|
+
export function findHttpRouteConflicts(routes) {
|
|
3
|
+
const dispatches = new Map();
|
|
4
|
+
const conflicts = [];
|
|
5
|
+
for (const route of routes) {
|
|
6
|
+
const dispatch = createHttpDispatchKey(route.method, parseHttpPath(route.path));
|
|
7
|
+
const existing = dispatches.get(dispatch) ??
|
|
8
|
+
[...dispatches.values()].find((candidate) => (route.method === '*' || candidate.method === '*') &&
|
|
9
|
+
createHttpDispatchKey('*', parseHttpPath(candidate.path)) ===
|
|
10
|
+
createHttpDispatchKey('*', parseHttpPath(route.path)));
|
|
11
|
+
if (existing) {
|
|
12
|
+
conflicts.push({ existing, route });
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
dispatches.set(dispatch, route);
|
|
16
|
+
}
|
|
17
|
+
return conflicts;
|
|
18
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validateSchema } from '../../core/index.js';
|
|
2
|
+
import { match } from 'ts-pattern';
|
|
2
3
|
export async function validateResponseHeaders(schema, headers) {
|
|
3
4
|
if (!schema) {
|
|
4
5
|
if (headers !== undefined) {
|
|
@@ -15,18 +16,17 @@ export async function validateResponseHeaders(schema, headers) {
|
|
|
15
16
|
return validated;
|
|
16
17
|
}
|
|
17
18
|
export function responseHeadersSchema(headers) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return undefined;
|
|
19
|
+
return match(headers)
|
|
20
|
+
.when(isStandardSchema, (schema) => schema)
|
|
21
|
+
.when(isResponseHeadersWithDefaults, (definition) => definition.schema)
|
|
22
|
+
.otherwise(() => undefined);
|
|
23
23
|
}
|
|
24
24
|
export function responseHeadersDefaults(headers) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
return match(headers)
|
|
26
|
+
.with(undefined, () => undefined)
|
|
27
|
+
.when(isStandardSchema, () => undefined)
|
|
28
|
+
.when(isResponseHeadersWithDefaults, (definition) => definition.defaults)
|
|
29
|
+
.otherwise((definition) => definition);
|
|
30
30
|
}
|
|
31
31
|
export function isStandardSchema(value) {
|
|
32
32
|
return typeof value === 'object' && value !== null && '~standard' in value;
|
package/dist/runtime/di.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isArgsClass, isEnvClass, runInInjectionContext, tokenName, } from '../core/index.js';
|
|
2
2
|
import { Logger } from './logger.js';
|
|
3
|
+
import { match } from 'ts-pattern';
|
|
3
4
|
export class DependencyResolutionError extends Error {
|
|
4
5
|
constructor(message) {
|
|
5
6
|
super(message);
|
|
@@ -133,49 +134,49 @@ export class Container {
|
|
|
133
134
|
}
|
|
134
135
|
#create(provider, lineage) {
|
|
135
136
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
return match(provider)
|
|
138
|
+
.with({ kind: 'value' }, (candidate) => {
|
|
139
|
+
this.#hooks.created?.(candidate, candidate.useValue);
|
|
140
|
+
return candidate.useValue;
|
|
141
|
+
})
|
|
142
|
+
.with({ kind: 'environment' }, (candidate) => {
|
|
143
|
+
if (this.#environment.has(candidate.provide)) {
|
|
144
|
+
return this.#environment.get(candidate.provide);
|
|
140
145
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
throw new DependencyResolutionError(`LUTRE_ENV_005: Environment ${candidate.provide.name} requires a runtime Environment source before Application initialization.`);
|
|
147
|
+
})
|
|
148
|
+
.with({ kind: 'arguments' }, (candidate) => {
|
|
149
|
+
if (this.#arguments.has(candidate.provide)) {
|
|
150
|
+
return this.#arguments.get(candidate.provide);
|
|
146
151
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
throw new DependencyResolutionError(`LUTRE_ARGS_005: Arguments ${candidate.provide.name} requires runtime Arguments before Application initialization.`);
|
|
153
|
+
})
|
|
154
|
+
.with({ kind: 'class' }, (candidate) => {
|
|
155
|
+
const value = this.#instantiate(candidate.useClass, lineage);
|
|
156
|
+
this.#hooks.created?.(candidate, value);
|
|
157
|
+
return value;
|
|
158
|
+
})
|
|
159
|
+
.with({ kind: 'factory' }, (candidate) => {
|
|
160
|
+
const dependencies = candidate.inject.map((token) => this.#resolve(token, tokenName(candidate.provide), lineage));
|
|
161
|
+
const value = candidate.useFactory(...dependencies);
|
|
162
|
+
if (isThenable(value)) {
|
|
163
|
+
throw new DependencyResolutionError('LUTRE_DI_ASYNC_FACTORY: Async factory providers are not supported. Move asynchronous resource initialization to application lifecycle.');
|
|
152
164
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
165
|
+
this.#hooks.created?.(candidate, value);
|
|
166
|
+
return value;
|
|
167
|
+
})
|
|
168
|
+
.with({ kind: 'conditional' }, (candidate) => {
|
|
169
|
+
const input = this.#resolve(candidate.select.contract, tokenName(candidate.provide), lineage);
|
|
170
|
+
const selected = input[candidate.select.key];
|
|
171
|
+
const implementation = candidate.mapping[selected];
|
|
172
|
+
if (!implementation) {
|
|
173
|
+
throw new DependencyResolutionError(`No conditional Provider matches ${candidate.select.key}=${String(selected)}`);
|
|
157
174
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
this.#hooks.created?.(provider, value);
|
|
165
|
-
return value;
|
|
166
|
-
}
|
|
167
|
-
case 'conditional': {
|
|
168
|
-
const input = this.#resolve(provider.select.contract, tokenName(provider.provide), lineage);
|
|
169
|
-
const selected = input[provider.select.key];
|
|
170
|
-
const implementation = provider.mapping[selected];
|
|
171
|
-
if (!implementation) {
|
|
172
|
-
throw new DependencyResolutionError(`No conditional Provider matches ${provider.select.key}=${String(selected)}`);
|
|
173
|
-
}
|
|
174
|
-
const value = this.#instantiate(implementation, lineage);
|
|
175
|
-
this.#hooks.created?.(provider, value);
|
|
176
|
-
return value;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
175
|
+
const value = this.#instantiate(implementation, lineage);
|
|
176
|
+
this.#hooks.created?.(candidate, value);
|
|
177
|
+
return value;
|
|
178
|
+
})
|
|
179
|
+
.exhaustive();
|
|
179
180
|
}
|
|
180
181
|
catch (error) {
|
|
181
182
|
if (provider.scope === 'application') {
|
package/dist/runtime/engine.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type RuntimeEngine = 'node' | 'bun' | '
|
|
1
|
+
export type RuntimeEngine = 'node' | 'bun' | 'cloudflare-workers' | 'aws-lambda' | 'electron' | 'unknown';
|
|
2
2
|
export declare function detectRuntimeEngine(): RuntimeEngine;
|
|
3
3
|
export declare function assertRuntimeEngine(expected: Exclude<RuntimeEngine, 'unknown'>): void;
|
package/dist/runtime/engine.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const runtimeLabels = {
|
|
2
2
|
node: 'Node.js',
|
|
3
3
|
bun: 'Bun',
|
|
4
|
-
deno: 'Deno',
|
|
5
4
|
'cloudflare-workers': 'Cloudflare Workers',
|
|
6
5
|
'aws-lambda': 'AWS Lambda',
|
|
7
6
|
electron: 'Electron',
|
|
@@ -14,7 +13,7 @@ export function detectRuntimeEngine() {
|
|
|
14
13
|
return 'bun';
|
|
15
14
|
}
|
|
16
15
|
if (globals.Deno !== undefined)
|
|
17
|
-
return '
|
|
16
|
+
return 'unknown';
|
|
18
17
|
if (globals.navigator?.userAgent === 'Cloudflare-Workers' ||
|
|
19
18
|
globals.WebSocketPair !== undefined) {
|
|
20
19
|
return 'cloudflare-workers';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** CLIが`@loutrejs/node`へ依存するとBun
|
|
1
|
+
/** CLIが`@loutrejs/node`へ依存するとBunでもNode.js専用moduleを評価するため、Capability metadataだけをruntime-neutral側へ分離する。 */
|
|
2
2
|
export declare const nodeRuntimeSupport: {
|
|
3
3
|
readonly runtime: 'node';
|
|
4
4
|
readonly capabilities: Set<string>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
/** CLIが`@loutrejs/node`へ依存するとBun
|
|
1
|
+
/** CLIが`@loutrejs/node`へ依存するとBunでもNode.js専用moduleを評価するため、Capability metadataだけをruntime-neutral側へ分離する。 */
|
|
2
2
|
export const nodeRuntimeSupport = {
|
|
3
3
|
runtime: 'node',
|
|
4
4
|
capabilities: new Set([
|
|
5
5
|
'http.server',
|
|
6
|
+
'websocket.server',
|
|
6
7
|
'http.request.streaming',
|
|
7
8
|
'http.response.streaming',
|
|
8
9
|
'stream.readable',
|
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',
|
|
@@ -415,14 +407,11 @@ function createTasksRuntime(executions, applicationRuntime) {
|
|
|
415
407
|
};
|
|
416
408
|
}
|
|
417
409
|
async function startTrigger(trigger, run, applicationRuntime) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
case 'queue-consumer':
|
|
424
|
-
return startQueueTrigger(trigger, run, applicationRuntime);
|
|
425
|
-
}
|
|
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();
|
|
426
415
|
}
|
|
427
416
|
function startCronTrigger(trigger, run) {
|
|
428
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;
|