@loutrejs/loutre 0.7.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.
Files changed (48) hide show
  1. package/dist/adapters/bun.d.ts +2 -4
  2. package/dist/adapters/bun.js +36 -28
  3. package/dist/adapters/cloudflare-workers.d.ts +2 -4
  4. package/dist/adapters/cloudflare-workers.js +6 -11
  5. package/dist/adapters/server-transports.d.ts +4 -0
  6. package/dist/adapters/server-transports.js +18 -0
  7. package/dist/adapters/websocket-native.d.ts +24 -0
  8. package/dist/adapters/websocket-native.js +150 -0
  9. package/dist/core/extension.d.ts +13 -1
  10. package/dist/core/extension.js +19 -0
  11. package/dist/core/index.d.ts +1 -1
  12. package/dist/core/model/modules.js +13 -17
  13. package/dist/core/model.js +14 -4
  14. package/dist/core/module.d.ts +3 -3
  15. package/dist/devtools/capsule.js +13 -11
  16. package/dist/devtools/values.js +10 -13
  17. package/dist/generated/version.d.ts +1 -1
  18. package/dist/generated/version.js +1 -1
  19. package/dist/graph/model.js +37 -40
  20. package/dist/http/auth.d.ts +2 -2
  21. package/dist/http/auth.js +9 -0
  22. package/dist/http/client.js +5 -5
  23. package/dist/http/extension.d.ts +37 -10
  24. package/dist/http/extension.js +117 -39
  25. package/dist/http/index.d.ts +3 -2
  26. package/dist/http/index.js +1 -1
  27. package/dist/http/openapi.d.ts +3 -4
  28. package/dist/http/openapi.js +45 -26
  29. package/dist/http/request-head.d.ts +6 -0
  30. package/dist/http/request-head.js +1 -0
  31. package/dist/http/route-validation.d.ts +9 -0
  32. package/dist/http/route-validation.js +18 -0
  33. package/dist/http/runtime/response.js +10 -10
  34. package/dist/runtime/di.js +40 -39
  35. package/dist/runtime/engine.d.ts +1 -1
  36. package/dist/runtime/engine.js +1 -2
  37. package/dist/runtime/node-capabilities.d.ts +1 -1
  38. package/dist/runtime/node-capabilities.js +2 -1
  39. package/dist/tasks/extension.js +103 -114
  40. package/dist/websocket/driver.d.ts +30 -0
  41. package/dist/websocket/driver.js +96 -0
  42. package/dist/websocket/extension.d.ts +33 -12
  43. package/dist/websocket/extension.js +60 -44
  44. package/dist/websocket/index.d.ts +3 -1
  45. package/dist/websocket/index.js +1 -0
  46. package/package.json +3 -6
  47. package/dist/adapters/deno.d.ts +0 -35
  48. package/dist/adapters/deno.js +0 -231
@@ -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 http = model.extensions.get(httpExecutionExtension);
21
- for (const execution of http?.executions ?? []) {
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
- if (isStandardSchema(headers))
248
- return headers;
249
- if (isResponseHeadersWithDefaults(headers))
250
- return headers.schema;
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
- if (headers === undefined || isStandardSchema(headers))
255
- return undefined;
256
- if (isResponseHeadersWithDefaults(headers))
257
- return headers.defaults;
258
- return headers;
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,6 @@
1
+ import type { StandardSchemaV1 } from '../core/index.js';
2
+ export interface HttpRequestHeadDefinition {
3
+ readonly params?: Readonly<Record<string, StandardSchemaV1>>;
4
+ readonly query?: StandardSchemaV1;
5
+ readonly headers?: StandardSchemaV1;
6
+ }
@@ -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
- if (isStandardSchema(headers))
19
- return headers;
20
- if (isResponseHeadersWithDefaults(headers))
21
- return headers.schema;
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
- if (headers === undefined || isStandardSchema(headers))
26
- return undefined;
27
- if (isResponseHeadersWithDefaults(headers))
28
- return headers.defaults;
29
- return headers;
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;
@@ -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
- switch (provider.kind) {
137
- case 'value': {
138
- this.#hooks.created?.(provider, provider.useValue);
139
- return provider.useValue;
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
- case 'environment': {
142
- if (this.#environment.has(provider.provide)) {
143
- return this.#environment.get(provider.provide);
144
- }
145
- throw new DependencyResolutionError(`LUTRE_ENV_005: Environment ${provider.provide.name} requires a runtime Environment source before Application initialization.`);
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
- case 'arguments': {
148
- if (this.#arguments.has(provider.provide)) {
149
- return this.#arguments.get(provider.provide);
150
- }
151
- throw new DependencyResolutionError(`LUTRE_ARGS_005: Arguments ${provider.provide.name} requires runtime Arguments before Application initialization.`);
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
- case 'class': {
154
- const value = this.#instantiate(provider.useClass, lineage);
155
- this.#hooks.created?.(provider, value);
156
- return value;
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
- case 'factory': {
159
- const dependencies = provider.inject.map((token) => this.#resolve(token, tokenName(provider.provide), lineage));
160
- const value = provider.useFactory(...dependencies);
161
- if (isThenable(value)) {
162
- throw new DependencyResolutionError('LUTRE_DI_ASYNC_FACTORY: Async factory providers are not supported. Move asynchronous resource initialization to application lifecycle.');
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') {
@@ -1,3 +1,3 @@
1
- export type RuntimeEngine = 'node' | 'bun' | 'deno' | 'cloudflare-workers' | 'aws-lambda' | 'electron' | 'unknown';
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;
@@ -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 'deno';
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/DenoでもNode.js専用moduleを評価するため、Capability metadataだけをruntime-neutral側へ分離する。 */
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/DenoでもNode.js専用moduleを評価するため、Capability metadataだけをruntime-neutral側へ分離する。 */
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',
@@ -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
- switch (definition.type) {
11
- case 'task':
12
- return {
13
- kind: 'execution',
14
- id: `task.${definition.name}`,
15
- executionKind: 'task.invocation',
16
- dependencies: collectInjectedDependencies({
17
- kind: 'execution',
18
- id: `task:${definition.name}`,
19
- name: definition.name,
20
- }, () => definition.factory()),
21
- capabilities: [],
22
- compiled: Object.freeze({
23
- type: 'task',
24
- taskExecutionId: definition[taskExecutionIdentityKey],
25
- name: definition.name,
26
- factory: definition.factory,
27
- }),
28
- };
29
- case 'cron':
30
- return {
31
- kind: 'execution',
32
- id: `trigger.${definition.name}`,
33
- executionKind: 'trigger.cron',
34
- dependencies: [],
35
- capabilities: [],
36
- compiled: Object.freeze({
37
- type: 'cron',
38
- name: definition.name,
39
- expression: definition.expression,
40
- timezone: definition.timezone,
41
- overlap: definition.overlap,
42
- taskExecutionId: definition.task[taskExecutionIdentityKey],
43
- taskName: definition.task.name,
44
- }),
45
- };
46
- case 'fixed-delay':
47
- return {
48
- kind: 'execution',
49
- id: `trigger.${definition.name}`,
50
- executionKind: 'trigger.fixed-delay',
51
- dependencies: [],
52
- capabilities: [],
53
- compiled: Object.freeze({
54
- type: 'fixed-delay',
55
- name: definition.name,
56
- delay: definition.delay,
57
- immediate: definition.immediate,
58
- taskExecutionId: definition.task[taskExecutionIdentityKey],
59
- taskName: definition.task.name,
60
- }),
61
- };
62
- case 'queue-consumer':
63
- return {
64
- kind: 'execution',
65
- id: `trigger.${definition.name}`,
66
- executionKind: 'trigger.queue-consumer',
67
- dependencies: [queueRuntimeToken(definition.queue)],
68
- capabilities: [],
69
- compiled: Object.freeze({
70
- type: 'queue-consumer',
71
- name: definition.name,
72
- queueName: definition.queue.name,
73
- queuePayload: definition.queue.payload,
74
- queueDriver: queueRuntimeToken(definition.queue),
75
- taskExecutionId: definition.task[taskExecutionIdentityKey],
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
- switch (definition.type) {
83
- case 'task':
84
- return [];
85
- case 'cron':
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
- switch (compiled.type) {
137
- case 'task':
138
- return { type: 'task', name: compiled.name };
139
- case 'cron':
140
- return {
141
- type: 'cron',
142
- name: compiled.name,
143
- expression: compiled.expression,
144
- timezone: compiled.timezone,
145
- overlap: compiled.overlap,
146
- task: compiled.taskName,
147
- };
148
- case 'fixed-delay':
149
- return {
150
- type: 'fixed-delay',
151
- name: compiled.name,
152
- delay: compiled.delay,
153
- immediate: compiled.immediate,
154
- task: compiled.taskName,
155
- };
156
- case 'queue-consumer':
157
- return {
158
- type: 'queue-consumer',
159
- name: compiled.name,
160
- queue: compiled.queueName,
161
- task: compiled.taskName,
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
- switch (trigger.type) {
419
- case 'cron':
420
- return startCronTrigger(trigger, run);
421
- case 'fixed-delay':
422
- return startFixedDelayTrigger(trigger, run);
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;