@cosmneo/onion-lasagna 1.0.0-beta.2 → 1.0.0-beta.3
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/CHANGELOG.md +10 -0
- package/dist/chunk-5NPX4ERK.js +56 -0
- package/dist/chunk-5NPX4ERK.js.map +1 -0
- package/dist/chunk-DKXZIFAG.js +236 -0
- package/dist/chunk-DKXZIFAG.js.map +1 -0
- package/dist/chunk-PS7ZFTW5.js +130 -0
- package/dist/chunk-PS7ZFTW5.js.map +1 -0
- package/dist/chunk-T4FBLQWF.js +9 -0
- package/dist/chunk-T4FBLQWF.js.map +1 -0
- package/dist/chunk-VC25BOVP.js +44 -0
- package/dist/chunk-VC25BOVP.js.map +1 -0
- package/dist/chunk-WMOQNNF5.js +1 -0
- package/dist/chunk-WMOQNNF5.js.map +1 -0
- package/dist/chunk-YFD3K4BS.js +38 -0
- package/dist/chunk-YFD3K4BS.js.map +1 -0
- package/dist/graphql/index.js +9 -9
- package/dist/index.js +9 -9
- package/dist/schedule/catalog/index.cjs +91 -0
- package/dist/schedule/catalog/index.cjs.map +1 -0
- package/dist/schedule/catalog/index.d.cts +69 -0
- package/dist/schedule/catalog/index.d.ts +69 -0
- package/dist/schedule/catalog/index.js +8 -0
- package/dist/schedule/catalog/index.js.map +1 -0
- package/dist/schedule/index.cjs +533 -0
- package/dist/schedule/index.cjs.map +1 -0
- package/dist/schedule/index.d.cts +9 -0
- package/dist/schedule/index.d.ts +9 -0
- package/dist/schedule/index.js +57 -0
- package/dist/schedule/index.js.map +1 -0
- package/dist/schedule/server/index.cjs +357 -0
- package/dist/schedule/server/index.cjs.map +1 -0
- package/dist/schedule/server/index.d.cts +335 -0
- package/dist/schedule/server/index.d.ts +335 -0
- package/dist/schedule/server/index.js +25 -0
- package/dist/schedule/server/index.js.map +1 -0
- package/dist/schedule/shared/index.cjs +98 -0
- package/dist/schedule/shared/index.cjs.map +1 -0
- package/dist/schedule/shared/index.d.cts +46 -0
- package/dist/schedule/shared/index.d.ts +46 -0
- package/dist/schedule/shared/index.js +13 -0
- package/dist/schedule/shared/index.js.map +1 -0
- package/dist/schedule/task/index.cjs +201 -0
- package/dist/schedule/task/index.cjs.map +1 -0
- package/dist/schedule/task/index.d.cts +184 -0
- package/dist/schedule/task/index.d.ts +184 -0
- package/dist/schedule/task/index.js +29 -0
- package/dist/schedule/task/index.js.map +1 -0
- package/dist/schedule-trigger.type-B_3Hmssm.d.ts +215 -0
- package/dist/schedule-trigger.type-DoMIjYOE.d.cts +215 -0
- package/dist/types-CuMzQKms.d.cts +58 -0
- package/dist/types-CuMzQKms.d.ts +58 -0
- package/package.json +31 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @cosmneo/onion-lasagna
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.3
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4f6bb6b: Add a `schedule` transport tier (scheduled work: recurring crons + one-time timers) — the fourth tier alongside `http`, `graphql`, and `events`.
|
|
8
|
+
|
|
9
|
+
Define a **task** (`defineScheduledTask`, what runs) and bind it with a separate **trigger** (`defineScheduleTriggers`, when/how), group with `defineScheduleRouter`/`mergeScheduleRouters`, wire handlers via `scheduleRoutes(router).handle/.handleWithUseCase().build()` → `UnifiedScheduleInput[]`, and run a fired schedule with the invocation primitives (`indexScheduleRoutes`, `findScheduleRoute`, `invokeScheduledTask`, `resolveScheduleTrigger`). A run returns a `ScheduleResult` (`completed | skipped | retry | failed`); `skipped` is only ever an explicit handler decision, and default error mapping classifies validation/domain errors as `failed` and transient/unknown as `retry`. `generateScheduleCatalog` produces a validated task+trigger inventory.
|
|
10
|
+
|
|
11
|
+
New subpath exports: `@cosmneo/onion-lasagna/schedule`, `/schedule/task`, `/schedule/server`, `/schedule/shared`, `/schedule/catalog`. The tier is execution-only (scheduling/cancellation, locking/overlap, and retry policy are deliberately out of scope and left to the app/delivery layer).
|
|
12
|
+
|
|
3
13
|
## 1.0.0-beta.2
|
|
4
14
|
|
|
5
15
|
## 1.0.0-beta.1
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isErrorType
|
|
3
|
+
} from "./chunk-RUDRJY45.js";
|
|
4
|
+
|
|
5
|
+
// src/presentation/schedule/shared/error-mapping.ts
|
|
6
|
+
var FAILED_ERROR_TYPES = [
|
|
7
|
+
"ObjectValidationError",
|
|
8
|
+
"InvalidRequestError",
|
|
9
|
+
"UseCaseError",
|
|
10
|
+
"DomainError",
|
|
11
|
+
"UnprocessableError",
|
|
12
|
+
"AccessDeniedError",
|
|
13
|
+
"ForbiddenError",
|
|
14
|
+
"UnauthorizedError",
|
|
15
|
+
"InvariantViolationError",
|
|
16
|
+
// DomainError subclasses
|
|
17
|
+
"PartialLoadError"
|
|
18
|
+
];
|
|
19
|
+
var RETRY_ERROR_TYPES = [
|
|
20
|
+
"NotFoundError",
|
|
21
|
+
"ConflictError",
|
|
22
|
+
"InfraError",
|
|
23
|
+
"DbError",
|
|
24
|
+
"NetworkError",
|
|
25
|
+
"TimeoutError",
|
|
26
|
+
"ExternalServiceError"
|
|
27
|
+
];
|
|
28
|
+
function mapErrorToScheduleResult(error) {
|
|
29
|
+
for (const typeName of FAILED_ERROR_TYPES) {
|
|
30
|
+
if (isErrorType(error, typeName)) {
|
|
31
|
+
return {
|
|
32
|
+
outcome: "failed",
|
|
33
|
+
reason: error.message,
|
|
34
|
+
errorType: typeName
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const typeName of RETRY_ERROR_TYPES) {
|
|
39
|
+
if (isErrorType(error, typeName)) {
|
|
40
|
+
return {
|
|
41
|
+
outcome: "retry",
|
|
42
|
+
reason: error.message
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred during scheduled task run";
|
|
47
|
+
return {
|
|
48
|
+
outcome: "retry",
|
|
49
|
+
reason: message
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
mapErrorToScheduleResult
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=chunk-5NPX4ERK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/presentation/schedule/shared/error-mapping.ts"],"sourcesContent":["/**\n * @fileoverview Error-to-ScheduleResult mapping for scheduled tasks.\n *\n * Maps caught errors to schedule run outcomes (failed/retry). Uses the same\n * name-based type checking as the HTTP error mapper for compatibility with\n * bundled/minified code.\n *\n * CRITICAL: validation/domain failures map to `failed`, NOT `skipped`.\n * `skipped` is ONLY producible by an explicit handler/resultMapper return\n * (\"nothing due\") — never by error mapping. Error mapping only ever yields\n * `failed` (permanent) or `retry` (transient/unknown — conservative so we\n * never silently lose a run).\n *\n * @module schedule/shared/error-mapping\n */\n\nimport { isErrorType } from '../../http/shared/error-mapping';\nimport type { ScheduleResult } from './types';\n\n// ============================================================================\n// Error Type Classifications\n// ============================================================================\n\n/**\n * Error types that indicate permanent failures (`failed`).\n * These errors will not resolve on retry.\n * Includes concrete DomainError subclasses so cross-realm name-checks work\n * even when instanceof fails.\n */\nconst FAILED_ERROR_TYPES = [\n 'ObjectValidationError',\n 'InvalidRequestError',\n 'UseCaseError',\n 'DomainError',\n 'UnprocessableError',\n 'AccessDeniedError',\n 'ForbiddenError',\n 'UnauthorizedError',\n 'InvariantViolationError',\n // DomainError subclasses\n 'PartialLoadError',\n];\n\n/**\n * Error types that indicate transient failures (`retry`).\n * These errors may resolve on subsequent attempts.\n */\nconst RETRY_ERROR_TYPES = [\n 'NotFoundError',\n 'ConflictError',\n 'InfraError',\n 'DbError',\n 'NetworkError',\n 'TimeoutError',\n 'ExternalServiceError',\n];\n\n// ============================================================================\n// Mapping Function\n// ============================================================================\n\n/**\n * Maps a caught error to a ScheduleResult.\n *\n * Default mapping strategy:\n *\n * | Error Type | Outcome | Rationale |\n * |-------------------------------------------------|---------|----------------------------------------|\n * | ObjectValidationError, InvalidRequestError | failed | Bad payload — retrying won't help |\n * | UseCaseError | failed | Business rule rejection — permanent |\n * | DomainError, InvariantViolationError | failed | Domain invariant — permanent |\n * | UnprocessableError | failed | Valid but not processable — permanent |\n * | AccessDeniedError, ForbiddenError, Unauthorized | failed | Permission — permanent |\n * | PartialLoadError | failed | Domain — permanent |\n * | NotFoundError | retry | Entity might not exist yet |\n * | ConflictError | retry | Concurrent write — may resolve |\n * | InfraError, DbError, NetworkError, TimeoutError | retry | Infrastructure/transient |\n * | ExternalServiceError | retry | Downstream transient |\n * | Unknown | retry | Conservative — don't lose runs |\n *\n * NOTE: `skipped` is NEVER returned here. A run that finds \"nothing due\" must\n * return `{ outcome: 'skipped', reason }` explicitly from its handler.\n *\n * @param error - The caught error\n * @returns A ScheduleResult indicating how the run should be handled\n */\nexport function mapErrorToScheduleResult(error: unknown): ScheduleResult {\n // Check failed errors first (permanent failures)\n for (const typeName of FAILED_ERROR_TYPES) {\n if (isErrorType(error, typeName)) {\n return {\n outcome: 'failed',\n reason: (error as { message: string }).message,\n errorType: typeName,\n };\n }\n }\n\n // Check retry errors (transient failures)\n for (const typeName of RETRY_ERROR_TYPES) {\n if (isErrorType(error, typeName)) {\n return {\n outcome: 'retry',\n reason: (error as { message: string }).message,\n };\n }\n }\n\n // Unknown errors — retry conservatively to avoid losing runs.\n const message =\n error instanceof Error ? error.message : 'Unknown error occurred during scheduled task run';\n\n return {\n outcome: 'retry',\n reason: message,\n };\n}\n"],"mappings":";;;;;AA6BA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AACF;AAMA,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA+BO,SAAS,yBAAyB,OAAgC;AAEvE,aAAW,YAAY,oBAAoB;AACzC,QAAI,YAAY,OAAO,QAAQ,GAAG;AAChC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAS,MAA8B;AAAA,QACvC,WAAW;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAGA,aAAW,YAAY,mBAAmB;AACxC,QAAI,YAAY,OAAO,QAAQ,GAAG;AAChC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAS,MAA8B;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU;AAE3C,SAAO;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AACF;","names":[]}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateTaskId
|
|
3
|
+
} from "./chunk-T4FBLQWF.js";
|
|
4
|
+
import {
|
|
5
|
+
mapErrorToScheduleResult
|
|
6
|
+
} from "./chunk-5NPX4ERK.js";
|
|
7
|
+
import {
|
|
8
|
+
collectScheduledTasks,
|
|
9
|
+
isCronScheduleTrigger,
|
|
10
|
+
isRateScheduleTrigger,
|
|
11
|
+
isScheduleRouterDefinition
|
|
12
|
+
} from "./chunk-YFD3K4BS.js";
|
|
13
|
+
|
|
14
|
+
// src/presentation/schedule/server/types.ts
|
|
15
|
+
function isSimpleScheduledTaskConfig(config) {
|
|
16
|
+
return "handler" in config && typeof config.handler === "function";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/presentation/schedule/server/create-schedule-routes.ts
|
|
20
|
+
function createScheduleRoutesInternal(router, tasks, options) {
|
|
21
|
+
const config = isScheduleRouterDefinition(router) ? router.tasks : router;
|
|
22
|
+
const collectedTasks = collectScheduledTasks(config);
|
|
23
|
+
const result = [];
|
|
24
|
+
const resolvedOptions = {
|
|
25
|
+
...options,
|
|
26
|
+
validatePayload: options?.validatePayload ?? true,
|
|
27
|
+
allowPartial: options?.allowPartial ?? false
|
|
28
|
+
};
|
|
29
|
+
for (const { key, task: taskDef } of collectedTasks) {
|
|
30
|
+
const taskConfig = tasks[key];
|
|
31
|
+
if (!taskConfig) {
|
|
32
|
+
if (resolvedOptions.allowPartial) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Missing handler for scheduled task "${key}". All scheduled tasks must have a handler configuration.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
result.push(createScheduledTask(key, taskDef, taskConfig, resolvedOptions));
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
function createScheduledTask(key, taskDef, config, options) {
|
|
44
|
+
const middleware = config.middleware ?? [];
|
|
45
|
+
const globalMiddleware = options?.middleware ?? [];
|
|
46
|
+
const allMiddleware = [...globalMiddleware, ...middleware];
|
|
47
|
+
const shouldValidatePayload = options.validatePayload ?? true;
|
|
48
|
+
const errorMapper = options.errorMapper ?? mapErrorToScheduleResult;
|
|
49
|
+
return {
|
|
50
|
+
type: taskDef.type,
|
|
51
|
+
metadata: {
|
|
52
|
+
taskId: generateTaskId(key),
|
|
53
|
+
summary: taskDef.docs.summary,
|
|
54
|
+
description: taskDef.docs.description,
|
|
55
|
+
tags: taskDef.docs.tags,
|
|
56
|
+
deprecated: taskDef.docs.deprecated
|
|
57
|
+
},
|
|
58
|
+
handler: async (raw) => {
|
|
59
|
+
try {
|
|
60
|
+
let validatedContext = raw.metadata;
|
|
61
|
+
if (taskDef.context) {
|
|
62
|
+
const contextResult = validateContextData(taskDef, raw.metadata);
|
|
63
|
+
if (!contextResult.success) {
|
|
64
|
+
const errors = contextResult.errors ?? [];
|
|
65
|
+
return {
|
|
66
|
+
outcome: "failed",
|
|
67
|
+
reason: `Context validation failed: ${errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
validatedContext = contextResult.data;
|
|
71
|
+
}
|
|
72
|
+
let validatedPayload = raw.payload;
|
|
73
|
+
if (shouldValidatePayload && taskDef.payload) {
|
|
74
|
+
const payloadResult = validatePayloadData(taskDef, raw.payload);
|
|
75
|
+
if (!payloadResult.success) {
|
|
76
|
+
const errors = payloadResult.errors ?? [];
|
|
77
|
+
return {
|
|
78
|
+
outcome: "failed",
|
|
79
|
+
reason: `Payload validation failed: ${errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
validatedPayload = payloadResult.data;
|
|
83
|
+
}
|
|
84
|
+
const validatedSchedule = {
|
|
85
|
+
payload: validatedPayload,
|
|
86
|
+
raw
|
|
87
|
+
};
|
|
88
|
+
const executePipeline = async () => {
|
|
89
|
+
if (isSimpleScheduledTaskConfig(config)) {
|
|
90
|
+
return config.handler(
|
|
91
|
+
validatedSchedule,
|
|
92
|
+
validatedContext
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
const { payloadMapper, useCase, resultMapper } = config;
|
|
96
|
+
const input = payloadMapper(
|
|
97
|
+
validatedSchedule,
|
|
98
|
+
validatedContext
|
|
99
|
+
);
|
|
100
|
+
const output = await useCase.execute(input);
|
|
101
|
+
return resultMapper ? resultMapper(output) : { outcome: "completed" };
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
if (allMiddleware.length === 0) {
|
|
105
|
+
return await executePipeline();
|
|
106
|
+
}
|
|
107
|
+
let index = 0;
|
|
108
|
+
const next = async () => {
|
|
109
|
+
if (index >= allMiddleware.length) {
|
|
110
|
+
return executePipeline();
|
|
111
|
+
}
|
|
112
|
+
const mw = allMiddleware[index++];
|
|
113
|
+
return mw(raw, next);
|
|
114
|
+
};
|
|
115
|
+
return await next();
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const mapped = errorMapper(error);
|
|
118
|
+
return mapped.outcome === "skipped" ? { outcome: "failed", reason: mapped.reason } : mapped;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function validatePayloadData(task, payload) {
|
|
124
|
+
const schema = task.payload;
|
|
125
|
+
if (!schema) return { success: true, data: payload };
|
|
126
|
+
const result = schema.validate(payload);
|
|
127
|
+
if (result.success) {
|
|
128
|
+
return { success: true, data: result.data };
|
|
129
|
+
}
|
|
130
|
+
const errors = result.issues.map((issue) => ({
|
|
131
|
+
...issue,
|
|
132
|
+
path: ["payload", ...issue.path]
|
|
133
|
+
}));
|
|
134
|
+
return { success: false, errors };
|
|
135
|
+
}
|
|
136
|
+
function validateContextData(task, metadata) {
|
|
137
|
+
const schema = task.context;
|
|
138
|
+
if (!schema) return { success: true, data: metadata };
|
|
139
|
+
const result = schema.validate(metadata);
|
|
140
|
+
if (result.success) {
|
|
141
|
+
return { success: true, data: result.data };
|
|
142
|
+
}
|
|
143
|
+
const errors = result.issues.map((issue) => ({
|
|
144
|
+
...issue,
|
|
145
|
+
path: ["context", ...issue.path]
|
|
146
|
+
}));
|
|
147
|
+
return { success: false, errors };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/presentation/schedule/server/schedule-routes-builder.ts
|
|
151
|
+
var ScheduleRoutesBuilderImpl = class _ScheduleRoutesBuilderImpl {
|
|
152
|
+
router;
|
|
153
|
+
tasks;
|
|
154
|
+
constructor(router, tasks) {
|
|
155
|
+
this.router = router;
|
|
156
|
+
this.tasks = tasks ?? /* @__PURE__ */ new Map();
|
|
157
|
+
}
|
|
158
|
+
handle(key, handlerOrConfig) {
|
|
159
|
+
const config = typeof handlerOrConfig === "function" ? { handler: handlerOrConfig } : handlerOrConfig;
|
|
160
|
+
const newTasks = new Map(this.tasks);
|
|
161
|
+
newTasks.set(key, config);
|
|
162
|
+
return new _ScheduleRoutesBuilderImpl(
|
|
163
|
+
this.router,
|
|
164
|
+
newTasks
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
handleWithUseCase(key, config) {
|
|
168
|
+
const newTasks = new Map(this.tasks);
|
|
169
|
+
newTasks.set(
|
|
170
|
+
key,
|
|
171
|
+
config
|
|
172
|
+
);
|
|
173
|
+
return new _ScheduleRoutesBuilderImpl(
|
|
174
|
+
this.router,
|
|
175
|
+
newTasks
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
build(options) {
|
|
179
|
+
return createScheduleRoutesInternal(this.router, Object.fromEntries(this.tasks), options);
|
|
180
|
+
}
|
|
181
|
+
buildPartial(options) {
|
|
182
|
+
return createScheduleRoutesInternal(this.router, Object.fromEntries(this.tasks), {
|
|
183
|
+
...options,
|
|
184
|
+
allowPartial: true
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
function scheduleRoutes(router) {
|
|
189
|
+
return new ScheduleRoutesBuilderImpl(router);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/presentation/schedule/server/invoke.ts
|
|
193
|
+
function indexScheduleRoutes(routes) {
|
|
194
|
+
const index = /* @__PURE__ */ new Map();
|
|
195
|
+
for (const route of routes) {
|
|
196
|
+
if (index.has(route.type)) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Duplicate scheduled task type "${route.type}": exactly one task may be registered per type. Fan-out is not supported \u2014 model multiple jobs as distinct types/triggers.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
index.set(route.type, route);
|
|
202
|
+
}
|
|
203
|
+
return index;
|
|
204
|
+
}
|
|
205
|
+
function findScheduleRoute(routes, type) {
|
|
206
|
+
return routes.find((route) => route.type === type);
|
|
207
|
+
}
|
|
208
|
+
async function invokeScheduledTask(routes, raw) {
|
|
209
|
+
const route = findScheduleRoute(routes, raw.type);
|
|
210
|
+
if (!route) {
|
|
211
|
+
return {
|
|
212
|
+
outcome: "failed",
|
|
213
|
+
reason: `No scheduled task for type "${raw.type}".`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return route.handler(raw);
|
|
217
|
+
}
|
|
218
|
+
function resolveScheduleTrigger(triggers, key) {
|
|
219
|
+
const byId = triggers[key];
|
|
220
|
+
if (byId) return byId;
|
|
221
|
+
for (const trigger of Object.values(triggers)) {
|
|
222
|
+
if (isCronScheduleTrigger(trigger) && trigger.cron === key) return trigger;
|
|
223
|
+
if (isRateScheduleTrigger(trigger) && trigger.rate === key) return trigger;
|
|
224
|
+
}
|
|
225
|
+
return void 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export {
|
|
229
|
+
isSimpleScheduledTaskConfig,
|
|
230
|
+
scheduleRoutes,
|
|
231
|
+
indexScheduleRoutes,
|
|
232
|
+
findScheduleRoute,
|
|
233
|
+
invokeScheduledTask,
|
|
234
|
+
resolveScheduleTrigger
|
|
235
|
+
};
|
|
236
|
+
//# sourceMappingURL=chunk-DKXZIFAG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/presentation/schedule/server/types.ts","../src/presentation/schedule/server/create-schedule-routes.ts","../src/presentation/schedule/server/schedule-routes-builder.ts","../src/presentation/schedule/server/invoke.ts"],"sourcesContent":["/**\n * @fileoverview Server types for the scheduled task system.\n *\n * @module schedule/server/types\n */\n\nimport type {\n ScheduledTaskDefinition,\n ScheduleRouterConfig,\n ScheduleRouterKeys,\n} from '../task/types';\nimport type { ScheduleResult } from '../shared/types';\n\n// Re-export UseCasePort from HTTP — same interface, no duplication\nexport type { UseCasePort } from '../../http/server/types';\n\n// ============================================================================\n// Raw Schedule (from the scheduling provider)\n// ============================================================================\n\n/**\n * Schedule metadata provided by the scheduling provider.\n */\nexport interface ScheduleMetadata {\n /**\n * Stable idempotency key for this run.\n * - Recurring: `${triggerId}:${scheduledFor}`\n * - One-time: the provider's timer id\n */\n readonly runId: string;\n\n /** ISO 8601 timestamp of when the run was due. */\n readonly scheduledFor: string;\n\n /** ISO 8601 timestamp of when the run actually fired. */\n readonly firedAt: string;\n\n /** The triggerId that produced this run (recurring runs). */\n readonly triggerId?: string;\n\n /** Source system / scheduler that fired the run. */\n readonly source?: string;\n\n /** Number of delivery attempts (1-based). */\n readonly attemptCount?: number;\n\n /** Timezone the trigger was evaluated in. */\n readonly timezone?: string;\n\n /** Additional metadata from the scheduling provider. */\n readonly [key: string]: unknown;\n}\n\n/**\n * Raw schedule run from the scheduling provider.\n * This is the input to scheduled tasks before validation.\n */\nexport interface RawSchedule {\n /** Task type string for routing. */\n readonly type: string;\n\n /** Run payload (unvalidated). */\n readonly payload: unknown;\n\n /** Run metadata from the scheduling provider. */\n readonly metadata: ScheduleMetadata;\n}\n\n// ============================================================================\n// Validated Schedule\n// ============================================================================\n\n/**\n * A validated schedule run with typed payload.\n * This is what tasks receive after validation passes.\n */\nexport interface ValidatedSchedule<T extends ScheduledTaskDefinition> {\n /** Validated run payload. */\n readonly payload: T['_types']['payload'];\n\n /** Raw schedule object for advanced use cases. */\n readonly raw: RawSchedule;\n}\n\n/**\n * Typed schedule context based on the task definition.\n * If the task defines a context schema, this will be the validated type.\n * Otherwise, it falls back to the generic ScheduleMetadata.\n */\nexport type TypedScheduleContext<T extends ScheduledTaskDefinition> =\n T['_types']['context'] extends undefined ? ScheduleMetadata : T['_types']['context'];\n\n// ============================================================================\n// Task Config Types\n// ============================================================================\n\n/**\n * Task configuration using the use case pattern.\n *\n * @typeParam T - The scheduled task definition type\n * @typeParam TInput - Use case input type\n * @typeParam TOutput - Use case output type\n */\nexport interface BuilderScheduledTaskConfig<\n T extends ScheduledTaskDefinition,\n TInput = void,\n TOutput = void,\n> {\n /**\n * Maps the validated run payload to use case input.\n * Both `schedule` and `ctx` are fully typed based on task schemas.\n */\n readonly payloadMapper: (schedule: ValidatedSchedule<T>, ctx: TypedScheduleContext<T>) => TInput;\n\n /** The use case to execute. */\n readonly useCase: { execute(input?: TInput): Promise<TOutput> };\n\n /**\n * Maps the use case output to a ScheduleResult.\n * If omitted, defaults to `{ outcome: 'completed' }`.\n */\n readonly resultMapper?: (output: TOutput) => ScheduleResult;\n\n /** Middleware to run before the task. */\n readonly middleware?: readonly ScheduleMiddlewareFunction[];\n}\n\n/**\n * Simple task function that directly returns a ScheduleResult.\n */\nexport type SimpleScheduledTaskFn<T extends ScheduledTaskDefinition> = (\n schedule: ValidatedSchedule<T>,\n ctx: TypedScheduleContext<T>,\n) => Promise<ScheduleResult> | ScheduleResult;\n\n/**\n * Configuration for a simple task (no use case).\n */\nexport interface SimpleScheduledTaskConfig<T extends ScheduledTaskDefinition> {\n readonly handler: SimpleScheduledTaskFn<T>;\n readonly middleware?: readonly ScheduleMiddlewareFunction[];\n}\n\n/**\n * Union of all scheduled task config types.\n * Used internally to store tasks in the builder.\n */\nexport type AnyScheduledTaskConfig<\n T extends ScheduledTaskDefinition,\n TInput = unknown,\n TOutput = unknown,\n> = BuilderScheduledTaskConfig<T, TInput, TOutput> | SimpleScheduledTaskConfig<T>;\n\n/**\n * Type guard to check if config is a simple scheduled task.\n */\nexport function isSimpleScheduledTaskConfig(\n config: AnyScheduledTaskConfig<ScheduledTaskDefinition, unknown, unknown>,\n): config is SimpleScheduledTaskConfig<ScheduledTaskDefinition> {\n return 'handler' in config && typeof config.handler === 'function';\n}\n\n/**\n * Schedule middleware function type.\n */\nexport type ScheduleMiddlewareFunction = (\n schedule: RawSchedule,\n next: () => Promise<ScheduleResult>,\n) => Promise<ScheduleResult>;\n\n// ============================================================================\n// Server Configuration\n// ============================================================================\n\n/**\n * Configuration mapping task keys to task configs.\n */\n// TInput/TOutput are user-defined per task - any is required for heterogeneous configs\nexport type ScheduleRoutesConfig<T extends ScheduleRouterConfig> = Record<\n ScheduleRouterKeys<T>,\n BuilderScheduledTaskConfig<any, any, any>\n>;\n\n/**\n * Options for creating schedule routes.\n */\nexport interface CreateScheduleRoutesOptions {\n /** Global middleware to run before all tasks. */\n readonly middleware?: readonly ScheduleMiddlewareFunction[];\n\n /**\n * Whether to validate incoming run payloads against task schemas.\n * When enabled, invalid payloads result in a `failed` outcome.\n * @default true\n */\n readonly validatePayload?: boolean;\n\n /**\n * Custom error mapper to override default error-to-ScheduleResult mapping.\n * If not provided, uses the built-in `mapErrorToScheduleResult`.\n *\n * Cannot return `skipped`: that outcome is reserved for explicit pipeline\n * decisions (handler / resultMapper / middleware). Error mapping only ever\n * yields `failed` (permanent) or `retry` (transient).\n */\n readonly errorMapper?: (error: unknown) => Exclude<ScheduleResult, { outcome: 'skipped' }>;\n\n /**\n * Allow partial task configuration (not all tasks need to be wired).\n * @default false\n * @internal Used by builder pattern's buildPartial()\n */\n readonly allowPartial?: boolean;\n}\n\n// ============================================================================\n// Unified Schedule Input (for scheduling adapters)\n// ============================================================================\n\n/**\n * Schedule input compatible with scheduling adapters.\n * This is the output of scheduleRoutes().build().\n */\nexport interface UnifiedScheduleInput {\n /** Task type string for routing. */\n readonly type: string;\n\n /** Task handler function. */\n readonly handler: (raw: RawSchedule) => Promise<ScheduleResult>;\n\n /** Task metadata for documentation. */\n readonly metadata: {\n readonly taskId?: string;\n readonly summary?: string;\n readonly description?: string;\n readonly tags?: readonly string[];\n readonly deprecated?: boolean;\n };\n}\n","/**\n * @fileoverview Internal implementation for creating schedule routes with auto-validation.\n *\n * Generates scheduled task handlers from a schedule router definition.\n * Each handler automatically validates the incoming run payload and context\n * against the task's schemas.\n *\n * @module schedule/server/create-schedule-routes\n * @internal\n */\n\nimport type { SchemaAdapter, ValidationIssue } from '../../http/schema/types';\nimport type {\n ScheduleRouterConfig,\n ScheduleRouterDefinition,\n ScheduledTaskDefinition,\n} from '../task/types';\nimport { isScheduleRouterDefinition, collectScheduledTasks } from '../task/types';\nimport { generateTaskId } from '../task/utils';\nimport { mapErrorToScheduleResult } from '../shared/error-mapping';\nimport type { ScheduleResult } from '../shared/types';\nimport type {\n AnyScheduledTaskConfig,\n CreateScheduleRoutesOptions,\n RawSchedule,\n ScheduleMetadata,\n UnifiedScheduleInput,\n ValidatedSchedule,\n} from './types';\nimport { isSimpleScheduledTaskConfig } from './types';\n\n/**\n * Internal implementation for creating schedule routes.\n * Used by the builder pattern (scheduleRoutes).\n *\n * @internal\n */\nexport function createScheduleRoutesInternal<T extends ScheduleRouterConfig>(\n router: T | ScheduleRouterDefinition<T>,\n tasks: Record<string, AnyScheduledTaskConfig<ScheduledTaskDefinition, unknown, unknown>>,\n options?: CreateScheduleRoutesOptions,\n): UnifiedScheduleInput[] {\n const config = isScheduleRouterDefinition(router) ? router.tasks : router;\n const collectedTasks = collectScheduledTasks(config);\n\n const result: UnifiedScheduleInput[] = [];\n\n const resolvedOptions: CreateScheduleRoutesOptions = {\n ...options,\n validatePayload: options?.validatePayload ?? true,\n allowPartial: options?.allowPartial ?? false,\n };\n\n for (const { key, task: taskDef } of collectedTasks) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const taskConfig = tasks[key] as\n | AnyScheduledTaskConfig<ScheduledTaskDefinition, any, any>\n | undefined;\n\n if (!taskConfig) {\n if (resolvedOptions.allowPartial) {\n continue;\n }\n throw new Error(\n `Missing handler for scheduled task \"${key}\". All scheduled tasks must have a handler configuration.`,\n );\n }\n\n result.push(createScheduledTask(key, taskDef, taskConfig, resolvedOptions));\n }\n\n return result;\n}\n\n/**\n * Creates a single scheduled task handler with validation and error mapping.\n */\nfunction createScheduledTask(\n key: string,\n taskDef: ScheduledTaskDefinition,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n config: AnyScheduledTaskConfig<ScheduledTaskDefinition, any, any>,\n options: CreateScheduleRoutesOptions,\n): UnifiedScheduleInput {\n const middleware = config.middleware ?? [];\n const globalMiddleware = options?.middleware ?? [];\n const allMiddleware = [...globalMiddleware, ...middleware];\n const shouldValidatePayload = options.validatePayload ?? true;\n const errorMapper = options.errorMapper ?? mapErrorToScheduleResult;\n\n return {\n type: taskDef.type,\n metadata: {\n taskId: generateTaskId(key),\n summary: taskDef.docs.summary,\n description: taskDef.docs.description,\n tags: taskDef.docs.tags as string[],\n deprecated: taskDef.docs.deprecated,\n },\n handler: async (raw: RawSchedule): Promise<ScheduleResult> => {\n try {\n // Validate context (if schema defined)\n let validatedContext: unknown = raw.metadata;\n if (taskDef.context) {\n const contextResult = validateContextData(taskDef, raw.metadata);\n if (!contextResult.success) {\n const errors = contextResult.errors ?? [];\n return {\n outcome: 'failed',\n reason: `Context validation failed: ${errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ')}`,\n };\n }\n validatedContext = contextResult.data;\n }\n\n // Validate payload (if enabled and schema defined)\n let validatedPayload: unknown = raw.payload;\n if (shouldValidatePayload && taskDef.payload) {\n const payloadResult = validatePayloadData(taskDef, raw.payload);\n if (!payloadResult.success) {\n const errors = payloadResult.errors ?? [];\n return {\n outcome: 'failed',\n reason: `Payload validation failed: ${errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ')}`,\n };\n }\n validatedPayload = payloadResult.data;\n }\n\n const validatedSchedule: ValidatedScheduleInternal = {\n payload: validatedPayload,\n raw,\n };\n\n // Execute the pipeline\n const executePipeline = async (): Promise<ScheduleResult> => {\n if (isSimpleScheduledTaskConfig(config)) {\n return config.handler(\n validatedSchedule as unknown as ValidatedSchedule<ScheduledTaskDefinition>,\n validatedContext as ScheduleMetadata,\n );\n } else {\n const { payloadMapper, useCase, resultMapper } = config;\n\n const input = payloadMapper(\n validatedSchedule as unknown as ValidatedSchedule<ScheduledTaskDefinition>,\n validatedContext as ScheduleMetadata,\n );\n\n const output = await useCase.execute(input);\n\n return resultMapper ? resultMapper(output) : { outcome: 'completed' };\n }\n };\n\n if (allMiddleware.length === 0) {\n return await executePipeline();\n }\n\n // Build middleware chain\n let index = 0;\n const next = async (): Promise<ScheduleResult> => {\n if (index >= allMiddleware.length) {\n return executePipeline();\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- index bounds checked above\n const mw = allMiddleware[index++]!;\n return mw(raw, next);\n };\n\n return await next();\n } catch (error) {\n // Error mapping must NEVER yield `skipped` — that outcome is reserved\n // for explicit pipeline decisions (handler / resultMapper / middleware).\n // Defensively coerce a stray `skipped` from an untyped (JS) custom mapper.\n const mapped: ScheduleResult = errorMapper(error);\n return mapped.outcome === 'skipped' ? { outcome: 'failed', reason: mapped.reason } : mapped;\n }\n },\n };\n}\n\n// ============================================================================\n// Validation Helpers\n// ============================================================================\n\ninterface ValidationResultInternal {\n success: boolean;\n errors?: ValidationIssue[];\n data?: unknown;\n}\n\n/**\n * Validates run payload against the task's payload schema.\n */\nfunction validatePayloadData(\n task: ScheduledTaskDefinition,\n payload: unknown,\n): ValidationResultInternal {\n const schema = task.payload as SchemaAdapter | undefined;\n if (!schema) return { success: true, data: payload };\n\n const result = schema.validate(payload);\n if (result.success) {\n return { success: true, data: result.data };\n }\n\n const errors = result.issues.map((issue) => ({\n ...issue,\n path: ['payload', ...issue.path],\n }));\n\n return { success: false, errors };\n}\n\n/**\n * Validates run metadata against the task's context schema.\n */\nfunction validateContextData(\n task: ScheduledTaskDefinition,\n metadata: ScheduleMetadata,\n): ValidationResultInternal {\n const schema = task.context as SchemaAdapter | undefined;\n if (!schema) return { success: true, data: metadata };\n\n const result = schema.validate(metadata);\n if (result.success) {\n return { success: true, data: result.data };\n }\n\n const errors = result.issues.map((issue) => ({\n ...issue,\n path: ['context', ...issue.path],\n }));\n\n return { success: false, errors };\n}\n\n/**\n * Internal validated schedule type with unknown fields.\n * Used inside createScheduledTask where specific types are erased.\n */\ninterface ValidatedScheduleInternal {\n readonly payload: unknown;\n readonly raw: RawSchedule;\n}\n","/**\n * @fileoverview Builder pattern for creating type-safe schedule routes.\n *\n * The `scheduleRoutes` function returns a builder that provides 100% type\n * inference for all task handler parameters — no manual type annotations\n * required.\n *\n * @module schedule/server/schedule-routes-builder\n */\n\nimport type {\n ScheduleRouterConfig,\n ScheduleRouterDefinition,\n GetScheduledTask,\n ScheduleRouterKeys,\n ScheduledTaskDefinition,\n} from '../task/types';\nimport type {\n AnyScheduledTaskConfig,\n BuilderScheduledTaskConfig,\n CreateScheduleRoutesOptions,\n SimpleScheduledTaskConfig,\n SimpleScheduledTaskFn,\n UnifiedScheduleInput,\n} from './types';\nimport { createScheduleRoutesInternal } from './create-schedule-routes';\n\n// ============================================================================\n// Builder Types\n// ============================================================================\n\n/**\n * Error type displayed when attempting to build() with missing handlers.\n * The `___missingTasks` property shows which tasks are missing.\n */\nexport interface MissingTasksError<TMissing extends string> {\n /**\n * This error indicates that not all scheduled tasks have been wired.\n * Use buildPartial() to build with only the defined tasks,\n * or add handlers for the missing tasks.\n */\n (options?: never): never;\n /** Scheduled tasks that are missing. */\n readonly ___missingTasks: TMissing;\n}\n\n/**\n * Builder interface for creating type-safe schedule routes.\n *\n * Each `.handle()` call captures the specific task type and provides\n * full type inference for payloadMapper and useCase.\n *\n * @typeParam T - The schedule router configuration type\n * @typeParam THandled - Union of task keys that have been wired (accumulates)\n *\n * @example\n * ```typescript\n * const routes = scheduleRoutes(billingSchedules)\n * .handleWithUseCase('dailyReconcile', {\n * payloadMapper: (schedule, ctx) => ({\n * tenantId: schedule.payload.tenantId, // Fully typed!\n * runId: ctx.runId, // Fully typed!\n * }),\n * useCase: reconcileUseCase,\n * })\n * .handle('monthlyInvoice', async () => {\n * await emitInvoices();\n * return { outcome: 'completed' as const };\n * })\n * .build();\n * ```\n */\nexport interface ScheduleRoutesBuilder<\n T extends ScheduleRouterConfig,\n THandled extends string = never,\n> {\n /**\n * Register a simple handler for a scheduled task.\n * The handler receives the validated schedule and context, returns a\n * ScheduleResult directly.\n */\n handle<K extends Exclude<ScheduleRouterKeys<T>, THandled>>(\n key: K,\n handlerOrConfig:\n | SimpleScheduledTaskFn<GetScheduledTask<T, K>>\n | SimpleScheduledTaskConfig<GetScheduledTask<T, K>>,\n ): ScheduleRoutesBuilder<T, THandled | K>;\n\n /**\n * Register a handler using the use case pattern.\n * Follows: payloadMapper → useCase.execute() → resultMapper (or completed)\n */\n handleWithUseCase<K extends Exclude<ScheduleRouterKeys<T>, THandled>, TInput, TOutput>(\n key: K,\n config: BuilderScheduledTaskConfig<GetScheduledTask<T, K>, TInput, TOutput>,\n ): ScheduleRoutesBuilder<T, THandled | K>;\n\n /**\n * Build the schedule routes array for scheduling adapter registration.\n *\n * This method is only available when ALL tasks have been wired.\n * If some tasks are missing, use `buildPartial()` instead.\n */\n build: [Exclude<ScheduleRouterKeys<T>, THandled>] extends [never]\n ? (options?: CreateScheduleRoutesOptions) => UnifiedScheduleInput[]\n : MissingTasksError<Exclude<ScheduleRouterKeys<T>, THandled>>;\n\n /**\n * Build schedule routes for only the defined tasks.\n * No compile-time enforcement of completeness.\n */\n buildPartial(options?: CreateScheduleRoutesOptions): UnifiedScheduleInput[];\n}\n\n// ============================================================================\n// Builder Implementation\n// ============================================================================\n\n/**\n * Internal builder implementation.\n *\n * Uses an immutable pattern where each handle() call returns a new\n * builder instance with the updated tasks map.\n */\nclass ScheduleRoutesBuilderImpl<T extends ScheduleRouterConfig, THandled extends string = never> {\n private readonly router: T | ScheduleRouterDefinition<T>;\n private readonly tasks: Map<\n string,\n AnyScheduledTaskConfig<ScheduledTaskDefinition, unknown, unknown>\n >;\n\n constructor(\n router: T | ScheduleRouterDefinition<T>,\n tasks?: Map<string, AnyScheduledTaskConfig<ScheduledTaskDefinition, unknown, unknown>>,\n ) {\n this.router = router;\n this.tasks = tasks ?? new Map();\n }\n\n handle<K extends Exclude<ScheduleRouterKeys<T>, THandled>>(\n key: K,\n handlerOrConfig:\n | SimpleScheduledTaskFn<GetScheduledTask<T, K>>\n | SimpleScheduledTaskConfig<GetScheduledTask<T, K>>,\n ): ScheduleRoutesBuilder<T, THandled | K> {\n const config: SimpleScheduledTaskConfig<ScheduledTaskDefinition> =\n typeof handlerOrConfig === 'function'\n ? { handler: handlerOrConfig as SimpleScheduledTaskFn<ScheduledTaskDefinition> }\n : (handlerOrConfig as SimpleScheduledTaskConfig<ScheduledTaskDefinition>);\n\n const newTasks = new Map(this.tasks);\n newTasks.set(key as string, config);\n\n return new ScheduleRoutesBuilderImpl<T, THandled | K>(\n this.router,\n newTasks,\n ) as unknown as ScheduleRoutesBuilder<T, THandled | K>;\n }\n\n handleWithUseCase<K extends Exclude<ScheduleRouterKeys<T>, THandled>, TInput, TOutput>(\n key: K,\n config: BuilderScheduledTaskConfig<GetScheduledTask<T, K>, TInput, TOutput>,\n ): ScheduleRoutesBuilder<T, THandled | K> {\n const newTasks = new Map(this.tasks);\n newTasks.set(\n key as string,\n config as BuilderScheduledTaskConfig<ScheduledTaskDefinition, unknown, unknown>,\n );\n\n return new ScheduleRoutesBuilderImpl<T, THandled | K>(\n this.router,\n newTasks,\n ) as unknown as ScheduleRoutesBuilder<T, THandled | K>;\n }\n\n build(options?: CreateScheduleRoutesOptions): UnifiedScheduleInput[] {\n return createScheduleRoutesInternal(this.router, Object.fromEntries(this.tasks), options);\n }\n\n buildPartial(options?: CreateScheduleRoutesOptions): UnifiedScheduleInput[] {\n return createScheduleRoutesInternal(this.router, Object.fromEntries(this.tasks), {\n ...options,\n allowPartial: true,\n });\n }\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Creates a type-safe schedule routes builder for a schedule router.\n *\n * The builder pattern provides 100% type inference for all task parameters:\n * - `schedule.payload` is typed from the task's payload schema\n * - `ctx` is typed from the task's context schema (or ScheduleMetadata)\n * - `output` in resultMapper is typed from the use case\n *\n * @param router - Schedule router definition or schedule router config\n * @returns Builder for registering task handlers\n *\n * @example Basic usage\n * ```typescript\n * import { scheduleRoutes } from '@cosmneo/onion-lasagna/schedule/server';\n * import { billingSchedules } from './schedules/router';\n *\n * const routes = scheduleRoutes(billingSchedules)\n * .handleWithUseCase('dailyReconcile', {\n * payloadMapper: (schedule, ctx) => ({\n * tenantId: schedule.payload.tenantId,\n * runId: ctx.runId,\n * }),\n * useCase: reconcileUseCase,\n * })\n * .handle('monthlyInvoice', async () => {\n * await emitInvoices();\n * return { outcome: 'completed' as const };\n * })\n * .build();\n * ```\n */\nexport function scheduleRoutes<T extends ScheduleRouterConfig>(\n router: T | ScheduleRouterDefinition<T>,\n): ScheduleRoutesBuilder<T, never> {\n return new ScheduleRoutesBuilderImpl(router) as unknown as ScheduleRoutesBuilder<T, never>;\n}\n","/**\n * @fileoverview Invocation PRIMITIVES for scheduled tasks.\n *\n * These are deliberately NOT a fan-out dispatcher. A scheduled run targets a\n * single task `type` (one task per type — fan-out is modeled as distinct\n * types/triggers, not multiple handlers on one type). The primitives let an\n * app/infra adapter index, look up, and invoke the right task for a raw run,\n * and resolve a trigger binding.\n *\n * @module schedule/server/invoke\n */\n\nimport type { ScheduleTrigger, ScheduleTriggerMap } from '../task/types';\nimport { isCronScheduleTrigger, isRateScheduleTrigger } from '../task/types';\nimport type { ScheduleResult } from '../shared/types';\nimport type { RawSchedule, UnifiedScheduleInput } from './types';\n\n/**\n * Indexes schedule routes by their task `type` for O(1) lookup.\n *\n * Throws on a DUPLICATE `type`: a single type must map to exactly one task.\n * This enforces the \"one task per type — no fan-out\" rule at the boundary.\n *\n * @param routes - The schedule routes (output of scheduleRoutes().build()).\n * @returns A Map keyed by task type.\n * @throws {Error} If two routes share the same `type`.\n */\nexport function indexScheduleRoutes(\n routes: readonly UnifiedScheduleInput[],\n): Map<string, UnifiedScheduleInput> {\n const index = new Map<string, UnifiedScheduleInput>();\n\n for (const route of routes) {\n if (index.has(route.type)) {\n throw new Error(\n `Duplicate scheduled task type \"${route.type}\": exactly one task may be registered ` +\n `per type. Fan-out is not supported — model multiple jobs as distinct types/triggers.`,\n );\n }\n index.set(route.type, route);\n }\n\n return index;\n}\n\n/**\n * Finds a single schedule route by its task `type`.\n *\n * @param routes - The schedule routes to search.\n * @param type - The task type to find.\n * @returns The matching route, or `undefined` if none matches.\n */\nexport function findScheduleRoute(\n routes: readonly UnifiedScheduleInput[],\n type: string,\n): UnifiedScheduleInput | undefined {\n return routes.find((route) => route.type === type);\n}\n\n/**\n * Invokes the scheduled task matching `raw.type`.\n *\n * If no task is registered for the type, returns a `failed` result rather than\n * throwing — a misrouted run is a permanent failure, not a transient one.\n *\n * @param routes - The schedule routes (output of scheduleRoutes().build()).\n * @param raw - The raw schedule run from the provider.\n * @returns The ScheduleResult from the task handler (or a `failed` miss result).\n */\nexport async function invokeScheduledTask(\n routes: readonly UnifiedScheduleInput[],\n raw: RawSchedule,\n): Promise<ScheduleResult> {\n const route = findScheduleRoute(routes, raw.type);\n\n if (!route) {\n return {\n outcome: 'failed',\n reason: `No scheduled task for type \"${raw.type}\".`,\n };\n }\n\n return route.handler(raw);\n}\n\n/**\n * Resolves a trigger from a trigger map by a lookup `key`.\n *\n * Lookup precedence:\n * 1. Direct `triggerId` key match (the common case — providers that carry the\n * stable triggerId).\n * 2. Fallback: match by timing expression — a trigger whose `cron` or `rate`\n * equals `key` (for providers keyed by the raw cron/rate expression rather\n * than by triggerId). The first trigger (in insertion order) whose\n * expression matches is returned.\n *\n * @param triggers - The trigger map (output of defineScheduleTriggers).\n * @param key - A triggerId, or a cron/rate expression.\n * @returns The matching trigger, or `undefined` if none matches.\n */\nexport function resolveScheduleTrigger(\n triggers: ScheduleTriggerMap,\n key: string,\n): ScheduleTrigger | undefined {\n // 1. Direct triggerId match.\n const byId = triggers[key];\n if (byId) return byId;\n\n // 2. Fallback: match by cron/rate expression.\n for (const trigger of Object.values(triggers)) {\n if (isCronScheduleTrigger(trigger) && trigger.cron === key) return trigger;\n if (isRateScheduleTrigger(trigger) && trigger.rate === key) return trigger;\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;AA4JO,SAAS,4BACd,QAC8D;AAC9D,SAAO,aAAa,UAAU,OAAO,OAAO,YAAY;AAC1D;;;AC3HO,SAAS,6BACd,QACA,OACA,SACwB;AACxB,QAAM,SAAS,2BAA2B,MAAM,IAAI,OAAO,QAAQ;AACnE,QAAM,iBAAiB,sBAAsB,MAAM;AAEnD,QAAM,SAAiC,CAAC;AAExC,QAAM,kBAA+C;AAAA,IACnD,GAAG;AAAA,IACH,iBAAiB,SAAS,mBAAmB;AAAA,IAC7C,cAAc,SAAS,gBAAgB;AAAA,EACzC;AAEA,aAAW,EAAE,KAAK,MAAM,QAAQ,KAAK,gBAAgB;AAEnD,UAAM,aAAa,MAAM,GAAG;AAI5B,QAAI,CAAC,YAAY;AACf,UAAI,gBAAgB,cAAc;AAChC;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR,uCAAuC,GAAG;AAAA,MAC5C;AAAA,IACF;AAEA,WAAO,KAAK,oBAAoB,KAAK,SAAS,YAAY,eAAe,CAAC;AAAA,EAC5E;AAEA,SAAO;AACT;AAKA,SAAS,oBACP,KACA,SAEA,QACA,SACsB;AACtB,QAAM,aAAa,OAAO,cAAc,CAAC;AACzC,QAAM,mBAAmB,SAAS,cAAc,CAAC;AACjD,QAAM,gBAAgB,CAAC,GAAG,kBAAkB,GAAG,UAAU;AACzD,QAAM,wBAAwB,QAAQ,mBAAmB;AACzD,QAAM,cAAc,QAAQ,eAAe;AAE3C,SAAO;AAAA,IACL,MAAM,QAAQ;AAAA,IACd,UAAU;AAAA,MACR,QAAQ,eAAe,GAAG;AAAA,MAC1B,SAAS,QAAQ,KAAK;AAAA,MACtB,aAAa,QAAQ,KAAK;AAAA,MAC1B,MAAM,QAAQ,KAAK;AAAA,MACnB,YAAY,QAAQ,KAAK;AAAA,IAC3B;AAAA,IACA,SAAS,OAAO,QAA8C;AAC5D,UAAI;AAEF,YAAI,mBAA4B,IAAI;AACpC,YAAI,QAAQ,SAAS;AACnB,gBAAM,gBAAgB,oBAAoB,SAAS,IAAI,QAAQ;AAC/D,cAAI,CAAC,cAAc,SAAS;AAC1B,kBAAM,SAAS,cAAc,UAAU,CAAC;AACxC,mBAAO;AAAA,cACL,SAAS;AAAA,cACT,QAAQ,8BAA8B,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,YACzG;AAAA,UACF;AACA,6BAAmB,cAAc;AAAA,QACnC;AAGA,YAAI,mBAA4B,IAAI;AACpC,YAAI,yBAAyB,QAAQ,SAAS;AAC5C,gBAAM,gBAAgB,oBAAoB,SAAS,IAAI,OAAO;AAC9D,cAAI,CAAC,cAAc,SAAS;AAC1B,kBAAM,SAAS,cAAc,UAAU,CAAC;AACxC,mBAAO;AAAA,cACL,SAAS;AAAA,cACT,QAAQ,8BAA8B,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,YACzG;AAAA,UACF;AACA,6BAAmB,cAAc;AAAA,QACnC;AAEA,cAAM,oBAA+C;AAAA,UACnD,SAAS;AAAA,UACT;AAAA,QACF;AAGA,cAAM,kBAAkB,YAAqC;AAC3D,cAAI,4BAA4B,MAAM,GAAG;AACvC,mBAAO,OAAO;AAAA,cACZ;AAAA,cACA;AAAA,YACF;AAAA,UACF,OAAO;AACL,kBAAM,EAAE,eAAe,SAAS,aAAa,IAAI;AAEjD,kBAAM,QAAQ;AAAA,cACZ;AAAA,cACA;AAAA,YACF;AAEA,kBAAM,SAAS,MAAM,QAAQ,QAAQ,KAAK;AAE1C,mBAAO,eAAe,aAAa,MAAM,IAAI,EAAE,SAAS,YAAY;AAAA,UACtE;AAAA,QACF;AAEA,YAAI,cAAc,WAAW,GAAG;AAC9B,iBAAO,MAAM,gBAAgB;AAAA,QAC/B;AAGA,YAAI,QAAQ;AACZ,cAAM,OAAO,YAAqC;AAChD,cAAI,SAAS,cAAc,QAAQ;AACjC,mBAAO,gBAAgB;AAAA,UACzB;AAEA,gBAAM,KAAK,cAAc,OAAO;AAChC,iBAAO,GAAG,KAAK,IAAI;AAAA,QACrB;AAEA,eAAO,MAAM,KAAK;AAAA,MACpB,SAAS,OAAO;AAId,cAAM,SAAyB,YAAY,KAAK;AAChD,eAAO,OAAO,YAAY,YAAY,EAAE,SAAS,UAAU,QAAQ,OAAO,OAAO,IAAI;AAAA,MACvF;AAAA,IACF;AAAA,EACF;AACF;AAeA,SAAS,oBACP,MACA,SAC0B;AAC1B,QAAM,SAAS,KAAK;AACpB,MAAI,CAAC,OAAQ,QAAO,EAAE,SAAS,MAAM,MAAM,QAAQ;AAEnD,QAAM,SAAS,OAAO,SAAS,OAAO;AACtC,MAAI,OAAO,SAAS;AAClB,WAAO,EAAE,SAAS,MAAM,MAAM,OAAO,KAAK;AAAA,EAC5C;AAEA,QAAM,SAAS,OAAO,OAAO,IAAI,CAAC,WAAW;AAAA,IAC3C,GAAG;AAAA,IACH,MAAM,CAAC,WAAW,GAAG,MAAM,IAAI;AAAA,EACjC,EAAE;AAEF,SAAO,EAAE,SAAS,OAAO,OAAO;AAClC;AAKA,SAAS,oBACP,MACA,UAC0B;AAC1B,QAAM,SAAS,KAAK;AACpB,MAAI,CAAC,OAAQ,QAAO,EAAE,SAAS,MAAM,MAAM,SAAS;AAEpD,QAAM,SAAS,OAAO,SAAS,QAAQ;AACvC,MAAI,OAAO,SAAS;AAClB,WAAO,EAAE,SAAS,MAAM,MAAM,OAAO,KAAK;AAAA,EAC5C;AAEA,QAAM,SAAS,OAAO,OAAO,IAAI,CAAC,WAAW;AAAA,IAC3C,GAAG;AAAA,IACH,MAAM,CAAC,WAAW,GAAG,MAAM,IAAI;AAAA,EACjC,EAAE;AAEF,SAAO,EAAE,SAAS,OAAO,OAAO;AAClC;;;AChHA,IAAM,4BAAN,MAAM,2BAA2F;AAAA,EAC9E;AAAA,EACA;AAAA,EAKjB,YACE,QACA,OACA;AACA,SAAK,SAAS;AACd,SAAK,QAAQ,SAAS,oBAAI,IAAI;AAAA,EAChC;AAAA,EAEA,OACE,KACA,iBAGwC;AACxC,UAAM,SACJ,OAAO,oBAAoB,aACvB,EAAE,SAAS,gBAAkE,IAC5E;AAEP,UAAM,WAAW,IAAI,IAAI,KAAK,KAAK;AACnC,aAAS,IAAI,KAAe,MAAM;AAElC,WAAO,IAAI;AAAA,MACT,KAAK;AAAA,MACL;AAAA,IACF;AAAA,EACF;AAAA,EAEA,kBACE,KACA,QACwC;AACxC,UAAM,WAAW,IAAI,IAAI,KAAK,KAAK;AACnC,aAAS;AAAA,MACP;AAAA,MACA;AAAA,IACF;AAEA,WAAO,IAAI;AAAA,MACT,KAAK;AAAA,MACL;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,SAA+D;AACnE,WAAO,6BAA6B,KAAK,QAAQ,OAAO,YAAY,KAAK,KAAK,GAAG,OAAO;AAAA,EAC1F;AAAA,EAEA,aAAa,SAA+D;AAC1E,WAAO,6BAA6B,KAAK,QAAQ,OAAO,YAAY,KAAK,KAAK,GAAG;AAAA,MAC/E,GAAG;AAAA,MACH,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AACF;AAqCO,SAAS,eACd,QACiC;AACjC,SAAO,IAAI,0BAA0B,MAAM;AAC7C;;;ACvMO,SAAS,oBACd,QACmC;AACnC,QAAM,QAAQ,oBAAI,IAAkC;AAEpD,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,IAAI,MAAM,IAAI,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,kCAAkC,MAAM,IAAI;AAAA,MAE9C;AAAA,IACF;AACA,UAAM,IAAI,MAAM,MAAM,KAAK;AAAA,EAC7B;AAEA,SAAO;AACT;AASO,SAAS,kBACd,QACA,MACkC;AAClC,SAAO,OAAO,KAAK,CAAC,UAAU,MAAM,SAAS,IAAI;AACnD;AAYA,eAAsB,oBACpB,QACA,KACyB;AACzB,QAAM,QAAQ,kBAAkB,QAAQ,IAAI,IAAI;AAEhD,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,+BAA+B,IAAI,IAAI;AAAA,IACjD;AAAA,EACF;AAEA,SAAO,MAAM,QAAQ,GAAG;AAC1B;AAiBO,SAAS,uBACd,UACA,KAC6B;AAE7B,QAAM,OAAO,SAAS,GAAG;AACzB,MAAI,KAAM,QAAO;AAGjB,aAAW,WAAW,OAAO,OAAO,QAAQ,GAAG;AAC7C,QAAI,sBAAsB,OAAO,KAAK,QAAQ,SAAS,IAAK,QAAO;AACnE,QAAI,sBAAsB,OAAO,KAAK,QAAQ,SAAS,IAAK,QAAO;AAAA,EACrE;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isScheduleRouterDefinition,
|
|
3
|
+
isScheduledTaskDefinition
|
|
4
|
+
} from "./chunk-YFD3K4BS.js";
|
|
5
|
+
|
|
6
|
+
// src/presentation/schedule/task/define-scheduled-task.ts
|
|
7
|
+
function defineScheduledTask(input) {
|
|
8
|
+
const definition = {
|
|
9
|
+
type: input.type,
|
|
10
|
+
payload: input.payload ?? void 0,
|
|
11
|
+
context: input.context ?? void 0,
|
|
12
|
+
docs: {
|
|
13
|
+
summary: input.docs?.summary,
|
|
14
|
+
description: input.docs?.description,
|
|
15
|
+
tags: input.docs?.tags,
|
|
16
|
+
deprecated: input.docs?.deprecated ?? false
|
|
17
|
+
},
|
|
18
|
+
_types: void 0
|
|
19
|
+
};
|
|
20
|
+
return Object.freeze(definition);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/presentation/schedule/task/define-schedule-router.ts
|
|
24
|
+
function defineScheduleRouter(tasks, options) {
|
|
25
|
+
const defaults = options?.defaults;
|
|
26
|
+
const processedTasks = defaults?.context || defaults?.tags ? applyScheduleRouterDefaults(tasks, defaults) : tasks;
|
|
27
|
+
const definition = {
|
|
28
|
+
tasks: processedTasks,
|
|
29
|
+
defaults,
|
|
30
|
+
_isScheduleRouter: true
|
|
31
|
+
};
|
|
32
|
+
return deepFreeze(definition);
|
|
33
|
+
}
|
|
34
|
+
function applyScheduleRouterDefaults(tasks, defaults) {
|
|
35
|
+
const result = {};
|
|
36
|
+
for (const [key, value] of Object.entries(tasks)) {
|
|
37
|
+
if (isScheduledTaskDefinition(value)) {
|
|
38
|
+
result[key] = applyDefaultsToScheduledTask(value, defaults);
|
|
39
|
+
} else if (isScheduleRouterDefinition(value)) {
|
|
40
|
+
result[key] = {
|
|
41
|
+
...value,
|
|
42
|
+
tasks: applyScheduleRouterDefaults(value.tasks, defaults)
|
|
43
|
+
};
|
|
44
|
+
} else if (typeof value === "object" && value !== null) {
|
|
45
|
+
result[key] = applyScheduleRouterDefaults(value, defaults);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function applyDefaultsToScheduledTask(task, defaults) {
|
|
51
|
+
const needsContext = defaults.context && !task.context;
|
|
52
|
+
const needsTags = defaults.tags && defaults.tags.length > 0;
|
|
53
|
+
if (!needsContext && !needsTags) return task;
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
...task,
|
|
56
|
+
context: task.context ?? defaults.context ?? void 0,
|
|
57
|
+
docs: {
|
|
58
|
+
...task.docs,
|
|
59
|
+
tags: mergeTags(defaults.tags, task.docs.tags)
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function mergeTags(routerTags, taskTags) {
|
|
64
|
+
if (!routerTags || routerTags.length === 0) return taskTags;
|
|
65
|
+
if (!taskTags || taskTags.length === 0) return routerTags;
|
|
66
|
+
const merged = [...routerTags];
|
|
67
|
+
for (const tag of taskTags) {
|
|
68
|
+
if (!merged.includes(tag)) {
|
|
69
|
+
merged.push(tag);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return merged;
|
|
73
|
+
}
|
|
74
|
+
function deepFreeze(obj) {
|
|
75
|
+
const propNames = Object.getOwnPropertyNames(obj);
|
|
76
|
+
for (const name of propNames) {
|
|
77
|
+
const value = obj[name];
|
|
78
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
79
|
+
deepFreeze(value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return Object.freeze(obj);
|
|
83
|
+
}
|
|
84
|
+
function extractTasks(input) {
|
|
85
|
+
return isScheduleRouterDefinition(input) ? input.tasks : input;
|
|
86
|
+
}
|
|
87
|
+
function isSubRouter(value) {
|
|
88
|
+
return typeof value === "object" && value !== null && !isScheduledTaskDefinition(value) && !isScheduleRouterDefinition(value);
|
|
89
|
+
}
|
|
90
|
+
function deepMergeConfigs(a, b) {
|
|
91
|
+
const result = { ...a };
|
|
92
|
+
for (const key of Object.keys(b)) {
|
|
93
|
+
const aVal = result[key];
|
|
94
|
+
const bVal = b[key];
|
|
95
|
+
if (isSubRouter(aVal) && isSubRouter(bVal)) {
|
|
96
|
+
result[key] = deepMergeConfigs(aVal, bVal);
|
|
97
|
+
} else {
|
|
98
|
+
result[key] = bVal;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
function mergeScheduleRouters(...routers) {
|
|
104
|
+
const merged = routers.map(extractTasks).reduce(deepMergeConfigs);
|
|
105
|
+
return defineScheduleRouter(merged);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/presentation/schedule/task/define-schedule-triggers.ts
|
|
109
|
+
function defineScheduleTriggers(triggers) {
|
|
110
|
+
for (const [triggerId, value] of Object.entries(triggers)) {
|
|
111
|
+
const t = value;
|
|
112
|
+
const hasCron = t.cron !== void 0;
|
|
113
|
+
const hasRate = t.rate !== void 0;
|
|
114
|
+
if (hasCron === hasRate) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Schedule trigger "${triggerId}" must have exactly one of 'cron' or 'rate' (got ${hasCron ? "both" : "neither"}).`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
Object.freeze(value);
|
|
120
|
+
}
|
|
121
|
+
return Object.freeze({ ...triggers });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
defineScheduledTask,
|
|
126
|
+
defineScheduleRouter,
|
|
127
|
+
mergeScheduleRouters,
|
|
128
|
+
defineScheduleTriggers
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=chunk-PS7ZFTW5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/presentation/schedule/task/define-scheduled-task.ts","../src/presentation/schedule/task/define-schedule-router.ts","../src/presentation/schedule/task/define-schedule-triggers.ts"],"sourcesContent":["/**\n * @fileoverview Factory function for creating scheduled task definitions.\n *\n * The `defineScheduledTask` function is the main entry point for defining\n * scheduled tasks. It mirrors `defineEventHandler` from the events layer but\n * is tailored for scheduled (recurring / one-time) work.\n *\n * NOTE: a task carries NO timing information — the WHEN/HOW is bound separately\n * via `defineScheduleTriggers` (see `ScheduleTrigger`).\n *\n * @module schedule/task/define-scheduled-task\n */\n\nimport type { SchemaAdapter, InferOutput } from '../../http/schema/types';\nimport type { ScheduledTaskDefinition } from './types';\n\n// ============================================================================\n// Input Types\n// ============================================================================\n\n/**\n * Input for defineScheduledTask.\n *\n * @example Basic task with payload schema\n * ```typescript\n * defineScheduledTask({\n * type: 'billing.dailyReconcile',\n * payload: zodSchema(z.object({ tenantId: z.string() })),\n * docs: { summary: 'Reconcile billing nightly', tags: ['billing'] },\n * })\n * ```\n *\n * @example With context schema\n * ```typescript\n * defineScheduledTask({\n * type: 'reports.weeklyDigest',\n * payload: zodSchema(reportPayloadSchema),\n * context: zodSchema(scheduleMetadataSchema),\n * })\n * ```\n */\ninterface DefineScheduledTaskInput<\n TType extends string,\n TPayload extends SchemaAdapter | undefined = undefined,\n TContext extends SchemaAdapter | undefined = undefined,\n> {\n /** Task type string used for routing (e.g., 'billing.dailyReconcile'). */\n readonly type: TType;\n\n /** Payload validation schema. */\n readonly payload?: TPayload;\n\n /** Context validation schema (validates schedule metadata). */\n readonly context?: TContext;\n\n /** Task documentation. */\n readonly docs?: {\n readonly summary?: string;\n readonly description?: string;\n readonly tags?: readonly string[];\n readonly deprecated?: boolean;\n };\n}\n\n// ============================================================================\n// Return type helpers\n// ============================================================================\n\ntype ResolvePayload<T> = T extends SchemaAdapter ? InferOutput<T> : undefined;\ntype ResolveContext<T> = T extends SchemaAdapter ? InferOutput<T> : undefined;\n\n// ============================================================================\n// Factory Function\n// ============================================================================\n\n/**\n * Creates a scheduled task definition from the provided configuration.\n *\n * This is the main entry point for defining scheduled tasks. The resulting\n * definition can be used for type-safe task registration, payload validation,\n * and catalog generation.\n *\n * @param input - Scheduled task configuration with optional schemas\n * @returns A frozen ScheduledTaskDefinition object with full type information\n */\nexport function defineScheduledTask<\n TType extends string,\n TPayload extends SchemaAdapter | undefined = undefined,\n TContext extends SchemaAdapter | undefined = undefined,\n>(\n input: DefineScheduledTaskInput<TType, TPayload, TContext>,\n): ScheduledTaskDefinition<TType, ResolvePayload<TPayload>, ResolveContext<TContext>> {\n const definition = {\n type: input.type,\n payload: input.payload ?? undefined,\n context: input.context ?? undefined,\n docs: {\n summary: input.docs?.summary,\n description: input.docs?.description,\n tags: input.docs?.tags,\n deprecated: input.docs?.deprecated ?? false,\n },\n _types: undefined as unknown,\n };\n\n return Object.freeze(definition) as ScheduledTaskDefinition<\n TType,\n ResolvePayload<TPayload>,\n ResolveContext<TContext>\n >;\n}\n","/**\n * @fileoverview Factory function for creating schedule router definitions.\n *\n * The `defineScheduleRouter` function groups scheduled tasks into a hierarchical\n * structure with optional router-level defaults for context and tags.\n *\n * @module schedule/task/define-schedule-router\n */\n\nimport type {\n ScheduleRouterConfig,\n ScheduleRouterDefaults,\n ScheduleRouterDefinition,\n ScheduledTaskDefinition,\n DeepMergeTwo,\n DeepMergeAll,\n} from './types';\nimport { isScheduledTaskDefinition, isScheduleRouterDefinition } from './types';\n\n/**\n * Options for schedule router definition.\n */\nexport interface DefineScheduleRouterOptions {\n /**\n * Default values applied to all child tasks.\n *\n * @example\n * ```typescript\n * defineScheduleRouter({\n * dailyReconcile: onDailyReconcile,\n * weeklyDigest: onWeeklyDigest,\n * }, {\n * defaults: {\n * context: zodSchema(scheduleMetadataSchema),\n * tags: ['billing'],\n * },\n * })\n * ```\n */\n readonly defaults?: ScheduleRouterDefaults;\n}\n\n/**\n * Creates a schedule router definition from a configuration object.\n *\n * @param tasks - Object containing scheduled tasks and nested routers\n * @param options - Optional router configuration\n * @returns A frozen ScheduleRouterDefinition object\n *\n * @example Basic router\n * ```typescript\n * const billingSchedules = defineScheduleRouter({\n * dailyReconcile: onDailyReconcile,\n * monthlyInvoice: onMonthlyInvoice,\n * });\n * ```\n *\n * @example Nested router\n * ```typescript\n * const schedules = defineScheduleRouter({\n * billing: {\n * dailyReconcile: onDailyReconcile,\n * },\n * reports: {\n * weeklyDigest: onWeeklyDigest,\n * },\n * });\n * ```\n */\nexport function defineScheduleRouter<T extends ScheduleRouterConfig>(\n tasks: T,\n options?: DefineScheduleRouterOptions,\n): ScheduleRouterDefinition<T> {\n const defaults = options?.defaults;\n\n const processedTasks =\n defaults?.context || defaults?.tags\n ? (applyScheduleRouterDefaults(tasks, defaults) as T)\n : tasks;\n\n const definition: ScheduleRouterDefinition<T> = {\n tasks: processedTasks,\n defaults,\n _isScheduleRouter: true,\n };\n\n return deepFreeze(definition);\n}\n\n/**\n * Recursively applies router-level defaults to all tasks in the tree.\n */\nfunction applyScheduleRouterDefaults(\n tasks: ScheduleRouterConfig,\n defaults: ScheduleRouterDefaults,\n): ScheduleRouterConfig {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(tasks)) {\n if (isScheduledTaskDefinition(value)) {\n result[key] = applyDefaultsToScheduledTask(value, defaults);\n } else if (isScheduleRouterDefinition(value)) {\n result[key] = {\n ...value,\n tasks: applyScheduleRouterDefaults(value.tasks, defaults),\n };\n } else if (typeof value === 'object' && value !== null) {\n result[key] = applyScheduleRouterDefaults(value as ScheduleRouterConfig, defaults);\n }\n }\n\n return result as ScheduleRouterConfig;\n}\n\n/**\n * Applies router-level defaults to a single scheduled task definition.\n */\nfunction applyDefaultsToScheduledTask(\n task: ScheduledTaskDefinition,\n defaults: ScheduleRouterDefaults,\n): ScheduledTaskDefinition {\n const needsContext = defaults.context && !task.context;\n const needsTags = defaults.tags && defaults.tags.length > 0;\n\n if (!needsContext && !needsTags) return task;\n\n return Object.freeze({\n ...task,\n context: task.context ?? defaults.context ?? undefined,\n docs: {\n ...task.docs,\n tags: mergeTags(defaults.tags, task.docs.tags),\n },\n }) as ScheduledTaskDefinition;\n}\n\n/**\n * Merges router-level tags with task-level tags, avoiding duplicates.\n */\nfunction mergeTags(\n routerTags?: readonly string[],\n taskTags?: readonly string[],\n): readonly string[] | undefined {\n if (!routerTags || routerTags.length === 0) return taskTags;\n if (!taskTags || taskTags.length === 0) return routerTags;\n\n const merged = [...routerTags];\n for (const tag of taskTags) {\n if (!merged.includes(tag)) {\n merged.push(tag);\n }\n }\n return merged;\n}\n\n/**\n * Deep freezes an object and all its nested objects.\n */\nfunction deepFreeze<T extends object>(obj: T): T {\n const propNames = Object.getOwnPropertyNames(obj) as (keyof T)[];\n\n for (const name of propNames) {\n const value = obj[name];\n if (value && typeof value === 'object' && !Object.isFrozen(value)) {\n deepFreeze(value);\n }\n }\n\n return Object.freeze(obj);\n}\n\n// ============================================================================\n// mergeScheduleRouters — variadic deep merge\n// ============================================================================\n\ntype ScheduleRouterInput<T extends ScheduleRouterConfig> = T | ScheduleRouterDefinition<T>;\n\n/** Extracts the raw ScheduleRouterConfig from either a plain config or a ScheduleRouterDefinition. */\nfunction extractTasks<T extends ScheduleRouterConfig>(input: ScheduleRouterInput<T>): T {\n return isScheduleRouterDefinition(input) ? input.tasks : input;\n}\n\n/** Returns true if `value` is a plain sub-router object. */\nfunction isSubRouter(value: unknown): value is ScheduleRouterConfig {\n return (\n typeof value === 'object' &&\n value !== null &&\n !isScheduledTaskDefinition(value) &&\n !isScheduleRouterDefinition(value)\n );\n}\n\n/** Recursively deep-merges two router configs. Sub-routers are merged; leaves are overwritten. */\nfunction deepMergeConfigs(a: ScheduleRouterConfig, b: ScheduleRouterConfig): ScheduleRouterConfig {\n const result: Record<string, unknown> = { ...a };\n\n for (const key of Object.keys(b)) {\n const aVal = result[key];\n const bVal = b[key];\n\n if (isSubRouter(aVal) && isSubRouter(bVal)) {\n result[key] = deepMergeConfigs(aVal, bVal);\n } else {\n result[key] = bVal;\n }\n }\n\n return result as ScheduleRouterConfig;\n}\n\n// Overloads for 2–8 routers\nexport function mergeScheduleRouters<\n T1 extends ScheduleRouterConfig,\n T2 extends ScheduleRouterConfig,\n>(\n r1: ScheduleRouterInput<T1>,\n r2: ScheduleRouterInput<T2>,\n): ScheduleRouterDefinition<DeepMergeTwo<T1, T2>>;\nexport function mergeScheduleRouters<\n T1 extends ScheduleRouterConfig,\n T2 extends ScheduleRouterConfig,\n T3 extends ScheduleRouterConfig,\n>(\n r1: ScheduleRouterInput<T1>,\n r2: ScheduleRouterInput<T2>,\n r3: ScheduleRouterInput<T3>,\n): ScheduleRouterDefinition<DeepMergeAll<[T1, T2, T3]>>;\nexport function mergeScheduleRouters<\n T1 extends ScheduleRouterConfig,\n T2 extends ScheduleRouterConfig,\n T3 extends ScheduleRouterConfig,\n T4 extends ScheduleRouterConfig,\n>(\n r1: ScheduleRouterInput<T1>,\n r2: ScheduleRouterInput<T2>,\n r3: ScheduleRouterInput<T3>,\n r4: ScheduleRouterInput<T4>,\n): ScheduleRouterDefinition<DeepMergeAll<[T1, T2, T3, T4]>>;\nexport function mergeScheduleRouters<\n T1 extends ScheduleRouterConfig,\n T2 extends ScheduleRouterConfig,\n T3 extends ScheduleRouterConfig,\n T4 extends ScheduleRouterConfig,\n T5 extends ScheduleRouterConfig,\n>(\n r1: ScheduleRouterInput<T1>,\n r2: ScheduleRouterInput<T2>,\n r3: ScheduleRouterInput<T3>,\n r4: ScheduleRouterInput<T4>,\n r5: ScheduleRouterInput<T5>,\n): ScheduleRouterDefinition<DeepMergeAll<[T1, T2, T3, T4, T5]>>;\nexport function mergeScheduleRouters<\n T1 extends ScheduleRouterConfig,\n T2 extends ScheduleRouterConfig,\n T3 extends ScheduleRouterConfig,\n T4 extends ScheduleRouterConfig,\n T5 extends ScheduleRouterConfig,\n T6 extends ScheduleRouterConfig,\n>(\n r1: ScheduleRouterInput<T1>,\n r2: ScheduleRouterInput<T2>,\n r3: ScheduleRouterInput<T3>,\n r4: ScheduleRouterInput<T4>,\n r5: ScheduleRouterInput<T5>,\n r6: ScheduleRouterInput<T6>,\n): ScheduleRouterDefinition<DeepMergeAll<[T1, T2, T3, T4, T5, T6]>>;\nexport function mergeScheduleRouters<\n T1 extends ScheduleRouterConfig,\n T2 extends ScheduleRouterConfig,\n T3 extends ScheduleRouterConfig,\n T4 extends ScheduleRouterConfig,\n T5 extends ScheduleRouterConfig,\n T6 extends ScheduleRouterConfig,\n T7 extends ScheduleRouterConfig,\n>(\n r1: ScheduleRouterInput<T1>,\n r2: ScheduleRouterInput<T2>,\n r3: ScheduleRouterInput<T3>,\n r4: ScheduleRouterInput<T4>,\n r5: ScheduleRouterInput<T5>,\n r6: ScheduleRouterInput<T6>,\n r7: ScheduleRouterInput<T7>,\n): ScheduleRouterDefinition<DeepMergeAll<[T1, T2, T3, T4, T5, T6, T7]>>;\nexport function mergeScheduleRouters<\n T1 extends ScheduleRouterConfig,\n T2 extends ScheduleRouterConfig,\n T3 extends ScheduleRouterConfig,\n T4 extends ScheduleRouterConfig,\n T5 extends ScheduleRouterConfig,\n T6 extends ScheduleRouterConfig,\n T7 extends ScheduleRouterConfig,\n T8 extends ScheduleRouterConfig,\n>(\n r1: ScheduleRouterInput<T1>,\n r2: ScheduleRouterInput<T2>,\n r3: ScheduleRouterInput<T3>,\n r4: ScheduleRouterInput<T4>,\n r5: ScheduleRouterInput<T5>,\n r6: ScheduleRouterInput<T6>,\n r7: ScheduleRouterInput<T7>,\n r8: ScheduleRouterInput<T8>,\n): ScheduleRouterDefinition<DeepMergeAll<[T1, T2, T3, T4, T5, T6, T7, T8]>>;\n\n// Variadic fallback for 9+\nexport function mergeScheduleRouters(\n ...routers: ScheduleRouterInput<ScheduleRouterConfig>[]\n): ScheduleRouterDefinition<ScheduleRouterConfig>;\n\n// Implementation\nexport function mergeScheduleRouters(\n ...routers: ScheduleRouterInput<ScheduleRouterConfig>[]\n): ScheduleRouterDefinition<ScheduleRouterConfig> {\n const merged = routers.map(extractTasks).reduce(deepMergeConfigs);\n return defineScheduleRouter(merged);\n}\n","/**\n * @fileoverview Factory function for creating schedule trigger maps.\n *\n * A trigger binds a task `type` to a timing expression (cron or rate). Triggers\n * are the WHEN/HOW binding and are kept SEPARATE from the task contract so the\n * same task can be wired to different triggers per deployment.\n *\n * @module schedule/task/define-schedule-triggers\n */\n\nimport type { ScheduleTriggerMap } from './types';\n\n/**\n * Creates a frozen trigger map from a triggerId → trigger configuration object.\n *\n * Each trigger is keyed by a stable `triggerId` (deployment-chosen). The\n * runtime uses the `triggerId` to derive a recurring run's idempotency key\n * (`${triggerId}:${scheduledFor}`).\n *\n * @param triggers - Map of triggerId → ScheduleTrigger\n * @returns A frozen ScheduleTriggerMap\n *\n * @example\n * ```typescript\n * const triggers = defineScheduleTriggers({\n * nightlyReconcile: { type: 'billing.dailyReconcile', cron: '0 2 * * *', timezone: 'UTC' },\n * healthPing: { type: 'ops.healthPing', rate: 'rate(5 minutes)' },\n * });\n * ```\n */\nexport function defineScheduleTriggers<T extends ScheduleTriggerMap>(triggers: T): Readonly<T> {\n for (const [triggerId, value] of Object.entries(triggers)) {\n // A trigger must have EXACTLY ONE timing expression — cron OR rate.\n const t = value as { cron?: unknown; rate?: unknown };\n const hasCron = t.cron !== undefined;\n const hasRate = t.rate !== undefined;\n if (hasCron === hasRate) {\n throw new Error(\n `Schedule trigger \"${triggerId}\" must have exactly one of 'cron' or 'rate' ` +\n `(got ${hasCron ? 'both' : 'neither'}).`,\n );\n }\n // Freeze each trigger as well as the outer map for full immutability.\n Object.freeze(value);\n }\n return Object.freeze({ ...triggers }) as Readonly<T>;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/presentation/schedule/task/utils.ts"],"sourcesContent":["/**\n * @fileoverview Utility functions for scheduled task routing.\n *\n * @module schedule/task/utils\n */\n\n/**\n * Generates a task ID from a dotted key path.\n *\n * Converts dot-separated keys to camelCase:\n * - `\"billing.reconcile\"` → `\"billingReconcile\"`\n * - `\"reports.weekly.digest\"` → `\"reportsWeeklyDigest\"`\n *\n * @param key - The dotted key path\n * @returns A camelCase task ID\n */\nexport function generateTaskId(key: string): string {\n return key.replace(/\\.(\\w)/g, (_, char: string) => char.toUpperCase());\n}\n"],"mappings":";AAgBO,SAAS,eAAe,KAAqB;AAClD,SAAO,IAAI,QAAQ,WAAW,CAAC,GAAG,SAAiB,KAAK,YAAY,CAAC;AACvE;","names":[]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectScheduledTasks,
|
|
3
|
+
isCronScheduleTrigger,
|
|
4
|
+
isScheduleRouterDefinition
|
|
5
|
+
} from "./chunk-YFD3K4BS.js";
|
|
6
|
+
|
|
7
|
+
// src/presentation/schedule/catalog/generate.ts
|
|
8
|
+
function generateScheduleCatalog(router, triggers = {}) {
|
|
9
|
+
const config = isScheduleRouterDefinition(router) ? router.tasks : router;
|
|
10
|
+
const collected = collectScheduledTasks(config);
|
|
11
|
+
const seenTypes = /* @__PURE__ */ new Set();
|
|
12
|
+
const tasks = collected.map(({ key, task }) => {
|
|
13
|
+
if (seenTypes.has(task.type)) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Duplicate scheduled task type "${task.type}" in catalog: each task type must be unique.`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
seenTypes.add(task.type);
|
|
19
|
+
return {
|
|
20
|
+
type: task.type,
|
|
21
|
+
key,
|
|
22
|
+
hasPayload: task.payload !== void 0,
|
|
23
|
+
hasContext: task.context !== void 0,
|
|
24
|
+
docs: task.docs
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
const triggerCatalog = Object.entries(triggers).map(
|
|
28
|
+
([triggerId, trigger]) => {
|
|
29
|
+
if (!seenTypes.has(trigger.type)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Schedule trigger "${triggerId}" references unknown task type "${trigger.type}": no task with that type is registered in the router.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const timezone = trigger.timezone ? { timezone: trigger.timezone } : {};
|
|
35
|
+
return isCronScheduleTrigger(trigger) ? { triggerId, type: trigger.type, cron: trigger.cron, ...timezone } : { triggerId, type: trigger.type, rate: trigger.rate, ...timezone };
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
return { tasks, triggers: triggerCatalog };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
generateScheduleCatalog
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=chunk-VC25BOVP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/presentation/schedule/catalog/generate.ts"],"sourcesContent":["/**\n * @fileoverview Schedule catalog generator.\n *\n * Builds a serializable inventory of a schedule router's tasks + their trigger\n * bindings, and validates their consistency. This is intentionally NOT a full\n * AsyncAPI-style document — it is a minimal, validated manifest that catches\n * wiring mistakes (duplicate task types, triggers pointing at unknown tasks)\n * and makes test harnesses / provider registration easy to build.\n *\n * Note: a `ScheduleTriggerMap` is a plain object keyed by `triggerId`, so\n * duplicate trigger ids are structurally impossible and need no check here.\n *\n * @module schedule/catalog/generate\n */\n\nimport {\n collectScheduledTasks,\n isScheduleRouterDefinition,\n isCronScheduleTrigger,\n} from '../task/types';\nimport type {\n ScheduleRouterConfig,\n ScheduleRouterDefinition,\n ScheduleTriggerMap,\n} from '../task/types';\nimport type {\n ScheduleCatalog,\n ScheduleTaskCatalogEntry,\n ScheduleTriggerCatalogEntry,\n} from './types';\n\n/**\n * Generates a validated catalog from a schedule router (and optional triggers).\n *\n * @param router - A schedule router config or definition.\n * @param triggers - Optional trigger map (output of `defineScheduleTriggers`).\n * @returns The serializable catalog inventory.\n * @throws {Error} On a duplicate task `type`, or a trigger whose `type` has no\n * registered task.\n */\nexport function generateScheduleCatalog(\n router: ScheduleRouterConfig | ScheduleRouterDefinition,\n triggers: ScheduleTriggerMap = {},\n): ScheduleCatalog {\n const config = isScheduleRouterDefinition(router) ? router.tasks : router;\n const collected = collectScheduledTasks(config);\n\n const seenTypes = new Set<string>();\n const tasks: ScheduleTaskCatalogEntry[] = collected.map(({ key, task }) => {\n if (seenTypes.has(task.type)) {\n throw new Error(\n `Duplicate scheduled task type \"${task.type}\" in catalog: each task type must be unique.`,\n );\n }\n seenTypes.add(task.type);\n return {\n type: task.type,\n key,\n hasPayload: task.payload !== undefined,\n hasContext: task.context !== undefined,\n docs: task.docs,\n };\n });\n\n const triggerCatalog: ScheduleTriggerCatalogEntry[] = Object.entries(triggers).map(\n ([triggerId, trigger]) => {\n if (!seenTypes.has(trigger.type)) {\n throw new Error(\n `Schedule trigger \"${triggerId}\" references unknown task type \"${trigger.type}\": ` +\n `no task with that type is registered in the router.`,\n );\n }\n const timezone = trigger.timezone ? { timezone: trigger.timezone } : {};\n return isCronScheduleTrigger(trigger)\n ? { triggerId, type: trigger.type, cron: trigger.cron, ...timezone }\n : { triggerId, type: trigger.type, rate: trigger.rate, ...timezone };\n },\n );\n\n return { tasks, triggers: triggerCatalog };\n}\n"],"mappings":";;;;;;;AAwCO,SAAS,wBACd,QACA,WAA+B,CAAC,GACf;AACjB,QAAM,SAAS,2BAA2B,MAAM,IAAI,OAAO,QAAQ;AACnE,QAAM,YAAY,sBAAsB,MAAM;AAE9C,QAAM,YAAY,oBAAI,IAAY;AAClC,QAAM,QAAoC,UAAU,IAAI,CAAC,EAAE,KAAK,KAAK,MAAM;AACzE,QAAI,UAAU,IAAI,KAAK,IAAI,GAAG;AAC5B,YAAM,IAAI;AAAA,QACR,kCAAkC,KAAK,IAAI;AAAA,MAC7C;AAAA,IACF;AACA,cAAU,IAAI,KAAK,IAAI;AACvB,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX;AAAA,MACA,YAAY,KAAK,YAAY;AAAA,MAC7B,YAAY,KAAK,YAAY;AAAA,MAC7B,MAAM,KAAK;AAAA,IACb;AAAA,EACF,CAAC;AAED,QAAM,iBAAgD,OAAO,QAAQ,QAAQ,EAAE;AAAA,IAC7E,CAAC,CAAC,WAAW,OAAO,MAAM;AACxB,UAAI,CAAC,UAAU,IAAI,QAAQ,IAAI,GAAG;AAChC,cAAM,IAAI;AAAA,UACR,qBAAqB,SAAS,mCAAmC,QAAQ,IAAI;AAAA,QAE/E;AAAA,MACF;AACA,YAAM,WAAW,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AACtE,aAAO,sBAAsB,OAAO,IAChC,EAAE,WAAW,MAAM,QAAQ,MAAM,MAAM,QAAQ,MAAM,GAAG,SAAS,IACjE,EAAE,WAAW,MAAM,QAAQ,MAAM,MAAM,QAAQ,MAAM,GAAG,SAAS;AAAA,IACvE;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,UAAU,eAAe;AAC3C;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-WMOQNNF5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|