@blokjs/trigger-cron 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # @blokjs/trigger-cron
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial public release of Blok packages.
8
+
9
+ This release includes:
10
+
11
+ - Core packages: @blokjs/shared, @blokjs/helper, @blokjs/runner
12
+ - Node packages: @blokjs/api-call, @blokjs/if-else, @blokjs/react
13
+ - Trigger packages: pubsub, queue, webhook, websocket, worker, cron, grpc
14
+ - CLI tool: blokctl
15
+ - Editor support: @blokjs/lsp-server, @blokjs/syntax
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies
20
+ - @blokjs/shared@0.2.0
21
+ - @blokjs/helper@0.2.0
22
+ - @blokjs/runner@0.2.0
@@ -0,0 +1,124 @@
1
+ /**
2
+ * CronTrigger - Scheduled workflow execution based on cron expressions
3
+ *
4
+ * Extends TriggerBase to support scheduled triggers:
5
+ * - Cron expressions (e.g., "0 * * * *" for hourly)
6
+ * - Timezone-aware scheduling
7
+ * - Overlap prevention
8
+ * - Manual trigger support
9
+ *
10
+ * Uses the 'cron' package for cron parsing and scheduling.
11
+ */
12
+ import type { CronTriggerOpts, HelperResponse } from "@blok/helper";
13
+ import { DefaultLogger, type GlobalOptions, type BlokService, TriggerBase, type TriggerResponse } from "@blok/runner";
14
+ import { CronJob } from "cron";
15
+ /**
16
+ * Scheduled job information
17
+ */
18
+ export interface ScheduledJob {
19
+ /** Unique job ID */
20
+ id: string;
21
+ /** Workflow path */
22
+ workflowPath: string;
23
+ /** Cron expression */
24
+ schedule: string;
25
+ /** Timezone */
26
+ timezone: string;
27
+ /** Allow overlapping executions */
28
+ overlap: boolean;
29
+ /** Whether the job is currently running */
30
+ running: boolean;
31
+ /** Last execution time */
32
+ lastRun?: Date;
33
+ /** Next scheduled time */
34
+ nextRun?: Date;
35
+ /** Internal CronJob instance */
36
+ job: CronJob;
37
+ }
38
+ /**
39
+ * Execution context passed to the workflow
40
+ */
41
+ export interface CronExecutionContext {
42
+ /** Job ID */
43
+ jobId: string;
44
+ /** Scheduled time (when it was supposed to run) */
45
+ scheduledTime: Date;
46
+ /** Actual execution time */
47
+ executionTime: Date;
48
+ /** Cron expression */
49
+ schedule: string;
50
+ /** Timezone */
51
+ timezone: string;
52
+ /** Whether this is a manual trigger */
53
+ manual: boolean;
54
+ }
55
+ /**
56
+ * Workflow model with cron trigger configuration
57
+ */
58
+ interface CronWorkflowModel {
59
+ path: string;
60
+ config: {
61
+ name: string;
62
+ version: string;
63
+ trigger?: {
64
+ cron?: CronTriggerOpts;
65
+ [key: string]: unknown;
66
+ };
67
+ [key: string]: unknown;
68
+ };
69
+ }
70
+ /**
71
+ * CronTrigger - Scheduled workflow execution
72
+ */
73
+ export declare abstract class CronTrigger extends TriggerBase {
74
+ protected nodeMap: GlobalOptions;
75
+ protected readonly tracer: import("@opentelemetry/api").Tracer;
76
+ protected readonly logger: DefaultLogger;
77
+ protected jobs: Map<string, ScheduledJob>;
78
+ protected abstract nodes: Record<string, BlokService<unknown>>;
79
+ protected abstract workflows: Record<string, HelperResponse>;
80
+ constructor();
81
+ /**
82
+ * Load nodes into the node map
83
+ */
84
+ loadNodes(): void;
85
+ /**
86
+ * Load workflows into the workflow map
87
+ */
88
+ loadWorkflows(): void;
89
+ /**
90
+ * Convert cron DateTime to Date
91
+ * The cron package uses luxon DateTime which has toJSDate()
92
+ */
93
+ protected toDate(dateTime: unknown): Date;
94
+ /**
95
+ * Start the cron scheduler - main entry point
96
+ */
97
+ listen(): Promise<number>;
98
+ /**
99
+ * Stop all cron jobs
100
+ */
101
+ stop(): Promise<void>;
102
+ protected onHmrWorkflowChange(): Promise<void>;
103
+ /**
104
+ * Manually trigger a specific job
105
+ */
106
+ triggerJob(jobId: string): Promise<TriggerResponse | null>;
107
+ /**
108
+ * Get all scheduled jobs
109
+ */
110
+ getJobs(): ScheduledJob[];
111
+ /**
112
+ * Get all workflows that have cron triggers
113
+ */
114
+ protected getCronWorkflows(): CronWorkflowModel[];
115
+ /**
116
+ * Get workflow by path
117
+ */
118
+ protected getWorkflowByPath(path: string): CronWorkflowModel | null;
119
+ /**
120
+ * Execute a workflow
121
+ */
122
+ protected executeWorkflow(jobId: string, workflow: CronWorkflowModel, config: CronTriggerOpts, manual: boolean): Promise<TriggerResponse>;
123
+ }
124
+ export default CronTrigger;
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ /**
3
+ * CronTrigger - Scheduled workflow execution based on cron expressions
4
+ *
5
+ * Extends TriggerBase to support scheduled triggers:
6
+ * - Cron expressions (e.g., "0 * * * *" for hourly)
7
+ * - Timezone-aware scheduling
8
+ * - Overlap prevention
9
+ * - Manual trigger support
10
+ *
11
+ * Uses the 'cron' package for cron parsing and scheduling.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.CronTrigger = void 0;
15
+ const runner_1 = require("@blok/runner");
16
+ const api_1 = require("@opentelemetry/api");
17
+ const cron_1 = require("cron");
18
+ const uuid_1 = require("uuid");
19
+ /**
20
+ * CronTrigger - Scheduled workflow execution
21
+ */
22
+ class CronTrigger extends runner_1.TriggerBase {
23
+ nodeMap = {};
24
+ tracer = api_1.trace.getTracer(process.env.PROJECT_NAME || "trigger-cron-workflow", process.env.PROJECT_VERSION || "0.0.1");
25
+ logger = new runner_1.DefaultLogger();
26
+ jobs = new Map();
27
+ constructor() {
28
+ super();
29
+ this.loadNodes();
30
+ this.loadWorkflows();
31
+ }
32
+ /**
33
+ * Load nodes into the node map
34
+ */
35
+ loadNodes() {
36
+ this.nodeMap.nodes = new runner_1.NodeMap();
37
+ const nodeKeys = Object.keys(this.nodes);
38
+ for (const key of nodeKeys) {
39
+ this.nodeMap.nodes.addNode(key, this.nodes[key]);
40
+ }
41
+ }
42
+ /**
43
+ * Load workflows into the workflow map
44
+ */
45
+ loadWorkflows() {
46
+ this.nodeMap.workflows = this.workflows;
47
+ }
48
+ /**
49
+ * Convert cron DateTime to Date
50
+ * The cron package uses luxon DateTime which has toJSDate()
51
+ */
52
+ toDate(dateTime) {
53
+ if (dateTime && typeof dateTime === "object" && "toJSDate" in dateTime) {
54
+ return dateTime.toJSDate();
55
+ }
56
+ return dateTime instanceof Date ? dateTime : new Date(dateTime);
57
+ }
58
+ /**
59
+ * Start the cron scheduler - main entry point
60
+ */
61
+ async listen() {
62
+ const startTime = this.startCounter();
63
+ try {
64
+ // Find all workflows with cron triggers
65
+ const cronWorkflows = this.getCronWorkflows();
66
+ if (cronWorkflows.length === 0) {
67
+ this.logger.log("No workflows with cron triggers found");
68
+ return this.endCounter(startTime);
69
+ }
70
+ // Create and start cron jobs for each workflow
71
+ for (const workflow of cronWorkflows) {
72
+ const config = workflow.config.trigger?.cron;
73
+ const jobId = `cron-${workflow.path}-${(0, uuid_1.v4)().slice(0, 8)}`;
74
+ this.logger.log(`Scheduling workflow: ${workflow.path} with schedule: ${config.schedule} (${config.timezone})`);
75
+ const job = new cron_1.CronJob(config.schedule, async () => {
76
+ await this.executeWorkflow(jobId, workflow, config, false);
77
+ }, null, // onComplete
78
+ false, // start
79
+ config.timezone);
80
+ const scheduledJob = {
81
+ id: jobId,
82
+ workflowPath: workflow.path,
83
+ schedule: config.schedule,
84
+ timezone: config.timezone,
85
+ overlap: config.overlap ?? false,
86
+ running: false,
87
+ nextRun: this.toDate(job.nextDate()),
88
+ job,
89
+ };
90
+ this.jobs.set(jobId, scheduledJob);
91
+ // Start the job
92
+ job.start();
93
+ this.logger.log(`Job ${jobId} started. Next run: ${scheduledJob.nextRun}`);
94
+ }
95
+ this.logger.log(`Cron trigger started. ${this.jobs.size} job(s) scheduled`);
96
+ // Enable HMR in development mode
97
+ if (process.env.BLOK_HMR === "true" || process.env.NODE_ENV === "development") {
98
+ await this.enableHotReload();
99
+ }
100
+ return this.endCounter(startTime);
101
+ }
102
+ catch (error) {
103
+ this.logger.error(`Failed to start cron trigger: ${error.message}`);
104
+ throw error;
105
+ }
106
+ }
107
+ /**
108
+ * Stop all cron jobs
109
+ */
110
+ async stop() {
111
+ for (const [jobId, scheduledJob] of this.jobs) {
112
+ scheduledJob.job.stop();
113
+ this.logger.log(`Job ${jobId} stopped`);
114
+ }
115
+ this.jobs.clear();
116
+ this.logger.log("Cron trigger stopped");
117
+ }
118
+ async onHmrWorkflowChange() {
119
+ this.logger.log("[HMR] Cron workflow changed, reloading...");
120
+ await this.waitForInFlightRequests();
121
+ await this.stop();
122
+ this.loadWorkflows();
123
+ await this.listen();
124
+ }
125
+ /**
126
+ * Manually trigger a specific job
127
+ */
128
+ async triggerJob(jobId) {
129
+ const scheduledJob = this.jobs.get(jobId);
130
+ if (!scheduledJob) {
131
+ this.logger.error(`Job not found: ${jobId}`);
132
+ return null;
133
+ }
134
+ // Get the workflow
135
+ const workflow = this.getWorkflowByPath(scheduledJob.workflowPath);
136
+ if (!workflow) {
137
+ this.logger.error(`Workflow not found: ${scheduledJob.workflowPath}`);
138
+ return null;
139
+ }
140
+ const config = workflow.config.trigger?.cron;
141
+ return this.executeWorkflow(jobId, workflow, config, true);
142
+ }
143
+ /**
144
+ * Get all scheduled jobs
145
+ */
146
+ getJobs() {
147
+ return Array.from(this.jobs.values()).map((job) => ({
148
+ ...job,
149
+ nextRun: this.toDate(job.job.nextDate()),
150
+ }));
151
+ }
152
+ /**
153
+ * Get all workflows that have cron triggers
154
+ */
155
+ getCronWorkflows() {
156
+ const workflows = [];
157
+ for (const [path, workflow] of Object.entries(this.nodeMap.workflows || {})) {
158
+ const workflowConfig = workflow._config;
159
+ if (workflowConfig?.trigger) {
160
+ const triggerType = Object.keys(workflowConfig.trigger)[0];
161
+ if (triggerType === "cron" && workflowConfig.trigger.cron) {
162
+ workflows.push({
163
+ path,
164
+ config: workflowConfig,
165
+ });
166
+ }
167
+ }
168
+ }
169
+ return workflows;
170
+ }
171
+ /**
172
+ * Get workflow by path
173
+ */
174
+ getWorkflowByPath(path) {
175
+ const workflow = this.nodeMap.workflows?.[path];
176
+ if (!workflow)
177
+ return null;
178
+ const workflowConfig = workflow._config;
179
+ return {
180
+ path,
181
+ config: workflowConfig,
182
+ };
183
+ }
184
+ /**
185
+ * Execute a workflow
186
+ */
187
+ async executeWorkflow(jobId, workflow, config, manual) {
188
+ const scheduledJob = this.jobs.get(jobId);
189
+ if (!scheduledJob) {
190
+ throw new Error(`Job not found: ${jobId}`);
191
+ }
192
+ // Check for overlap
193
+ if (scheduledJob.running && !scheduledJob.overlap) {
194
+ this.logger.log(`Skipping ${jobId}: previous execution still running (overlap disabled)`);
195
+ return { ctx: {}, metrics: {} };
196
+ }
197
+ const executionId = (0, uuid_1.v4)();
198
+ const lastDate = scheduledJob.job.lastDate();
199
+ const scheduledTime = lastDate ? new Date(lastDate) : new Date();
200
+ const executionTime = new Date();
201
+ const defaultMeter = api_1.metrics.getMeter("default");
202
+ const cronExecutions = defaultMeter.createCounter("cron_executions", {
203
+ description: "Cron job executions",
204
+ });
205
+ const cronErrors = defaultMeter.createCounter("cron_errors", {
206
+ description: "Cron job execution errors",
207
+ });
208
+ return new Promise((resolve) => {
209
+ this.tracer.startActiveSpan(`cron:${workflow.path}`, async (span) => {
210
+ scheduledJob.running = true;
211
+ try {
212
+ const start = performance.now();
213
+ // Initialize configuration for this workflow
214
+ await this.configuration.init(workflow.path, this.nodeMap);
215
+ // Create context
216
+ const ctx = this.createContext(undefined, workflow.path, executionId);
217
+ // Create execution context
218
+ const cronContext = {
219
+ jobId,
220
+ scheduledTime,
221
+ executionTime,
222
+ schedule: config.schedule,
223
+ timezone: config.timezone,
224
+ manual,
225
+ };
226
+ // Populate request with cron context
227
+ ctx.request = {
228
+ body: cronContext,
229
+ headers: {
230
+ "x-cron-job-id": jobId,
231
+ "x-cron-schedule": config.schedule,
232
+ "x-cron-timezone": config.timezone,
233
+ "x-cron-manual": String(manual),
234
+ },
235
+ query: {},
236
+ params: {
237
+ jobId,
238
+ schedule: config.schedule,
239
+ },
240
+ };
241
+ // Store cron context in vars
242
+ if (!ctx.vars)
243
+ ctx.vars = {};
244
+ ctx.vars._cron_context = {
245
+ jobId,
246
+ scheduledTime: scheduledTime.toISOString(),
247
+ executionTime: executionTime.toISOString(),
248
+ schedule: config.schedule,
249
+ timezone: config.timezone,
250
+ manual: String(manual),
251
+ };
252
+ ctx.logger.log(`Executing cron job: ${jobId} (${manual ? "manual" : "scheduled"})`);
253
+ // Execute workflow
254
+ const response = await this.run(ctx);
255
+ const end = performance.now();
256
+ // Update job state
257
+ scheduledJob.lastRun = executionTime;
258
+ scheduledJob.nextRun = this.toDate(scheduledJob.job.nextDate());
259
+ // Set span attributes
260
+ span.setAttribute("success", true);
261
+ span.setAttribute("job_id", jobId);
262
+ span.setAttribute("workflow_path", workflow.path);
263
+ span.setAttribute("schedule", config.schedule);
264
+ span.setAttribute("timezone", config.timezone);
265
+ span.setAttribute("manual", manual);
266
+ span.setAttribute("elapsed_ms", end - start);
267
+ span.setStatus({ code: api_1.SpanStatusCode.OK });
268
+ // Record metrics
269
+ cronExecutions.add(1, {
270
+ env: process.env.NODE_ENV,
271
+ job_id: jobId,
272
+ workflow_name: this.configuration.name,
273
+ manual: String(manual),
274
+ success: "true",
275
+ });
276
+ ctx.logger.log(`Cron job completed in ${(end - start).toFixed(2)}ms: ${jobId}`);
277
+ resolve(response);
278
+ }
279
+ catch (error) {
280
+ const errorMessage = error.message;
281
+ // Set span error
282
+ span.setAttribute("success", false);
283
+ span.recordException(error);
284
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: errorMessage });
285
+ // Record error metrics
286
+ cronErrors.add(1, {
287
+ env: process.env.NODE_ENV,
288
+ job_id: jobId,
289
+ workflow_name: this.configuration?.name || "unknown",
290
+ manual: String(manual),
291
+ });
292
+ this.logger.error(`Cron job failed ${jobId}: ${errorMessage}`, error.stack);
293
+ resolve({ ctx: {}, metrics: {} });
294
+ }
295
+ finally {
296
+ scheduledJob.running = false;
297
+ span.end();
298
+ }
299
+ });
300
+ });
301
+ }
302
+ }
303
+ exports.CronTrigger = CronTrigger;
304
+ exports.default = CronTrigger;
305
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ3JvblRyaWdnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvQ3JvblRyaWdnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7Ozs7O0dBVUc7OztBQUdILHlDQU9zQjtBQUV0Qiw0Q0FBK0U7QUFDL0UsK0JBQStCO0FBQy9CLCtCQUFrQztBQTREbEM7O0dBRUc7QUFDSCxNQUFzQixXQUFZLFNBQVEsb0JBQVc7SUFDMUMsT0FBTyxHQUFrQixFQUFtQixDQUFDO0lBQ3BDLE1BQU0sR0FBRyxXQUFLLENBQUMsU0FBUyxDQUMxQyxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksSUFBSSx1QkFBdUIsRUFDbkQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxlQUFlLElBQUksT0FBTyxDQUN0QyxDQUFDO0lBQ2lCLE1BQU0sR0FBRyxJQUFJLHNCQUFhLEVBQUUsQ0FBQztJQUN0QyxJQUFJLEdBQThCLElBQUksR0FBRyxFQUFFLENBQUM7SUFNdEQ7UUFDQyxLQUFLLEVBQUUsQ0FBQztRQUNSLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztRQUNqQixJQUFJLENBQUMsYUFBYSxFQUFFLENBQUM7SUFDdEIsQ0FBQztJQUVEOztPQUVHO0lBQ0gsU0FBUztRQUNSLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxHQUFHLElBQUksZ0JBQU8sRUFBRSxDQUFDO1FBQ25DLE1BQU0sUUFBUSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3pDLEtBQUssTUFBTSxHQUFHLElBQUksUUFBUSxFQUFFLENBQUM7WUFDNUIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFDbEQsQ0FBQztJQUNGLENBQUM7SUFFRDs7T0FFRztJQUNILGFBQWE7UUFDWixJQUFJLENBQUMsT0FBTyxDQUFDLFNB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QUFJLFNBQVM7d0JBQ3BELE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDO3FCQUN0QixDQUFDLENBQUM7b0JBRUgsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsbUJBQW1CLEtBQUssS0FBSyxZQUFZLEVBQUUsRUFBRyxLQUFlLENBQUMsS0FBSyxDQUFDLENBQUM7b0JBRXZGLE9BQU8sQ0FBQyxFQUFFLEdBQUcsRUFBRSxFQUFhLEVBQUUsT0FBTyxFQUFFLEVBQWlCLEVBQUUsQ0FBQyxDQUFDO2dCQUM3RCxDQUFDO3dCQUFTLENBQUM7b0JBQ1YsWUFBWSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUM7b0JBQzdCLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztnQkFDWixDQUFDO1lBQ0YsQ0FBQyxDQUFDLENBQUM7UUFDSixDQUFDLENBQUMsQ0FBQztJQUNKLENBQUM7Q0FDRDtBQXRWRCxrQ0FzVkM7QUFFRCxrQkFBZSxXQUFXLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENyb25UcmlnZ2VyIC0gU2NoZWR1bGVkIHdvcmtmbG93IGV4ZWN1dGlvbiBiYXNlZCBvbiBjcm9uIGV4cHJlc3Npb25zXG4gKlxuICogRXh0ZW5kcyBUcmlnZ2VyQmFzZSB0byBzdXBwb3J0IHNjaGVkdWxlZCB0cmlnZ2VyczpcbiAqIC0gQ3JvbiBleHByZXNzaW9ucyAoZS5nLiwgXCIwICogKiAqICpcIiBmb3IgaG91cmx5KVxuICogLSBUaW1lem9uZS1hd2FyZSBzY2hlZHVsaW5nXG4gKiAtIE92ZXJsYXAgcHJldmVudGlvblxuICogLSBNYW51YWwgdHJpZ2dlciBzdXBwb3J0XG4gKlxuICogVXNlcyB0aGUgJ2Nyb24nIHBhY2thZ2UgZm9yIGNyb24gcGFyc2luZyBhbmQgc2NoZWR1bGluZy5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IENyb25UcmlnZ2VyT3B0cywgSGVscGVyUmVzcG9uc2UgfSBmcm9tIFwiQGJsb2svaGVscGVyXCI7XG5pbXBvcnQge1xuXHREZWZhdWx0TG9nZ2VyLFxuXHR0eXBlIEdsb2JhbE9wdGlvbnMsXG5cdHR5cGUgQmxva1NlcnZpY2UsXG5cdE5vZGVNYXAsXG5cdFRyaWdnZXJCYXNlLFxuXHR0eXBlIFRyaWdnZXJSZXNwb25zZSxcbn0gZnJvbSBcIkBibG9rL3J1bm5lclwiO1xuaW1wb3J0IHR5cGUgeyBDb250ZXh0LCBNZXRyaWNzVHlwZSwgUmVxdWVzdENvbnRleHQgfSBmcm9tIFwiQGJsb2svc2hhcmVkXCI7XG5pbXBvcnQgeyB0eXBlIFNwYW4sIFNwYW5TdGF0dXNDb2RlLCBtZXRyaWNzLCB0cmFjZSB9IGZyb20gXCJAb3BlbnRlbGVtZXRyeS9hcGlcIjtcbmltcG9ydCB7IENyb25Kb2IgfSBmcm9tIFwiY3JvblwiO1xuaW1wb3J0IHsgdjQgYXMgdXVpZCB9IGZyb20gXCJ1dWlkXCI7XG5cbi8qKlxuICogU2NoZWR1bGVkIGpvYiBpbmZvcm1hdGlvblxuICovXG5leHBvcnQgaW50ZXJmYWNlIFNjaGVkdWxlZEpvYiB7XG5cdC8qKiBVbmlxdWUgam9iIElEICovXG5cdGlkOiBzdHJpbmc7XG5cdC8qKiBXb3JrZmxvdyBwYXRoICovXG5cdHdvcmtmbG93UGF0aDogc3RyaW5nO1xuXHQvKiogQ3JvbiBleHByZXNzaW9uICovXG5cdHNjaGVkdWxlOiBzdHJpbmc7XG5cdC8qKiBUaW1lem9uZSAqL1xuXHR0aW1lem9uZTogc3RyaW5nO1xuXHQvKiogQWxsb3cgb3ZlcmxhcHBpbmcgZXhlY3V0aW9ucyAqL1xuXHRvdmVybGFwOiBib29sZWFuO1xuXHQvKiogV2hldGhlciB0aGUgam9iIGlzIGN1cnJlbnRseSBydW5uaW5nICovXG5cdHJ1bm5pbmc6IGJvb2xlYW47XG5cdC8qKiBMYXN0IGV4ZWN1dGlvbiB0aW1lICovXG5cdGxhc3RSdW4/OiBEYXRlO1xuXHQvKiogTmV4dCBzY2hlZHVsZWQgdGltZSAqL1xuXHRuZXh0UnVuPzogRGF0ZTtcblx0LyoqIEludGVybmFsIENyb25Kb2IgaW5zdGFuY2UgKi9cblx0am9iOiBDcm9uSm9iO1xufVxuXG4vKipcbiAqIEV4ZWN1dGlvbiBjb250ZXh0IHBhc3NlZCB0byB0aGUgd29ya2Zsb3dcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBDcm9uRXhlY3V0aW9uQ29udGV4dCB7XG5cdC8qKiBKb2IgSUQgKi9cblx0am9iSWQ6IHN0cmluZztcblx0LyoqIFNjaGVkdWxlZCB0aW1lICh3aGVuIGl0IHdhcyBzdXBwb3NlZCB0byBydW4pICovXG5cdHNjaGVkdWxlZFRpbWU6IERhdGU7XG5cdC8qKiBBY3R1YWwgZXhlY3V0aW9uIHRpbWUgKi9cblx0ZXhlY3V0aW9uVGltZTogRGF0ZTtcblx0LyoqIENyb24gZXhwcmVzc2lvbiAqL1xuXHRzY2hlZHVsZTogc3RyaW5nO1xuXHQvKiogVGltZXpvbmUgKi9cblx0dGltZXpvbmU6IHN0cmluZztcblx0LyoqIFdoZXRoZXIgdGhpcyBpcyBhIG1hbnVhbCB0cmlnZ2VyICovXG5cdG1hbnVhbDogYm9vbGVhbjtcbn1cblxuLyoqXG4gKiBXb3JrZmxvdyBtb2RlbCB3aXRoIGNyb24gdHJpZ2dlciBjb25maWd1cmF0aW9uXG4gKi9cbmludGVyZmFjZSBDcm9uV29ya2Zsb3dNb2RlbCB7XG5cdHBhdGg6IHN0cmluZztcblx0Y29uZmlnOiB7XG5cdFx0bmFtZTogc3RyaW5nO1xuXHRcdHZlcnNpb246IHN0cmluZztcblx0XHR0cmlnZ2VyPzoge1xuXHRcdFx0Y3Jvbj86IENyb25UcmlnZ2VyT3B0cztcblx0XHRcdFtrZXk6IHN0cmluZ106IHVua25vd247XG5cdFx0fTtcblx0XHRba2V5OiBzdHJpbmddOiB1bmtub3duO1xuXHR9O1xufVxuXG4vKipcbiAqIENyb25UcmlnZ2VyIC0gU2NoZWR1bGVkIHdvcmtmbG93IGV4ZWN1dGlvblxuICovXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgQ3JvblRyaWdnZXIgZXh0ZW5kcyBUcmlnZ2VyQmFzZSB7XG5cdHByb3RlY3RlZCBub2RlTWFwOiBHbG9iYWxPcHRpb25zID0ge30gYXMgR2xvYmFsT3B0aW9ucztcblx0cHJvdGVjdGVkIHJlYWRvbmx5IHRyYWNlciA9IHRyYWNlLmdldFRyYWNlcihcblx0XHRwcm9jZXNzLmVudi5QUk9KRUNUX05BTUUgfHwgXCJ0cmlnZ2VyLWNyb24td29ya2Zsb3dcIixcblx0XHRwcm9jZXNzLmVudi5QUk9KRUNUX1ZFUlNJT04gfHwgXCIwLjAuMVwiLFxuXHQpO1xuXHRwcm90ZWN0ZWQgcmVhZG9ubHkgbG9nZ2VyID0gbmV3IERlZmF1bHRMb2dnZXIoKTtcblx0cHJvdGVjdGVkIGpvYnM6IE1hcDxzdHJpbmcsIFNjaGVkdWxlZEpvYj4gPSBuZXcgTWFwKCk7XG5cblx0Ly8gU3ViY2xhc3NlcyBwcm92aWRlIHRoZXNlXG5cdHByb3RlY3RlZCBhYnN0cmFjdCBub2RlczogUmVjb3JkPHN0cmluZywgQmxva1NlcnZpY2U8dW5rbm93bj4+O1xuXHRwcm90ZWN0ZWQgYWJzdHJhY3Qgd29ya2Zsb3dzOiBSZWNvcmQ8c3RyaW5nLCBIZWxwZXJSZXNwb25zZT47XG5cblx0Y29uc3RydWN0b3IoKSB7XG5cdFx0c3VwZXIoKTtcblx0XHR0aGlzLmxvYWROb2RlcygpO1xuXHRcdHRoaXMubG9hZFdvcmtmbG93cygpO1xuXHR9XG5cblx0LyoqXG5cdCAqIExvYWQgbm9kZXMgaW50byB0aGUgbm9kZSBtYXBcblx0ICovXG5cdGxvYWROb2RlcygpOiB2b2lkIHtcblx0XHR0aGlzLm5vZGVNYXAubm9kZXMgPSBuZXcgTm9kZU1hcCgpO1xuXHRcdGNvbnN0IG5vZGVLZXlzID0gT2JqZWN0LmtleXModGhpcy5ub2Rlcyk7XG5cdFx0Zm9yIChjb25zdCBrZXkgb2Ygbm9kZUtleXMpIHtcblx0XHRcdHRoaXMubm9kZU1hcC5ub2Rlcy5hZGROb2RlKGtleSwgdGhpcy5ub2Rlc1trZXldKTtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogTG9hZCB3b3JrZmxvd3MgaW50byB0aGUgd29ya2Zsb3cgbWFwXG5cdCAqL1xuXHRsb2FkV29ya2Zsb3dzKCk6IHZvaWQge1xuXHRcdHRoaXMubm9kZU1hcC53b3JrZmxvd3MgPSB0aGlzLndvcmtmbG93cztcblx0fVxuXG5cdC8qKlxuXHQgKiBDb252ZXJ0IGNyb24gRGF0ZVRpbWUgdG8gRGF0ZVxuXHQgKiBUaGUgY3JvbiBwYWNrYWdlIHVzZXMgbHV4b24gRGF0ZVRpbWUgd2hpY2ggaGFzIHRvSlNEYXRlKClcblx0ICovXG5cdHByb3RlY3RlZCB0b0RhdGUoZGF0ZVRpbWU6IHVua25vd24pOiBEYXRlIHtcblx0XHRpZiAoZGF0ZVRpbWUgJiYgdHlwZW9mIGRhdGVUaW1lID09PSBcIm9iamVjdFwiICYmIFwidG9KU0RhdGVcIiBpbiBkYXRlVGltZSkge1xuXHRcdFx0cmV0dXJuIChkYXRlVGltZSBhcyB7IHRvSlNEYXRlOiAoKSA9PiBEYXRlIH0pLnRvSlNEYXRlKCk7XG5cdFx0fVxuXHRcdHJldHVybiBkYXRlVGltZSBpbnN0YW5jZW9mIERhdGUgPyBkYXRlVGltZSA6IG5ldyBEYXRlKGRhdGVUaW1lIGFzIHN0cmluZyB8IG51bWJlcik7XG5cdH1cblxuXHQvKipcblx0ICogU3RhcnQgdGhlIGNyb24gc2NoZWR1bGVyIC0gbWFpbiBlbnRyeSBwb2ludFxuXHQgKi9cblx0YXN5bmMgbGlzdGVuKCk6IFByb21pc2U8bnVtYmVyPiB7XG5cdFx0Y29uc3Qgc3RhcnRUaW1lID0gdGhpcy5zdGFydENvdW50ZXIoKTtcblxuXHRcdHRyeSB7XG5cdFx0XHQvLyBGaW5kIGFsbCB3b3JrZmxvd3Mgd2l0aCBjcm9uIHRyaWdnZXJzXG5cdFx0XHRjb25zdCBjcm9uV29ya2Zsb3dzID0gdGhpcy5nZXRDcm9uV29ya2Zsb3dzKCk7XG5cblx0XHRcdGlmIChjcm9uV29ya2Zsb3dzLmxlbmd0aCA9PT0gMCkge1xuXHRcdFx0XHR0aGlzLmxvZ2dlci5sb2coXCJObyB3b3JrZmxvd3Mgd2l0aCBjcm9uIHRyaWdnZXJzIGZvdW5kXCIpO1xuXHRcdFx0XHRyZXR1cm4gdGhpcy5lbmRDb3VudGVyKHN0YXJ0VGltZSk7XG5cdFx0XHR9XG5cblx0XHRcdC8vIENyZWF0ZSBhbmQgc3RhcnQgY3JvbiBqb2JzIGZvciBlYWNoIHdvcmtmbG93XG5cdFx0XHRmb3IgKGNvbnN0IHdvcmtmbG93IG9mIGNyb25Xb3JrZmxvd3MpIHtcblx0XHRcdFx0Y29uc3QgY29uZmlnID0gd29ya2Zsb3cuY29uZmlnLnRyaWdnZXI/LmNyb24gYXMgQ3JvblRyaWdnZXJPcHRzO1xuXHRcdFx0XHRjb25zdCBqb2JJZCA9IGBjcm9uLSR7d29ya2Zsb3cucGF0aH0tJHt1dWlkKCkuc2xpY2UoMCwgOCl9YDtcblxuXHRcdFx0XHR0aGlzLmxvZ2dlci5sb2coYFNjaGVkdWxpbmcgd29ya2Zsb3c6ICR7d29ya2Zsb3cucGF0aH0gd2l0aCBzY2hlZHVsZTogJHtjb25maWcuc2NoZWR1bGV9ICgke2NvbmZpZy50aW1lem9uZX0pYCk7XG5cblx0XHRcdFx0Y29uc3Qgam9iID0gbmV3IENyb25Kb2IoXG5cdFx0XHRcdFx0Y29uZmlnLnNjaGVkdWxlLFxuXHRcdFx0XHRcdGFzeW5jICgpID0+IHtcblx0XHRcdFx0XHRcdGF3YWl0IHRoaXMuZXhlY3V0ZVdvcmtmbG93KGpvYklkLCB3b3JrZmxvdywgY29uZmlnLCBmYWxzZSk7XG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRudWxsLCAvLyBvbkNvbXBsZXRlXG5cdFx0XHRcdFx0ZmFsc2UsIC8vIHN0YXJ0XG5cdFx0XHRcdFx0Y29uZmlnLnRpbWV6b25lLFxuXHRcdFx0XHQpO1xuXG5cdFx0XHRcdGNvbnN0IHNjaGVkdWxlZEpvYjogU2NoZWR1bGVkSm9iID0ge1xuXHRcdFx0XHRcdGlkOiBqb2JJZCxcblx0XHRcdFx0XHR3b3JrZmxvd1BhdGg6IHdvcmtmbG93LnBhdGgsXG5cdFx0XHRcdFx0c2NoZWR1bGU6IGNvbmZpZy5zY2hlZHVsZSxcblx0XHRcdFx0XHR0aW1lem9uZTogY29uZmlnLnRpbWV6b25lLFxuXHRcdFx0XHRcdG92ZXJsYXA6IGNvbmZpZy5vdmVybGFwID8/IGZhbHNlLFxuXHRcdFx0XHRcdHJ1bm5pbmc6IGZhbHNlLFxuXHRcdFx0XHRcdG5leHRSdW46IHRoaXMudG9EYXRlKGpvYi5uZXh0RGF0ZSgpKSxcblx0XHRcdFx0XHRqb2IsXG5cdFx0XHRcdH07XG5cblx0XHRcdFx0dGhpcy5qb2JzLnNldChqb2JJZCwgc2NoZWR1bGVkSm9iKTtcblxuXHRcdFx0XHQvLyBTdGFydCB0aGUgam9iXG5cdFx0XHRcdGpvYi5zdGFydCgpO1xuXHRcdFx0XHR0aGlzLmxvZ2dlci5sb2coYEpvYiAke2pvYklkfSBzdGFydGVkLiBOZXh0IHJ1bjogJHtzY2hlZHVsZWRKb2IubmV4dFJ1bn1gKTtcblx0XHRcdH1cblxuXHRcdFx0dGhpcy5sb2dnZXIubG9nKGBDcm9uIHRyaWdnZXIgc3RhcnRlZC4gJHt0aGlzLmpvYnMuc2l6ZX0gam9iKHMpIHNjaGVkdWxlZGApO1xuXG5cdFx0XHQvLyBFbmFibGUgSE1SIGluIGRldmVsb3BtZW50IG1vZGVcblx0XHRcdGlmIChwcm9jZXNzLmVudi5CTE9LX0hNUiA9PT0gXCJ0cnVlXCIgfHwgcHJvY2Vzcy5lbnYuTk9ERV9FTlYgPT09IFwiZGV2ZWxvcG1lbnRcIikge1xuXHRcdFx0XHRhd2FpdCB0aGlzLmVuYWJsZUhvdFJlbG9hZCgpO1xuXHRcdFx0fVxuXG5cdFx0XHRyZXR1cm4gdGhpcy5lbmRDb3VudGVyKHN0YXJ0VGltZSk7XG5cdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdHRoaXMubG9nZ2VyLmVycm9yKGBGYWlsZWQgdG8gc3RhcnQgY3JvbiB0cmlnZ2VyOiAkeyhlcnJvciBhcyBFcnJvcikubWVzc2FnZX1gKTtcblx0XHRcdHRocm93IGVycm9yO1xuXHRcdH1cblx0fVxuXG5cdC8qKlxuXHQgKiBTdG9wIGFsbCBjcm9uIGpvYnNcblx0ICovXG5cdGFzeW5jIHN0b3AoKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0Zm9yIChjb25zdCBbam9iSWQsIHNjaGVkdWxlZEpvYl0gb2YgdGhpcy5qb2JzKSB7XG5cdFx0XHRzY2hlZHVsZWRKb2Iuam9iLnN0b3AoKTtcblx0XHRcdHRoaXMubG9nZ2VyLmxvZyhgSm9iICR7am9iSWR9IHN0b3BwZWRgKTtcblx0XHR9XG5cdFx0dGhpcy5qb2JzLmNsZWFyKCk7XG5cdFx0dGhpcy5sb2dnZXIubG9nKFwiQ3JvbiB0cmlnZ2VyIHN0b3BwZWRcIik7XG5cdH1cblxuXHRwcm90ZWN0ZWQgb3ZlcnJpZGUgYXN5bmMgb25IbXJXb3JrZmxvd0NoYW5nZSgpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHR0aGlzLmxvZ2dlci5sb2coXCJbSE1SXSBDcm9uIHdvcmtmbG93IGNoYW5nZWQsIHJlbG9hZGluZy4uLlwiKTtcblx0XHRhd2FpdCB0aGlzLndhaXRGb3JJbkZsaWdodFJlcXVlc3RzKCk7XG5cdFx0YXdhaXQgdGhpcy5zdG9wKCk7XG5cdFx0dGhpcy5sb2FkV29ya2Zsb3dzKCk7XG5cdFx0YXdhaXQgdGhpcy5saXN0ZW4oKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBNYW51YWxseSB0cmlnZ2VyIGEgc3BlY2lmaWMgam9iXG5cdCAqL1xuXHRhc3luYyB0cmlnZ2VySm9iKGpvYklkOiBzdHJpbmcpOiBQcm9taXNlPFRyaWdnZXJSZXNwb25zZSB8IG51bGw+IHtcblx0XHRjb25zdCBzY2hlZHVsZWRKb2IgPSB0aGlzLmpvYnMuZ2V0KGpvYklkKTtcblx0XHRpZiAoIXNjaGVkdWxlZEpvYikge1xuXHRcdFx0dGhpcy5sb2dnZXIuZXJyb3IoYEpvYiBub3QgZm91bmQ6ICR7am9iSWR9YCk7XG5cdFx0XHRyZXR1cm4gbnVsbDtcblx0XHR9XG5cblx0XHQvLyBHZXQgdGhlIHdvcmtmbG93XG5cdFx0Y29uc3Qgd29ya2Zsb3cgPSB0aGlzLmdldFdvcmtmbG93QnlQYXRoKHNjaGVkdWxlZEpvYi53b3JrZmxvd1BhdGgpO1xuXHRcdGlmICghd29ya2Zsb3cpIHtcblx0XHRcdHRoaXMubG9nZ2VyLmVycm9yKGBXb3JrZmxvdyBub3QgZm91bmQ6ICR7c2NoZWR1bGVkSm9iLndvcmtmbG93UGF0aH1gKTtcblx0XHRcdHJldHVybiBudWxsO1xuXHRcdH1cblxuXHRcdGNvbnN0IGNvbmZpZyA9IHdvcmtmbG93LmNvbmZpZy50cmlnZ2VyPy5jcm9uIGFzIENyb25UcmlnZ2VyT3B0cztcblx0XHRyZXR1cm4gdGhpcy5leGVjdXRlV29ya2Zsb3coam9iSWQsIHdvcmtmbG93LCBjb25maWcsIHRydWUpO1xuXHR9XG5cblx0LyoqXG5cdCAqIEdldCBhbGwgc2NoZWR1bGVkIGpvYnNcblx0ICovXG5cdGdldEpvYnMoKTogU2NoZWR1bGVkSm9iW10ge1xuXHRcdHJldHVybiBBcnJheS5mcm9tKHRoaXMuam9icy52YWx1ZXMoKSkubWFwKChqb2IpID0+ICh7XG5cdFx0XHQuLi5qb2IsXG5cdFx0XHRuZXh0UnVuOiB0aGlzLnRvRGF0ZShqb2Iuam9iLm5leHREYXRlKCkpLFxuXHRcdH0pKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBHZXQgYWxsIHdvcmtmbG93cyB0aGF0IGhhdmUgY3JvbiB0cmlnZ2Vyc1xuXHQgKi9cblx0cHJvdGVjdGVkIGdldENyb25Xb3JrZmxvd3MoKTogQ3JvbldvcmtmbG93TW9kZWxbXSB7XG5cdFx0Y29uc3Qgd29ya2Zsb3dzOiBDcm9uV29ya2Zsb3dNb2RlbFtdID0gW107XG5cblx0XHRmb3IgKGNvbnN0IFtwYXRoLCB3b3JrZmxvd10gb2YgT2JqZWN0LmVudHJpZXModGhpcy5ub2RlTWFwLndvcmtmbG93cyB8fCB7fSkpIHtcblx0XHRcdGNvbnN0IHdvcmtmbG93Q29uZmlnID0gKHdvcmtmbG93IGFzIHVua25vd24gYXMgeyBfY29uZmlnOiBDcm9uV29ya2Zsb3dNb2RlbFtcImNvbmZpZ1wiXSB9KS5fY29uZmlnO1xuXG5cdFx0XHRpZiAod29ya2Zsb3dDb25maWc/LnRyaWdnZXIpIHtcblx0XHRcdFx0Y29uc3QgdHJpZ2dlclR5cGUgPSBPYmplY3Qua2V5cyh3b3JrZmxvd0NvbmZpZy50cmlnZ2VyKVswXTtcblxuXHRcdFx0XHRpZiAodHJpZ2dlclR5cGUgPT09IFwiY3JvblwiICYmIHdvcmtmbG93Q29uZmlnLnRyaWdnZXIuY3Jvbikge1xuXHRcdFx0XHRcdHdvcmtmbG93cy5wdXNoKHtcblx0XHRcdFx0XHRcdHBhdGgsXG5cdFx0XHRcdFx0XHRjb25maWc6IHdvcmtmbG93Q29uZmlnLFxuXHRcdFx0XHRcdH0pO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0cmV0dXJuIHdvcmtmbG93cztcblx0fVxuXG5cdC8qKlxuXHQgKiBHZXQgd29ya2Zsb3cgYnkgcGF0aFxuXHQgKi9cblx0cHJvdGVjdGVkIGdldFdvcmtmbG93QnlQYXRoKHBhdGg6IHN0cmluZyk6IENyb25Xb3JrZmxvd01vZGVsIHwgbnVsbCB7XG5cdFx0Y29uc3Qgd29ya2Zsb3cgPSB0aGlzLm5vZGVNYXAud29ya2Zsb3dzPy5bcGF0aF07XG5cdFx0aWYgKCF3b3JrZmxvdykgcmV0dXJuIG51bGw7XG5cblx0XHRjb25zdCB3b3JrZmxvd0NvbmZpZyA9ICh3b3JrZmxvdyBhcyB1bmtub3duIGFzIHsgX2NvbmZpZzogQ3JvbldvcmtmbG93TW9kZWxbXCJjb25maWdcIl0gfSkuX2NvbmZpZztcblx0XHRyZXR1cm4ge1xuXHRcdFx0cGF0aCxcblx0XHRcdGNvbmZpZzogd29ya2Zsb3dDb25maWcsXG5cdFx0fTtcblx0fVxuXG5cdC8qKlxuXHQgKiBFeGVjdXRlIGEgd29ya2Zsb3dcblx0ICovXG5cdHByb3RlY3RlZCBhc3luYyBleGVjdXRlV29ya2Zsb3coXG5cdFx0am9iSWQ6IHN0cmluZyxcblx0XHR3b3JrZmxvdzogQ3JvbldvcmtmbG93TW9kZWwsXG5cdFx0Y29uZmlnOiBDcm9uVHJpZ2dlck9wdHMsXG5cdFx0bWFudWFsOiBib29sZWFuLFxuXHQpOiBQcm9taXNlPFRyaWdnZXJSZXNwb25zZT4ge1xuXHRcdGNvbnN0IHNjaGVkdWxlZEpvYiA9IHRoaXMuam9icy5nZXQoam9iSWQpO1xuXHRcdGlmICghc2NoZWR1bGVkSm9iKSB7XG5cdFx0XHR0aHJvdyBuZXcgRXJyb3IoYEpvYiBub3QgZm91bmQ6ICR7am9iSWR9YCk7XG5cdFx0fVxuXG5cdFx0Ly8gQ2hlY2sgZm9yIG92ZXJsYXBcblx0XHRpZiAoc2NoZWR1bGVkSm9iLnJ1bm5pbmcgJiYgIXNjaGVkdWxlZEpvYi5vdmVybGFwKSB7XG5cdFx0XHR0aGlzLmxvZ2dlci5sb2coYFNraXBwaW5nICR7am9iSWR9OiBwcmV2aW91cyBleGVjdXRpb24gc3RpbGwgcnVubmluZyAob3ZlcmxhcCBkaXNhYmxlZClgKTtcblx0XHRcdHJldHVybiB7IGN0eDoge30gYXMgQ29udGV4dCwgbWV0cmljczoge30gYXMgTWV0cmljc1R5cGUgfTtcblx0XHR9XG5cblx0XHRjb25zdCBleGVjdXRpb25JZCA9IHV1aWQoKTtcblx0XHRjb25zdCBsYXN0RGF0ZSA9IHNjaGVkdWxlZEpvYi5qb2IubGFzdERhdGUoKTtcblx0XHRjb25zdCBzY2hlZHVsZWRUaW1lID0gbGFzdERhdGUgPyBuZXcgRGF0ZShsYXN0RGF0ZSBhcyB1bmtub3duIGFzIHN0cmluZyB8IG51bWJlcikgOiBuZXcgRGF0ZSgpO1xuXHRcdGNvbnN0IGV4ZWN1dGlvblRpbWUgPSBuZXcgRGF0ZSgpO1xuXG5cdFx0Y29uc3QgZGVmYXVsdE1ldGVyID0gbWV0cmljcy5nZXRNZXRlcihcImRlZmF1bHRcIik7XG5cdFx0Y29uc3QgY3JvbkV4ZWN1dGlvbnMgPSBkZWZhdWx0TWV0ZXIuY3JlYXRlQ291bnRlcihcImNyb25fZXhlY3V0aW9uc1wiLCB7XG5cdFx0XHRkZXNjcmlwdGlvbjogXCJDcm9uIGpvYiBleGVjdXRpb25zXCIsXG5cdFx0fSk7XG5cdFx0Y29uc3QgY3JvbkVycm9ycyA9IGRlZmF1bHRNZXRlci5jcmVhdGVDb3VudGVyKFwiY3Jvbl9lcnJvcnNcIiwge1xuXHRcdFx0ZGVzY3JpcHRpb246IFwiQ3JvbiBqb2IgZXhlY3V0aW9uIGVycm9yc1wiLFxuXHRcdH0pO1xuXG5cdFx0cmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7XG5cdFx0XHR0aGlzLnRyYWNlci5zdGFydEFjdGl2ZVNwYW4oYGNyb246JHt3b3JrZmxvdy5wYXRofWAsIGFzeW5jIChzcGFuOiBTcGFuKSA9PiB7XG5cdFx0XHRcdHNjaGVkdWxlZEpvYi5ydW5uaW5nID0gdHJ1ZTtcblxuXHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdGNvbnN0IHN0YXJ0ID0gcGVyZm9ybWFuY2Uubm93KCk7XG5cblx0XHRcdFx0XHQvLyBJbml0aWFsaXplIGNvbmZpZ3VyYXRpb24gZm9yIHRoaXMgd29ya2Zsb3dcblx0XHRcdFx0XHRhd2FpdCB0aGlzLmNvbmZpZ3VyYXRpb24uaW5pdCh3b3JrZmxvdy5wYXRoLCB0aGlzLm5vZGVNYXApO1xuXG5cdFx0XHRcdFx0Ly8gQ3JlYXRlIGNvbnRleHRcblx0XHRcdFx0XHRjb25zdCBjdHg6IENvbnRleHQgPSB0aGlzLmNyZWF0ZUNvbnRleHQodW5kZWZpbmVkLCB3b3JrZmxvdy5wYXRoLCBleGVjdXRpb25JZCk7XG5cblx0XHRcdFx0XHQvLyBDcmVhdGUgZXhlY3V0aW9uIGNvbnRleHRcblx0XHRcdFx0XHRjb25zdCBjcm9uQ29udGV4dDogQ3JvbkV4ZWN1dGlvbkNvbnRleHQgPSB7XG5cdFx0XHRcdFx0XHRqb2JJZCxcblx0XHRcdFx0XHRcdHNjaGVkdWxlZFRpbWUsXG5cdFx0XHRcdFx0XHRleGVjdXRpb25UaW1lLFxuXHRcdFx0XHRcdFx0c2NoZWR1bGU6IGNvbmZpZy5zY2hlZHVsZSxcblx0XHRcdFx0XHRcdHRpbWV6b25lOiBjb25maWcudGltZXpvbmUsXG5cdFx0XHRcdFx0XHRtYW51YWwsXG5cdFx0XHRcdFx0fTtcblxuXHRcdFx0XHRcdC8vIFBvcHVsYXRlIHJlcXVlc3Qgd2l0aCBjcm9uIGNvbnRleHRcblx0XHRcdFx0XHRjdHgucmVxdWVzdCA9IHtcblx0XHRcdFx0XHRcdGJvZHk6IGNyb25Db250ZXh0LFxuXHRcdFx0XHRcdFx0aGVhZGVyczoge1xuXHRcdFx0XHRcdFx0XHRcIngtY3Jvbi1qb2ItaWRcIjogam9iSWQsXG5cdFx0XHRcdFx0XHRcdFwieC1jcm9uLXNjaGVkdWxlXCI6IGNvbmZpZy5zY2hlZHVsZSxcblx0XHRcdFx0XHRcdFx0XCJ4LWNyb24tdGltZXpvbmVcIjogY29uZmlnLnRpbWV6b25lLFxuXHRcdFx0XHRcdFx0XHRcIngtY3Jvbi1tYW51YWxcIjogU3RyaW5nKG1hbnVhbCksXG5cdFx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdFx0cXVlcnk6IHt9LFxuXHRcdFx0XHRcdFx0cGFyYW1zOiB7XG5cdFx0XHRcdFx0XHRcdGpvYklkLFxuXHRcdFx0XHRcdFx0XHRzY2hlZHVsZTogY29uZmlnLnNjaGVkdWxlLFxuXHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHR9IGFzIHVua25vd24gYXMgUmVxdWVzdENvbnRleHQ7XG5cblx0XHRcdFx0XHQvLyBTdG9yZSBjcm9uIGNvbnRleHQgaW4gdmFyc1xuXHRcdFx0XHRcdGlmICghY3R4LnZhcnMpIGN0eC52YXJzID0ge307XG5cdFx0XHRcdFx0Y3R4LnZhcnMuX2Nyb25fY29udGV4dCA9IHtcblx0XHRcdFx0XHRcdGpvYklkLFxuXHRcdFx0XHRcdFx0c2NoZWR1bGVkVGltZTogc2NoZWR1bGVkVGltZS50b0lTT1N0cmluZygpLFxuXHRcdFx0XHRcdFx0ZXhlY3V0aW9uVGltZTogZXhlY3V0aW9uVGltZS50b0lTT1N0cmluZygpLFxuXHRcdFx0XHRcdFx0c2NoZWR1bGU6IGNvbmZpZy5zY2hlZHVsZSxcblx0XHRcdFx0XHRcdHRpbWV6b25lOiBjb25maWcudGltZXpvbmUsXG5cdFx0XHRcdFx0XHRtYW51YWw6IFN0cmluZyhtYW51YWwpLFxuXHRcdFx0XHRcdH07XG5cblx0XHRcdFx0XHRjdHgubG9nZ2VyLmxvZyhgRXhlY3V0aW5nIGNyb24gam9iOiAke2pvYklkfSAoJHttYW51YWwgPyBcIm1hbnVhbFwiIDogXCJzY2hlZHVsZWRcIn0pYCk7XG5cblx0XHRcdFx0XHQvLyBFeGVjdXRlIHdvcmtmbG93XG5cdFx0XHRcdFx0Y29uc3QgcmVzcG9uc2U6IFRyaWdnZXJSZXNwb25zZSA9IGF3YWl0IHRoaXMucnVuKGN0eCk7XG5cdFx0XHRcdFx0Y29uc3QgZW5kID0gcGVyZm9ybWFuY2Uubm93KCk7XG5cblx0XHRcdFx0XHQvLyBVcGRhdGUgam9iIHN0YXRlXG5cdFx0XHRcdFx0c2NoZWR1bGVkSm9iLmxhc3RSdW4gPSBleGVjdXRpb25UaW1lO1xuXHRcdFx0XHRcdHNjaGVkdWxlZEpvYi5uZXh0UnVuID0gdGhpcy50b0RhdGUoc2NoZWR1bGVkSm9iLmpvYi5uZXh0RGF0ZSgpKTtcblxuXHRcdFx0XHRcdC8vIFNldCBzcGFuIGF0dHJpYnV0ZXNcblx0XHRcdFx0XHRzcGFuLnNldEF0dHJpYnV0ZShcInN1Y2Nlc3NcIiwgdHJ1ZSk7XG5cdFx0XHRcdFx0c3Bhbi5zZXRBdHRyaWJ1dGUoXCJqb2JfaWRcIiwgam9iSWQpO1xuXHRcdFx0XHRcdHNwYW4uc2V0QXR0cmlidXRlKFwid29ya2Zsb3dfcGF0aFwiLCB3b3JrZmxvdy5wYXRoKTtcblx0XHRcdFx0XHRzcGFuLnNldEF0dHJpYnV0ZShcInNjaGVkdWxlXCIsIGNvbmZpZy5zY2hlZHVsZSk7XG5cdFx0XHRcdFx0c3Bhbi5zZXRBdHRyaWJ1dGUoXCJ0aW1lem9uZVwiLCBjb25maWcudGltZXpvbmUpO1xuXHRcdFx0XHRcdHNwYW4uc2V0QXR0cmlidXRlKFwibWFudWFsXCIsIG1hbnVhbCk7XG5cdFx0XHRcdFx0c3Bhbi5zZXRBdHRyaWJ1dGUoXCJlbGFwc2VkX21zXCIsIGVuZCAtIHN0YXJ0KTtcblx0XHRcdFx0XHRzcGFuLnNldFN0YXR1cyh7IGNvZGU6IFNwYW5TdGF0dXNDb2RlLk9LIH0pO1xuXG5cdFx0XHRcdFx0Ly8gUmVjb3JkIG1ldHJpY3Ncblx0XHRcdFx0XHRjcm9uRXhlY3V0aW9ucy5hZGQoMSwge1xuXHRcdFx0XHRcdFx0ZW52OiBwcm9jZXNzLmVudi5OT0RFX0VOVixcblx0XHRcdFx0XHRcdGpvYl9pZDogam9iSWQsXG5cdFx0XHRcdFx0XHR3b3JrZmxvd19uYW1lOiB0aGlzLmNvbmZpZ3VyYXRpb24ubmFtZSxcblx0XHRcdFx0XHRcdG1hbnVhbDogU3RyaW5nKG1hbnVhbCksXG5cdFx0XHRcdFx0XHRzdWNjZXNzOiBcInRydWVcIixcblx0XHRcdFx0XHR9KTtcblxuXHRcdFx0XHRcdGN0eC5sb2dnZXIubG9nKGBDcm9uIGpvYiBjb21wbGV0ZWQgaW4gJHsoZW5kIC0gc3RhcnQpLnRvRml4ZWQoMil9bXM6ICR7am9iSWR9YCk7XG5cblx0XHRcdFx0XHRyZXNvbHZlKHJlc3BvbnNlKTtcblx0XHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdFx0XHRjb25zdCBlcnJvck1lc3NhZ2UgPSAoZXJyb3IgYXMgRXJyb3IpLm1lc3NhZ2U7XG5cblx0XHRcdFx0XHQvLyBTZXQgc3BhbiBlcnJvclxuXHRcdFx0XHRcdHNwYW4uc2V0QXR0cmlidXRlKFwic3VjY2Vzc1wiLCBmYWxzZSk7XG5cdFx0XHRcdFx0c3Bhbi5yZWNvcmRFeGNlcHRpb24oZXJyb3IgYXMgRXJyb3IpO1xuXHRcdFx0XHRcdHNwYW4uc2V0U3RhdHVzKHsgY29kZTogU3BhblN0YXR1c0NvZGUuRVJST1IsIG1lc3NhZ2U6IGVycm9yTWVzc2FnZSB9KTtcblxuXHRcdFx0XHRcdC8vIFJlY29yZCBlcnJvciBtZXRyaWNzXG5cdFx0XHRcdFx0Y3JvbkVycm9ycy5hZGQoMSwge1xuXHRcdFx0XHRcdFx0ZW52OiBwcm9jZXNzLmVudi5OT0RFX0VOVixcblx0XHRcdFx0XHRcdGpvYl9pZDogam9iSWQsXG5cdFx0XHRcdFx0XHR3b3JrZmxvd19uYW1lOiB0aGlzLmNvbmZpZ3VyYXRpb24/Lm5hbWUgfHwgXCJ1bmtub3duXCIsXG5cdFx0XHRcdFx0XHRtYW51YWw6IFN0cmluZyhtYW51YWwpLFxuXHRcdFx0XHRcdH0pO1xuXG5cdFx0XHRcdFx0dGhpcy5sb2dnZXIuZXJyb3IoYENyb24gam9iIGZhaWxlZCAke2pvYklkfTogJHtlcnJvck1lc3NhZ2V9YCwgKGVycm9yIGFzIEVycm9yKS5zdGFjayk7XG5cblx0XHRcdFx0XHRyZXNvbHZlKHsgY3R4OiB7fSBhcyBDb250ZXh0LCBtZXRyaWNzOiB7fSBhcyBNZXRyaWNzVHlwZSB9KTtcblx0XHRcdFx0fSBmaW5hbGx5IHtcblx0XHRcdFx0XHRzY2hlZHVsZWRKb2IucnVubmluZyA9IGZhbHNlO1xuXHRcdFx0XHRcdHNwYW4uZW5kKCk7XG5cdFx0XHRcdH1cblx0XHRcdH0pO1xuXHRcdH0pO1xuXHR9XG59XG5cbmV4cG9ydCBkZWZhdWx0IENyb25UcmlnZ2VyO1xuIl19
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @blok/trigger-cron
3
+ *
4
+ * Cron/scheduled trigger for Blok workflows.
5
+ * Execute workflows on a schedule using cron expressions.
6
+ *
7
+ * Features:
8
+ * - Standard cron expression syntax
9
+ * - Timezone-aware scheduling
10
+ * - Overlap prevention
11
+ * - Manual trigger support
12
+ * - Job management API
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { CronTrigger } from "@blok/trigger-cron";
17
+ *
18
+ * class MyCronTrigger extends CronTrigger {
19
+ * protected nodes = myNodes;
20
+ * protected workflows = myWorkflows;
21
+ * }
22
+ *
23
+ * const trigger = new MyCronTrigger();
24
+ * await trigger.listen();
25
+ *
26
+ * // List all scheduled jobs
27
+ * const jobs = trigger.getJobs();
28
+ *
29
+ * // Manually trigger a job
30
+ * await trigger.triggerJob("cron-my-workflow-abc123");
31
+ * ```
32
+ *
33
+ * Workflow Definition:
34
+ * ```typescript
35
+ * Workflow({ name: "daily-cleanup", version: "1.0.0" })
36
+ * .addTrigger("cron", {
37
+ * schedule: "0 2 * * *", // Run at 2 AM daily
38
+ * timezone: "America/New_York",
39
+ * overlap: false,
40
+ * })
41
+ * .addStep({ ... });
42
+ * ```
43
+ *
44
+ * Cron Expression Format:
45
+ * ```
46
+ * * * * * * *
47
+ * │ │ │ │ │ │
48
+ * │ │ │ │ │ └── Day of week (0-7, Sun=0,7)
49
+ * │ │ │ │ └──── Month (1-12)
50
+ * │ │ │ └────── Day of month (1-31)
51
+ * │ │ └──────── Hour (0-23)
52
+ * │ └────────── Minute (0-59)
53
+ * └──────────── Second (0-59, optional)
54
+ * ```
55
+ *
56
+ * Common schedules:
57
+ * - "* * * * *" - Every minute
58
+ * - "0 * * * *" - Every hour
59
+ * - "0 0 * * *" - Every day at midnight
60
+ * - "0 0 * * 0" - Every Sunday at midnight
61
+ * - "0 0 1 * *" - First day of every month
62
+ */
63
+ export { CronTrigger, type ScheduledJob, type CronExecutionContext, } from "./CronTrigger";
64
+ export type { CronTriggerOpts } from "@blok/helper";
package/dist/index.js ADDED
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ /**
3
+ * @blok/trigger-cron
4
+ *
5
+ * Cron/scheduled trigger for Blok workflows.
6
+ * Execute workflows on a schedule using cron expressions.
7
+ *
8
+ * Features:
9
+ * - Standard cron expression syntax
10
+ * - Timezone-aware scheduling
11
+ * - Overlap prevention
12
+ * - Manual trigger support
13
+ * - Job management API
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { CronTrigger } from "@blok/trigger-cron";
18
+ *
19
+ * class MyCronTrigger extends CronTrigger {
20
+ * protected nodes = myNodes;
21
+ * protected workflows = myWorkflows;
22
+ * }
23
+ *
24
+ * const trigger = new MyCronTrigger();
25
+ * await trigger.listen();
26
+ *
27
+ * // List all scheduled jobs
28
+ * const jobs = trigger.getJobs();
29
+ *
30
+ * // Manually trigger a job
31
+ * await trigger.triggerJob("cron-my-workflow-abc123");
32
+ * ```
33
+ *
34
+ * Workflow Definition:
35
+ * ```typescript
36
+ * Workflow({ name: "daily-cleanup", version: "1.0.0" })
37
+ * .addTrigger("cron", {
38
+ * schedule: "0 2 * * *", // Run at 2 AM daily
39
+ * timezone: "America/New_York",
40
+ * overlap: false,
41
+ * })
42
+ * .addStep({ ... });
43
+ * ```
44
+ *
45
+ * Cron Expression Format:
46
+ * ```
47
+ * * * * * * *
48
+ * │ │ │ │ │ │
49
+ * │ │ │ │ │ └── Day of week (0-7, Sun=0,7)
50
+ * │ │ │ │ └──── Month (1-12)
51
+ * │ │ │ └────── Day of month (1-31)
52
+ * │ │ └──────── Hour (0-23)
53
+ * │ └────────── Minute (0-59)
54
+ * └──────────── Second (0-59, optional)
55
+ * ```
56
+ *
57
+ * Common schedules:
58
+ * - "* * * * *" - Every minute
59
+ * - "0 * * * *" - Every hour
60
+ * - "0 0 * * *" - Every day at midnight
61
+ * - "0 0 * * 0" - Every Sunday at midnight
62
+ * - "0 0 1 * *" - First day of every month
63
+ */
64
+ Object.defineProperty(exports, "__esModule", { value: true });
65
+ exports.CronTrigger = void 0;
66
+ // Core exports
67
+ var CronTrigger_1 = require("./CronTrigger");
68
+ Object.defineProperty(exports, "CronTrigger", { enumerable: true, get: function () { return CronTrigger_1.CronTrigger; } });
69
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBNkRHOzs7QUFFSCxlQUFlO0FBQ2YsNkNBSXVCO0FBSHRCLDBHQUFBLFdBQVcsT0FBQSIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGJsb2svdHJpZ2dlci1jcm9uXG4gKlxuICogQ3Jvbi9zY2hlZHVsZWQgdHJpZ2dlciBmb3IgQmxvayB3b3JrZmxvd3MuXG4gKiBFeGVjdXRlIHdvcmtmbG93cyBvbiBhIHNjaGVkdWxlIHVzaW5nIGNyb24gZXhwcmVzc2lvbnMuXG4gKlxuICogRmVhdHVyZXM6XG4gKiAtIFN0YW5kYXJkIGNyb24gZXhwcmVzc2lvbiBzeW50YXhcbiAqIC0gVGltZXpvbmUtYXdhcmUgc2NoZWR1bGluZ1xuICogLSBPdmVybGFwIHByZXZlbnRpb25cbiAqIC0gTWFudWFsIHRyaWdnZXIgc3VwcG9ydFxuICogLSBKb2IgbWFuYWdlbWVudCBBUElcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogaW1wb3J0IHsgQ3JvblRyaWdnZXIgfSBmcm9tIFwiQGJsb2svdHJpZ2dlci1jcm9uXCI7XG4gKlxuICogY2xhc3MgTXlDcm9uVHJpZ2dlciBleHRlbmRzIENyb25UcmlnZ2VyIHtcbiAqICAgcHJvdGVjdGVkIG5vZGVzID0gbXlOb2RlcztcbiAqICAgcHJvdGVjdGVkIHdvcmtmbG93cyA9IG15V29ya2Zsb3dzO1xuICogfVxuICpcbiAqIGNvbnN0IHRyaWdnZXIgPSBuZXcgTXlDcm9uVHJpZ2dlcigpO1xuICogYXdhaXQgdHJpZ2dlci5saXN0ZW4oKTtcbiAqXG4gKiAvLyBMaXN0IGFsbCBzY2hlZHVsZWQgam9ic1xuICogY29uc3Qgam9icyA9IHRyaWdnZXIuZ2V0Sm9icygpO1xuICpcbiAqIC8vIE1hbnVhbGx5IHRyaWdnZXIgYSBqb2JcbiAqIGF3YWl0IHRyaWdnZXIudHJpZ2dlckpvYihcImNyb24tbXktd29ya2Zsb3ctYWJjMTIzXCIpO1xuICogYGBgXG4gKlxuICogV29ya2Zsb3cgRGVmaW5pdGlvbjpcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIFdvcmtmbG93KHsgbmFtZTogXCJkYWlseS1jbGVhbnVwXCIsIHZlcnNpb246IFwiMS4wLjBcIiB9KVxuICogICAuYWRkVHJpZ2dlcihcImNyb25cIiwge1xuICogICAgIHNjaGVkdWxlOiBcIjAgMiAqICogKlwiLCAgLy8gUnVuIGF0IDIgQU0gZGFpbHlcbiAqICAgICB0aW1lem9uZTogXCJBbWVyaWNhL05ld19Zb3JrXCIsXG4gKiAgICAgb3ZlcmxhcDogZmFsc2UsXG4gKiAgIH0pXG4gKiAgIC5hZGRTdGVwKHsgLi4uIH0pO1xuICogYGBgXG4gKlxuICogQ3JvbiBFeHByZXNzaW9uIEZvcm1hdDpcbiAqIGBgYFxuICogKiAqICogKiAqICpcbiAqIOKUgiDilIIg4pSCIOKUgiDilIIg4pSCXG4gKiDilIIg4pSCIOKUgiDilIIg4pSCIOKUlOKUgOKUgCBEYXkgb2Ygd2VlayAoMC03LCBTdW49MCw3KVxuICog4pSCIOKUgiDilIIg4pSCIOKUlOKUgOKUgOKUgOKUgCBNb250aCAoMS0xMilcbiAqIOKUgiDilIIg4pSCIOKUlOKUgOKUgOKUgOKUgOKUgOKUgCBEYXkgb2YgbW9udGggKDEtMzEpXG4gKiDilIIg4pSCIOKUlOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgCBIb3VyICgwLTIzKVxuICog4pSCIOKUlOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgCBNaW51dGUgKDAtNTkpXG4gKiDilJTilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAgU2Vjb25kICgwLTU5LCBvcHRpb25hbClcbiAqIGBgYFxuICpcbiAqIENvbW1vbiBzY2hlZHVsZXM6XG4gKiAtIFwiKiAqICogKiAqXCIgLSBFdmVyeSBtaW51dGVcbiAqIC0gXCIwICogKiAqICpcIiAtIEV2ZXJ5IGhvdXJcbiAqIC0gXCIwIDAgKiAqICpcIiAtIEV2ZXJ5IGRheSBhdCBtaWRuaWdodFxuICogLSBcIjAgMCAqICogMFwiIC0gRXZlcnkgU3VuZGF5IGF0IG1pZG5pZ2h0XG4gKiAtIFwiMCAwIDEgKiAqXCIgLSBGaXJzdCBkYXkgb2YgZXZlcnkgbW9udGhcbiAqL1xuXG4vLyBDb3JlIGV4cG9ydHNcbmV4cG9ydCB7XG5cdENyb25UcmlnZ2VyLFxuXHR0eXBlIFNjaGVkdWxlZEpvYixcblx0dHlwZSBDcm9uRXhlY3V0aW9uQ29udGV4dCxcbn0gZnJvbSBcIi4vQ3JvblRyaWdnZXJcIjtcblxuLy8gUmUtZXhwb3J0IHR5cGVzIGZyb20gaGVscGVyIGZvciBjb252ZW5pZW5jZVxuZXhwb3J0IHR5cGUgeyBDcm9uVHJpZ2dlck9wdHMgfSBmcm9tIFwiQGJsb2svaGVscGVyXCI7XG4iXX0=
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@blokjs/trigger-cron",
3
+ "version": "0.2.0",
4
+ "description": "Cron/scheduled trigger for Blok workflows - supports cron expressions and interval-based scheduling",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "rm -rf dist && bun run tsc",
10
+ "build:dev": "tsc --watch",
11
+ "test": "vitest run",
12
+ "test:dev": "vitest"
13
+ },
14
+ "author": "Deskree Technologies Inc.",
15
+ "license": "Apache-2.0",
16
+ "dependencies": {
17
+ "@blokjs/helper": "workspace:*",
18
+ "@blokjs/runner": "workspace:*",
19
+ "@blokjs/shared": "workspace:*",
20
+ "@opentelemetry/api": "^1.9.0",
21
+ "cron": "^4.4.0",
22
+ "uuid": "^11.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/cron": "^2.4.3",
26
+ "@types/node": "^22.15.21",
27
+ "@types/uuid": "^11.0.0",
28
+ "typescript": "^5.8.3",
29
+ "vitest": "^4.0.18"
30
+ },
31
+ "private": false,
32
+ "publishConfig": {
33
+ "access": "public"
34
+ }
35
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * CronTrigger Tests
3
+ */
4
+
5
+ import { describe, expect, it, vi } from "vitest";
6
+
7
+ describe("CronTrigger", () => {
8
+ describe("ScheduledJob Interface", () => {
9
+ it("should accept valid scheduled job structure", () => {
10
+ const mockJob = {
11
+ nextDate: () => new Date(),
12
+ lastDate: () => new Date(),
13
+ start: vi.fn(),
14
+ stop: vi.fn(),
15
+ };
16
+
17
+ const scheduledJob = {
18
+ id: "cron-test-abc123",
19
+ workflowPath: "daily-cleanup",
20
+ schedule: "0 2 * * *",
21
+ timezone: "America/New_York",
22
+ overlap: false,
23
+ running: false,
24
+ lastRun: new Date(),
25
+ nextRun: new Date(),
26
+ job: mockJob,
27
+ };
28
+
29
+ expect(scheduledJob.id).toBe("cron-test-abc123");
30
+ expect(scheduledJob.schedule).toBe("0 2 * * *");
31
+ expect(scheduledJob.timezone).toBe("America/New_York");
32
+ expect(scheduledJob.overlap).toBe(false);
33
+ });
34
+ });
35
+
36
+ describe("CronExecutionContext Interface", () => {
37
+ it("should create valid execution context", () => {
38
+ const context = {
39
+ jobId: "cron-job-123",
40
+ scheduledTime: new Date("2024-01-01T02:00:00Z"),
41
+ executionTime: new Date("2024-01-01T02:00:01Z"),
42
+ schedule: "0 2 * * *",
43
+ timezone: "UTC",
44
+ manual: false,
45
+ };
46
+
47
+ expect(context.jobId).toBe("cron-job-123");
48
+ expect(context.scheduledTime.toISOString()).toBe("2024-01-01T02:00:00.000Z");
49
+ expect(context.manual).toBe(false);
50
+ });
51
+
52
+ it("should support manual trigger context", () => {
53
+ const context = {
54
+ jobId: "cron-job-456",
55
+ scheduledTime: new Date(),
56
+ executionTime: new Date(),
57
+ schedule: "0 * * * *",
58
+ timezone: "Europe/London",
59
+ manual: true,
60
+ };
61
+
62
+ expect(context.manual).toBe(true);
63
+ });
64
+ });
65
+
66
+ describe("CronTriggerOpts Schema", () => {
67
+ it("should validate cron trigger configuration", () => {
68
+ const validConfig = {
69
+ schedule: "0 * * * *",
70
+ timezone: "America/Los_Angeles",
71
+ overlap: false,
72
+ };
73
+
74
+ expect(validConfig.schedule).toBe("0 * * * *");
75
+ expect(validConfig.timezone).toBe("America/Los_Angeles");
76
+ expect(validConfig.overlap).toBe(false);
77
+ });
78
+
79
+ it("should support common cron expressions", () => {
80
+ const schedules = [
81
+ { expr: "* * * * *", desc: "Every minute" },
82
+ { expr: "0 * * * *", desc: "Every hour" },
83
+ { expr: "0 0 * * *", desc: "Every day at midnight" },
84
+ { expr: "0 0 * * 0", desc: "Every Sunday" },
85
+ { expr: "0 0 1 * *", desc: "First day of month" },
86
+ { expr: "*/5 * * * *", desc: "Every 5 minutes" },
87
+ { expr: "0 9-17 * * 1-5", desc: "Hourly, 9-5 on weekdays" },
88
+ ];
89
+
90
+ for (const schedule of schedules) {
91
+ expect(schedule.expr).toBeDefined();
92
+ expect(schedule.desc).toBeDefined();
93
+ }
94
+ });
95
+ });
96
+
97
+ describe("Timezone Support", () => {
98
+ it("should support common timezones", () => {
99
+ const timezones = [
100
+ "UTC",
101
+ "America/New_York",
102
+ "America/Los_Angeles",
103
+ "Europe/London",
104
+ "Europe/Paris",
105
+ "Asia/Tokyo",
106
+ "Australia/Sydney",
107
+ ];
108
+
109
+ for (const tz of timezones) {
110
+ const config = {
111
+ schedule: "0 0 * * *",
112
+ timezone: tz,
113
+ overlap: false,
114
+ };
115
+ expect(config.timezone).toBe(tz);
116
+ }
117
+ });
118
+ });
119
+ });
@@ -0,0 +1,432 @@
1
+ /**
2
+ * CronTrigger - Scheduled workflow execution based on cron expressions
3
+ *
4
+ * Extends TriggerBase to support scheduled triggers:
5
+ * - Cron expressions (e.g., "0 * * * *" for hourly)
6
+ * - Timezone-aware scheduling
7
+ * - Overlap prevention
8
+ * - Manual trigger support
9
+ *
10
+ * Uses the 'cron' package for cron parsing and scheduling.
11
+ */
12
+
13
+ import type { CronTriggerOpts, HelperResponse } from "@blokjs/helper";
14
+ import {
15
+ DefaultLogger,
16
+ type GlobalOptions,
17
+ type BlokService,
18
+ NodeMap,
19
+ TriggerBase,
20
+ type TriggerResponse,
21
+ } from "@blokjs/runner";
22
+ import type { Context, MetricsType, RequestContext } from "@blokjs/shared";
23
+ import { type Span, SpanStatusCode, metrics, trace } from "@opentelemetry/api";
24
+ import { CronJob } from "cron";
25
+ import { v4 as uuid } from "uuid";
26
+
27
+ /**
28
+ * Scheduled job information
29
+ */
30
+ export interface ScheduledJob {
31
+ /** Unique job ID */
32
+ id: string;
33
+ /** Workflow path */
34
+ workflowPath: string;
35
+ /** Cron expression */
36
+ schedule: string;
37
+ /** Timezone */
38
+ timezone: string;
39
+ /** Allow overlapping executions */
40
+ overlap: boolean;
41
+ /** Whether the job is currently running */
42
+ running: boolean;
43
+ /** Last execution time */
44
+ lastRun?: Date;
45
+ /** Next scheduled time */
46
+ nextRun?: Date;
47
+ /** Internal CronJob instance */
48
+ job: CronJob;
49
+ }
50
+
51
+ /**
52
+ * Execution context passed to the workflow
53
+ */
54
+ export interface CronExecutionContext {
55
+ /** Job ID */
56
+ jobId: string;
57
+ /** Scheduled time (when it was supposed to run) */
58
+ scheduledTime: Date;
59
+ /** Actual execution time */
60
+ executionTime: Date;
61
+ /** Cron expression */
62
+ schedule: string;
63
+ /** Timezone */
64
+ timezone: string;
65
+ /** Whether this is a manual trigger */
66
+ manual: boolean;
67
+ }
68
+
69
+ /**
70
+ * Workflow model with cron trigger configuration
71
+ */
72
+ interface CronWorkflowModel {
73
+ path: string;
74
+ config: {
75
+ name: string;
76
+ version: string;
77
+ trigger?: {
78
+ cron?: CronTriggerOpts;
79
+ [key: string]: unknown;
80
+ };
81
+ [key: string]: unknown;
82
+ };
83
+ }
84
+
85
+ /**
86
+ * CronTrigger - Scheduled workflow execution
87
+ */
88
+ export abstract class CronTrigger extends TriggerBase {
89
+ protected nodeMap: GlobalOptions = {} as GlobalOptions;
90
+ protected readonly tracer = trace.getTracer(
91
+ process.env.PROJECT_NAME || "trigger-cron-workflow",
92
+ process.env.PROJECT_VERSION || "0.0.1",
93
+ );
94
+ protected readonly logger = new DefaultLogger();
95
+ protected jobs: Map<string, ScheduledJob> = new Map();
96
+
97
+ // Subclasses provide these
98
+ protected abstract nodes: Record<string, BlokService<unknown>>;
99
+ protected abstract workflows: Record<string, HelperResponse>;
100
+
101
+ constructor() {
102
+ super();
103
+ this.loadNodes();
104
+ this.loadWorkflows();
105
+ }
106
+
107
+ /**
108
+ * Load nodes into the node map
109
+ */
110
+ loadNodes(): void {
111
+ this.nodeMap.nodes = new NodeMap();
112
+ const nodeKeys = Object.keys(this.nodes);
113
+ for (const key of nodeKeys) {
114
+ this.nodeMap.nodes.addNode(key, this.nodes[key]);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Load workflows into the workflow map
120
+ */
121
+ loadWorkflows(): void {
122
+ this.nodeMap.workflows = this.workflows;
123
+ }
124
+
125
+ /**
126
+ * Convert cron DateTime to Date
127
+ * The cron package uses luxon DateTime which has toJSDate()
128
+ */
129
+ protected toDate(dateTime: unknown): Date {
130
+ if (dateTime && typeof dateTime === "object" && "toJSDate" in dateTime) {
131
+ return (dateTime as { toJSDate: () => Date }).toJSDate();
132
+ }
133
+ return dateTime instanceof Date ? dateTime : new Date(dateTime as string | number);
134
+ }
135
+
136
+ /**
137
+ * Start the cron scheduler - main entry point
138
+ */
139
+ async listen(): Promise<number> {
140
+ const startTime = this.startCounter();
141
+
142
+ try {
143
+ // Find all workflows with cron triggers
144
+ const cronWorkflows = this.getCronWorkflows();
145
+
146
+ if (cronWorkflows.length === 0) {
147
+ this.logger.log("No workflows with cron triggers found");
148
+ return this.endCounter(startTime);
149
+ }
150
+
151
+ // Create and start cron jobs for each workflow
152
+ for (const workflow of cronWorkflows) {
153
+ const config = workflow.config.trigger?.cron as CronTriggerOpts;
154
+ const jobId = `cron-${workflow.path}-${uuid().slice(0, 8)}`;
155
+
156
+ this.logger.log(`Scheduling workflow: ${workflow.path} with schedule: ${config.schedule} (${config.timezone})`);
157
+
158
+ const job = new CronJob(
159
+ config.schedule,
160
+ async () => {
161
+ await this.executeWorkflow(jobId, workflow, config, false);
162
+ },
163
+ null, // onComplete
164
+ false, // start
165
+ config.timezone,
166
+ );
167
+
168
+ const scheduledJob: ScheduledJob = {
169
+ id: jobId,
170
+ workflowPath: workflow.path,
171
+ schedule: config.schedule,
172
+ timezone: config.timezone,
173
+ overlap: config.overlap ?? false,
174
+ running: false,
175
+ nextRun: this.toDate(job.nextDate()),
176
+ job,
177
+ };
178
+
179
+ this.jobs.set(jobId, scheduledJob);
180
+
181
+ // Start the job
182
+ job.start();
183
+ this.logger.log(`Job ${jobId} started. Next run: ${scheduledJob.nextRun}`);
184
+ }
185
+
186
+ this.logger.log(`Cron trigger started. ${this.jobs.size} job(s) scheduled`);
187
+
188
+ // Enable HMR in development mode
189
+ if (process.env.BLOK_HMR === "true" || process.env.NODE_ENV === "development") {
190
+ await this.enableHotReload();
191
+ }
192
+
193
+ return this.endCounter(startTime);
194
+ } catch (error) {
195
+ this.logger.error(`Failed to start cron trigger: ${(error as Error).message}`);
196
+ throw error;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Stop all cron jobs
202
+ */
203
+ async stop(): Promise<void> {
204
+ for (const [jobId, scheduledJob] of this.jobs) {
205
+ scheduledJob.job.stop();
206
+ this.logger.log(`Job ${jobId} stopped`);
207
+ }
208
+ this.jobs.clear();
209
+ this.logger.log("Cron trigger stopped");
210
+ }
211
+
212
+ protected override async onHmrWorkflowChange(): Promise<void> {
213
+ this.logger.log("[HMR] Cron workflow changed, reloading...");
214
+ await this.waitForInFlightRequests();
215
+ await this.stop();
216
+ this.loadWorkflows();
217
+ await this.listen();
218
+ }
219
+
220
+ /**
221
+ * Manually trigger a specific job
222
+ */
223
+ async triggerJob(jobId: string): Promise<TriggerResponse | null> {
224
+ const scheduledJob = this.jobs.get(jobId);
225
+ if (!scheduledJob) {
226
+ this.logger.error(`Job not found: ${jobId}`);
227
+ return null;
228
+ }
229
+
230
+ // Get the workflow
231
+ const workflow = this.getWorkflowByPath(scheduledJob.workflowPath);
232
+ if (!workflow) {
233
+ this.logger.error(`Workflow not found: ${scheduledJob.workflowPath}`);
234
+ return null;
235
+ }
236
+
237
+ const config = workflow.config.trigger?.cron as CronTriggerOpts;
238
+ return this.executeWorkflow(jobId, workflow, config, true);
239
+ }
240
+
241
+ /**
242
+ * Get all scheduled jobs
243
+ */
244
+ getJobs(): ScheduledJob[] {
245
+ return Array.from(this.jobs.values()).map((job) => ({
246
+ ...job,
247
+ nextRun: this.toDate(job.job.nextDate()),
248
+ }));
249
+ }
250
+
251
+ /**
252
+ * Get all workflows that have cron triggers
253
+ */
254
+ protected getCronWorkflows(): CronWorkflowModel[] {
255
+ const workflows: CronWorkflowModel[] = [];
256
+
257
+ for (const [path, workflow] of Object.entries(this.nodeMap.workflows || {})) {
258
+ const workflowConfig = (workflow as unknown as { _config: CronWorkflowModel["config"] })._config;
259
+
260
+ if (workflowConfig?.trigger) {
261
+ const triggerType = Object.keys(workflowConfig.trigger)[0];
262
+
263
+ if (triggerType === "cron" && workflowConfig.trigger.cron) {
264
+ workflows.push({
265
+ path,
266
+ config: workflowConfig,
267
+ });
268
+ }
269
+ }
270
+ }
271
+
272
+ return workflows;
273
+ }
274
+
275
+ /**
276
+ * Get workflow by path
277
+ */
278
+ protected getWorkflowByPath(path: string): CronWorkflowModel | null {
279
+ const workflow = this.nodeMap.workflows?.[path];
280
+ if (!workflow) return null;
281
+
282
+ const workflowConfig = (workflow as unknown as { _config: CronWorkflowModel["config"] })._config;
283
+ return {
284
+ path,
285
+ config: workflowConfig,
286
+ };
287
+ }
288
+
289
+ /**
290
+ * Execute a workflow
291
+ */
292
+ protected async executeWorkflow(
293
+ jobId: string,
294
+ workflow: CronWorkflowModel,
295
+ config: CronTriggerOpts,
296
+ manual: boolean,
297
+ ): Promise<TriggerResponse> {
298
+ const scheduledJob = this.jobs.get(jobId);
299
+ if (!scheduledJob) {
300
+ throw new Error(`Job not found: ${jobId}`);
301
+ }
302
+
303
+ // Check for overlap
304
+ if (scheduledJob.running && !scheduledJob.overlap) {
305
+ this.logger.log(`Skipping ${jobId}: previous execution still running (overlap disabled)`);
306
+ return { ctx: {} as Context, metrics: {} as MetricsType };
307
+ }
308
+
309
+ const executionId = uuid();
310
+ const lastDate = scheduledJob.job.lastDate();
311
+ const scheduledTime = lastDate ? new Date(lastDate as unknown as string | number) : new Date();
312
+ const executionTime = new Date();
313
+
314
+ const defaultMeter = metrics.getMeter("default");
315
+ const cronExecutions = defaultMeter.createCounter("cron_executions", {
316
+ description: "Cron job executions",
317
+ });
318
+ const cronErrors = defaultMeter.createCounter("cron_errors", {
319
+ description: "Cron job execution errors",
320
+ });
321
+
322
+ return new Promise((resolve) => {
323
+ this.tracer.startActiveSpan(`cron:${workflow.path}`, async (span: Span) => {
324
+ scheduledJob.running = true;
325
+
326
+ try {
327
+ const start = performance.now();
328
+
329
+ // Initialize configuration for this workflow
330
+ await this.configuration.init(workflow.path, this.nodeMap);
331
+
332
+ // Create context
333
+ const ctx: Context = this.createContext(undefined, workflow.path, executionId);
334
+
335
+ // Create execution context
336
+ const cronContext: CronExecutionContext = {
337
+ jobId,
338
+ scheduledTime,
339
+ executionTime,
340
+ schedule: config.schedule,
341
+ timezone: config.timezone,
342
+ manual,
343
+ };
344
+
345
+ // Populate request with cron context
346
+ ctx.request = {
347
+ body: cronContext,
348
+ headers: {
349
+ "x-cron-job-id": jobId,
350
+ "x-cron-schedule": config.schedule,
351
+ "x-cron-timezone": config.timezone,
352
+ "x-cron-manual": String(manual),
353
+ },
354
+ query: {},
355
+ params: {
356
+ jobId,
357
+ schedule: config.schedule,
358
+ },
359
+ } as unknown as RequestContext;
360
+
361
+ // Store cron context in vars
362
+ if (!ctx.vars) ctx.vars = {};
363
+ ctx.vars._cron_context = {
364
+ jobId,
365
+ scheduledTime: scheduledTime.toISOString(),
366
+ executionTime: executionTime.toISOString(),
367
+ schedule: config.schedule,
368
+ timezone: config.timezone,
369
+ manual: String(manual),
370
+ };
371
+
372
+ ctx.logger.log(`Executing cron job: ${jobId} (${manual ? "manual" : "scheduled"})`);
373
+
374
+ // Execute workflow
375
+ const response: TriggerResponse = await this.run(ctx);
376
+ const end = performance.now();
377
+
378
+ // Update job state
379
+ scheduledJob.lastRun = executionTime;
380
+ scheduledJob.nextRun = this.toDate(scheduledJob.job.nextDate());
381
+
382
+ // Set span attributes
383
+ span.setAttribute("success", true);
384
+ span.setAttribute("job_id", jobId);
385
+ span.setAttribute("workflow_path", workflow.path);
386
+ span.setAttribute("schedule", config.schedule);
387
+ span.setAttribute("timezone", config.timezone);
388
+ span.setAttribute("manual", manual);
389
+ span.setAttribute("elapsed_ms", end - start);
390
+ span.setStatus({ code: SpanStatusCode.OK });
391
+
392
+ // Record metrics
393
+ cronExecutions.add(1, {
394
+ env: process.env.NODE_ENV,
395
+ job_id: jobId,
396
+ workflow_name: this.configuration.name,
397
+ manual: String(manual),
398
+ success: "true",
399
+ });
400
+
401
+ ctx.logger.log(`Cron job completed in ${(end - start).toFixed(2)}ms: ${jobId}`);
402
+
403
+ resolve(response);
404
+ } catch (error) {
405
+ const errorMessage = (error as Error).message;
406
+
407
+ // Set span error
408
+ span.setAttribute("success", false);
409
+ span.recordException(error as Error);
410
+ span.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });
411
+
412
+ // Record error metrics
413
+ cronErrors.add(1, {
414
+ env: process.env.NODE_ENV,
415
+ job_id: jobId,
416
+ workflow_name: this.configuration?.name || "unknown",
417
+ manual: String(manual),
418
+ });
419
+
420
+ this.logger.error(`Cron job failed ${jobId}: ${errorMessage}`, (error as Error).stack);
421
+
422
+ resolve({ ctx: {} as Context, metrics: {} as MetricsType });
423
+ } finally {
424
+ scheduledJob.running = false;
425
+ span.end();
426
+ }
427
+ });
428
+ });
429
+ }
430
+ }
431
+
432
+ export default CronTrigger;
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @blokjs/trigger-cron
3
+ *
4
+ * Cron/scheduled trigger for Blok workflows.
5
+ * Execute workflows on a schedule using cron expressions.
6
+ *
7
+ * Features:
8
+ * - Standard cron expression syntax
9
+ * - Timezone-aware scheduling
10
+ * - Overlap prevention
11
+ * - Manual trigger support
12
+ * - Job management API
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { CronTrigger } from "@blokjs/trigger-cron";
17
+ *
18
+ * class MyCronTrigger extends CronTrigger {
19
+ * protected nodes = myNodes;
20
+ * protected workflows = myWorkflows;
21
+ * }
22
+ *
23
+ * const trigger = new MyCronTrigger();
24
+ * await trigger.listen();
25
+ *
26
+ * // List all scheduled jobs
27
+ * const jobs = trigger.getJobs();
28
+ *
29
+ * // Manually trigger a job
30
+ * await trigger.triggerJob("cron-my-workflow-abc123");
31
+ * ```
32
+ *
33
+ * Workflow Definition:
34
+ * ```typescript
35
+ * Workflow({ name: "daily-cleanup", version: "1.0.0" })
36
+ * .addTrigger("cron", {
37
+ * schedule: "0 2 * * *", // Run at 2 AM daily
38
+ * timezone: "America/New_York",
39
+ * overlap: false,
40
+ * })
41
+ * .addStep({ ... });
42
+ * ```
43
+ *
44
+ * Cron Expression Format:
45
+ * ```
46
+ * * * * * * *
47
+ * │ │ │ │ │ │
48
+ * │ │ │ │ │ └── Day of week (0-7, Sun=0,7)
49
+ * │ │ │ │ └──── Month (1-12)
50
+ * │ │ │ └────── Day of month (1-31)
51
+ * │ │ └──────── Hour (0-23)
52
+ * │ └────────── Minute (0-59)
53
+ * └──────────── Second (0-59, optional)
54
+ * ```
55
+ *
56
+ * Common schedules:
57
+ * - "* * * * *" - Every minute
58
+ * - "0 * * * *" - Every hour
59
+ * - "0 0 * * *" - Every day at midnight
60
+ * - "0 0 * * 0" - Every Sunday at midnight
61
+ * - "0 0 1 * *" - First day of every month
62
+ */
63
+
64
+ // Core exports
65
+ export {
66
+ CronTrigger,
67
+ type ScheduledJob,
68
+ type CronExecutionContext,
69
+ } from "./CronTrigger";
70
+
71
+ // Re-export types from helper for convenience
72
+ export type { CronTriggerOpts } from "@blokjs/helper";
package/tsconfig.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "ts-node": {
3
+ "transpileOnly": true
4
+ },
5
+ "compilerOptions": {
6
+ "target": "ES2022",
7
+ "module": "es2022",
8
+ "lib": ["ES2022"],
9
+ "declaration": true,
10
+ "strict": true,
11
+ "noImplicitAny": true,
12
+ "strictNullChecks": true,
13
+ "noImplicitThis": true,
14
+ "alwaysStrict": true,
15
+ "noUnusedLocals": false,
16
+ "noUnusedParameters": false,
17
+ "noImplicitReturns": true,
18
+ "noFallthroughCasesInSwitch": false,
19
+ "inlineSourceMap": true,
20
+ "inlineSources": true,
21
+ "experimentalDecorators": true,
22
+ "emitDecoratorMetadata": true,
23
+ "skipLibCheck": true,
24
+ "esModuleInterop": true,
25
+ "resolveJsonModule": true,
26
+ "outDir": "./dist",
27
+ "rootDir": "./src",
28
+ "moduleResolution": "bundler"
29
+ },
30
+ "include": ["src/**/*"],
31
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
32
+ }