@happyvertical/smrt-reports 0.37.2 → 0.37.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/scheduler.js CHANGED
@@ -1,284 +1,251 @@
1
- import { createHash } from "node:crypto";
2
- import { EventEmitter } from "node:events";
3
- import { field, smrt, SmrtObject, ObjectRegistry, GlobalInterceptors } from "@happyvertical/smrt-core";
4
- import { backgroundEligible, getNextCronDate, SmrtJobCollection, validateCronExpression } from "@happyvertical/smrt-jobs";
5
- import { tenantId, TenantScoped, getTenantId } from "@happyvertical/smrt-tenancy";
6
1
  import { buildReportDefinition } from "./compiler.js";
2
+ import { REPORT_SCHEDULER_TABLES, REPORT_SCHEDULES_TABLE, assertReportTablesReady, scopeKeyForTenant } from "./state.js";
7
3
  import { refreshReport } from "./refresh.js";
8
- import { assertReportTablesReady, REPORT_SCHEDULES_TABLE, REPORT_SCHEDULER_TABLES, scopeKeyForTenant } from "./state.js";
4
+ import { GlobalInterceptors, ObjectRegistry, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
5
+ import { createHash } from "node:crypto";
6
+ import { TenantScoped, getTenantId, tenantId } from "@happyvertical/smrt-tenancy";
7
+ import { EventEmitter } from "node:events";
8
+ import { SmrtJobCollection, backgroundEligible, getNextCronDate, validateCronExpression } from "@happyvertical/smrt-jobs";
9
+ //#region src/scheduler.ts
9
10
  var __defProp = Object.defineProperty;
10
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
12
  var __decorateClass = (decorators, target, key, kind) => {
12
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
13
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
14
- if (decorator = decorators[i])
15
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
16
- if (kind && result) __defProp(target, key, result);
17
- return result;
13
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
14
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
15
+ if (kind && result) __defProp(target, key, result);
16
+ return result;
18
17
  };
19
- const INTERNAL_SURFACE = {
20
- api: false,
21
- cli: {
22
- include: ["list", "get"],
23
- skipApiCheck: true,
24
- http: false
25
- },
26
- mcp: false
18
+ var INTERNAL_SURFACE = {
19
+ api: false,
20
+ cli: {
21
+ include: ["list", "get"],
22
+ skipApiCheck: true,
23
+ http: false
24
+ },
25
+ mcp: false
27
26
  };
28
27
  function stableUuid(values) {
29
- const hash = createHash("sha256").update(JSON.stringify(values)).digest("hex");
30
- const variant = (Number.parseInt(hash[16], 16) & 3 | 8).toString(16);
31
- return [
32
- hash.slice(0, 8),
33
- hash.slice(8, 12),
34
- `4${hash.slice(13, 16)}`,
35
- `${variant}${hash.slice(17, 20)}`,
36
- hash.slice(20, 32)
37
- ].join("-");
28
+ const hash = createHash("sha256").update(JSON.stringify(values)).digest("hex");
29
+ const variant = (Number.parseInt(hash[16], 16) & 3 | 8).toString(16);
30
+ return [
31
+ hash.slice(0, 8),
32
+ hash.slice(8, 12),
33
+ `4${hash.slice(13, 16)}`,
34
+ `${variant}${hash.slice(17, 20)}`,
35
+ hash.slice(20, 32)
36
+ ].join("-");
38
37
  }
39
38
  function canonicalClassName(reportCtor) {
40
- const registered = ObjectRegistry.getClassByConstructor(reportCtor) ?? ObjectRegistry.getClass(reportCtor.name);
41
- return registered?.qualifiedName ?? registered?.name ?? reportCtor.name;
39
+ const registered = ObjectRegistry.getClassByConstructor(reportCtor) ?? ObjectRegistry.getClass(reportCtor.name);
40
+ return registered?.qualifiedName ?? registered?.name ?? reportCtor.name;
42
41
  }
43
42
  function resolveReportClass(name) {
44
- const registered = ObjectRegistry.getClassByQualifiedName(name) ?? ObjectRegistry.getClass(name);
45
- if (!registered) {
46
- throw new Error(`Unknown report class: ${name}`);
47
- }
48
- return registered.constructor;
43
+ const registered = ObjectRegistry.getClassByQualifiedName(name) ?? ObjectRegistry.getClass(name);
44
+ if (!registered) throw new Error(`Unknown report class: ${name}`);
45
+ return registered.constructor;
49
46
  }
50
47
  function reportSourceName(source) {
51
- if (typeof source === "string") return source;
52
- return source.name;
48
+ if (typeof source === "string") return source;
49
+ return source.name;
53
50
  }
54
51
  function sourceMatches(definition, instance, context) {
55
- const configured = definition.refresh?.onChange;
56
- if (!configured || configured.length === 0) return false;
57
- const eventNames = /* @__PURE__ */ new Set([
58
- context.className,
59
- instance.constructor.name
60
- ]);
61
- const registered = ObjectRegistry.getClassByConstructor(
62
- instance.constructor
63
- );
64
- if (registered?.qualifiedName) eventNames.add(registered.qualifiedName);
65
- if (registered?.name) eventNames.add(registered.name);
66
- for (const source of configured) {
67
- const name = reportSourceName(source);
68
- const registeredSource = ObjectRegistry.getClassByQualifiedName(name) ?? ObjectRegistry.getClass(name);
69
- if (eventNames.has(name) || registeredSource?.name && eventNames.has(registeredSource.name) || registeredSource?.qualifiedName && eventNames.has(registeredSource.qualifiedName)) {
70
- return true;
71
- }
72
- }
73
- return false;
52
+ const configured = definition.refresh?.onChange;
53
+ if (!configured || configured.length === 0) return false;
54
+ const eventNames = /* @__PURE__ */ new Set([context.className, instance.constructor.name]);
55
+ const registered = ObjectRegistry.getClassByConstructor(instance.constructor);
56
+ if (registered?.qualifiedName) eventNames.add(registered.qualifiedName);
57
+ if (registered?.name) eventNames.add(registered.name);
58
+ for (const source of configured) {
59
+ const name = reportSourceName(source);
60
+ const registeredSource = ObjectRegistry.getClassByQualifiedName(name) ?? ObjectRegistry.getClass(name);
61
+ if (eventNames.has(name) || registeredSource?.name && eventNames.has(registeredSource.name) || registeredSource?.qualifiedName && eventNames.has(registeredSource.qualifiedName)) return true;
62
+ }
63
+ return false;
74
64
  }
75
65
  function tenantIdFromInstance(instance) {
76
- const value = instance.tenantId;
77
- return typeof value === "string" && value.length > 0 ? value : getTenantId() ?? null;
66
+ const value = instance.tenantId;
67
+ return typeof value === "string" && value.length > 0 ? value : getTenantId() ?? null;
78
68
  }
79
69
  function changedRowSnapshot(instance) {
80
- const serializable = instance.toJSON();
81
- return serializable && typeof serializable === "object" ? serializable : {};
70
+ const serializable = instance.toJSON();
71
+ return serializable && typeof serializable === "object" ? serializable : {};
82
72
  }
83
- let SmrtReportRefreshTask = class extends SmrtObject {
84
- tenantId = null;
85
- reportClass = "";
86
- mode = "incremental";
87
- trigger = "job";
88
- args = {};
89
- async run(args = {}) {
90
- const reportClass = args.reportClass || this.reportClass;
91
- if (!reportClass) {
92
- throw new Error("Report refresh job requires reportClass");
93
- }
94
- const reportCtor = resolveReportClass(reportClass);
95
- return refreshReport(reportCtor, {
96
- db: this.db,
97
- mode: args.mode ?? this.mode,
98
- trigger: args.trigger ?? this.trigger,
99
- tenantId: args.tenantId,
100
- tenantIds: args.tenantIds,
101
- adapterType: args.adapterType,
102
- scheduleId: args.scheduleId ?? args._scheduleId,
103
- changedRows: args.changedRows
104
- });
105
- }
73
+ var SmrtReportRefreshTask = class extends SmrtObject {
74
+ tenantId = null;
75
+ reportClass = "";
76
+ mode = "incremental";
77
+ trigger = "job";
78
+ args = {};
79
+ async run(args = {}) {
80
+ const reportClass = args.reportClass || this.reportClass;
81
+ if (!reportClass) throw new Error("Report refresh job requires reportClass");
82
+ return refreshReport(resolveReportClass(reportClass), {
83
+ db: this.db,
84
+ mode: args.mode ?? this.mode,
85
+ trigger: args.trigger ?? this.trigger,
86
+ tenantId: args.tenantId,
87
+ tenantIds: args.tenantIds,
88
+ adapterType: args.adapterType,
89
+ scheduleId: args.scheduleId ?? args._scheduleId,
90
+ changedRows: args.changedRows
91
+ });
92
+ }
106
93
  };
107
- __decorateClass([
108
- tenantId({ nullable: true })
109
- ], SmrtReportRefreshTask.prototype, "tenantId", 2);
110
- __decorateClass([
111
- field({ type: "text", required: true })
112
- ], SmrtReportRefreshTask.prototype, "reportClass", 2);
113
- __decorateClass([
114
- field({ type: "text", required: true })
115
- ], SmrtReportRefreshTask.prototype, "mode", 2);
116
- __decorateClass([
117
- field({ type: "text", required: true })
118
- ], SmrtReportRefreshTask.prototype, "trigger", 2);
119
- __decorateClass([
120
- field({ type: "json" })
121
- ], SmrtReportRefreshTask.prototype, "args", 2);
122
- __decorateClass([
123
- backgroundEligible()
124
- ], SmrtReportRefreshTask.prototype, "run", 1);
125
- SmrtReportRefreshTask = __decorateClass([
126
- TenantScoped({ mode: "optional" }),
127
- smrt({
128
- tableName: "_smrt_report_refresh_tasks",
129
- ...INTERNAL_SURFACE
130
- })
131
- ], SmrtReportRefreshTask);
94
+ __decorateClass([tenantId({ nullable: true })], SmrtReportRefreshTask.prototype, "tenantId", 2);
95
+ __decorateClass([field({
96
+ type: "text",
97
+ required: true
98
+ })], SmrtReportRefreshTask.prototype, "reportClass", 2);
99
+ __decorateClass([field({
100
+ type: "text",
101
+ required: true
102
+ })], SmrtReportRefreshTask.prototype, "mode", 2);
103
+ __decorateClass([field({
104
+ type: "text",
105
+ required: true
106
+ })], SmrtReportRefreshTask.prototype, "trigger", 2);
107
+ __decorateClass([field({ type: "json" })], SmrtReportRefreshTask.prototype, "args", 2);
108
+ __decorateClass([backgroundEligible()], SmrtReportRefreshTask.prototype, "run", 1);
109
+ SmrtReportRefreshTask = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
110
+ tableName: "_smrt_report_refresh_tasks",
111
+ ...INTERNAL_SURFACE
112
+ })], SmrtReportRefreshTask);
132
113
  async function enqueueReportRefresh(options) {
133
- await ObjectRegistry.ensureManifestLoaded("SmrtJob");
134
- const collection = await SmrtJobCollection.create({ db: options.db });
135
- const taskType = canonicalClassName(SmrtReportRefreshTask);
136
- const scheduleId = options.scheduleId ?? options._scheduleId;
137
- return collection.enqueueJob(
138
- {
139
- tenantId: options.tenantId ?? null,
140
- queue: options.queue ?? "reports",
141
- objectType: taskType,
142
- objectId: null,
143
- method: "run",
144
- args: {
145
- reportClass: options.reportClass,
146
- mode: options.mode,
147
- trigger: options.trigger ?? "job",
148
- tenantId: options.tenantId,
149
- tenantIds: options.tenantIds,
150
- adapterType: options.adapterType,
151
- changedRows: options.changedRows,
152
- scheduleId,
153
- _scheduleId: scheduleId
154
- },
155
- priority: options.priority ?? 70,
156
- timeout: options.timeout ?? 36e5,
157
- maxAttempts: options.maxAttempts ?? 3
158
- },
159
- { tenantJobCap: options.tenantJobCap }
160
- );
114
+ await ObjectRegistry.ensureManifestLoaded("SmrtJob");
115
+ const collection = await SmrtJobCollection.create({ db: options.db });
116
+ const taskType = canonicalClassName(SmrtReportRefreshTask);
117
+ const scheduleId = options.scheduleId ?? options._scheduleId;
118
+ return collection.enqueueJob({
119
+ tenantId: options.tenantId ?? null,
120
+ queue: options.queue ?? "reports",
121
+ objectType: taskType,
122
+ objectId: null,
123
+ method: "run",
124
+ args: {
125
+ reportClass: options.reportClass,
126
+ mode: options.mode,
127
+ trigger: options.trigger ?? "job",
128
+ tenantId: options.tenantId,
129
+ tenantIds: options.tenantIds,
130
+ adapterType: options.adapterType,
131
+ changedRows: options.changedRows,
132
+ scheduleId,
133
+ _scheduleId: scheduleId
134
+ },
135
+ priority: options.priority ?? 70,
136
+ timeout: options.timeout ?? 36e5,
137
+ maxAttempts: options.maxAttempts ?? 3
138
+ }, { tenantJobCap: options.tenantJobCap });
161
139
  }
162
140
  async function ensureReportRefreshSchedules(options) {
163
- await assertReportTablesReady(options.db, REPORT_SCHEDULER_TABLES);
164
- for (const reportCtor of options.reports) {
165
- const definition = await buildReportDefinition(reportCtor);
166
- const refresh = definition.refresh;
167
- if (!refresh || refresh.manual) continue;
168
- const reportClass = canonicalClassName(reportCtor);
169
- const targetTenants = refresh.tenantFanout ? options.tenantIds : [null];
170
- if (refresh.tenantFanout && (!targetTenants || targetTenants.length === 0)) {
171
- throw new Error(
172
- `${definition.reportClassName} refresh.tenantFanout requires tenantIds when creating schedules.`
173
- );
174
- }
175
- const schedules = [
176
- refresh.schedule ? {
177
- cron: refresh.schedule,
178
- mode: refresh.mode ?? "incremental",
179
- trigger: "schedule"
180
- } : null,
181
- refresh.fullRebuildSchedule ? {
182
- cron: refresh.fullRebuildSchedule,
183
- mode: "rebuild",
184
- trigger: "schedule"
185
- } : null
186
- ].filter(Boolean);
187
- for (const schedule of schedules) {
188
- validateCronExpression(schedule.cron);
189
- for (const tenantId2 of targetTenants ?? []) {
190
- const scopeKey = scopeKeyForTenant(tenantId2);
191
- const id = stableUuid([
192
- "schedule",
193
- reportClass,
194
- scopeKey,
195
- schedule.cron,
196
- schedule.mode
197
- ]);
198
- const now = (/* @__PURE__ */ new Date()).toISOString();
199
- await options.db.upsert(
200
- REPORT_SCHEDULES_TABLE,
201
- ["report_class", "scope_key", "cron", "mode"],
202
- {
203
- id,
204
- slug: id,
205
- context: scopeKey,
206
- tenant_id: tenantId2,
207
- scope_key: scopeKey,
208
- report_class: reportClass,
209
- cron: schedule.cron,
210
- trigger: schedule.trigger,
211
- mode: schedule.mode,
212
- enabled: true,
213
- status: "active",
214
- next_run: getNextCronDate(schedule.cron).toISOString(),
215
- last_run: null,
216
- last_status: null,
217
- last_error: null,
218
- run_count: 0,
219
- success_count: 0,
220
- failure_count: 0,
221
- running_count: 0,
222
- max_concurrent: 1,
223
- queue: options.queue ?? "reports",
224
- priority: options.priority ?? 70,
225
- timeout: options.timeout ?? 36e5,
226
- created_at: now,
227
- updated_at: now
228
- }
229
- );
230
- }
231
- }
232
- }
141
+ await assertReportTablesReady(options.db, REPORT_SCHEDULER_TABLES);
142
+ for (const reportCtor of options.reports) {
143
+ const definition = await buildReportDefinition(reportCtor);
144
+ const refresh = definition.refresh;
145
+ if (!refresh || refresh.manual) continue;
146
+ const reportClass = canonicalClassName(reportCtor);
147
+ const targetTenants = refresh.tenantFanout ? options.tenantIds : [null];
148
+ if (refresh.tenantFanout && (!targetTenants || targetTenants.length === 0)) throw new Error(`${definition.reportClassName} refresh.tenantFanout requires tenantIds when creating schedules.`);
149
+ const schedules = [refresh.schedule ? {
150
+ cron: refresh.schedule,
151
+ mode: refresh.mode ?? "incremental",
152
+ trigger: "schedule"
153
+ } : null, refresh.fullRebuildSchedule ? {
154
+ cron: refresh.fullRebuildSchedule,
155
+ mode: "rebuild",
156
+ trigger: "schedule"
157
+ } : null].filter(Boolean);
158
+ for (const schedule of schedules) {
159
+ validateCronExpression(schedule.cron);
160
+ for (const tenantId2 of targetTenants ?? []) {
161
+ const scopeKey = scopeKeyForTenant(tenantId2);
162
+ const id = stableUuid([
163
+ "schedule",
164
+ reportClass,
165
+ scopeKey,
166
+ schedule.cron,
167
+ schedule.mode
168
+ ]);
169
+ const now = (/* @__PURE__ */ new Date()).toISOString();
170
+ await options.db.upsert(REPORT_SCHEDULES_TABLE, [
171
+ "report_class",
172
+ "scope_key",
173
+ "cron",
174
+ "mode"
175
+ ], {
176
+ id,
177
+ slug: id,
178
+ context: scopeKey,
179
+ tenant_id: tenantId2,
180
+ scope_key: scopeKey,
181
+ report_class: reportClass,
182
+ cron: schedule.cron,
183
+ trigger: schedule.trigger,
184
+ mode: schedule.mode,
185
+ enabled: true,
186
+ status: "active",
187
+ next_run: getNextCronDate(schedule.cron).toISOString(),
188
+ last_run: null,
189
+ last_status: null,
190
+ last_error: null,
191
+ run_count: 0,
192
+ success_count: 0,
193
+ failure_count: 0,
194
+ running_count: 0,
195
+ max_concurrent: 1,
196
+ queue: options.queue ?? "reports",
197
+ priority: options.priority ?? 70,
198
+ timeout: options.timeout ?? 36e5,
199
+ created_at: now,
200
+ updated_at: now
201
+ });
202
+ }
203
+ }
204
+ }
233
205
  }
234
- class ReportScheduleRunner extends EventEmitter {
235
- id;
236
- config;
237
- db = null;
238
- running = false;
239
- pollTimer = null;
240
- constructor(config = {}) {
241
- super();
242
- this.config = {
243
- id: config.id || `reports_${stableUuid([Date.now()]).slice(0, 8)}`,
244
- pollInterval: config.pollInterval ?? 6e4,
245
- batchSize: config.batchSize ?? 50
246
- };
247
- this.id = this.config.id;
248
- }
249
- async initialize(db) {
250
- this.db = db;
251
- await assertReportTablesReady(db, REPORT_SCHEDULER_TABLES);
252
- }
253
- async start() {
254
- if (this.running) return;
255
- if (!this.db) {
256
- throw new Error(
257
- "ReportScheduleRunner not initialized. Call initialize() first."
258
- );
259
- }
260
- this.running = true;
261
- this.startPolling();
262
- this.emit("runner:started");
263
- }
264
- async stop() {
265
- if (!this.running) return;
266
- this.running = false;
267
- if (this.pollTimer) {
268
- clearTimeout(this.pollTimer);
269
- this.pollTimer = null;
270
- }
271
- this.emit("runner:stopped");
272
- }
273
- isRunning() {
274
- return this.running;
275
- }
276
- async handleJobCompletion(scheduleId, success, errorMessage) {
277
- if (!this.db) return;
278
- const now = (/* @__PURE__ */ new Date()).toISOString();
279
- if (success) {
280
- await this.db.query(
281
- `UPDATE ${REPORT_SCHEDULES_TABLE}
206
+ var ReportScheduleRunner = class extends EventEmitter {
207
+ id;
208
+ config;
209
+ db = null;
210
+ running = false;
211
+ pollTimer = null;
212
+ constructor(config = {}) {
213
+ super();
214
+ this.config = {
215
+ id: config.id || `reports_${stableUuid([Date.now()]).slice(0, 8)}`,
216
+ pollInterval: config.pollInterval ?? 6e4,
217
+ batchSize: config.batchSize ?? 50
218
+ };
219
+ this.id = this.config.id;
220
+ }
221
+ async initialize(db) {
222
+ this.db = db;
223
+ await assertReportTablesReady(db, REPORT_SCHEDULER_TABLES);
224
+ }
225
+ async start() {
226
+ if (this.running) return;
227
+ if (!this.db) throw new Error("ReportScheduleRunner not initialized. Call initialize() first.");
228
+ this.running = true;
229
+ this.startPolling();
230
+ this.emit("runner:started");
231
+ }
232
+ async stop() {
233
+ if (!this.running) return;
234
+ this.running = false;
235
+ if (this.pollTimer) {
236
+ clearTimeout(this.pollTimer);
237
+ this.pollTimer = null;
238
+ }
239
+ this.emit("runner:stopped");
240
+ }
241
+ isRunning() {
242
+ return this.running;
243
+ }
244
+ async handleJobCompletion(scheduleId, success, errorMessage) {
245
+ if (!this.db) return;
246
+ const now = (/* @__PURE__ */ new Date()).toISOString();
247
+ if (success) {
248
+ await this.db.query(`UPDATE ${REPORT_SCHEDULES_TABLE}
282
249
  SET running_count = CASE WHEN COALESCE(running_count, 0) > 0 THEN running_count - 1 ELSE 0 END,
283
250
  last_run = ?,
284
251
  last_status = 'success',
@@ -286,17 +253,12 @@ class ReportScheduleRunner extends EventEmitter {
286
253
  run_count = COALESCE(run_count, 0) + 1,
287
254
  success_count = COALESCE(success_count, 0) + 1,
288
255
  updated_at = ?
289
- WHERE id = ?`,
290
- now,
291
- now,
292
- scheduleId
293
- );
294
- this.emit("schedule:completed", scheduleId);
295
- return;
296
- }
297
- const safeError = errorMessage ?? "Unknown error";
298
- await this.db.query(
299
- `UPDATE ${REPORT_SCHEDULES_TABLE}
256
+ WHERE id = ?`, now, now, scheduleId);
257
+ this.emit("schedule:completed", scheduleId);
258
+ return;
259
+ }
260
+ const safeError = errorMessage ?? "Unknown error";
261
+ await this.db.query(`UPDATE ${REPORT_SCHEDULES_TABLE}
300
262
  SET running_count = CASE WHEN COALESCE(running_count, 0) > 0 THEN running_count - 1 ELSE 0 END,
301
263
  last_run = ?,
302
264
  last_status = 'failed',
@@ -304,146 +266,119 @@ class ReportScheduleRunner extends EventEmitter {
304
266
  run_count = COALESCE(run_count, 0) + 1,
305
267
  failure_count = COALESCE(failure_count, 0) + 1,
306
268
  updated_at = ?
307
- WHERE id = ?`,
308
- now,
309
- safeError,
310
- now,
311
- scheduleId
312
- );
313
- this.emit("schedule:failed", scheduleId, safeError);
314
- }
315
- startPolling() {
316
- const poll = async () => {
317
- if (!this.running) return;
318
- try {
319
- await this.poll();
320
- } catch (error) {
321
- this.emit("runner:error", error);
322
- }
323
- if (this.running) {
324
- this.pollTimer = setTimeout(poll, this.config.pollInterval);
325
- if (typeof this.pollTimer.unref === "function") {
326
- this.pollTimer.unref();
327
- }
328
- }
329
- };
330
- poll();
331
- }
332
- async poll() {
333
- if (!this.db) return;
334
- const result = await this.db.query(
335
- `SELECT * FROM ${REPORT_SCHEDULES_TABLE}
269
+ WHERE id = ?`, now, safeError, now, scheduleId);
270
+ this.emit("schedule:failed", scheduleId, safeError);
271
+ }
272
+ startPolling() {
273
+ const poll = async () => {
274
+ if (!this.running) return;
275
+ try {
276
+ await this.poll();
277
+ } catch (error) {
278
+ this.emit("runner:error", error);
279
+ }
280
+ if (this.running) {
281
+ this.pollTimer = setTimeout(poll, this.config.pollInterval);
282
+ if (typeof this.pollTimer.unref === "function") this.pollTimer.unref();
283
+ }
284
+ };
285
+ poll();
286
+ }
287
+ async poll() {
288
+ if (!this.db) return;
289
+ const result = await this.db.query(`SELECT * FROM ${REPORT_SCHEDULES_TABLE}
336
290
  WHERE enabled = true
337
291
  AND status = 'active'
338
292
  AND next_run <= ?
339
293
  AND COALESCE(running_count, 0) < COALESCE(max_concurrent, 1)
340
294
  ORDER BY next_run ASC
341
- LIMIT ?`,
342
- (/* @__PURE__ */ new Date()).toISOString(),
343
- this.config.batchSize
344
- );
345
- for (const row of result.rows) {
346
- await this.triggerSchedule(row);
347
- }
348
- }
349
- async triggerSchedule(row) {
350
- if (!this.db) return;
351
- const schedule = {
352
- id: String(row.id),
353
- reportClass: String(row.report_class),
354
- tenantId: typeof row.tenant_id === "string" && row.tenant_id.length > 0 ? row.tenant_id : null,
355
- cron: String(row.cron),
356
- mode: row.mode || "incremental"
357
- };
358
- try {
359
- const nextRun = getNextCronDate(schedule.cron);
360
- await enqueueReportRefresh({
361
- db: this.db,
362
- reportClass: schedule.reportClass,
363
- mode: schedule.mode,
364
- trigger: row.trigger || "schedule",
365
- tenantId: schedule.tenantId,
366
- scheduleId: schedule.id,
367
- queue: String(row.queue || "reports"),
368
- priority: Number(row.priority ?? 70),
369
- timeout: Number(row.timeout ?? 36e5)
370
- });
371
- await this.db.query(
372
- `UPDATE ${REPORT_SCHEDULES_TABLE}
295
+ LIMIT ?`, (/* @__PURE__ */ new Date()).toISOString(), this.config.batchSize);
296
+ for (const row of result.rows) await this.triggerSchedule(row);
297
+ }
298
+ async triggerSchedule(row) {
299
+ if (!this.db) return;
300
+ const schedule = {
301
+ id: String(row.id),
302
+ reportClass: String(row.report_class),
303
+ tenantId: typeof row.tenant_id === "string" && row.tenant_id.length > 0 ? row.tenant_id : null,
304
+ cron: String(row.cron),
305
+ mode: row.mode || "incremental"
306
+ };
307
+ try {
308
+ const nextRun = getNextCronDate(schedule.cron);
309
+ await enqueueReportRefresh({
310
+ db: this.db,
311
+ reportClass: schedule.reportClass,
312
+ mode: schedule.mode,
313
+ trigger: row.trigger || "schedule",
314
+ tenantId: schedule.tenantId,
315
+ scheduleId: schedule.id,
316
+ queue: String(row.queue || "reports"),
317
+ priority: Number(row.priority ?? 70),
318
+ timeout: Number(row.timeout ?? 36e5)
319
+ });
320
+ await this.db.query(`UPDATE ${REPORT_SCHEDULES_TABLE}
373
321
  SET running_count = COALESCE(running_count, 0) + 1,
374
322
  next_run = ?,
375
323
  updated_at = ?
376
- WHERE id = ?`,
377
- nextRun.toISOString(),
378
- (/* @__PURE__ */ new Date()).toISOString(),
379
- schedule.id
380
- );
381
- this.emit("schedule:triggered", schedule);
382
- } catch (error) {
383
- await this.db.query(
384
- `UPDATE ${REPORT_SCHEDULES_TABLE}
324
+ WHERE id = ?`, nextRun.toISOString(), (/* @__PURE__ */ new Date()).toISOString(), schedule.id);
325
+ this.emit("schedule:triggered", schedule);
326
+ } catch (error) {
327
+ await this.db.query(`UPDATE ${REPORT_SCHEDULES_TABLE}
385
328
  SET last_error = ?,
386
329
  updated_at = ?
387
- WHERE id = ?`,
388
- error instanceof Error ? error.message : String(error),
389
- (/* @__PURE__ */ new Date()).toISOString(),
390
- schedule.id
391
- );
392
- this.emit("schedule:error", schedule, error);
393
- }
394
- }
395
- }
330
+ WHERE id = ?`, error instanceof Error ? error.message : String(error), (/* @__PURE__ */ new Date()).toISOString(), schedule.id);
331
+ this.emit("schedule:error", schedule, error);
332
+ }
333
+ }
334
+ };
396
335
  function registerReportRefreshInterceptor(options) {
397
- const name = options.name ?? "smrt-reports-refresh";
398
- GlobalInterceptors.register({
399
- name,
400
- priority: -10,
401
- async afterSave(instance, context) {
402
- await triggerReportsForInstance(options, instance, context);
403
- },
404
- async afterDelete(instance, context) {
405
- await triggerReportsForInstance(options, instance, context);
406
- }
407
- });
408
- return () => GlobalInterceptors.unregister(name);
336
+ const name = options.name ?? "smrt-reports-refresh";
337
+ GlobalInterceptors.register({
338
+ name,
339
+ priority: -10,
340
+ async afterSave(instance, context) {
341
+ await triggerReportsForInstance(options, instance, context);
342
+ },
343
+ async afterDelete(instance, context) {
344
+ await triggerReportsForInstance(options, instance, context);
345
+ }
346
+ });
347
+ return () => GlobalInterceptors.unregister(name);
409
348
  }
410
349
  async function triggerReportsForInstance(options, instance, context) {
411
- for (const reportCtor of options.reports) {
412
- const definition = await buildReportDefinition(reportCtor);
413
- if (definition.refresh?.manual) continue;
414
- if (!sourceMatches(definition, instance, context)) continue;
415
- const mode = definition.refresh?.mode ?? "incremental";
416
- const tenantId2 = tenantIdFromInstance(instance);
417
- const changedRows = [changedRowSnapshot(instance)];
418
- if (options.enqueue === false) {
419
- await refreshReport(reportCtor, {
420
- db: options.db,
421
- mode,
422
- trigger: "change",
423
- tenantId: tenantId2,
424
- changedRows
425
- });
426
- continue;
427
- }
428
- await enqueueReportRefresh({
429
- db: options.db,
430
- reportClass: canonicalClassName(reportCtor),
431
- mode,
432
- trigger: "change",
433
- tenantId: tenantId2,
434
- queue: options.queue,
435
- priority: options.priority,
436
- timeout: options.timeout,
437
- tenantJobCap: options.tenantJobCap,
438
- changedRows
439
- });
440
- }
350
+ for (const reportCtor of options.reports) {
351
+ const definition = await buildReportDefinition(reportCtor);
352
+ if (definition.refresh?.manual) continue;
353
+ if (!sourceMatches(definition, instance, context)) continue;
354
+ const mode = definition.refresh?.mode ?? "incremental";
355
+ const tenantId2 = tenantIdFromInstance(instance);
356
+ const changedRows = [changedRowSnapshot(instance)];
357
+ if (options.enqueue === false) {
358
+ await refreshReport(reportCtor, {
359
+ db: options.db,
360
+ mode,
361
+ trigger: "change",
362
+ tenantId: tenantId2,
363
+ changedRows
364
+ });
365
+ continue;
366
+ }
367
+ await enqueueReportRefresh({
368
+ db: options.db,
369
+ reportClass: canonicalClassName(reportCtor),
370
+ mode,
371
+ trigger: "change",
372
+ tenantId: tenantId2,
373
+ queue: options.queue,
374
+ priority: options.priority,
375
+ timeout: options.timeout,
376
+ tenantJobCap: options.tenantJobCap,
377
+ changedRows
378
+ });
379
+ }
441
380
  }
442
- export {
443
- ReportScheduleRunner,
444
- SmrtReportRefreshTask,
445
- enqueueReportRefresh,
446
- ensureReportRefreshSchedules,
447
- registerReportRefreshInterceptor
448
- };
449
- //# sourceMappingURL=scheduler.js.map
381
+ //#endregion
382
+ export { ReportScheduleRunner, SmrtReportRefreshTask, enqueueReportRefresh, ensureReportRefreshSchedules, registerReportRefreshInterceptor };
383
+
384
+ //# sourceMappingURL=scheduler.js.map