@happyvertical/smrt-reports 0.54.2 → 0.54.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.
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-reports",
5
- "packageVersion": "0.54.2",
5
+ "packageVersion": "0.54.3",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-reports:SmrtReport": {
8
8
  "name": "smrtreport",
@@ -2161,5 +2161,6 @@
2161
2161
  "@happyvertical/smrt-core",
2162
2162
  "@happyvertical/smrt-jobs",
2163
2163
  "@happyvertical/smrt-tenancy"
2164
- ]
2164
+ ],
2165
+ "stampsConstructors": true
2165
2166
  }
package/dist/scheduler.js CHANGED
@@ -9,12 +9,19 @@ import { SmrtJobCollection, backgroundEligible, getActiveJobExecutionContext, ge
9
9
  //#region src/scheduler.ts
10
10
  var __defProp = Object.defineProperty;
11
11
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value
17
+ }) : obj[key] = value;
12
18
  var __decorateClass = (decorators, target, key, kind) => {
13
19
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
14
20
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
15
21
  if (kind && result) __defProp(target, key, result);
16
22
  return result;
17
23
  };
24
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
18
25
  var INTERNAL_SURFACE = {
19
26
  api: false,
20
27
  cli: {
@@ -178,6 +185,7 @@ var SmrtReportRefreshTask = class extends SmrtObject {
178
185
  });
179
186
  }
180
187
  };
188
+ __publicField(SmrtReportRefreshTask, "__smrtPackage__", "@happyvertical/smrt-reports");
181
189
  __decorateClass([tenantId({ nullable: true })], SmrtReportRefreshTask.prototype, "tenantId", 2);
182
190
  __decorateClass([field({
183
191
  type: "text",
@@ -205,6 +213,7 @@ var SmrtPrincipalReportRefreshTask = class extends SmrtReportRefreshTask {
205
213
  }, context);
206
214
  }
207
215
  };
216
+ __publicField(SmrtPrincipalReportRefreshTask, "__smrtPackage__", "@happyvertical/smrt-reports");
208
217
  SmrtPrincipalReportRefreshTask = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
209
218
  tableName: "_smrt_principal_report_refresh_tasks",
210
219
  ...INTERNAL_SURFACE
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.js","names":["tenantId"],"sources":["../src/scheduler.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { EventEmitter } from 'node:events';\nimport {\n field,\n GlobalInterceptors,\n type InterceptorContext,\n ObjectRegistry,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n backgroundEligible,\n type DurableJobPayloadIntegrity,\n type DurableJobPayloadSigner,\n getActiveJobExecutionContext,\n getNextCronDate,\n isRunnerExecutionContext,\n type JobExecutionContext,\n type SmrtJob,\n SmrtJobCollection,\n validateCronExpression,\n} from '@happyvertical/smrt-jobs';\nimport {\n getTenantId,\n TenantScoped,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface, SqlAdapterType } from '@happyvertical/sql';\nimport { buildReportDefinition } from './compiler.js';\nimport { refreshReport } from './refresh.js';\nimport {\n assertReportTablesReady,\n REPORT_SCHEDULER_TABLES,\n REPORT_SCHEDULES_TABLE,\n scopeKeyForTenant,\n} from './state.js';\nimport type {\n ReportDefinition,\n ReportRefreshMode,\n ReportRefreshTrigger,\n ReportSource,\n} from './types.js';\n\ntype ReportCtor = new (...args: any[]) => SmrtObject;\n\nexport interface ReportRefreshJobArgs {\n reportClass?: string;\n mode?: ReportRefreshMode;\n trigger?: ReportRefreshTrigger;\n tenantId?: string | null;\n tenantIds?: string[];\n scheduleId?: string;\n adapterType?: SqlAdapterType;\n changedRows?: Record<string, unknown>[];\n _scheduleId?: string;\n executionAuthority?: ReportRefreshExecutionAuthority;\n integrity?: DurableJobPayloadIntegrity;\n}\n\nexport interface ReportExecutionPrincipalReference {\n version: 1;\n actorUserId: string;\n tenantId: string | null;\n onBehalfOfUserId?: string | null;\n actsAsProfileId?: string | null;\n agentClass?: string | null;\n}\n\nexport interface ReportRefreshExecutionAuthority {\n version: 1;\n hostId: string;\n principal: ReportExecutionPrincipalReference;\n}\n\nexport interface ReportRefreshExecutionAuthorityContext {\n phase: 'execute';\n reportClass: string;\n mode: ReportRefreshMode;\n trigger: ReportRefreshTrigger;\n tenantId: string | null;\n}\n\nexport interface ReportRefreshExecutionAuditEvent\n extends ReportRefreshExecutionAuthorityContext {\n outcome: 'allowed' | 'denied';\n principal: ReportExecutionPrincipalReference;\n reason?: string;\n}\n\nexport interface ReportRefreshExecutionAuthorityHost {\n authorize(\n principal: Readonly<ReportExecutionPrincipalReference>,\n context: Readonly<ReportRefreshExecutionAuthorityContext>,\n ): Promise<void> | void;\n audit(\n event: Readonly<ReportRefreshExecutionAuditEvent>,\n ): Promise<void> | void;\n}\n\nexport interface EnqueueReportRefreshOptions extends ReportRefreshJobArgs {\n report?: ReportCtor;\n reportClass: string;\n db: DatabaseInterface;\n queue?: string;\n priority?: number;\n timeout?: number;\n maxAttempts?: number;\n tenantJobCap?: number;\n /** Server-only signer; register the same key in every worker process. */\n integritySigner?: DurableJobPayloadSigner;\n}\n\nexport interface EnsureReportSchedulesOptions {\n db: DatabaseInterface;\n reports: ReportCtor[];\n tenantIds?: string[];\n queue?: string;\n priority?: number;\n timeout?: number;\n}\n\nexport interface ReportScheduleRunnerConfig {\n id?: string;\n pollInterval?: number;\n batchSize?: number;\n integritySigner?: DurableJobPayloadSigner;\n}\n\nexport interface ReportScheduleInfo {\n id: string;\n reportClass: string;\n tenantId: string | null;\n cron: string;\n mode: ReportRefreshMode;\n}\n\nexport interface ReportScheduleRunnerEvents {\n 'schedule:triggered': (schedule: ReportScheduleInfo) => void;\n 'schedule:error': (schedule: ReportScheduleInfo, error: Error) => void;\n 'schedule:completed': (scheduleId: string) => void;\n 'schedule:failed': (scheduleId: string, error: string) => void;\n 'runner:started': () => void;\n 'runner:stopped': () => void;\n 'runner:error': (error: Error) => void;\n}\n\nexport interface ReportRefreshInterceptorOptions {\n db: DatabaseInterface;\n reports: ReportCtor[];\n enqueue?: boolean;\n queue?: string;\n priority?: number;\n timeout?: number;\n tenantJobCap?: number;\n name?: string;\n integritySigner?: DurableJobPayloadSigner;\n}\n\nconst INTERNAL_SURFACE = {\n api: false,\n cli: {\n include: ['list', 'get'],\n skipApiCheck: true,\n http: false,\n },\n mcp: false,\n};\n\nconst executionAuthorityHosts = new Map<\n string,\n ReportRefreshExecutionAuthorityHost\n>();\nconst jobIntegritySigners = new Map<string, DurableJobPayloadSigner>();\n\nexport function registerReportRefreshJobIntegritySigner(\n signer: DurableJobPayloadSigner,\n): () => void {\n const existing = jobIntegritySigners.get(signer.keyId);\n if (existing && existing !== signer) {\n throw new Error(\n `Report refresh job integrity signer already registered: ${signer.keyId}`,\n );\n }\n jobIntegritySigners.set(signer.keyId, signer);\n return () => {\n if (jobIntegritySigners.get(signer.keyId) === signer) {\n jobIntegritySigners.delete(signer.keyId);\n }\n };\n}\n\n/**\n * Maintenance callers historically configured only their runner/interceptor;\n * they do not have a request host from which to obtain a signer. When one\n * worker-shared signer is registered, use it as the application default. More\n * than one key is ambiguous and remains fail-closed until the caller selects\n * one explicitly.\n */\nfunction resolveReportRefreshIntegritySigner(\n configured: DurableJobPayloadSigner | undefined,\n): DurableJobPayloadSigner | undefined {\n if (configured) return configured;\n if (jobIntegritySigners.size !== 1) return undefined;\n return jobIntegritySigners.values().next().value;\n}\n\nfunction unsignedReportRefreshJobArgs(\n args: ReportRefreshJobArgs,\n): Omit<ReportRefreshJobArgs, 'integrity'> {\n const {\n integrity: _integrity,\n _scheduleId: _internalScheduleId,\n ...unsigned\n } = args;\n return unsigned;\n}\n\nfunction assertReportRefreshJobIntegrity(args: ReportRefreshJobArgs): void {\n const integrity = args.integrity;\n const signer = integrity\n ? jobIntegritySigners.get(integrity.keyId)\n : undefined;\n if (\n !integrity ||\n !signer?.verify(unsignedReportRefreshJobArgs(args), integrity)\n ) {\n throw new Error('Invalid durable report refresh job integrity binding');\n }\n}\n\nfunction assertReportRefreshJobTarget(\n args: ReportRefreshJobArgs,\n context: JobExecutionContext | undefined,\n): void {\n if (!context) return;\n if (!isRunnerExecutionContext(context)) {\n throw new Error('Invalid durable report refresh job context');\n }\n const expectedType = canonicalClassName(\n (args.trigger ?? 'job') === 'manual'\n ? SmrtPrincipalReportRefreshTask\n : SmrtReportRefreshTask,\n );\n if (context.job.objectType !== expectedType || context.job.method !== 'run') {\n throw new Error('Invalid durable report refresh job target');\n }\n}\n\nexport function registerReportRefreshExecutionAuthorityHost(\n hostId: string,\n host: ReportRefreshExecutionAuthorityHost,\n): () => void {\n if (!hostId || hostId.length > 256) {\n throw new Error(\n 'Report refresh authority hostId must contain 1-256 characters',\n );\n }\n const existing = executionAuthorityHosts.get(hostId);\n if (existing && existing !== host) {\n throw new Error(\n `Report refresh authority host already registered: ${hostId}`,\n );\n }\n executionAuthorityHosts.set(hostId, host);\n return () => {\n if (executionAuthorityHosts.get(hostId) === host) {\n executionAuthorityHosts.delete(hostId);\n }\n };\n}\n\nfunction stableUuid(values: unknown[]): string {\n const hash = createHash('sha256')\n .update(JSON.stringify(values))\n .digest('hex');\n const variant = ((Number.parseInt(hash[16], 16) & 0x3) | 0x8).toString(16);\n return [\n hash.slice(0, 8),\n hash.slice(8, 12),\n `4${hash.slice(13, 16)}`,\n `${variant}${hash.slice(17, 20)}`,\n hash.slice(20, 32),\n ].join('-');\n}\n\nfunction canonicalClassName(reportCtor: ReportCtor): string {\n const registered =\n ObjectRegistry.getClassByConstructor(reportCtor) ??\n ObjectRegistry.getClass(reportCtor.name);\n return registered?.qualifiedName ?? registered?.name ?? reportCtor.name;\n}\n\nfunction resolveReportClass(name: string): ReportCtor {\n const registered =\n ObjectRegistry.getClassByQualifiedName(name) ??\n ObjectRegistry.getClass(name);\n if (!registered) {\n throw new Error(`Unknown report class: ${name}`);\n }\n return registered.constructor as unknown as ReportCtor;\n}\n\nfunction reportSourceName(source: ReportSource): string {\n if (typeof source === 'string') return source;\n return source.name;\n}\n\nfunction sourceMatches(\n definition: ReportDefinition,\n instance: SmrtObject,\n context: InterceptorContext,\n): boolean {\n const configured = definition.refresh?.onChange;\n if (!configured || configured.length === 0) return false;\n\n const eventNames = new Set<string>([\n context.className,\n instance.constructor.name,\n ]);\n const registered = ObjectRegistry.getClassByConstructor(\n instance.constructor as ReportCtor,\n );\n if (registered?.qualifiedName) eventNames.add(registered.qualifiedName);\n if (registered?.name) eventNames.add(registered.name);\n\n for (const source of configured) {\n const name = reportSourceName(source);\n const registeredSource =\n ObjectRegistry.getClassByQualifiedName(name) ??\n ObjectRegistry.getClass(name);\n if (\n eventNames.has(name) ||\n (registeredSource?.name && eventNames.has(registeredSource.name)) ||\n (registeredSource?.qualifiedName &&\n eventNames.has(registeredSource.qualifiedName))\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction tenantIdFromInstance(instance: SmrtObject): string | null {\n const value = (instance as unknown as { tenantId?: unknown }).tenantId;\n return typeof value === 'string' && value.length > 0\n ? value\n : (getTenantId() ?? null);\n}\n\nfunction changedRowSnapshot(instance: SmrtObject): Record<string, unknown> {\n const serializable = instance.toJSON();\n return serializable && typeof serializable === 'object'\n ? (serializable as Record<string, unknown>)\n : {};\n}\n\nasync function authorizeReportRefreshExecution(\n args: ReportRefreshJobArgs,\n reportClass: string,\n jobTenantId: string | null,\n): Promise<void> {\n if ((args.tenantId ?? null) !== jobTenantId) {\n throw new Error('Invalid report refresh execution tenant');\n }\n const authority = args.executionAuthority;\n if ((args.trigger ?? 'job') === 'manual' && !authority) {\n throw new Error('Manual report refresh execution authority is missing');\n }\n // Scheduled and on-change maintenance jobs predate user-bound actions and\n // intentionally run under the worker's system authority.\n if (!authority) return;\n const principal = authority.principal;\n const tenantId = args.tenantId ?? null;\n if (\n authority.version !== 1 ||\n principal?.version !== 1 ||\n !authority.hostId ||\n !principal.actorUserId ||\n principal.tenantId !== tenantId ||\n principal.tenantId !== jobTenantId\n ) {\n throw new Error('Invalid report refresh execution authority');\n }\n const host = executionAuthorityHosts.get(authority.hostId);\n if (!host) {\n throw new Error(\n `No report refresh authority host registered for ${authority.hostId}`,\n );\n }\n const context: ReportRefreshExecutionAuthorityContext = {\n phase: 'execute',\n reportClass,\n mode: args.mode ?? 'incremental',\n trigger: args.trigger ?? 'job',\n tenantId,\n };\n try {\n await host.authorize(\n Object.freeze({ ...principal }),\n Object.freeze(context),\n );\n } catch {\n await host.audit({\n ...context,\n outcome: 'denied',\n principal: Object.freeze({ ...principal }),\n reason: 'current_authority_denied',\n });\n throw new Error('Report refresh execution authority denied');\n }\n await host.audit({\n ...context,\n outcome: 'allowed',\n principal: Object.freeze({ ...principal }),\n });\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_report_refresh_tasks',\n ...INTERNAL_SURFACE,\n})\nexport class SmrtReportRefreshTask extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ type: 'text', required: true })\n reportClass: string = '';\n\n @field({ type: 'text', required: true })\n mode: ReportRefreshMode = 'incremental';\n\n @field({ type: 'text', required: true })\n trigger: ReportRefreshTrigger = 'job';\n\n @field({ type: 'json' })\n args: ReportRefreshJobArgs = {};\n\n @backgroundEligible()\n async run(\n args: ReportRefreshJobArgs = {},\n context?: JobExecutionContext,\n ): Promise<unknown> {\n assertReportRefreshJobIntegrity(args);\n const executionContext = getActiveJobExecutionContext() ?? context;\n assertReportRefreshJobTarget(args, executionContext);\n const reportClass = args.reportClass;\n if (!reportClass) {\n throw new Error('Report refresh job requires reportClass');\n }\n const mode = args.mode ?? 'incremental';\n const trigger = args.trigger ?? 'job';\n\n const reportCtor = resolveReportClass(reportClass);\n // A runner context, including its explicit global `null` tenant, owns the\n // scope. Only direct callers without runner context may use instance scope.\n let jobTenantId: string | null;\n if (executionContext) {\n const runnerTenantId = executionContext.job.tenantId;\n if (runnerTenantId === null) {\n jobTenantId = null;\n } else if (\n typeof runnerTenantId === 'string' &&\n runnerTenantId.length > 0\n ) {\n jobTenantId = runnerTenantId;\n } else {\n throw new Error('Invalid report refresh execution tenant');\n }\n } else {\n jobTenantId = tenantIdFromInstance(this);\n }\n await authorizeReportRefreshExecution(args, reportClass, jobTenantId);\n return refreshReport(reportCtor, {\n db: this.db,\n mode,\n trigger,\n tenantId: jobTenantId,\n tenantIds: args.tenantIds,\n adapterType: args.adapterType,\n scheduleId: args.scheduleId ?? args._scheduleId,\n changedRows: args.changedRows,\n });\n }\n}\n\n/** Worker target whose authority requirement cannot be downgraded by job args. */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_principal_report_refresh_tasks',\n ...INTERNAL_SURFACE,\n})\nexport class SmrtPrincipalReportRefreshTask extends SmrtReportRefreshTask {\n override async run(\n args: ReportRefreshJobArgs = {},\n context?: JobExecutionContext,\n ): Promise<unknown> {\n return super.run({ ...args, trigger: 'manual' }, context);\n }\n}\n\nexport async function enqueueReportRefresh(\n options: EnqueueReportRefreshOptions,\n): Promise<SmrtJob> {\n const integritySigner = resolveReportRefreshIntegritySigner(\n options.integritySigner,\n );\n if (!integritySigner) {\n throw new Error(\n 'Report refresh queue requires a durable job integrity signer',\n );\n }\n if (options.trigger === 'manual' && !options.executionAuthority) {\n throw new Error('Manual report refresh requires execution-time authority');\n }\n if (\n options.executionAuthority &&\n (options.executionAuthority.version !== 1 ||\n options.executionAuthority.principal?.version !== 1 ||\n !options.executionAuthority.hostId ||\n !options.executionAuthority.principal.actorUserId ||\n options.executionAuthority.principal.tenantId !==\n (options.tenantId ?? null))\n ) {\n throw new Error('Invalid report refresh execution authority');\n }\n if (\n options.executionAuthority &&\n (options.trigger !== 'manual' || (options.tenantIds?.length ?? 0) > 0)\n ) {\n throw new Error(\n 'Principal-bound report refresh requires one manual tenant scope',\n );\n }\n await ObjectRegistry.ensureManifestLoaded('SmrtJob');\n const collection = await SmrtJobCollection.create({ db: options.db });\n const taskType = canonicalClassName(\n options.trigger === 'manual'\n ? SmrtPrincipalReportRefreshTask\n : SmrtReportRefreshTask,\n );\n const scheduleId = options.scheduleId ?? options._scheduleId;\n\n const unsignedArgs: Omit<ReportRefreshJobArgs, 'integrity'> = {\n reportClass: options.reportClass,\n mode: options.mode ?? 'incremental',\n trigger: options.trigger ?? 'job',\n tenantId: options.tenantId,\n tenantIds: options.tenantIds,\n adapterType: options.adapterType,\n changedRows: options.changedRows,\n scheduleId,\n executionAuthority: options.executionAuthority,\n };\n const integrity = integritySigner.sign(unsignedArgs);\n if (!integritySigner.verify(unsignedArgs, integrity)) {\n throw new Error(\n 'Report refresh job integrity signer rejected its queued payload',\n );\n }\n return collection.enqueueJob(\n {\n tenantId: options.tenantId ?? null,\n queue: options.queue ?? 'reports',\n objectType: taskType,\n objectId: null,\n method: 'run',\n args: { ...unsignedArgs, _scheduleId: scheduleId, integrity },\n priority: options.priority ?? 70,\n timeout: options.timeout ?? 3600000,\n maxAttempts: options.maxAttempts ?? 3,\n },\n { tenantJobCap: options.tenantJobCap },\n );\n}\n\nexport async function ensureReportRefreshSchedules(\n options: EnsureReportSchedulesOptions,\n): Promise<void> {\n await assertReportTablesReady(options.db, REPORT_SCHEDULER_TABLES);\n\n for (const reportCtor of options.reports) {\n const definition = await buildReportDefinition(reportCtor);\n const refresh = definition.refresh;\n if (!refresh || refresh.manual) continue;\n\n const reportClass = canonicalClassName(reportCtor);\n const targetTenants = refresh.tenantFanout\n ? options.tenantIds\n : [null as string | null];\n if (\n refresh.tenantFanout &&\n (!targetTenants || targetTenants.length === 0)\n ) {\n throw new Error(\n `${definition.reportClassName} refresh.tenantFanout requires tenantIds when creating schedules.`,\n );\n }\n\n const schedules = [\n refresh.schedule\n ? {\n cron: refresh.schedule,\n mode: refresh.mode ?? 'incremental',\n trigger: 'schedule' as const,\n }\n : null,\n refresh.fullRebuildSchedule\n ? {\n cron: refresh.fullRebuildSchedule,\n mode: 'rebuild' as const,\n trigger: 'schedule' as const,\n }\n : null,\n ].filter(Boolean) as Array<{\n cron: string;\n mode: ReportRefreshMode;\n trigger: ReportRefreshTrigger;\n }>;\n\n for (const schedule of schedules) {\n validateCronExpression(schedule.cron);\n for (const tenantId of targetTenants ?? []) {\n const scopeKey = scopeKeyForTenant(tenantId);\n const id = stableUuid([\n 'schedule',\n reportClass,\n scopeKey,\n schedule.cron,\n schedule.mode,\n ]);\n const now = new Date().toISOString();\n await options.db.upsert(\n REPORT_SCHEDULES_TABLE,\n ['report_class', 'scope_key', 'cron', 'mode'],\n {\n id,\n slug: id,\n context: scopeKey,\n tenant_id: tenantId,\n scope_key: scopeKey,\n report_class: reportClass,\n cron: schedule.cron,\n trigger: schedule.trigger,\n mode: schedule.mode,\n enabled: true,\n status: 'active',\n next_run: getNextCronDate(schedule.cron).toISOString(),\n last_run: null,\n last_status: null,\n last_error: null,\n run_count: 0,\n success_count: 0,\n failure_count: 0,\n running_count: 0,\n max_concurrent: 1,\n queue: options.queue ?? 'reports',\n priority: options.priority ?? 70,\n timeout: options.timeout ?? 3600000,\n created_at: now,\n updated_at: now,\n },\n );\n }\n }\n }\n}\n\nexport class ReportScheduleRunner extends EventEmitter {\n readonly id: string;\n private readonly config: Required<\n Omit<ReportScheduleRunnerConfig, 'integritySigner'>\n > &\n Pick<ReportScheduleRunnerConfig, 'integritySigner'>;\n private db: DatabaseInterface | null = null;\n private running = false;\n private pollTimer: NodeJS.Timeout | null = null;\n\n constructor(config: ReportScheduleRunnerConfig = {}) {\n super();\n this.config = {\n id: config.id || `reports_${stableUuid([Date.now()]).slice(0, 8)}`,\n pollInterval: config.pollInterval ?? 60000,\n batchSize: config.batchSize ?? 50,\n integritySigner: config.integritySigner,\n };\n this.id = this.config.id;\n }\n\n async initialize(db: DatabaseInterface): Promise<void> {\n this.db = db;\n await assertReportTablesReady(db, REPORT_SCHEDULER_TABLES);\n }\n\n async start(): Promise<void> {\n if (this.running) return;\n if (!this.db) {\n throw new Error(\n 'ReportScheduleRunner not initialized. Call initialize() first.',\n );\n }\n this.running = true;\n this.startPolling();\n this.emit('runner:started');\n }\n\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n this.emit('runner:stopped');\n }\n\n isRunning(): boolean {\n return this.running;\n }\n\n async handleJobCompletion(\n scheduleId: string,\n success: boolean,\n errorMessage?: string,\n ): Promise<void> {\n if (!this.db) return;\n const now = new Date().toISOString();\n if (success) {\n await this.db.query(\n `UPDATE ${REPORT_SCHEDULES_TABLE}\n SET running_count = CASE WHEN COALESCE(running_count, 0) > 0 THEN running_count - 1 ELSE 0 END,\n last_run = ?,\n last_status = 'success',\n last_error = NULL,\n run_count = COALESCE(run_count, 0) + 1,\n success_count = COALESCE(success_count, 0) + 1,\n updated_at = ?\n WHERE id = ?`,\n now,\n now,\n scheduleId,\n );\n this.emit('schedule:completed', scheduleId);\n return;\n }\n\n const safeError = errorMessage ?? 'Unknown error';\n await this.db.query(\n `UPDATE ${REPORT_SCHEDULES_TABLE}\n SET running_count = CASE WHEN COALESCE(running_count, 0) > 0 THEN running_count - 1 ELSE 0 END,\n last_run = ?,\n last_status = 'failed',\n last_error = ?,\n run_count = COALESCE(run_count, 0) + 1,\n failure_count = COALESCE(failure_count, 0) + 1,\n updated_at = ?\n WHERE id = ?`,\n now,\n safeError,\n now,\n scheduleId,\n );\n this.emit('schedule:failed', scheduleId, safeError);\n }\n\n private startPolling(): void {\n const poll = async () => {\n if (!this.running) return;\n try {\n await this.poll();\n } catch (error) {\n this.emit('runner:error', error as Error);\n }\n if (this.running) {\n this.pollTimer = setTimeout(poll, this.config.pollInterval);\n if (typeof this.pollTimer.unref === 'function') {\n this.pollTimer.unref();\n }\n }\n };\n poll();\n }\n\n async poll(): Promise<void> {\n if (!this.db) return;\n const result = await this.db.query(\n `SELECT * FROM ${REPORT_SCHEDULES_TABLE}\n WHERE enabled = true\n AND status = 'active'\n AND next_run <= ?\n AND COALESCE(running_count, 0) < COALESCE(max_concurrent, 1)\n ORDER BY next_run ASC\n LIMIT ?`,\n new Date().toISOString(),\n this.config.batchSize,\n );\n\n for (const row of result.rows) {\n await this.triggerSchedule(row as ReportScheduleRow);\n }\n }\n\n private async triggerSchedule(row: ReportScheduleRow): Promise<void> {\n if (!this.db) return;\n const schedule: ReportScheduleInfo = {\n id: String(row.id),\n reportClass: String(row.report_class),\n tenantId:\n typeof row.tenant_id === 'string' && row.tenant_id.length > 0\n ? row.tenant_id\n : null,\n cron: String(row.cron),\n mode: (row.mode as ReportRefreshMode) || 'incremental',\n };\n\n try {\n const nextRun = getNextCronDate(schedule.cron);\n await enqueueReportRefresh({\n db: this.db,\n reportClass: schedule.reportClass,\n mode: schedule.mode,\n trigger: (row.trigger as ReportRefreshTrigger) || 'schedule',\n tenantId: schedule.tenantId,\n scheduleId: schedule.id,\n queue: String(row.queue || 'reports'),\n priority: Number(row.priority ?? 70),\n timeout: Number(row.timeout ?? 3600000),\n integritySigner: this.config.integritySigner,\n });\n await this.db.query(\n `UPDATE ${REPORT_SCHEDULES_TABLE}\n SET running_count = COALESCE(running_count, 0) + 1,\n next_run = ?,\n updated_at = ?\n WHERE id = ?`,\n nextRun.toISOString(),\n new Date().toISOString(),\n schedule.id,\n );\n this.emit('schedule:triggered', schedule);\n } catch (error) {\n await this.db.query(\n `UPDATE ${REPORT_SCHEDULES_TABLE}\n SET last_error = ?,\n updated_at = ?\n WHERE id = ?`,\n error instanceof Error ? error.message : String(error),\n new Date().toISOString(),\n schedule.id,\n );\n this.emit('schedule:error', schedule, error as Error);\n }\n }\n}\n\ninterface ReportScheduleRow {\n id: unknown;\n tenant_id: unknown;\n report_class: unknown;\n cron: unknown;\n trigger: unknown;\n mode: unknown;\n queue: unknown;\n priority: unknown;\n timeout: unknown;\n}\n\nexport function registerReportRefreshInterceptor(\n options: ReportRefreshInterceptorOptions,\n): () => boolean {\n const name = options.name ?? 'smrt-reports-refresh';\n GlobalInterceptors.register({\n name,\n priority: -10,\n async afterSave(instance, context) {\n await triggerReportsForInstance(options, instance, context);\n },\n async afterDelete(instance, context) {\n await triggerReportsForInstance(options, instance, context);\n },\n });\n return () => GlobalInterceptors.unregister(name);\n}\n\nasync function triggerReportsForInstance(\n options: ReportRefreshInterceptorOptions,\n instance: SmrtObject,\n context: InterceptorContext,\n): Promise<void> {\n for (const reportCtor of options.reports) {\n const definition = await buildReportDefinition(reportCtor);\n if (definition.refresh?.manual) continue;\n if (!sourceMatches(definition, instance, context)) continue;\n\n const mode = definition.refresh?.mode ?? 'incremental';\n const tenantId = tenantIdFromInstance(instance);\n const changedRows = [changedRowSnapshot(instance)];\n if (options.enqueue === false) {\n await refreshReport(reportCtor, {\n db: options.db,\n mode,\n trigger: 'change',\n tenantId,\n changedRows,\n });\n continue;\n }\n\n await enqueueReportRefresh({\n db: options.db,\n reportClass: canonicalClassName(reportCtor),\n mode,\n trigger: 'change',\n tenantId,\n queue: options.queue,\n priority: options.priority,\n timeout: options.timeout,\n tenantJobCap: options.tenantJobCap,\n changedRows,\n integritySigner: options.integritySigner,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"scheduler.js","names":["tenantId"],"sources":["../src/scheduler.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { EventEmitter } from 'node:events';\nimport {\n field,\n GlobalInterceptors,\n type InterceptorContext,\n ObjectRegistry,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n backgroundEligible,\n type DurableJobPayloadIntegrity,\n type DurableJobPayloadSigner,\n getActiveJobExecutionContext,\n getNextCronDate,\n isRunnerExecutionContext,\n type JobExecutionContext,\n type SmrtJob,\n SmrtJobCollection,\n validateCronExpression,\n} from '@happyvertical/smrt-jobs';\nimport {\n getTenantId,\n TenantScoped,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface, SqlAdapterType } from '@happyvertical/sql';\nimport { buildReportDefinition } from './compiler.js';\nimport { refreshReport } from './refresh.js';\nimport {\n assertReportTablesReady,\n REPORT_SCHEDULER_TABLES,\n REPORT_SCHEDULES_TABLE,\n scopeKeyForTenant,\n} from './state.js';\nimport type {\n ReportDefinition,\n ReportRefreshMode,\n ReportRefreshTrigger,\n ReportSource,\n} from './types.js';\n\ntype ReportCtor = new (...args: any[]) => SmrtObject;\n\nexport interface ReportRefreshJobArgs {\n reportClass?: string;\n mode?: ReportRefreshMode;\n trigger?: ReportRefreshTrigger;\n tenantId?: string | null;\n tenantIds?: string[];\n scheduleId?: string;\n adapterType?: SqlAdapterType;\n changedRows?: Record<string, unknown>[];\n _scheduleId?: string;\n executionAuthority?: ReportRefreshExecutionAuthority;\n integrity?: DurableJobPayloadIntegrity;\n}\n\nexport interface ReportExecutionPrincipalReference {\n version: 1;\n actorUserId: string;\n tenantId: string | null;\n onBehalfOfUserId?: string | null;\n actsAsProfileId?: string | null;\n agentClass?: string | null;\n}\n\nexport interface ReportRefreshExecutionAuthority {\n version: 1;\n hostId: string;\n principal: ReportExecutionPrincipalReference;\n}\n\nexport interface ReportRefreshExecutionAuthorityContext {\n phase: 'execute';\n reportClass: string;\n mode: ReportRefreshMode;\n trigger: ReportRefreshTrigger;\n tenantId: string | null;\n}\n\nexport interface ReportRefreshExecutionAuditEvent\n extends ReportRefreshExecutionAuthorityContext {\n outcome: 'allowed' | 'denied';\n principal: ReportExecutionPrincipalReference;\n reason?: string;\n}\n\nexport interface ReportRefreshExecutionAuthorityHost {\n authorize(\n principal: Readonly<ReportExecutionPrincipalReference>,\n context: Readonly<ReportRefreshExecutionAuthorityContext>,\n ): Promise<void> | void;\n audit(\n event: Readonly<ReportRefreshExecutionAuditEvent>,\n ): Promise<void> | void;\n}\n\nexport interface EnqueueReportRefreshOptions extends ReportRefreshJobArgs {\n report?: ReportCtor;\n reportClass: string;\n db: DatabaseInterface;\n queue?: string;\n priority?: number;\n timeout?: number;\n maxAttempts?: number;\n tenantJobCap?: number;\n /** Server-only signer; register the same key in every worker process. */\n integritySigner?: DurableJobPayloadSigner;\n}\n\nexport interface EnsureReportSchedulesOptions {\n db: DatabaseInterface;\n reports: ReportCtor[];\n tenantIds?: string[];\n queue?: string;\n priority?: number;\n timeout?: number;\n}\n\nexport interface ReportScheduleRunnerConfig {\n id?: string;\n pollInterval?: number;\n batchSize?: number;\n integritySigner?: DurableJobPayloadSigner;\n}\n\nexport interface ReportScheduleInfo {\n id: string;\n reportClass: string;\n tenantId: string | null;\n cron: string;\n mode: ReportRefreshMode;\n}\n\nexport interface ReportScheduleRunnerEvents {\n 'schedule:triggered': (schedule: ReportScheduleInfo) => void;\n 'schedule:error': (schedule: ReportScheduleInfo, error: Error) => void;\n 'schedule:completed': (scheduleId: string) => void;\n 'schedule:failed': (scheduleId: string, error: string) => void;\n 'runner:started': () => void;\n 'runner:stopped': () => void;\n 'runner:error': (error: Error) => void;\n}\n\nexport interface ReportRefreshInterceptorOptions {\n db: DatabaseInterface;\n reports: ReportCtor[];\n enqueue?: boolean;\n queue?: string;\n priority?: number;\n timeout?: number;\n tenantJobCap?: number;\n name?: string;\n integritySigner?: DurableJobPayloadSigner;\n}\n\nconst INTERNAL_SURFACE = {\n api: false,\n cli: {\n include: ['list', 'get'],\n skipApiCheck: true,\n http: false,\n },\n mcp: false,\n};\n\nconst executionAuthorityHosts = new Map<\n string,\n ReportRefreshExecutionAuthorityHost\n>();\nconst jobIntegritySigners = new Map<string, DurableJobPayloadSigner>();\n\nexport function registerReportRefreshJobIntegritySigner(\n signer: DurableJobPayloadSigner,\n): () => void {\n const existing = jobIntegritySigners.get(signer.keyId);\n if (existing && existing !== signer) {\n throw new Error(\n `Report refresh job integrity signer already registered: ${signer.keyId}`,\n );\n }\n jobIntegritySigners.set(signer.keyId, signer);\n return () => {\n if (jobIntegritySigners.get(signer.keyId) === signer) {\n jobIntegritySigners.delete(signer.keyId);\n }\n };\n}\n\n/**\n * Maintenance callers historically configured only their runner/interceptor;\n * they do not have a request host from which to obtain a signer. When one\n * worker-shared signer is registered, use it as the application default. More\n * than one key is ambiguous and remains fail-closed until the caller selects\n * one explicitly.\n */\nfunction resolveReportRefreshIntegritySigner(\n configured: DurableJobPayloadSigner | undefined,\n): DurableJobPayloadSigner | undefined {\n if (configured) return configured;\n if (jobIntegritySigners.size !== 1) return undefined;\n return jobIntegritySigners.values().next().value;\n}\n\nfunction unsignedReportRefreshJobArgs(\n args: ReportRefreshJobArgs,\n): Omit<ReportRefreshJobArgs, 'integrity'> {\n const {\n integrity: _integrity,\n _scheduleId: _internalScheduleId,\n ...unsigned\n } = args;\n return unsigned;\n}\n\nfunction assertReportRefreshJobIntegrity(args: ReportRefreshJobArgs): void {\n const integrity = args.integrity;\n const signer = integrity\n ? jobIntegritySigners.get(integrity.keyId)\n : undefined;\n if (\n !integrity ||\n !signer?.verify(unsignedReportRefreshJobArgs(args), integrity)\n ) {\n throw new Error('Invalid durable report refresh job integrity binding');\n }\n}\n\nfunction assertReportRefreshJobTarget(\n args: ReportRefreshJobArgs,\n context: JobExecutionContext | undefined,\n): void {\n if (!context) return;\n if (!isRunnerExecutionContext(context)) {\n throw new Error('Invalid durable report refresh job context');\n }\n const expectedType = canonicalClassName(\n (args.trigger ?? 'job') === 'manual'\n ? SmrtPrincipalReportRefreshTask\n : SmrtReportRefreshTask,\n );\n if (context.job.objectType !== expectedType || context.job.method !== 'run') {\n throw new Error('Invalid durable report refresh job target');\n }\n}\n\nexport function registerReportRefreshExecutionAuthorityHost(\n hostId: string,\n host: ReportRefreshExecutionAuthorityHost,\n): () => void {\n if (!hostId || hostId.length > 256) {\n throw new Error(\n 'Report refresh authority hostId must contain 1-256 characters',\n );\n }\n const existing = executionAuthorityHosts.get(hostId);\n if (existing && existing !== host) {\n throw new Error(\n `Report refresh authority host already registered: ${hostId}`,\n );\n }\n executionAuthorityHosts.set(hostId, host);\n return () => {\n if (executionAuthorityHosts.get(hostId) === host) {\n executionAuthorityHosts.delete(hostId);\n }\n };\n}\n\nfunction stableUuid(values: unknown[]): string {\n const hash = createHash('sha256')\n .update(JSON.stringify(values))\n .digest('hex');\n const variant = ((Number.parseInt(hash[16], 16) & 0x3) | 0x8).toString(16);\n return [\n hash.slice(0, 8),\n hash.slice(8, 12),\n `4${hash.slice(13, 16)}`,\n `${variant}${hash.slice(17, 20)}`,\n hash.slice(20, 32),\n ].join('-');\n}\n\nfunction canonicalClassName(reportCtor: ReportCtor): string {\n const registered =\n ObjectRegistry.getClassByConstructor(reportCtor) ??\n ObjectRegistry.getClass(reportCtor.name);\n return registered?.qualifiedName ?? registered?.name ?? reportCtor.name;\n}\n\nfunction resolveReportClass(name: string): ReportCtor {\n const registered =\n ObjectRegistry.getClassByQualifiedName(name) ??\n ObjectRegistry.getClass(name);\n if (!registered) {\n throw new Error(`Unknown report class: ${name}`);\n }\n return registered.constructor as unknown as ReportCtor;\n}\n\nfunction reportSourceName(source: ReportSource): string {\n if (typeof source === 'string') return source;\n return source.name;\n}\n\nfunction sourceMatches(\n definition: ReportDefinition,\n instance: SmrtObject,\n context: InterceptorContext,\n): boolean {\n const configured = definition.refresh?.onChange;\n if (!configured || configured.length === 0) return false;\n\n const eventNames = new Set<string>([\n context.className,\n instance.constructor.name,\n ]);\n const registered = ObjectRegistry.getClassByConstructor(\n instance.constructor as ReportCtor,\n );\n if (registered?.qualifiedName) eventNames.add(registered.qualifiedName);\n if (registered?.name) eventNames.add(registered.name);\n\n for (const source of configured) {\n const name = reportSourceName(source);\n const registeredSource =\n ObjectRegistry.getClassByQualifiedName(name) ??\n ObjectRegistry.getClass(name);\n if (\n eventNames.has(name) ||\n (registeredSource?.name && eventNames.has(registeredSource.name)) ||\n (registeredSource?.qualifiedName &&\n eventNames.has(registeredSource.qualifiedName))\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction tenantIdFromInstance(instance: SmrtObject): string | null {\n const value = (instance as unknown as { tenantId?: unknown }).tenantId;\n return typeof value === 'string' && value.length > 0\n ? value\n : (getTenantId() ?? null);\n}\n\nfunction changedRowSnapshot(instance: SmrtObject): Record<string, unknown> {\n const serializable = instance.toJSON();\n return serializable && typeof serializable === 'object'\n ? (serializable as Record<string, unknown>)\n : {};\n}\n\nasync function authorizeReportRefreshExecution(\n args: ReportRefreshJobArgs,\n reportClass: string,\n jobTenantId: string | null,\n): Promise<void> {\n if ((args.tenantId ?? null) !== jobTenantId) {\n throw new Error('Invalid report refresh execution tenant');\n }\n const authority = args.executionAuthority;\n if ((args.trigger ?? 'job') === 'manual' && !authority) {\n throw new Error('Manual report refresh execution authority is missing');\n }\n // Scheduled and on-change maintenance jobs predate user-bound actions and\n // intentionally run under the worker's system authority.\n if (!authority) return;\n const principal = authority.principal;\n const tenantId = args.tenantId ?? null;\n if (\n authority.version !== 1 ||\n principal?.version !== 1 ||\n !authority.hostId ||\n !principal.actorUserId ||\n principal.tenantId !== tenantId ||\n principal.tenantId !== jobTenantId\n ) {\n throw new Error('Invalid report refresh execution authority');\n }\n const host = executionAuthorityHosts.get(authority.hostId);\n if (!host) {\n throw new Error(\n `No report refresh authority host registered for ${authority.hostId}`,\n );\n }\n const context: ReportRefreshExecutionAuthorityContext = {\n phase: 'execute',\n reportClass,\n mode: args.mode ?? 'incremental',\n trigger: args.trigger ?? 'job',\n tenantId,\n };\n try {\n await host.authorize(\n Object.freeze({ ...principal }),\n Object.freeze(context),\n );\n } catch {\n await host.audit({\n ...context,\n outcome: 'denied',\n principal: Object.freeze({ ...principal }),\n reason: 'current_authority_denied',\n });\n throw new Error('Report refresh execution authority denied');\n }\n await host.audit({\n ...context,\n outcome: 'allowed',\n principal: Object.freeze({ ...principal }),\n });\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_report_refresh_tasks',\n ...INTERNAL_SURFACE,\n})\nexport class SmrtReportRefreshTask extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-reports\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ type: 'text', required: true })\n reportClass: string = '';\n\n @field({ type: 'text', required: true })\n mode: ReportRefreshMode = 'incremental';\n\n @field({ type: 'text', required: true })\n trigger: ReportRefreshTrigger = 'job';\n\n @field({ type: 'json' })\n args: ReportRefreshJobArgs = {};\n\n @backgroundEligible()\n async run(\n args: ReportRefreshJobArgs = {},\n context?: JobExecutionContext,\n ): Promise<unknown> {\n assertReportRefreshJobIntegrity(args);\n const executionContext = getActiveJobExecutionContext() ?? context;\n assertReportRefreshJobTarget(args, executionContext);\n const reportClass = args.reportClass;\n if (!reportClass) {\n throw new Error('Report refresh job requires reportClass');\n }\n const mode = args.mode ?? 'incremental';\n const trigger = args.trigger ?? 'job';\n\n const reportCtor = resolveReportClass(reportClass);\n // A runner context, including its explicit global `null` tenant, owns the\n // scope. Only direct callers without runner context may use instance scope.\n let jobTenantId: string | null;\n if (executionContext) {\n const runnerTenantId = executionContext.job.tenantId;\n if (runnerTenantId === null) {\n jobTenantId = null;\n } else if (\n typeof runnerTenantId === 'string' &&\n runnerTenantId.length > 0\n ) {\n jobTenantId = runnerTenantId;\n } else {\n throw new Error('Invalid report refresh execution tenant');\n }\n } else {\n jobTenantId = tenantIdFromInstance(this);\n }\n await authorizeReportRefreshExecution(args, reportClass, jobTenantId);\n return refreshReport(reportCtor, {\n db: this.db,\n mode,\n trigger,\n tenantId: jobTenantId,\n tenantIds: args.tenantIds,\n adapterType: args.adapterType,\n scheduleId: args.scheduleId ?? args._scheduleId,\n changedRows: args.changedRows,\n });\n }\n}\n\n/** Worker target whose authority requirement cannot be downgraded by job args. */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_principal_report_refresh_tasks',\n ...INTERNAL_SURFACE,\n})\nexport class SmrtPrincipalReportRefreshTask extends SmrtReportRefreshTask { static __smrtPackage__ = \"@happyvertical/smrt-reports\";\n override async run(\n args: ReportRefreshJobArgs = {},\n context?: JobExecutionContext,\n ): Promise<unknown> {\n return super.run({ ...args, trigger: 'manual' }, context);\n }\n}\n\nexport async function enqueueReportRefresh(\n options: EnqueueReportRefreshOptions,\n): Promise<SmrtJob> {\n const integritySigner = resolveReportRefreshIntegritySigner(\n options.integritySigner,\n );\n if (!integritySigner) {\n throw new Error(\n 'Report refresh queue requires a durable job integrity signer',\n );\n }\n if (options.trigger === 'manual' && !options.executionAuthority) {\n throw new Error('Manual report refresh requires execution-time authority');\n }\n if (\n options.executionAuthority &&\n (options.executionAuthority.version !== 1 ||\n options.executionAuthority.principal?.version !== 1 ||\n !options.executionAuthority.hostId ||\n !options.executionAuthority.principal.actorUserId ||\n options.executionAuthority.principal.tenantId !==\n (options.tenantId ?? null))\n ) {\n throw new Error('Invalid report refresh execution authority');\n }\n if (\n options.executionAuthority &&\n (options.trigger !== 'manual' || (options.tenantIds?.length ?? 0) > 0)\n ) {\n throw new Error(\n 'Principal-bound report refresh requires one manual tenant scope',\n );\n }\n await ObjectRegistry.ensureManifestLoaded('SmrtJob');\n const collection = await SmrtJobCollection.create({ db: options.db });\n const taskType = canonicalClassName(\n options.trigger === 'manual'\n ? SmrtPrincipalReportRefreshTask\n : SmrtReportRefreshTask,\n );\n const scheduleId = options.scheduleId ?? options._scheduleId;\n\n const unsignedArgs: Omit<ReportRefreshJobArgs, 'integrity'> = {\n reportClass: options.reportClass,\n mode: options.mode ?? 'incremental',\n trigger: options.trigger ?? 'job',\n tenantId: options.tenantId,\n tenantIds: options.tenantIds,\n adapterType: options.adapterType,\n changedRows: options.changedRows,\n scheduleId,\n executionAuthority: options.executionAuthority,\n };\n const integrity = integritySigner.sign(unsignedArgs);\n if (!integritySigner.verify(unsignedArgs, integrity)) {\n throw new Error(\n 'Report refresh job integrity signer rejected its queued payload',\n );\n }\n return collection.enqueueJob(\n {\n tenantId: options.tenantId ?? null,\n queue: options.queue ?? 'reports',\n objectType: taskType,\n objectId: null,\n method: 'run',\n args: { ...unsignedArgs, _scheduleId: scheduleId, integrity },\n priority: options.priority ?? 70,\n timeout: options.timeout ?? 3600000,\n maxAttempts: options.maxAttempts ?? 3,\n },\n { tenantJobCap: options.tenantJobCap },\n );\n}\n\nexport async function ensureReportRefreshSchedules(\n options: EnsureReportSchedulesOptions,\n): Promise<void> {\n await assertReportTablesReady(options.db, REPORT_SCHEDULER_TABLES);\n\n for (const reportCtor of options.reports) {\n const definition = await buildReportDefinition(reportCtor);\n const refresh = definition.refresh;\n if (!refresh || refresh.manual) continue;\n\n const reportClass = canonicalClassName(reportCtor);\n const targetTenants = refresh.tenantFanout\n ? options.tenantIds\n : [null as string | null];\n if (\n refresh.tenantFanout &&\n (!targetTenants || targetTenants.length === 0)\n ) {\n throw new Error(\n `${definition.reportClassName} refresh.tenantFanout requires tenantIds when creating schedules.`,\n );\n }\n\n const schedules = [\n refresh.schedule\n ? {\n cron: refresh.schedule,\n mode: refresh.mode ?? 'incremental',\n trigger: 'schedule' as const,\n }\n : null,\n refresh.fullRebuildSchedule\n ? {\n cron: refresh.fullRebuildSchedule,\n mode: 'rebuild' as const,\n trigger: 'schedule' as const,\n }\n : null,\n ].filter(Boolean) as Array<{\n cron: string;\n mode: ReportRefreshMode;\n trigger: ReportRefreshTrigger;\n }>;\n\n for (const schedule of schedules) {\n validateCronExpression(schedule.cron);\n for (const tenantId of targetTenants ?? []) {\n const scopeKey = scopeKeyForTenant(tenantId);\n const id = stableUuid([\n 'schedule',\n reportClass,\n scopeKey,\n schedule.cron,\n schedule.mode,\n ]);\n const now = new Date().toISOString();\n await options.db.upsert(\n REPORT_SCHEDULES_TABLE,\n ['report_class', 'scope_key', 'cron', 'mode'],\n {\n id,\n slug: id,\n context: scopeKey,\n tenant_id: tenantId,\n scope_key: scopeKey,\n report_class: reportClass,\n cron: schedule.cron,\n trigger: schedule.trigger,\n mode: schedule.mode,\n enabled: true,\n status: 'active',\n next_run: getNextCronDate(schedule.cron).toISOString(),\n last_run: null,\n last_status: null,\n last_error: null,\n run_count: 0,\n success_count: 0,\n failure_count: 0,\n running_count: 0,\n max_concurrent: 1,\n queue: options.queue ?? 'reports',\n priority: options.priority ?? 70,\n timeout: options.timeout ?? 3600000,\n created_at: now,\n updated_at: now,\n },\n );\n }\n }\n }\n}\n\nexport class ReportScheduleRunner extends EventEmitter {\n readonly id: string;\n private readonly config: Required<\n Omit<ReportScheduleRunnerConfig, 'integritySigner'>\n > &\n Pick<ReportScheduleRunnerConfig, 'integritySigner'>;\n private db: DatabaseInterface | null = null;\n private running = false;\n private pollTimer: NodeJS.Timeout | null = null;\n\n constructor(config: ReportScheduleRunnerConfig = {}) {\n super();\n this.config = {\n id: config.id || `reports_${stableUuid([Date.now()]).slice(0, 8)}`,\n pollInterval: config.pollInterval ?? 60000,\n batchSize: config.batchSize ?? 50,\n integritySigner: config.integritySigner,\n };\n this.id = this.config.id;\n }\n\n async initialize(db: DatabaseInterface): Promise<void> {\n this.db = db;\n await assertReportTablesReady(db, REPORT_SCHEDULER_TABLES);\n }\n\n async start(): Promise<void> {\n if (this.running) return;\n if (!this.db) {\n throw new Error(\n 'ReportScheduleRunner not initialized. Call initialize() first.',\n );\n }\n this.running = true;\n this.startPolling();\n this.emit('runner:started');\n }\n\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n this.emit('runner:stopped');\n }\n\n isRunning(): boolean {\n return this.running;\n }\n\n async handleJobCompletion(\n scheduleId: string,\n success: boolean,\n errorMessage?: string,\n ): Promise<void> {\n if (!this.db) return;\n const now = new Date().toISOString();\n if (success) {\n await this.db.query(\n `UPDATE ${REPORT_SCHEDULES_TABLE}\n SET running_count = CASE WHEN COALESCE(running_count, 0) > 0 THEN running_count - 1 ELSE 0 END,\n last_run = ?,\n last_status = 'success',\n last_error = NULL,\n run_count = COALESCE(run_count, 0) + 1,\n success_count = COALESCE(success_count, 0) + 1,\n updated_at = ?\n WHERE id = ?`,\n now,\n now,\n scheduleId,\n );\n this.emit('schedule:completed', scheduleId);\n return;\n }\n\n const safeError = errorMessage ?? 'Unknown error';\n await this.db.query(\n `UPDATE ${REPORT_SCHEDULES_TABLE}\n SET running_count = CASE WHEN COALESCE(running_count, 0) > 0 THEN running_count - 1 ELSE 0 END,\n last_run = ?,\n last_status = 'failed',\n last_error = ?,\n run_count = COALESCE(run_count, 0) + 1,\n failure_count = COALESCE(failure_count, 0) + 1,\n updated_at = ?\n WHERE id = ?`,\n now,\n safeError,\n now,\n scheduleId,\n );\n this.emit('schedule:failed', scheduleId, safeError);\n }\n\n private startPolling(): void {\n const poll = async () => {\n if (!this.running) return;\n try {\n await this.poll();\n } catch (error) {\n this.emit('runner:error', error as Error);\n }\n if (this.running) {\n this.pollTimer = setTimeout(poll, this.config.pollInterval);\n if (typeof this.pollTimer.unref === 'function') {\n this.pollTimer.unref();\n }\n }\n };\n poll();\n }\n\n async poll(): Promise<void> {\n if (!this.db) return;\n const result = await this.db.query(\n `SELECT * FROM ${REPORT_SCHEDULES_TABLE}\n WHERE enabled = true\n AND status = 'active'\n AND next_run <= ?\n AND COALESCE(running_count, 0) < COALESCE(max_concurrent, 1)\n ORDER BY next_run ASC\n LIMIT ?`,\n new Date().toISOString(),\n this.config.batchSize,\n );\n\n for (const row of result.rows) {\n await this.triggerSchedule(row as ReportScheduleRow);\n }\n }\n\n private async triggerSchedule(row: ReportScheduleRow): Promise<void> {\n if (!this.db) return;\n const schedule: ReportScheduleInfo = {\n id: String(row.id),\n reportClass: String(row.report_class),\n tenantId:\n typeof row.tenant_id === 'string' && row.tenant_id.length > 0\n ? row.tenant_id\n : null,\n cron: String(row.cron),\n mode: (row.mode as ReportRefreshMode) || 'incremental',\n };\n\n try {\n const nextRun = getNextCronDate(schedule.cron);\n await enqueueReportRefresh({\n db: this.db,\n reportClass: schedule.reportClass,\n mode: schedule.mode,\n trigger: (row.trigger as ReportRefreshTrigger) || 'schedule',\n tenantId: schedule.tenantId,\n scheduleId: schedule.id,\n queue: String(row.queue || 'reports'),\n priority: Number(row.priority ?? 70),\n timeout: Number(row.timeout ?? 3600000),\n integritySigner: this.config.integritySigner,\n });\n await this.db.query(\n `UPDATE ${REPORT_SCHEDULES_TABLE}\n SET running_count = COALESCE(running_count, 0) + 1,\n next_run = ?,\n updated_at = ?\n WHERE id = ?`,\n nextRun.toISOString(),\n new Date().toISOString(),\n schedule.id,\n );\n this.emit('schedule:triggered', schedule);\n } catch (error) {\n await this.db.query(\n `UPDATE ${REPORT_SCHEDULES_TABLE}\n SET last_error = ?,\n updated_at = ?\n WHERE id = ?`,\n error instanceof Error ? error.message : String(error),\n new Date().toISOString(),\n schedule.id,\n );\n this.emit('schedule:error', schedule, error as Error);\n }\n }\n}\n\ninterface ReportScheduleRow {\n id: unknown;\n tenant_id: unknown;\n report_class: unknown;\n cron: unknown;\n trigger: unknown;\n mode: unknown;\n queue: unknown;\n priority: unknown;\n timeout: unknown;\n}\n\nexport function registerReportRefreshInterceptor(\n options: ReportRefreshInterceptorOptions,\n): () => boolean {\n const name = options.name ?? 'smrt-reports-refresh';\n GlobalInterceptors.register({\n name,\n priority: -10,\n async afterSave(instance, context) {\n await triggerReportsForInstance(options, instance, context);\n },\n async afterDelete(instance, context) {\n await triggerReportsForInstance(options, instance, context);\n },\n });\n return () => GlobalInterceptors.unregister(name);\n}\n\nasync function triggerReportsForInstance(\n options: ReportRefreshInterceptorOptions,\n instance: SmrtObject,\n context: InterceptorContext,\n): Promise<void> {\n for (const reportCtor of options.reports) {\n const definition = await buildReportDefinition(reportCtor);\n if (definition.refresh?.manual) continue;\n if (!sourceMatches(definition, instance, context)) continue;\n\n const mode = definition.refresh?.mode ?? 'incremental';\n const tenantId = tenantIdFromInstance(instance);\n const changedRows = [changedRowSnapshot(instance)];\n if (options.enqueue === false) {\n await refreshReport(reportCtor, {\n db: options.db,\n mode,\n trigger: 'change',\n tenantId,\n changedRows,\n });\n continue;\n }\n\n await enqueueReportRefresh({\n db: options.db,\n reportClass: canonicalClassName(reportCtor),\n mode,\n trigger: 'change',\n tenantId,\n queue: options.queue,\n priority: options.priority,\n timeout: options.timeout,\n tenantJobCap: options.tenantJobCap,\n changedRows,\n integritySigner: options.integritySigner,\n });\n }\n}\n"],"mappings":"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"}
@@ -3,12 +3,12 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-reports",
6
- "packageVersion": "0.54.2",
6
+ "packageVersion": "0.54.3",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "1a276fd0b098e44ed2ec5a3e7bf6ade50a027bdc96ef407a5c9b7a90eb086d5f",
11
- "packageJson": "c919d7d17589ddd02f23f189de2bf7bea19e0bfc9c4336b1a36b17ee601031e9",
10
+ "manifest": "704a04378fdeb678d7747865fb31e3bd9d998a8937086d2e0dc4787fff469b67",
11
+ "packageJson": "5364c7f780b58b0a8cedc1ba0bb0e4c8e31d2fdbe085120b047cb21f17c5e484",
12
12
  "agents": "c4cb7fa4c5b144ba48f382d3ecbf74b76dd1361e843a7f2747e7e035719910da"
13
13
  },
14
14
  "exports": [
package/dist/state.js CHANGED
@@ -3,12 +3,19 @@ import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
3
3
  //#region src/state.ts
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
7
+ enumerable: true,
8
+ configurable: true,
9
+ writable: true,
10
+ value
11
+ }) : obj[key] = value;
6
12
  var __decorateClass = (decorators, target, key, kind) => {
7
13
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
8
14
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
9
15
  if (kind && result) __defProp(target, key, result);
10
16
  return result;
11
17
  };
18
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
12
19
  var REPORT_RUNS_TABLE = "_smrt_report_runs";
13
20
  var REPORT_WATERMARKS_TABLE = "_smrt_report_watermarks";
14
21
  var REPORT_LOCKS_TABLE = "_smrt_report_locks";
@@ -52,6 +59,7 @@ var SmrtReportRun = class extends SmrtObject {
52
59
  error = null;
53
60
  metadata = {};
54
61
  };
62
+ __publicField(SmrtReportRun, "__smrtPackage__", "@happyvertical/smrt-reports");
55
63
  __decorateClass([tenantId({ nullable: true })], SmrtReportRun.prototype, "tenantId", 2);
56
64
  __decorateClass([field({
57
65
  type: "text",
@@ -122,6 +130,7 @@ var SmrtReportWatermark = class extends SmrtObject {
122
130
  watermarkValue = null;
123
131
  lastRunId = null;
124
132
  };
133
+ __publicField(SmrtReportWatermark, "__smrtPackage__", "@happyvertical/smrt-reports");
125
134
  __decorateClass([tenantId({ nullable: true })], SmrtReportWatermark.prototype, "tenantId", 2);
126
135
  __decorateClass([field({
127
136
  type: "text",
@@ -169,6 +178,7 @@ var SmrtReportLock = class extends SmrtObject {
169
178
  heartbeatAt = null;
170
179
  expiresAt = null;
171
180
  };
181
+ __publicField(SmrtReportLock, "__smrtPackage__", "@happyvertical/smrt-reports");
172
182
  __decorateClass([tenantId({ nullable: true })], SmrtReportLock.prototype, "tenantId", 2);
173
183
  __decorateClass([field({
174
184
  type: "text",
@@ -224,6 +234,7 @@ var SmrtReportSchedule = class extends SmrtObject {
224
234
  priority = 70;
225
235
  timeout = 36e5;
226
236
  };
237
+ __publicField(SmrtReportSchedule, "__smrtPackage__", "@happyvertical/smrt-reports");
227
238
  __decorateClass([tenantId({ nullable: true })], SmrtReportSchedule.prototype, "tenantId", 2);
228
239
  __decorateClass([field({
229
240
  type: "text",
package/dist/state.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","names":["tenantId"],"sources":["../src/state.ts"],"sourcesContent":["import {\n field,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport type { ReportRefreshMode, ReportRefreshTrigger } from './types.js';\n\nexport const REPORT_RUNS_TABLE = '_smrt_report_runs';\nexport const REPORT_WATERMARKS_TABLE = '_smrt_report_watermarks';\nexport const REPORT_LOCKS_TABLE = '_smrt_report_locks';\nexport const REPORT_SCHEDULES_TABLE = '_smrt_report_schedules';\nexport const REPORT_REFRESH_TASKS_TABLE = '_smrt_report_refresh_tasks';\n\nexport const REPORT_RUNTIME_TABLES = [\n REPORT_RUNS_TABLE,\n REPORT_WATERMARKS_TABLE,\n REPORT_LOCKS_TABLE,\n] as const;\n\nexport const REPORT_SCHEDULER_TABLES = [\n REPORT_SCHEDULES_TABLE,\n REPORT_REFRESH_TASKS_TABLE,\n] as const;\n\nexport type ReportRunStatus = 'running' | 'success' | 'failed' | 'skipped';\nexport type ReportScheduleStatus = 'active' | 'paused' | 'disabled' | 'error';\n\nexport function scopeKeyForTenant(tenantId: string | null | undefined): string {\n return tenantId ? `tenant:${tenantId}` : 'global';\n}\n\nexport async function assertReportTablesReady(\n db: DatabaseInterface,\n tables: readonly string[] = REPORT_RUNTIME_TABLES,\n): Promise<void> {\n for (const table of tables) {\n const ready = await db.tableExists(table);\n if (!ready) {\n throw new Error(\n `Report runtime table '${table}' does not exist. ` +\n 'Run smrt db:migrate before refreshing reports.',\n );\n }\n }\n}\n\nconst INTERNAL_SURFACE = {\n api: false,\n cli: {\n include: ['list', 'get'],\n skipApiCheck: true,\n http: false,\n },\n mcp: false,\n};\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_report_runs',\n ...INTERNAL_SURFACE,\n})\nexport class SmrtReportRun extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ type: 'text', required: true })\n scopeKey: string = 'global';\n\n @field({ type: 'text', required: true })\n reportClass: string = '';\n\n @field({ type: 'text', required: true })\n sourceClass: string = '';\n\n @field({ type: 'text', required: true })\n mode: ReportRefreshMode = 'rebuild';\n\n @field({ type: 'text', required: true })\n trigger: ReportRefreshTrigger = 'manual';\n\n @field({ type: 'text', required: true })\n status: ReportRunStatus = 'running';\n\n @field({ type: 'datetime', required: true })\n startedAt: Date = new Date();\n\n @field({ type: 'datetime', nullable: true })\n completedAt: Date | null = null;\n\n @field({ type: 'integer', required: true })\n rowCount: number = 0;\n\n @field({ type: 'integer', required: true })\n changedGroupCount: number = 0;\n\n @field({ type: 'text', nullable: true })\n watermarkBefore: string | null = null;\n\n @field({ type: 'text', nullable: true })\n watermarkAfter: string | null = null;\n\n @field({ type: 'text', nullable: true })\n error: string | null = null;\n\n @field({ type: 'json' })\n metadata: Record<string, unknown> = {};\n}\n\nexport class SmrtReportRunCollection extends SmrtCollection<SmrtReportRun> {\n static readonly _itemClass = SmrtReportRun;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_report_watermarks',\n conflictColumns: [\n 'report_class',\n 'scope_key',\n 'source_class',\n 'watermark_column',\n ],\n ...INTERNAL_SURFACE,\n})\nexport class SmrtReportWatermark extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ type: 'text', required: true })\n scopeKey: string = 'global';\n\n @field({ type: 'text', required: true })\n reportClass: string = '';\n\n @field({ type: 'text', required: true })\n sourceClass: string = '';\n\n @field({ type: 'text', required: true })\n watermarkColumn: string = '';\n\n @field({ type: 'text', nullable: true })\n watermarkValue: string | null = null;\n\n @field({ type: 'text', nullable: true })\n lastRunId: string | null = null;\n}\n\nexport class SmrtReportWatermarkCollection extends SmrtCollection<SmrtReportWatermark> {\n static readonly _itemClass = SmrtReportWatermark;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_report_locks',\n conflictColumns: ['report_class', 'scope_key'],\n ...INTERNAL_SURFACE,\n})\nexport class SmrtReportLock extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ type: 'text', required: true })\n scopeKey: string = 'global';\n\n @field({ type: 'text', required: true })\n reportClass: string = '';\n\n @field({ type: 'text', nullable: true })\n ownerId: string | null = null;\n\n @field({ type: 'datetime', nullable: true })\n acquiredAt: Date | null = null;\n\n @field({ type: 'datetime', nullable: true })\n heartbeatAt: Date | null = null;\n\n @field({ type: 'datetime', nullable: true })\n expiresAt: Date | null = null;\n}\n\nexport class SmrtReportLockCollection extends SmrtCollection<SmrtReportLock> {\n static readonly _itemClass = SmrtReportLock;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_report_schedules',\n conflictColumns: ['report_class', 'scope_key', 'cron', 'mode'],\n ...INTERNAL_SURFACE,\n})\nexport class SmrtReportSchedule extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ type: 'text', required: true })\n scopeKey: string = 'global';\n\n @field({ type: 'text', required: true })\n reportClass: string = '';\n\n @field({ type: 'text', required: true })\n cron: string = '';\n\n @field({ type: 'text', required: true })\n trigger: ReportRefreshTrigger = 'schedule';\n\n @field({ type: 'text', required: true })\n mode: ReportRefreshMode = 'incremental';\n\n @field({ type: 'boolean', required: true })\n enabled: boolean = true;\n\n @field({ type: 'text', required: true })\n status: ReportScheduleStatus = 'active';\n\n @field({ type: 'datetime', nullable: true })\n nextRun: Date | null = null;\n\n @field({ type: 'datetime', nullable: true })\n lastRun: Date | null = null;\n\n @field({ type: 'text', nullable: true })\n lastStatus: 'success' | 'failed' | null = null;\n\n @field({ type: 'text', nullable: true })\n lastError: string | null = null;\n\n @field({ type: 'integer', required: true })\n runCount: number = 0;\n\n @field({ type: 'integer', required: true })\n successCount: number = 0;\n\n @field({ type: 'integer', required: true })\n failureCount: number = 0;\n\n @field({ type: 'integer', required: true })\n runningCount: number = 0;\n\n @field({ type: 'integer', required: true })\n maxConcurrent: number = 1;\n\n @field({ type: 'text', required: true })\n queue: string = 'reports';\n\n @field({ type: 'integer', required: true })\n priority: number = 70;\n\n @field({ type: 'integer', required: true })\n timeout: number = 3600000;\n}\n\nexport class SmrtReportScheduleCollection extends SmrtCollection<SmrtReportSchedule> {\n static readonly _itemClass = SmrtReportSchedule;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"state.js","names":["tenantId"],"sources":["../src/state.ts"],"sourcesContent":["import {\n field,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport type { ReportRefreshMode, ReportRefreshTrigger } from './types.js';\n\nexport const REPORT_RUNS_TABLE = '_smrt_report_runs';\nexport const REPORT_WATERMARKS_TABLE = '_smrt_report_watermarks';\nexport const REPORT_LOCKS_TABLE = '_smrt_report_locks';\nexport const REPORT_SCHEDULES_TABLE = '_smrt_report_schedules';\nexport const REPORT_REFRESH_TASKS_TABLE = '_smrt_report_refresh_tasks';\n\nexport const REPORT_RUNTIME_TABLES = [\n REPORT_RUNS_TABLE,\n REPORT_WATERMARKS_TABLE,\n REPORT_LOCKS_TABLE,\n] as const;\n\nexport const REPORT_SCHEDULER_TABLES = [\n REPORT_SCHEDULES_TABLE,\n REPORT_REFRESH_TASKS_TABLE,\n] as const;\n\nexport type ReportRunStatus = 'running' | 'success' | 'failed' | 'skipped';\nexport type ReportScheduleStatus = 'active' | 'paused' | 'disabled' | 'error';\n\nexport function scopeKeyForTenant(tenantId: string | null | undefined): string {\n return tenantId ? `tenant:${tenantId}` : 'global';\n}\n\nexport async function assertReportTablesReady(\n db: DatabaseInterface,\n tables: readonly string[] = REPORT_RUNTIME_TABLES,\n): Promise<void> {\n for (const table of tables) {\n const ready = await db.tableExists(table);\n if (!ready) {\n throw new Error(\n `Report runtime table '${table}' does not exist. ` +\n 'Run smrt db:migrate before refreshing reports.',\n );\n }\n }\n}\n\nconst INTERNAL_SURFACE = {\n api: false,\n cli: {\n include: ['list', 'get'],\n skipApiCheck: true,\n http: false,\n },\n mcp: false,\n};\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_report_runs',\n ...INTERNAL_SURFACE,\n})\nexport class SmrtReportRun extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-reports\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ type: 'text', required: true })\n scopeKey: string = 'global';\n\n @field({ type: 'text', required: true })\n reportClass: string = '';\n\n @field({ type: 'text', required: true })\n sourceClass: string = '';\n\n @field({ type: 'text', required: true })\n mode: ReportRefreshMode = 'rebuild';\n\n @field({ type: 'text', required: true })\n trigger: ReportRefreshTrigger = 'manual';\n\n @field({ type: 'text', required: true })\n status: ReportRunStatus = 'running';\n\n @field({ type: 'datetime', required: true })\n startedAt: Date = new Date();\n\n @field({ type: 'datetime', nullable: true })\n completedAt: Date | null = null;\n\n @field({ type: 'integer', required: true })\n rowCount: number = 0;\n\n @field({ type: 'integer', required: true })\n changedGroupCount: number = 0;\n\n @field({ type: 'text', nullable: true })\n watermarkBefore: string | null = null;\n\n @field({ type: 'text', nullable: true })\n watermarkAfter: string | null = null;\n\n @field({ type: 'text', nullable: true })\n error: string | null = null;\n\n @field({ type: 'json' })\n metadata: Record<string, unknown> = {};\n}\n\nexport class SmrtReportRunCollection extends SmrtCollection<SmrtReportRun> {\n static readonly _itemClass = SmrtReportRun;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_report_watermarks',\n conflictColumns: [\n 'report_class',\n 'scope_key',\n 'source_class',\n 'watermark_column',\n ],\n ...INTERNAL_SURFACE,\n})\nexport class SmrtReportWatermark extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-reports\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ type: 'text', required: true })\n scopeKey: string = 'global';\n\n @field({ type: 'text', required: true })\n reportClass: string = '';\n\n @field({ type: 'text', required: true })\n sourceClass: string = '';\n\n @field({ type: 'text', required: true })\n watermarkColumn: string = '';\n\n @field({ type: 'text', nullable: true })\n watermarkValue: string | null = null;\n\n @field({ type: 'text', nullable: true })\n lastRunId: string | null = null;\n}\n\nexport class SmrtReportWatermarkCollection extends SmrtCollection<SmrtReportWatermark> {\n static readonly _itemClass = SmrtReportWatermark;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_report_locks',\n conflictColumns: ['report_class', 'scope_key'],\n ...INTERNAL_SURFACE,\n})\nexport class SmrtReportLock extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-reports\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ type: 'text', required: true })\n scopeKey: string = 'global';\n\n @field({ type: 'text', required: true })\n reportClass: string = '';\n\n @field({ type: 'text', nullable: true })\n ownerId: string | null = null;\n\n @field({ type: 'datetime', nullable: true })\n acquiredAt: Date | null = null;\n\n @field({ type: 'datetime', nullable: true })\n heartbeatAt: Date | null = null;\n\n @field({ type: 'datetime', nullable: true })\n expiresAt: Date | null = null;\n}\n\nexport class SmrtReportLockCollection extends SmrtCollection<SmrtReportLock> {\n static readonly _itemClass = SmrtReportLock;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: '_smrt_report_schedules',\n conflictColumns: ['report_class', 'scope_key', 'cron', 'mode'],\n ...INTERNAL_SURFACE,\n})\nexport class SmrtReportSchedule extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-reports\";\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @field({ type: 'text', required: true })\n scopeKey: string = 'global';\n\n @field({ type: 'text', required: true })\n reportClass: string = '';\n\n @field({ type: 'text', required: true })\n cron: string = '';\n\n @field({ type: 'text', required: true })\n trigger: ReportRefreshTrigger = 'schedule';\n\n @field({ type: 'text', required: true })\n mode: ReportRefreshMode = 'incremental';\n\n @field({ type: 'boolean', required: true })\n enabled: boolean = true;\n\n @field({ type: 'text', required: true })\n status: ReportScheduleStatus = 'active';\n\n @field({ type: 'datetime', nullable: true })\n nextRun: Date | null = null;\n\n @field({ type: 'datetime', nullable: true })\n lastRun: Date | null = null;\n\n @field({ type: 'text', nullable: true })\n lastStatus: 'success' | 'failed' | null = null;\n\n @field({ type: 'text', nullable: true })\n lastError: string | null = null;\n\n @field({ type: 'integer', required: true })\n runCount: number = 0;\n\n @field({ type: 'integer', required: true })\n successCount: number = 0;\n\n @field({ type: 'integer', required: true })\n failureCount: number = 0;\n\n @field({ type: 'integer', required: true })\n runningCount: number = 0;\n\n @field({ type: 'integer', required: true })\n maxConcurrent: number = 1;\n\n @field({ type: 'text', required: true })\n queue: string = 'reports';\n\n @field({ type: 'integer', required: true })\n priority: number = 70;\n\n @field({ type: 'integer', required: true })\n timeout: number = 3600000;\n}\n\nexport class SmrtReportScheduleCollection extends SmrtCollection<SmrtReportSchedule> {\n static readonly _itemClass = SmrtReportSchedule;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-reports",
3
- "version": "0.54.2",
3
+ "version": "0.54.3",
4
4
  "description": "Materialized aggregate report models for SMRT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -43,14 +43,14 @@
43
43
  "./manifest.json": "./dist/manifest.json"
44
44
  },
45
45
  "dependencies": {
46
- "@happyvertical/smrt-core": "0.54.2",
47
- "@happyvertical/smrt-jobs": "0.54.2",
48
- "@happyvertical/smrt-types": "0.54.2",
49
- "@happyvertical/smrt-tenancy": "0.54.2",
50
- "@happyvertical/sql": "^0.100.3"
46
+ "@happyvertical/smrt-core": "0.54.3",
47
+ "@happyvertical/smrt-jobs": "0.54.3",
48
+ "@happyvertical/smrt-types": "0.54.3",
49
+ "@happyvertical/smrt-tenancy": "0.54.3",
50
+ "@happyvertical/sql": "^0.102.1"
51
51
  },
52
52
  "devDependencies": {
53
- "@happyvertical/smrt-vitest": "0.54.2",
53
+ "@happyvertical/smrt-vitest": "0.54.3",
54
54
  "@types/node": "24.13.6",
55
55
  "typescript": "6.0.3",
56
56
  "vite": "8.1.4",