@happyvertical/smrt-reports 0.57.31 → 0.57.33
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/AGENTS.md +1 -0
- package/README.md +64 -3
- package/dist/adapter.d.ts +18 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/chunks/compiler-IUB2YNgM.js +341 -0
- package/dist/chunks/compiler-IUB2YNgM.js.map +1 -0
- package/dist/compiler.d.ts +2 -0
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +2 -171
- package/dist/decorators.d.ts +22 -22
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +16 -8
- package/dist/decorators.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -20
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/refresh.d.ts +0 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +6 -18
- package/dist/refresh.js.map +1 -1
- package/dist/registry-columns.d.ts +14 -0
- package/dist/registry-columns.d.ts.map +1 -0
- package/dist/runtime-compiler.d.ts.map +1 -1
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +2 -2
- package/dist/scheduler.js.map +1 -1
- package/dist/smrt-knowledge.json +7 -7
- package/dist/state.js +1 -1
- package/dist/state.js.map +1 -1
- package/dist/types.d.ts +64 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/views.d.ts.map +1 -1
- package/package.json +9 -9
- package/dist/compiler.js.map +0 -1
package/dist/manifest.json
CHANGED
package/dist/refresh.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { type DatabaseInterface, type SqlAdapterType } from '@happyvertical/sql'
|
|
|
3
3
|
import type { ReportDefinition, ReportRefreshOptions, ReportRefreshResult, ReportSource } from './types.js';
|
|
4
4
|
type ReportCtor = new (...args: any[]) => SmrtObject;
|
|
5
5
|
export declare function adapterTypeFromDb(db: DatabaseInterface, fallback?: SqlAdapterType): SqlAdapterType;
|
|
6
|
-
export declare function tenantColumn(className: string): Promise<string | null>;
|
|
7
6
|
export declare function refreshReport(reportCtor: ReportCtor, options?: ReportRefreshOptions): Promise<ReportRefreshResult>;
|
|
8
7
|
export declare function reportRowIdentity(row: Record<string, any>, definition: ReportDefinition): string;
|
|
9
8
|
export declare function reportSourceClassName(source: ReportSource): string;
|
package/dist/refresh.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh.d.ts","sourceRoot":"","sources":["../src/refresh.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAG3E,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAGpB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"refresh.d.ts","sourceRoot":"","sources":["../src/refresh.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAG3E,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAGpB,MAAM,oBAAoB,CAAC;AAc5B,OAAO,KAAK,EAGV,gBAAgB,EAEhB,oBAAoB,EACpB,mBAAmB,EACnB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,KAAK,UAAU,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,CAAC;AAoCrD,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,iBAAiB,EACrB,QAAQ,CAAC,EAAE,cAAc,GACxB,cAAc,CAgBhB;AAm7BD,wBAAsB,aAAa,CACjC,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CA8B9B;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACxB,UAAU,EAAE,gBAAgB,GAC3B,MAAM,CAKR;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAElE"}
|
package/dist/refresh.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildReportDefinition,
|
|
1
|
+
import { a as getReportGroupingColumns, c as tenantColumn, n as buildReportDefinition, o as fieldColumn, r as compileReportDefinition } from "./chunks/compiler-IUB2YNgM.js";
|
|
2
2
|
import { REPORT_LOCKS_TABLE, REPORT_RUNS_TABLE, REPORT_WATERMARKS_TABLE, assertReportTablesReady, scopeKeyForTenant } from "./state.js";
|
|
3
3
|
import { ObjectRegistry } from "@happyvertical/smrt-core";
|
|
4
4
|
import { toSnakeCase } from "@happyvertical/smrt-core/utils";
|
|
@@ -67,19 +67,6 @@ function sourceClassName(source) {
|
|
|
67
67
|
if (typeof source === "string") return source;
|
|
68
68
|
return source.name;
|
|
69
69
|
}
|
|
70
|
-
async function fieldColumn(className, requested) {
|
|
71
|
-
const fields = await ObjectRegistry.getAllFields(className);
|
|
72
|
-
const requestedColumn = toSnakeCase(requested);
|
|
73
|
-
for (const [fieldName] of fields.entries()) if (fieldName === requested || toSnakeCase(fieldName) === requestedColumn) return requestedColumn;
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
async function tenantColumn(className) {
|
|
77
|
-
const configuredField = ObjectRegistry.getClass(className)?.tenantScopedConfig?.field;
|
|
78
|
-
if (configuredField) return toSnakeCase(configuredField);
|
|
79
|
-
const fields = await ObjectRegistry.getAllFields(className);
|
|
80
|
-
for (const [fieldName, field] of fields.entries()) if (field?._meta?.__tenancy?.isTenantIdField) return toSnakeCase(fieldName);
|
|
81
|
-
return await fieldColumn(className, "tenantId");
|
|
82
|
-
}
|
|
83
70
|
async function resolveScope(definition, options) {
|
|
84
71
|
const tenantId = options.tenantId !== void 0 ? options.tenantId : getTenantId() ?? null;
|
|
85
72
|
const sourceTenantColumn = await tenantColumn(definition.sourceClassName);
|
|
@@ -116,12 +103,13 @@ function andHaving(base, extra) {
|
|
|
116
103
|
};
|
|
117
104
|
}
|
|
118
105
|
function withRefreshFilters(spec, scope, extraWhere = {}) {
|
|
106
|
+
const qualify = (conditions) => spec.fromAlias ? Object.fromEntries(Object.entries(conditions).map(([key, value]) => [`${spec.fromAlias}.${key}`, value])) : conditions;
|
|
119
107
|
return {
|
|
120
108
|
...spec,
|
|
121
|
-
where: andWhere(spec.where, {
|
|
109
|
+
where: andWhere(spec.where, qualify({
|
|
122
110
|
...tenantWhere(scope),
|
|
123
111
|
...extraWhere
|
|
124
|
-
})
|
|
112
|
+
}))
|
|
125
113
|
};
|
|
126
114
|
}
|
|
127
115
|
async function resolveWatermarkConfig(definition) {
|
|
@@ -455,7 +443,7 @@ async function refreshReportOnce(reportCtor, options) {
|
|
|
455
443
|
if (!options.db) throw new Error("refreshReport requires a database handle");
|
|
456
444
|
const definition = await buildReportDefinition(reportCtor);
|
|
457
445
|
const reportTable = getReportTableName(reportCtor);
|
|
458
|
-
const requestedMode = options.mode ?? definition.refresh?.mode ?? "rebuild";
|
|
446
|
+
const requestedMode = definition.joins?.length ? "rebuild" : options.mode ?? definition.refresh?.mode ?? "rebuild";
|
|
459
447
|
if (!await tableExists(options.db, reportTable)) throw new Error(`Report table '${reportTable}' does not exist for ${reportCtor.name}. Run smrt db:migrate before refreshing reports.`);
|
|
460
448
|
const requiredTables = requiredReportRuntimeTables(requestedMode, options);
|
|
461
449
|
if (requiredTables.length > 0) await assertReportTablesReady(options.db, requiredTables);
|
|
@@ -529,6 +517,6 @@ function reportSourceClassName(source) {
|
|
|
529
517
|
return sourceClassName(source);
|
|
530
518
|
}
|
|
531
519
|
//#endregion
|
|
532
|
-
export { adapterTypeFromDb, refreshReport, reportRowIdentity, reportSourceClassName
|
|
520
|
+
export { adapterTypeFromDb, refreshReport, reportRowIdentity, reportSourceClassName };
|
|
533
521
|
|
|
534
522
|
//# sourceMappingURL=refresh.js.map
|
package/dist/refresh.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh.js","names":["runId"],"sources":["../src/refresh.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { ObjectRegistry, type SmrtObject } from '@happyvertical/smrt-core';\nimport { toSnakeCase } from '@happyvertical/smrt-core/utils';\nimport { getTenantId, withTenant } from '@happyvertical/smrt-tenancy';\nimport {\n buildAggregate,\n buildWhere,\n type DatabaseInterface,\n type SqlAdapterType,\n tableExists,\n validateColumnName,\n} from '@happyvertical/sql';\nimport {\n buildReportDefinition,\n compileReportDefinition,\n getReportGroupingColumns,\n} from './compiler.js';\nimport {\n assertReportTablesReady,\n REPORT_LOCKS_TABLE,\n REPORT_RUNS_TABLE,\n REPORT_WATERMARKS_TABLE,\n scopeKeyForTenant,\n} from './state.js';\nimport type {\n AggregateSelectExpr,\n AggregateSpec,\n ReportDefinition,\n ReportRefreshMode,\n ReportRefreshOptions,\n ReportRefreshResult,\n ReportSource,\n} from './types.js';\n\ntype ReportCtor = new (...args: any[]) => SmrtObject;\n\ninterface RefreshScope {\n tenantId: string | null;\n scopeKey: string;\n sourceTenantColumn: string | null;\n reportTenantColumn: string | null;\n}\n\ninterface WatermarkConfig {\n watermarkColumn: string;\n softDeleteColumn: string;\n}\n\ninterface RefreshWorkResult {\n rowCount: number;\n changedGroupCount: number;\n watermarkBefore?: string | null;\n watermarkAfter?: string | null;\n mode: ReportRefreshMode;\n}\n\ninterface ReportLockHandle {\n ownerId: string;\n release(): Promise<void>;\n}\n\nfunction isSqlAdapterType(value: unknown): value is SqlAdapterType {\n return (\n value === 'sqlite' ||\n value === 'postgres' ||\n value === 'duckdb' ||\n value === 'json'\n );\n}\n\nexport function adapterTypeFromDb(\n db: DatabaseInterface,\n fallback?: SqlAdapterType,\n): SqlAdapterType {\n if (isSqlAdapterType(fallback)) return fallback;\n\n const url = String(db.url ?? '');\n if (url.startsWith('postgres://') || url.startsWith('postgresql://')) {\n return 'postgres';\n }\n if (url.endsWith('.duckdb')) {\n return 'duckdb';\n }\n if (url === ':memory:' || url.endsWith('.sqlite') || url.endsWith('.db')) {\n return 'sqlite';\n }\n throw new Error(\n 'Report refresh requires a database adapter type. Pass { adapterType }.',\n );\n}\n\nfunction stableReportId(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 getReportTableName(reportCtor: ReportCtor): string {\n const registered =\n ObjectRegistry.getClassByConstructor(reportCtor) ??\n ObjectRegistry.getClass(reportCtor.name);\n const tableName = registered\n ? ObjectRegistry.getTableName(registered.qualifiedName ?? registered.name)\n : ObjectRegistry.getTableName(reportCtor.name);\n if (!tableName) {\n throw new Error(`No report table registered for ${reportCtor.name}`);\n }\n return tableName;\n}\n\nfunction getActualGroupingColumns(\n definition: Pick<ReportDefinition, 'fields'>,\n): string[] {\n return definition.fields\n .filter(\n (field) =>\n field.report?.kind === 'group' || field.report?.kind === 'bucket',\n )\n .map((field) => field.columnName ?? toSnakeCase(field.fieldName));\n}\n\nfunction materializeRows(\n rows: Record<string, any>[],\n definition: ReportDefinition,\n refreshedAt: Date,\n scope: RefreshScope,\n): Record<string, any>[] {\n const groupingColumns = getActualGroupingColumns(definition);\n const now = refreshedAt.toISOString();\n\n return rows.map((row, index) => {\n const groupingValues =\n groupingColumns.length === 0\n ? [definition.reportClassName, scope.scopeKey, index]\n : [\n definition.reportClassName,\n scope.scopeKey,\n ...groupingColumns.map((column) => row[column]),\n ];\n const id = stableReportId(groupingValues);\n const tenantFields = scope.reportTenantColumn\n ? { [scope.reportTenantColumn]: scope.tenantId }\n : {};\n\n return {\n id,\n slug: id,\n context: scope.scopeKey,\n ...tenantFields,\n ...row,\n refreshed_at: now,\n created_at: now,\n updated_at: now,\n };\n });\n}\n\nfunction sourceClassName(source: ReportSource): string {\n if (typeof source === 'string') return source;\n return source.name;\n}\n\nasync function fieldColumn(\n className: string,\n requested: string,\n): Promise<string | null> {\n const fields = await ObjectRegistry.getAllFields(className);\n const requestedColumn = toSnakeCase(requested);\n\n for (const [fieldName] of fields.entries()) {\n if (fieldName === requested || toSnakeCase(fieldName) === requestedColumn) {\n return requestedColumn;\n }\n }\n\n return null;\n}\n\nexport async function tenantColumn(className: string): Promise<string | null> {\n const registered = ObjectRegistry.getClass(className);\n const configuredField = registered?.tenantScopedConfig?.field;\n if (configuredField) {\n return toSnakeCase(configuredField);\n }\n\n const fields = await ObjectRegistry.getAllFields(className);\n for (const [fieldName, field] of fields.entries()) {\n if (field?._meta?.__tenancy?.isTenantIdField) {\n return toSnakeCase(fieldName);\n }\n }\n\n const tenantIdField = await fieldColumn(className, 'tenantId');\n return tenantIdField;\n}\n\nasync function resolveScope(\n definition: ReportDefinition,\n options: ReportRefreshOptions,\n): Promise<RefreshScope> {\n const tenantId =\n options.tenantId !== undefined ? options.tenantId : (getTenantId() ?? null);\n const sourceTenantColumn = await tenantColumn(definition.sourceClassName);\n const reportTenantColumn = await tenantColumn(definition.reportClassName);\n\n if (tenantId && !sourceTenantColumn) {\n throw new Error(\n `Tenant-scoped refresh for ${definition.reportClassName} requires source ` +\n `${definition.sourceClassName} to expose a tenantId field.`,\n );\n }\n if (tenantId && !reportTenantColumn) {\n throw new Error(\n `Tenant-scoped refresh for ${definition.reportClassName} requires the report table to expose a tenantId field.`,\n );\n }\n\n return {\n tenantId,\n scopeKey: scopeKeyForTenant(tenantId),\n sourceTenantColumn,\n reportTenantColumn,\n };\n}\n\nfunction tenantWhere(scope: RefreshScope): Record<string, unknown> {\n if (!scope.sourceTenantColumn) return {};\n return { [scope.sourceTenantColumn]: scope.tenantId };\n}\n\nfunction andWhere(\n base: AggregateSpec['where'],\n extra: Record<string, unknown>,\n): AggregateSpec['where'] {\n if (Object.keys(extra).length === 0) return base;\n if (!base) return extra;\n if (Array.isArray(base)) {\n return base.map((andGroup) => [...andGroup, extra]);\n }\n return { ...base, ...extra };\n}\n\nfunction andHaving(\n base: AggregateSpec['having'],\n extra: Record<string, unknown>,\n): AggregateSpec['having'] {\n if (Object.keys(extra).length === 0) return base;\n if (!base) return extra;\n if (Array.isArray(base)) {\n return base.map((andGroup) => [...andGroup, extra]);\n }\n return { ...base, ...extra };\n}\n\nfunction withRefreshFilters(\n spec: AggregateSpec,\n scope: RefreshScope,\n extraWhere: Record<string, unknown> = {},\n): AggregateSpec {\n return {\n ...spec,\n where: andWhere(spec.where, {\n ...tenantWhere(scope),\n ...extraWhere,\n }),\n };\n}\n\nasync function resolveWatermarkConfig(\n definition: ReportDefinition,\n): Promise<WatermarkConfig> {\n const configured = definition.refresh ?? {};\n const watermarkField = configured.watermarkColumn ?? 'updatedAt';\n const softDeleteField = configured.softDeleteColumn ?? 'deletedAt';\n const watermarkColumn = await fieldColumn(\n definition.sourceClassName,\n watermarkField,\n );\n const softDeleteColumn = await fieldColumn(\n definition.sourceClassName,\n softDeleteField,\n );\n\n if (!watermarkColumn) {\n throw new Error(\n `Incremental report ${definition.reportClassName} requires source ` +\n `${definition.sourceClassName} to define watermark column '${watermarkField}'.`,\n );\n }\n if (!softDeleteColumn) {\n throw new Error(\n `Incremental report ${definition.reportClassName} requires source ` +\n `${definition.sourceClassName} to define soft-delete column '${softDeleteField}'.`,\n );\n }\n\n return { watermarkColumn, softDeleteColumn };\n}\n\nfunction compileChangedGroupSpec(\n definition: ReportDefinition,\n scope: RefreshScope,\n watermark: WatermarkConfig,\n watermarkBefore: string,\n): AggregateSpec {\n const spec = compileReportDefinition(definition);\n const select = spec.select.filter(\n (expr): expr is AggregateSelectExpr => !('fn' in expr),\n );\n\n return withRefreshFilters(\n {\n ...spec,\n // Ignore the report filter while finding changed groups so rows that\n // just stopped matching `report.where` still trigger a recompute/delete.\n where: undefined,\n select:\n select.length > 0\n ? select\n : [{ fn: 'count', as: '__smrt_changed_count' }],\n groupBy: select.length > 0 ? spec.groupBy : [],\n having: undefined,\n },\n scope,\n { [`${watermark.watermarkColumn} >`]: watermarkBefore },\n );\n}\n\nfunction compileAffectedGroupSpec(\n definition: ReportDefinition,\n scope: RefreshScope,\n watermark: WatermarkConfig,\n groupRow: Record<string, unknown>,\n): AggregateSpec {\n const groupingColumns = getActualGroupingColumns(definition);\n const groupHaving = Object.fromEntries(\n groupingColumns.map((column) => [column, groupRow[column]]),\n );\n const spec = withRefreshFilters(compileReportDefinition(definition), scope, {\n [watermark.softDeleteColumn]: null,\n });\n return {\n ...spec,\n having: andHaving(spec.having, groupHaving),\n };\n}\n\nfunction conflictColumns(\n definition: ReportDefinition,\n scope: RefreshScope,\n): string[] {\n const groupingColumns = getActualGroupingColumns(definition);\n if (groupingColumns.length === 0) return ['id'];\n return [\n ...(scope.reportTenantColumn ? [scope.reportTenantColumn] : []),\n ...groupingColumns,\n ];\n}\n\nfunction predicateSql(\n tableName: string,\n predicate: Record<string, unknown>,\n): { sql: string; values: unknown[] } {\n validateColumnName(tableName);\n const clauses: string[] = [];\n const values: unknown[] = [];\n\n for (const [column, value] of Object.entries(predicate)) {\n validateColumnName(column);\n if (value === null || value === undefined) {\n clauses.push(`${column} IS NULL`);\n continue;\n }\n clauses.push(`${column} = ?`);\n values.push(value);\n }\n\n return {\n sql: clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : '',\n values,\n };\n}\n\nfunction dedupeGroupRows(\n definition: ReportDefinition,\n rows: Array<Record<string, unknown> | null>,\n): Record<string, unknown>[] {\n const groupingColumns = getActualGroupingColumns(definition);\n const groups = new Map<string, Record<string, unknown>>();\n\n for (const row of rows) {\n if (!row) continue;\n const groupRow =\n groupingColumns.length === 0\n ? {}\n : Object.fromEntries(\n groupingColumns.map((column) => [column, row[column] ?? null]),\n );\n const key =\n groupingColumns.length === 0\n ? '__smrt_global_group__'\n : JSON.stringify(groupingColumns.map((column) => groupRow[column]));\n if (!groups.has(key)) {\n groups.set(key, groupRow);\n }\n }\n\n return [...groups.values()];\n}\n\nasync function readMaterializedGroups(\n db: DatabaseInterface,\n tableName: string,\n definition: ReportDefinition,\n scope: RefreshScope,\n): Promise<Record<string, unknown>[]> {\n const groupingColumns = getActualGroupingColumns(definition);\n if (groupingColumns.length === 0) {\n return [{}];\n }\n\n const where = scope.reportTenantColumn\n ? predicateSql(tableName, { [scope.reportTenantColumn]: scope.tenantId })\n : { sql: '', values: [] as unknown[] };\n const columns = groupingColumns.map((column) => validateColumnName(column));\n const result = await db.query(\n `SELECT ${columns.join(', ')} FROM ${validateColumnName(tableName)} ${where.sql}`,\n ...where.values,\n );\n\n return result.rows\n .map((row) => parseMaybeJson<Record<string, unknown>>(row))\n .filter((row): row is Record<string, unknown> => Boolean(row));\n}\n\nasync function deleteMaterializedGroup(\n db: DatabaseInterface,\n tableName: string,\n definition: ReportDefinition,\n scope: RefreshScope,\n groupRow: Record<string, unknown>,\n): Promise<void> {\n const predicate: Record<string, unknown> = {};\n if (scope.reportTenantColumn) {\n predicate[scope.reportTenantColumn] = scope.tenantId;\n }\n for (const column of getActualGroupingColumns(definition)) {\n predicate[column] = groupRow[column];\n }\n\n const where = predicateSql(tableName, predicate);\n await db.query(\n `DELETE FROM ${validateColumnName(tableName)} ${where.sql}`,\n ...where.values,\n );\n}\n\nasync function replaceRows(\n db: DatabaseInterface,\n tableName: string,\n rows: Record<string, any>[],\n scope: RefreshScope,\n): Promise<void> {\n validateColumnName(tableName);\n const run = async (tx: DatabaseInterface) => {\n if (scope.reportTenantColumn) {\n const where = predicateSql(tableName, {\n [scope.reportTenantColumn]: scope.tenantId,\n });\n await tx.query(`DELETE FROM ${tableName} ${where.sql}`, ...where.values);\n } else {\n await tx.query(`DELETE FROM ${tableName}`);\n }\n if (rows.length > 0) {\n await tx.insert(tableName, rows);\n }\n };\n\n if (db.transaction) {\n await db.transaction(run);\n return;\n }\n\n await run(db);\n}\n\nfunction parseMaybeJson<T>(value: T | string | null | undefined): T | null {\n if (value == null) return null;\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value) as T;\n } catch {\n return value as T;\n }\n}\n\nfunction watermarkValue(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (value instanceof Date) return value.toISOString();\n return String(value);\n}\n\nasync function readWatermark(\n db: DatabaseInterface,\n definition: ReportDefinition,\n scope: RefreshScope,\n watermarkColumn: string,\n): Promise<string | null> {\n const result = await db.query(\n `SELECT watermark_value FROM ${REPORT_WATERMARKS_TABLE}\n WHERE report_class = ?\n AND scope_key = ?\n AND source_class = ?\n AND watermark_column = ?\n LIMIT 1`,\n definition.reportClassName,\n scope.scopeKey,\n definition.sourceClassName,\n watermarkColumn,\n );\n const row = result.rows[0] as { watermark_value?: unknown } | undefined;\n return watermarkValue(row?.watermark_value);\n}\n\nasync function writeWatermark(\n db: DatabaseInterface,\n definition: ReportDefinition,\n scope: RefreshScope,\n watermarkColumn: string,\n value: string | null,\n runId: string | undefined,\n): Promise<void> {\n if (value === null) return;\n const id = stableReportId([\n 'watermark',\n definition.reportClassName,\n scope.scopeKey,\n definition.sourceClassName,\n watermarkColumn,\n ]);\n const now = new Date().toISOString();\n await db.upsert(\n REPORT_WATERMARKS_TABLE,\n ['report_class', 'scope_key', 'source_class', 'watermark_column'],\n {\n id,\n slug: id,\n context: scope.scopeKey,\n tenant_id: scope.tenantId,\n scope_key: scope.scopeKey,\n report_class: definition.reportClassName,\n source_class: definition.sourceClassName,\n watermark_column: watermarkColumn,\n watermark_value: value,\n last_run_id: runId ?? null,\n created_at: now,\n updated_at: now,\n },\n );\n}\n\nasync function maxWatermark(\n db: DatabaseInterface,\n definition: ReportDefinition,\n scope: RefreshScope,\n watermarkColumn: string,\n adapterType: SqlAdapterType,\n): Promise<string | null> {\n const where = andWhere(undefined, tenantWhere(scope));\n const builtWhere = where\n ? buildWhere(where, 1, adapterType)\n : { sql: '', values: [] };\n const result = await db.query(\n `SELECT MAX(${validateColumnName(watermarkColumn)}) AS watermark FROM ${validateColumnName(definition.sourceTable)} ${builtWhere.sql}`,\n ...builtWhere.values,\n );\n const row = result.rows[0] as { watermark?: unknown } | undefined;\n return watermarkValue(row?.watermark);\n}\n\nasync function startRun(\n db: DatabaseInterface,\n definition: ReportDefinition,\n scope: RefreshScope,\n options: ReportRefreshOptions,\n mode: ReportRefreshMode,\n watermarkBefore?: string | null,\n): Promise<string | undefined> {\n if (options.trackRuns === false) return undefined;\n const id = randomUUID();\n const now = new Date().toISOString();\n await db.insert(REPORT_RUNS_TABLE, {\n id,\n slug: id,\n context: scope.scopeKey,\n tenant_id: scope.tenantId,\n scope_key: scope.scopeKey,\n report_class: definition.reportClassName,\n source_class: definition.sourceClassName,\n mode,\n trigger: options.trigger ?? 'manual',\n status: 'running',\n started_at: now,\n row_count: 0,\n changed_group_count: 0,\n watermark_before: watermarkBefore ?? null,\n watermark_after: null,\n error: null,\n metadata: {\n scheduleId: options.scheduleId,\n },\n created_at: now,\n updated_at: now,\n });\n return id;\n}\n\nasync function completeRun(\n db: DatabaseInterface,\n runId: string | undefined,\n status: 'success' | 'failed' | 'skipped',\n data: {\n rowCount?: number;\n changedGroupCount?: number;\n watermarkAfter?: string | null;\n error?: unknown;\n } = {},\n): Promise<void> {\n if (!runId) return;\n const error =\n data.error instanceof Error\n ? data.error.message\n : data.error\n ? String(data.error)\n : null;\n await db.update(\n REPORT_RUNS_TABLE,\n { id: runId },\n {\n status,\n completed_at: new Date().toISOString(),\n row_count: data.rowCount ?? 0,\n changed_group_count: data.changedGroupCount ?? 0,\n watermark_after: data.watermarkAfter ?? null,\n error,\n updated_at: new Date().toISOString(),\n },\n );\n}\n\nasync function acquireLock(\n db: DatabaseInterface,\n definition: ReportDefinition,\n scope: RefreshScope,\n ttlMs: number,\n): Promise<ReportLockHandle | null> {\n const now = new Date();\n const nowIso = now.toISOString();\n const ownerId = randomUUID();\n const expiresAt = new Date(now.getTime() + ttlMs);\n const expiresAtIso = expiresAt.toISOString();\n const id = stableReportId([\n 'lock',\n definition.reportClassName,\n scope.scopeKey,\n ]);\n await db.query(\n `INSERT INTO ${REPORT_LOCKS_TABLE} (\n id,\n slug,\n context,\n tenant_id,\n scope_key,\n report_class,\n owner_id,\n acquired_at,\n heartbeat_at,\n expires_at,\n created_at,\n updated_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(report_class, scope_key) DO UPDATE SET\n tenant_id = excluded.tenant_id,\n owner_id = excluded.owner_id,\n acquired_at = excluded.acquired_at,\n heartbeat_at = excluded.heartbeat_at,\n expires_at = excluded.expires_at,\n updated_at = excluded.updated_at\n WHERE ${REPORT_LOCKS_TABLE}.owner_id IS NULL\n OR ${REPORT_LOCKS_TABLE}.expires_at IS NULL\n OR ${REPORT_LOCKS_TABLE}.expires_at <= ?`,\n id,\n id,\n scope.scopeKey,\n scope.tenantId,\n scope.scopeKey,\n definition.reportClassName,\n ownerId,\n nowIso,\n nowIso,\n expiresAtIso,\n nowIso,\n nowIso,\n nowIso,\n );\n\n const claimed = await db.query(\n `SELECT owner_id FROM ${REPORT_LOCKS_TABLE}\n WHERE report_class = ? AND scope_key = ?\n LIMIT 1`,\n definition.reportClassName,\n scope.scopeKey,\n );\n const row = claimed.rows[0] as { owner_id?: unknown } | undefined;\n if (row?.owner_id !== ownerId) {\n return null;\n }\n\n return {\n ownerId,\n async release() {\n await db.query(\n `UPDATE ${REPORT_LOCKS_TABLE}\n SET owner_id = NULL,\n heartbeat_at = NULL,\n expires_at = NULL,\n updated_at = ?\n WHERE report_class = ?\n AND scope_key = ?\n AND owner_id = ?`,\n new Date().toISOString(),\n definition.reportClassName,\n scope.scopeKey,\n ownerId,\n );\n },\n };\n}\n\nasync function rebuildReport(\n db: DatabaseInterface,\n definition: ReportDefinition,\n reportTable: string,\n scope: RefreshScope,\n adapterType: SqlAdapterType,\n runId: string | undefined,\n watermark?: WatermarkConfig | null,\n): Promise<RefreshWorkResult> {\n const explicitWatermarkColumn =\n definition.refresh?.watermarkColumn &&\n (await fieldColumn(\n definition.sourceClassName,\n definition.refresh.watermarkColumn,\n ));\n const explicitSoftDeleteColumn =\n definition.refresh?.softDeleteColumn &&\n (await fieldColumn(\n definition.sourceClassName,\n definition.refresh.softDeleteColumn,\n ));\n const watermarkColumn =\n watermark?.watermarkColumn ?? explicitWatermarkColumn ?? null;\n const softDeleteColumn =\n watermark?.softDeleteColumn ?? explicitSoftDeleteColumn ?? null;\n const deletedFilter = softDeleteColumn ? { [softDeleteColumn]: null } : {};\n const spec = withRefreshFilters(\n compileReportDefinition(definition),\n scope,\n deletedFilter,\n );\n const aggregate = buildAggregate(spec, 1, adapterType);\n const result = await db.query(aggregate.sql, ...aggregate.values);\n const refreshedAt = new Date();\n const rows = materializeRows(result.rows, definition, refreshedAt, scope);\n\n await replaceRows(db, reportTable, rows, scope);\n\n const watermarkAfter = watermarkColumn\n ? await maxWatermark(db, definition, scope, watermarkColumn, adapterType)\n : null;\n if (watermarkColumn) {\n await writeWatermark(\n db,\n definition,\n scope,\n watermarkColumn,\n watermarkAfter,\n runId,\n );\n }\n\n return {\n rowCount: rows.length,\n changedGroupCount: rows.length,\n watermarkAfter,\n mode: 'rebuild',\n };\n}\n\nasync function incrementalReport(\n db: DatabaseInterface,\n definition: ReportDefinition,\n reportTable: string,\n scope: RefreshScope,\n adapterType: SqlAdapterType,\n runId: string | undefined,\n changedRows: Record<string, unknown>[] = [],\n): Promise<RefreshWorkResult> {\n const watermark = await resolveWatermarkConfig(definition);\n const watermarkBefore = await readWatermark(\n db,\n definition,\n scope,\n watermark.watermarkColumn,\n );\n\n if (!watermarkBefore) {\n const seeded = await rebuildReport(\n db,\n definition,\n reportTable,\n scope,\n adapterType,\n runId,\n watermark,\n );\n return {\n ...seeded,\n watermarkBefore,\n };\n }\n\n const changedSpec = compileChangedGroupSpec(\n definition,\n scope,\n watermark,\n watermarkBefore,\n );\n const changedAggregate = buildAggregate(changedSpec, 1, adapterType);\n const changed = await db.query(\n changedAggregate.sql,\n ...changedAggregate.values,\n );\n const changedGroups = changed.rows.map((row) =>\n parseMaybeJson<Record<string, unknown>>(row),\n );\n const materializedGroups =\n changedGroups.length > 0 || changedRows.length > 0\n ? await readMaterializedGroups(db, reportTable, definition, scope)\n : [];\n const affectedGroups = dedupeGroupRows(definition, [\n ...changedGroups,\n ...materializedGroups,\n ]);\n\n if (affectedGroups.length === 0) {\n return {\n rowCount: 0,\n changedGroupCount: 0,\n watermarkBefore,\n watermarkAfter: watermarkBefore,\n mode: 'incremental',\n };\n }\n\n let rowCount = 0;\n const conflicts = conflictColumns(definition, scope);\n\n for (const groupRow of affectedGroups) {\n const affectedSpec = compileAffectedGroupSpec(\n definition,\n scope,\n watermark,\n groupRow,\n );\n const aggregate = buildAggregate(affectedSpec, 1, adapterType);\n const result = await db.query(aggregate.sql, ...aggregate.values);\n const refreshedAt = new Date();\n const rows = materializeRows(result.rows, definition, refreshedAt, scope);\n\n if (rows.length === 0) {\n await deleteMaterializedGroup(\n db,\n reportTable,\n definition,\n scope,\n groupRow,\n );\n continue;\n }\n\n for (const row of rows) {\n await db.upsert(reportTable, conflicts, row);\n rowCount += 1;\n }\n }\n\n const watermarkAfter = await maxWatermark(\n db,\n definition,\n scope,\n watermark.watermarkColumn,\n adapterType,\n );\n await writeWatermark(\n db,\n definition,\n scope,\n watermark.watermarkColumn,\n watermarkAfter,\n runId,\n );\n\n return {\n rowCount,\n changedGroupCount: affectedGroups.length,\n watermarkBefore,\n watermarkAfter,\n mode: 'incremental',\n };\n}\n\nfunction requiredReportRuntimeTables(\n mode: ReportRefreshMode,\n options: ReportRefreshOptions,\n): string[] {\n const tables = new Set<string>();\n if (options.trackRuns !== false) tables.add(REPORT_RUNS_TABLE);\n if (mode === 'incremental') tables.add(REPORT_WATERMARKS_TABLE);\n if (options.lock !== false) tables.add(REPORT_LOCKS_TABLE);\n return [...tables];\n}\n\nasync function refreshReportOnce(\n reportCtor: ReportCtor,\n options: ReportRefreshOptions,\n): Promise<ReportRefreshResult> {\n if (!options.db) {\n throw new Error('refreshReport requires a database handle');\n }\n\n const definition = await buildReportDefinition(reportCtor);\n const reportTable = getReportTableName(reportCtor);\n const requestedMode = options.mode ?? definition.refresh?.mode ?? 'rebuild';\n const exists = await tableExists(options.db, reportTable);\n if (!exists) {\n throw new Error(\n `Report table '${reportTable}' does not exist for ${reportCtor.name}. ` +\n `Run smrt db:migrate before refreshing reports.`,\n );\n }\n\n const requiredTables = requiredReportRuntimeTables(requestedMode, options);\n if (requiredTables.length > 0) {\n await assertReportTablesReady(options.db, requiredTables);\n }\n\n const adapterType = adapterTypeFromDb(options.db, options.adapterType);\n const scope = await resolveScope(definition, options);\n const lock =\n options.lock === false\n ? null\n : await acquireLock(\n options.db,\n definition,\n scope,\n options.lockTtlMs ?? 15 * 60 * 1000,\n );\n\n if (options.lock !== false && !lock) {\n const runId = await startRun(\n options.db,\n definition,\n scope,\n options,\n requestedMode,\n );\n await completeRun(options.db, runId, 'skipped');\n return {\n rowCount: 0,\n refreshedAt: new Date(),\n mode: requestedMode,\n tenantId: scope.tenantId,\n runId,\n changedGroupCount: 0,\n skipped: true,\n };\n }\n\n let runId: string | undefined;\n try {\n runId = await startRun(\n options.db,\n definition,\n scope,\n options,\n requestedMode,\n );\n const work =\n requestedMode === 'incremental'\n ? await incrementalReport(\n options.db,\n definition,\n reportTable,\n scope,\n adapterType,\n runId,\n options.changedRows,\n )\n : await rebuildReport(\n options.db,\n definition,\n reportTable,\n scope,\n adapterType,\n runId,\n );\n\n await completeRun(options.db, runId, 'success', {\n rowCount: work.rowCount,\n changedGroupCount: work.changedGroupCount,\n watermarkAfter: work.watermarkAfter,\n });\n\n return {\n rowCount: work.rowCount,\n refreshedAt: new Date(),\n mode: work.mode,\n tenantId: scope.tenantId,\n runId,\n changedGroupCount: work.changedGroupCount,\n };\n } catch (error) {\n await completeRun(options.db, runId, 'failed', { error });\n throw error;\n } finally {\n await lock?.release();\n }\n}\n\nexport async function refreshReport(\n reportCtor: ReportCtor,\n options: ReportRefreshOptions = {},\n): Promise<ReportRefreshResult> {\n if (options.tenantIds && options.tenantIds.length > 0) {\n if (!options.db) {\n throw new Error('refreshReport requires a database handle');\n }\n const results: ReportRefreshResult[] = [];\n for (const tenantId of options.tenantIds) {\n const result = await withTenant({ tenantId }, () =>\n refreshReportOnce(reportCtor, {\n ...options,\n tenantId,\n tenantIds: undefined,\n }),\n );\n results.push(result);\n }\n return {\n rowCount: results.reduce((sum, result) => sum + result.rowCount, 0),\n refreshedAt: new Date(),\n mode: results[0]?.mode ?? options.mode ?? 'rebuild',\n tenantId: null,\n changedGroupCount: results.reduce(\n (sum, result) => sum + (result.changedGroupCount ?? 0),\n 0,\n ),\n tenantResults: results,\n };\n }\n\n return refreshReportOnce(reportCtor, options);\n}\n\nexport function reportRowIdentity(\n row: Record<string, any>,\n definition: ReportDefinition,\n): string {\n const groupingColumns = getReportGroupingColumns(definition);\n return stableReportId(\n groupingColumns.map((column) => row[toSnakeCase(column)]),\n );\n}\n\nexport function reportSourceClassName(source: ReportSource): string {\n return sourceClassName(source);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"refresh.js","names":["runId"],"sources":["../src/refresh.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { ObjectRegistry, type SmrtObject } from '@happyvertical/smrt-core';\nimport { toSnakeCase } from '@happyvertical/smrt-core/utils';\nimport { getTenantId, withTenant } from '@happyvertical/smrt-tenancy';\nimport {\n buildAggregate,\n buildWhere,\n type DatabaseInterface,\n type SqlAdapterType,\n tableExists,\n validateColumnName,\n} from '@happyvertical/sql';\nimport {\n buildReportDefinition,\n compileReportDefinition,\n getReportGroupingColumns,\n} from './compiler.js';\nimport { fieldColumn, tenantColumn } from './registry-columns.js';\nimport {\n assertReportTablesReady,\n REPORT_LOCKS_TABLE,\n REPORT_RUNS_TABLE,\n REPORT_WATERMARKS_TABLE,\n scopeKeyForTenant,\n} from './state.js';\nimport type {\n AggregateSelectExpr,\n AggregateSpec,\n ReportDefinition,\n ReportRefreshMode,\n ReportRefreshOptions,\n ReportRefreshResult,\n ReportSource,\n} from './types.js';\n\ntype ReportCtor = new (...args: any[]) => SmrtObject;\n\ninterface RefreshScope {\n tenantId: string | null;\n scopeKey: string;\n sourceTenantColumn: string | null;\n reportTenantColumn: string | null;\n}\n\ninterface WatermarkConfig {\n watermarkColumn: string;\n softDeleteColumn: string;\n}\n\ninterface RefreshWorkResult {\n rowCount: number;\n changedGroupCount: number;\n watermarkBefore?: string | null;\n watermarkAfter?: string | null;\n mode: ReportRefreshMode;\n}\n\ninterface ReportLockHandle {\n ownerId: string;\n release(): Promise<void>;\n}\n\nfunction isSqlAdapterType(value: unknown): value is SqlAdapterType {\n return (\n value === 'sqlite' ||\n value === 'postgres' ||\n value === 'duckdb' ||\n value === 'json'\n );\n}\n\nexport function adapterTypeFromDb(\n db: DatabaseInterface,\n fallback?: SqlAdapterType,\n): SqlAdapterType {\n if (isSqlAdapterType(fallback)) return fallback;\n\n const url = String(db.url ?? '');\n if (url.startsWith('postgres://') || url.startsWith('postgresql://')) {\n return 'postgres';\n }\n if (url.endsWith('.duckdb')) {\n return 'duckdb';\n }\n if (url === ':memory:' || url.endsWith('.sqlite') || url.endsWith('.db')) {\n return 'sqlite';\n }\n throw new Error(\n 'Report refresh requires a database adapter type. Pass { adapterType }.',\n );\n}\n\nfunction stableReportId(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 getReportTableName(reportCtor: ReportCtor): string {\n const registered =\n ObjectRegistry.getClassByConstructor(reportCtor) ??\n ObjectRegistry.getClass(reportCtor.name);\n const tableName = registered\n ? ObjectRegistry.getTableName(registered.qualifiedName ?? registered.name)\n : ObjectRegistry.getTableName(reportCtor.name);\n if (!tableName) {\n throw new Error(`No report table registered for ${reportCtor.name}`);\n }\n return tableName;\n}\n\nfunction getActualGroupingColumns(\n definition: Pick<ReportDefinition, 'fields'>,\n): string[] {\n return definition.fields\n .filter(\n (field) =>\n field.report?.kind === 'group' || field.report?.kind === 'bucket',\n )\n .map((field) => field.columnName ?? toSnakeCase(field.fieldName));\n}\n\nfunction materializeRows(\n rows: Record<string, any>[],\n definition: ReportDefinition,\n refreshedAt: Date,\n scope: RefreshScope,\n): Record<string, any>[] {\n const groupingColumns = getActualGroupingColumns(definition);\n const now = refreshedAt.toISOString();\n\n return rows.map((row, index) => {\n const groupingValues =\n groupingColumns.length === 0\n ? [definition.reportClassName, scope.scopeKey, index]\n : [\n definition.reportClassName,\n scope.scopeKey,\n ...groupingColumns.map((column) => row[column]),\n ];\n const id = stableReportId(groupingValues);\n const tenantFields = scope.reportTenantColumn\n ? { [scope.reportTenantColumn]: scope.tenantId }\n : {};\n\n return {\n id,\n slug: id,\n context: scope.scopeKey,\n ...tenantFields,\n ...row,\n refreshed_at: now,\n created_at: now,\n updated_at: now,\n };\n });\n}\n\nfunction sourceClassName(source: ReportSource): string {\n if (typeof source === 'string') return source;\n return source.name;\n}\n\nasync function resolveScope(\n definition: ReportDefinition,\n options: ReportRefreshOptions,\n): Promise<RefreshScope> {\n const tenantId =\n options.tenantId !== undefined ? options.tenantId : (getTenantId() ?? null);\n const sourceTenantColumn = await tenantColumn(definition.sourceClassName);\n const reportTenantColumn = await tenantColumn(definition.reportClassName);\n\n if (tenantId && !sourceTenantColumn) {\n throw new Error(\n `Tenant-scoped refresh for ${definition.reportClassName} requires source ` +\n `${definition.sourceClassName} to expose a tenantId field.`,\n );\n }\n if (tenantId && !reportTenantColumn) {\n throw new Error(\n `Tenant-scoped refresh for ${definition.reportClassName} requires the report table to expose a tenantId field.`,\n );\n }\n\n return {\n tenantId,\n scopeKey: scopeKeyForTenant(tenantId),\n sourceTenantColumn,\n reportTenantColumn,\n };\n}\n\nfunction tenantWhere(scope: RefreshScope): Record<string, unknown> {\n if (!scope.sourceTenantColumn) return {};\n return { [scope.sourceTenantColumn]: scope.tenantId };\n}\n\nfunction andWhere(\n base: AggregateSpec['where'],\n extra: Record<string, unknown>,\n): AggregateSpec['where'] {\n if (Object.keys(extra).length === 0) return base;\n if (!base) return extra;\n if (Array.isArray(base)) {\n return base.map((andGroup) => [...andGroup, extra]);\n }\n return { ...base, ...extra };\n}\n\nfunction andHaving(\n base: AggregateSpec['having'],\n extra: Record<string, unknown>,\n): AggregateSpec['having'] {\n if (Object.keys(extra).length === 0) return base;\n if (!base) return extra;\n if (Array.isArray(base)) {\n return base.map((andGroup) => [...andGroup, extra]);\n }\n return { ...base, ...extra };\n}\n\nfunction withRefreshFilters(\n spec: AggregateSpec,\n scope: RefreshScope,\n extraWhere: Record<string, unknown> = {},\n): AggregateSpec {\n // The source's own columns (tenant, soft delete, watermark) are qualified by\n // its alias once the query joins other tables, which have columns of the same\n // names.\n const qualify = (conditions: Record<string, unknown>) =>\n spec.fromAlias\n ? Object.fromEntries(\n Object.entries(conditions).map(([key, value]) => [\n `${spec.fromAlias}.${key}`,\n value,\n ]),\n )\n : conditions;\n return {\n ...spec,\n where: andWhere(\n spec.where,\n qualify({\n ...tenantWhere(scope),\n ...extraWhere,\n }),\n ),\n };\n}\n\nasync function resolveWatermarkConfig(\n definition: ReportDefinition,\n): Promise<WatermarkConfig> {\n const configured = definition.refresh ?? {};\n const watermarkField = configured.watermarkColumn ?? 'updatedAt';\n const softDeleteField = configured.softDeleteColumn ?? 'deletedAt';\n const watermarkColumn = await fieldColumn(\n definition.sourceClassName,\n watermarkField,\n );\n const softDeleteColumn = await fieldColumn(\n definition.sourceClassName,\n softDeleteField,\n );\n\n if (!watermarkColumn) {\n throw new Error(\n `Incremental report ${definition.reportClassName} requires source ` +\n `${definition.sourceClassName} to define watermark column '${watermarkField}'.`,\n );\n }\n if (!softDeleteColumn) {\n throw new Error(\n `Incremental report ${definition.reportClassName} requires source ` +\n `${definition.sourceClassName} to define soft-delete column '${softDeleteField}'.`,\n );\n }\n\n return { watermarkColumn, softDeleteColumn };\n}\n\nfunction compileChangedGroupSpec(\n definition: ReportDefinition,\n scope: RefreshScope,\n watermark: WatermarkConfig,\n watermarkBefore: string,\n): AggregateSpec {\n const spec = compileReportDefinition(definition);\n const select = spec.select.filter(\n (expr): expr is AggregateSelectExpr => !('fn' in expr),\n );\n\n return withRefreshFilters(\n {\n ...spec,\n // Ignore the report filter while finding changed groups so rows that\n // just stopped matching `report.where` still trigger a recompute/delete.\n where: undefined,\n select:\n select.length > 0\n ? select\n : [{ fn: 'count', as: '__smrt_changed_count' }],\n groupBy: select.length > 0 ? spec.groupBy : [],\n having: undefined,\n },\n scope,\n { [`${watermark.watermarkColumn} >`]: watermarkBefore },\n );\n}\n\nfunction compileAffectedGroupSpec(\n definition: ReportDefinition,\n scope: RefreshScope,\n watermark: WatermarkConfig,\n groupRow: Record<string, unknown>,\n): AggregateSpec {\n const groupingColumns = getActualGroupingColumns(definition);\n const groupHaving = Object.fromEntries(\n groupingColumns.map((column) => [column, groupRow[column]]),\n );\n const spec = withRefreshFilters(compileReportDefinition(definition), scope, {\n [watermark.softDeleteColumn]: null,\n });\n return {\n ...spec,\n having: andHaving(spec.having, groupHaving),\n };\n}\n\nfunction conflictColumns(\n definition: ReportDefinition,\n scope: RefreshScope,\n): string[] {\n const groupingColumns = getActualGroupingColumns(definition);\n if (groupingColumns.length === 0) return ['id'];\n return [\n ...(scope.reportTenantColumn ? [scope.reportTenantColumn] : []),\n ...groupingColumns,\n ];\n}\n\nfunction predicateSql(\n tableName: string,\n predicate: Record<string, unknown>,\n): { sql: string; values: unknown[] } {\n validateColumnName(tableName);\n const clauses: string[] = [];\n const values: unknown[] = [];\n\n for (const [column, value] of Object.entries(predicate)) {\n validateColumnName(column);\n if (value === null || value === undefined) {\n clauses.push(`${column} IS NULL`);\n continue;\n }\n clauses.push(`${column} = ?`);\n values.push(value);\n }\n\n return {\n sql: clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : '',\n values,\n };\n}\n\nfunction dedupeGroupRows(\n definition: ReportDefinition,\n rows: Array<Record<string, unknown> | null>,\n): Record<string, unknown>[] {\n const groupingColumns = getActualGroupingColumns(definition);\n const groups = new Map<string, Record<string, unknown>>();\n\n for (const row of rows) {\n if (!row) continue;\n const groupRow =\n groupingColumns.length === 0\n ? {}\n : Object.fromEntries(\n groupingColumns.map((column) => [column, row[column] ?? null]),\n );\n const key =\n groupingColumns.length === 0\n ? '__smrt_global_group__'\n : JSON.stringify(groupingColumns.map((column) => groupRow[column]));\n if (!groups.has(key)) {\n groups.set(key, groupRow);\n }\n }\n\n return [...groups.values()];\n}\n\nasync function readMaterializedGroups(\n db: DatabaseInterface,\n tableName: string,\n definition: ReportDefinition,\n scope: RefreshScope,\n): Promise<Record<string, unknown>[]> {\n const groupingColumns = getActualGroupingColumns(definition);\n if (groupingColumns.length === 0) {\n return [{}];\n }\n\n const where = scope.reportTenantColumn\n ? predicateSql(tableName, { [scope.reportTenantColumn]: scope.tenantId })\n : { sql: '', values: [] as unknown[] };\n const columns = groupingColumns.map((column) => validateColumnName(column));\n const result = await db.query(\n `SELECT ${columns.join(', ')} FROM ${validateColumnName(tableName)} ${where.sql}`,\n ...where.values,\n );\n\n return result.rows\n .map((row) => parseMaybeJson<Record<string, unknown>>(row))\n .filter((row): row is Record<string, unknown> => Boolean(row));\n}\n\nasync function deleteMaterializedGroup(\n db: DatabaseInterface,\n tableName: string,\n definition: ReportDefinition,\n scope: RefreshScope,\n groupRow: Record<string, unknown>,\n): Promise<void> {\n const predicate: Record<string, unknown> = {};\n if (scope.reportTenantColumn) {\n predicate[scope.reportTenantColumn] = scope.tenantId;\n }\n for (const column of getActualGroupingColumns(definition)) {\n predicate[column] = groupRow[column];\n }\n\n const where = predicateSql(tableName, predicate);\n await db.query(\n `DELETE FROM ${validateColumnName(tableName)} ${where.sql}`,\n ...where.values,\n );\n}\n\nasync function replaceRows(\n db: DatabaseInterface,\n tableName: string,\n rows: Record<string, any>[],\n scope: RefreshScope,\n): Promise<void> {\n validateColumnName(tableName);\n const run = async (tx: DatabaseInterface) => {\n if (scope.reportTenantColumn) {\n const where = predicateSql(tableName, {\n [scope.reportTenantColumn]: scope.tenantId,\n });\n await tx.query(`DELETE FROM ${tableName} ${where.sql}`, ...where.values);\n } else {\n await tx.query(`DELETE FROM ${tableName}`);\n }\n if (rows.length > 0) {\n await tx.insert(tableName, rows);\n }\n };\n\n if (db.transaction) {\n await db.transaction(run);\n return;\n }\n\n await run(db);\n}\n\nfunction parseMaybeJson<T>(value: T | string | null | undefined): T | null {\n if (value == null) return null;\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value) as T;\n } catch {\n return value as T;\n }\n}\n\nfunction watermarkValue(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (value instanceof Date) return value.toISOString();\n return String(value);\n}\n\nasync function readWatermark(\n db: DatabaseInterface,\n definition: ReportDefinition,\n scope: RefreshScope,\n watermarkColumn: string,\n): Promise<string | null> {\n const result = await db.query(\n `SELECT watermark_value FROM ${REPORT_WATERMARKS_TABLE}\n WHERE report_class = ?\n AND scope_key = ?\n AND source_class = ?\n AND watermark_column = ?\n LIMIT 1`,\n definition.reportClassName,\n scope.scopeKey,\n definition.sourceClassName,\n watermarkColumn,\n );\n const row = result.rows[0] as { watermark_value?: unknown } | undefined;\n return watermarkValue(row?.watermark_value);\n}\n\nasync function writeWatermark(\n db: DatabaseInterface,\n definition: ReportDefinition,\n scope: RefreshScope,\n watermarkColumn: string,\n value: string | null,\n runId: string | undefined,\n): Promise<void> {\n if (value === null) return;\n const id = stableReportId([\n 'watermark',\n definition.reportClassName,\n scope.scopeKey,\n definition.sourceClassName,\n watermarkColumn,\n ]);\n const now = new Date().toISOString();\n await db.upsert(\n REPORT_WATERMARKS_TABLE,\n ['report_class', 'scope_key', 'source_class', 'watermark_column'],\n {\n id,\n slug: id,\n context: scope.scopeKey,\n tenant_id: scope.tenantId,\n scope_key: scope.scopeKey,\n report_class: definition.reportClassName,\n source_class: definition.sourceClassName,\n watermark_column: watermarkColumn,\n watermark_value: value,\n last_run_id: runId ?? null,\n created_at: now,\n updated_at: now,\n },\n );\n}\n\nasync function maxWatermark(\n db: DatabaseInterface,\n definition: ReportDefinition,\n scope: RefreshScope,\n watermarkColumn: string,\n adapterType: SqlAdapterType,\n): Promise<string | null> {\n const where = andWhere(undefined, tenantWhere(scope));\n const builtWhere = where\n ? buildWhere(where, 1, adapterType)\n : { sql: '', values: [] };\n const result = await db.query(\n `SELECT MAX(${validateColumnName(watermarkColumn)}) AS watermark FROM ${validateColumnName(definition.sourceTable)} ${builtWhere.sql}`,\n ...builtWhere.values,\n );\n const row = result.rows[0] as { watermark?: unknown } | undefined;\n return watermarkValue(row?.watermark);\n}\n\nasync function startRun(\n db: DatabaseInterface,\n definition: ReportDefinition,\n scope: RefreshScope,\n options: ReportRefreshOptions,\n mode: ReportRefreshMode,\n watermarkBefore?: string | null,\n): Promise<string | undefined> {\n if (options.trackRuns === false) return undefined;\n const id = randomUUID();\n const now = new Date().toISOString();\n await db.insert(REPORT_RUNS_TABLE, {\n id,\n slug: id,\n context: scope.scopeKey,\n tenant_id: scope.tenantId,\n scope_key: scope.scopeKey,\n report_class: definition.reportClassName,\n source_class: definition.sourceClassName,\n mode,\n trigger: options.trigger ?? 'manual',\n status: 'running',\n started_at: now,\n row_count: 0,\n changed_group_count: 0,\n watermark_before: watermarkBefore ?? null,\n watermark_after: null,\n error: null,\n metadata: {\n scheduleId: options.scheduleId,\n },\n created_at: now,\n updated_at: now,\n });\n return id;\n}\n\nasync function completeRun(\n db: DatabaseInterface,\n runId: string | undefined,\n status: 'success' | 'failed' | 'skipped',\n data: {\n rowCount?: number;\n changedGroupCount?: number;\n watermarkAfter?: string | null;\n error?: unknown;\n } = {},\n): Promise<void> {\n if (!runId) return;\n const error =\n data.error instanceof Error\n ? data.error.message\n : data.error\n ? String(data.error)\n : null;\n await db.update(\n REPORT_RUNS_TABLE,\n { id: runId },\n {\n status,\n completed_at: new Date().toISOString(),\n row_count: data.rowCount ?? 0,\n changed_group_count: data.changedGroupCount ?? 0,\n watermark_after: data.watermarkAfter ?? null,\n error,\n updated_at: new Date().toISOString(),\n },\n );\n}\n\nasync function acquireLock(\n db: DatabaseInterface,\n definition: ReportDefinition,\n scope: RefreshScope,\n ttlMs: number,\n): Promise<ReportLockHandle | null> {\n const now = new Date();\n const nowIso = now.toISOString();\n const ownerId = randomUUID();\n const expiresAt = new Date(now.getTime() + ttlMs);\n const expiresAtIso = expiresAt.toISOString();\n const id = stableReportId([\n 'lock',\n definition.reportClassName,\n scope.scopeKey,\n ]);\n await db.query(\n `INSERT INTO ${REPORT_LOCKS_TABLE} (\n id,\n slug,\n context,\n tenant_id,\n scope_key,\n report_class,\n owner_id,\n acquired_at,\n heartbeat_at,\n expires_at,\n created_at,\n updated_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(report_class, scope_key) DO UPDATE SET\n tenant_id = excluded.tenant_id,\n owner_id = excluded.owner_id,\n acquired_at = excluded.acquired_at,\n heartbeat_at = excluded.heartbeat_at,\n expires_at = excluded.expires_at,\n updated_at = excluded.updated_at\n WHERE ${REPORT_LOCKS_TABLE}.owner_id IS NULL\n OR ${REPORT_LOCKS_TABLE}.expires_at IS NULL\n OR ${REPORT_LOCKS_TABLE}.expires_at <= ?`,\n id,\n id,\n scope.scopeKey,\n scope.tenantId,\n scope.scopeKey,\n definition.reportClassName,\n ownerId,\n nowIso,\n nowIso,\n expiresAtIso,\n nowIso,\n nowIso,\n nowIso,\n );\n\n const claimed = await db.query(\n `SELECT owner_id FROM ${REPORT_LOCKS_TABLE}\n WHERE report_class = ? AND scope_key = ?\n LIMIT 1`,\n definition.reportClassName,\n scope.scopeKey,\n );\n const row = claimed.rows[0] as { owner_id?: unknown } | undefined;\n if (row?.owner_id !== ownerId) {\n return null;\n }\n\n return {\n ownerId,\n async release() {\n await db.query(\n `UPDATE ${REPORT_LOCKS_TABLE}\n SET owner_id = NULL,\n heartbeat_at = NULL,\n expires_at = NULL,\n updated_at = ?\n WHERE report_class = ?\n AND scope_key = ?\n AND owner_id = ?`,\n new Date().toISOString(),\n definition.reportClassName,\n scope.scopeKey,\n ownerId,\n );\n },\n };\n}\n\nasync function rebuildReport(\n db: DatabaseInterface,\n definition: ReportDefinition,\n reportTable: string,\n scope: RefreshScope,\n adapterType: SqlAdapterType,\n runId: string | undefined,\n watermark?: WatermarkConfig | null,\n): Promise<RefreshWorkResult> {\n const explicitWatermarkColumn =\n definition.refresh?.watermarkColumn &&\n (await fieldColumn(\n definition.sourceClassName,\n definition.refresh.watermarkColumn,\n ));\n const explicitSoftDeleteColumn =\n definition.refresh?.softDeleteColumn &&\n (await fieldColumn(\n definition.sourceClassName,\n definition.refresh.softDeleteColumn,\n ));\n const watermarkColumn =\n watermark?.watermarkColumn ?? explicitWatermarkColumn ?? null;\n const softDeleteColumn =\n watermark?.softDeleteColumn ?? explicitSoftDeleteColumn ?? null;\n const deletedFilter = softDeleteColumn ? { [softDeleteColumn]: null } : {};\n const spec = withRefreshFilters(\n compileReportDefinition(definition),\n scope,\n deletedFilter,\n );\n const aggregate = buildAggregate(spec, 1, adapterType);\n const result = await db.query(aggregate.sql, ...aggregate.values);\n const refreshedAt = new Date();\n const rows = materializeRows(result.rows, definition, refreshedAt, scope);\n\n await replaceRows(db, reportTable, rows, scope);\n\n const watermarkAfter = watermarkColumn\n ? await maxWatermark(db, definition, scope, watermarkColumn, adapterType)\n : null;\n if (watermarkColumn) {\n await writeWatermark(\n db,\n definition,\n scope,\n watermarkColumn,\n watermarkAfter,\n runId,\n );\n }\n\n return {\n rowCount: rows.length,\n changedGroupCount: rows.length,\n watermarkAfter,\n mode: 'rebuild',\n };\n}\n\nasync function incrementalReport(\n db: DatabaseInterface,\n definition: ReportDefinition,\n reportTable: string,\n scope: RefreshScope,\n adapterType: SqlAdapterType,\n runId: string | undefined,\n changedRows: Record<string, unknown>[] = [],\n): Promise<RefreshWorkResult> {\n const watermark = await resolveWatermarkConfig(definition);\n const watermarkBefore = await readWatermark(\n db,\n definition,\n scope,\n watermark.watermarkColumn,\n );\n\n if (!watermarkBefore) {\n const seeded = await rebuildReport(\n db,\n definition,\n reportTable,\n scope,\n adapterType,\n runId,\n watermark,\n );\n return {\n ...seeded,\n watermarkBefore,\n };\n }\n\n const changedSpec = compileChangedGroupSpec(\n definition,\n scope,\n watermark,\n watermarkBefore,\n );\n const changedAggregate = buildAggregate(changedSpec, 1, adapterType);\n const changed = await db.query(\n changedAggregate.sql,\n ...changedAggregate.values,\n );\n const changedGroups = changed.rows.map((row) =>\n parseMaybeJson<Record<string, unknown>>(row),\n );\n const materializedGroups =\n changedGroups.length > 0 || changedRows.length > 0\n ? await readMaterializedGroups(db, reportTable, definition, scope)\n : [];\n const affectedGroups = dedupeGroupRows(definition, [\n ...changedGroups,\n ...materializedGroups,\n ]);\n\n if (affectedGroups.length === 0) {\n return {\n rowCount: 0,\n changedGroupCount: 0,\n watermarkBefore,\n watermarkAfter: watermarkBefore,\n mode: 'incremental',\n };\n }\n\n let rowCount = 0;\n const conflicts = conflictColumns(definition, scope);\n\n for (const groupRow of affectedGroups) {\n const affectedSpec = compileAffectedGroupSpec(\n definition,\n scope,\n watermark,\n groupRow,\n );\n const aggregate = buildAggregate(affectedSpec, 1, adapterType);\n const result = await db.query(aggregate.sql, ...aggregate.values);\n const refreshedAt = new Date();\n const rows = materializeRows(result.rows, definition, refreshedAt, scope);\n\n if (rows.length === 0) {\n await deleteMaterializedGroup(\n db,\n reportTable,\n definition,\n scope,\n groupRow,\n );\n continue;\n }\n\n for (const row of rows) {\n await db.upsert(reportTable, conflicts, row);\n rowCount += 1;\n }\n }\n\n const watermarkAfter = await maxWatermark(\n db,\n definition,\n scope,\n watermark.watermarkColumn,\n adapterType,\n );\n await writeWatermark(\n db,\n definition,\n scope,\n watermark.watermarkColumn,\n watermarkAfter,\n runId,\n );\n\n return {\n rowCount,\n changedGroupCount: affectedGroups.length,\n watermarkBefore,\n watermarkAfter,\n mode: 'incremental',\n };\n}\n\nfunction requiredReportRuntimeTables(\n mode: ReportRefreshMode,\n options: ReportRefreshOptions,\n): string[] {\n const tables = new Set<string>();\n if (options.trackRuns !== false) tables.add(REPORT_RUNS_TABLE);\n if (mode === 'incremental') tables.add(REPORT_WATERMARKS_TABLE);\n if (options.lock !== false) tables.add(REPORT_LOCKS_TABLE);\n return [...tables];\n}\n\nasync function refreshReportOnce(\n reportCtor: ReportCtor,\n options: ReportRefreshOptions,\n): Promise<ReportRefreshResult> {\n if (!options.db) {\n throw new Error('refreshReport requires a database handle');\n }\n\n const definition = await buildReportDefinition(reportCtor);\n const reportTable = getReportTableName(reportCtor);\n // A joined row's change does not move the source watermark, so incremental\n // cannot see it. A joined report always rebuilds, whatever the trigger\n // (schedule, change or job) defaulted to; declaring `incremental` on one is\n // refused when its definition is built.\n const requestedMode: ReportRefreshMode = definition.joins?.length\n ? 'rebuild'\n : (options.mode ?? definition.refresh?.mode ?? 'rebuild');\n const exists = await tableExists(options.db, reportTable);\n if (!exists) {\n throw new Error(\n `Report table '${reportTable}' does not exist for ${reportCtor.name}. ` +\n `Run smrt db:migrate before refreshing reports.`,\n );\n }\n\n const requiredTables = requiredReportRuntimeTables(requestedMode, options);\n if (requiredTables.length > 0) {\n await assertReportTablesReady(options.db, requiredTables);\n }\n\n const adapterType = adapterTypeFromDb(options.db, options.adapterType);\n const scope = await resolveScope(definition, options);\n const lock =\n options.lock === false\n ? null\n : await acquireLock(\n options.db,\n definition,\n scope,\n options.lockTtlMs ?? 15 * 60 * 1000,\n );\n\n if (options.lock !== false && !lock) {\n const runId = await startRun(\n options.db,\n definition,\n scope,\n options,\n requestedMode,\n );\n await completeRun(options.db, runId, 'skipped');\n return {\n rowCount: 0,\n refreshedAt: new Date(),\n mode: requestedMode,\n tenantId: scope.tenantId,\n runId,\n changedGroupCount: 0,\n skipped: true,\n };\n }\n\n let runId: string | undefined;\n try {\n runId = await startRun(\n options.db,\n definition,\n scope,\n options,\n requestedMode,\n );\n const work =\n requestedMode === 'incremental'\n ? await incrementalReport(\n options.db,\n definition,\n reportTable,\n scope,\n adapterType,\n runId,\n options.changedRows,\n )\n : await rebuildReport(\n options.db,\n definition,\n reportTable,\n scope,\n adapterType,\n runId,\n );\n\n await completeRun(options.db, runId, 'success', {\n rowCount: work.rowCount,\n changedGroupCount: work.changedGroupCount,\n watermarkAfter: work.watermarkAfter,\n });\n\n return {\n rowCount: work.rowCount,\n refreshedAt: new Date(),\n mode: work.mode,\n tenantId: scope.tenantId,\n runId,\n changedGroupCount: work.changedGroupCount,\n };\n } catch (error) {\n await completeRun(options.db, runId, 'failed', { error });\n throw error;\n } finally {\n await lock?.release();\n }\n}\n\nexport async function refreshReport(\n reportCtor: ReportCtor,\n options: ReportRefreshOptions = {},\n): Promise<ReportRefreshResult> {\n if (options.tenantIds && options.tenantIds.length > 0) {\n if (!options.db) {\n throw new Error('refreshReport requires a database handle');\n }\n const results: ReportRefreshResult[] = [];\n for (const tenantId of options.tenantIds) {\n const result = await withTenant({ tenantId }, () =>\n refreshReportOnce(reportCtor, {\n ...options,\n tenantId,\n tenantIds: undefined,\n }),\n );\n results.push(result);\n }\n return {\n rowCount: results.reduce((sum, result) => sum + result.rowCount, 0),\n refreshedAt: new Date(),\n mode: results[0]?.mode ?? options.mode ?? 'rebuild',\n tenantId: null,\n changedGroupCount: results.reduce(\n (sum, result) => sum + (result.changedGroupCount ?? 0),\n 0,\n ),\n tenantResults: results,\n };\n }\n\n return refreshReportOnce(reportCtor, options);\n}\n\nexport function reportRowIdentity(\n row: Record<string, any>,\n definition: ReportDefinition,\n): string {\n const groupingColumns = getReportGroupingColumns(definition);\n return stableReportId(\n groupingColumns.map((column) => row[toSnakeCase(column)]),\n );\n}\n\nexport function reportSourceClassName(source: ReportSource): string {\n return sourceClassName(source);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function fieldColumn(className: string, requested: string): Promise<string | null>;
|
|
2
|
+
export declare function tenantColumn(className: string): Promise<string | null>;
|
|
3
|
+
/** The registry field properties that decide who may read a column. */
|
|
4
|
+
export interface PolicyField {
|
|
5
|
+
sensitive?: boolean;
|
|
6
|
+
readPermission?: string;
|
|
7
|
+
sensitivity?: unknown;
|
|
8
|
+
_meta?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export declare function fieldPolicy(field: PolicyField | undefined): {
|
|
11
|
+
sensitive: boolean;
|
|
12
|
+
readPermission?: string;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=registry-columns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-columns.d.ts","sourceRoot":"","sources":["../src/registry-columns.ts"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWxB;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgB5E;AAED,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG;IAC3D,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAkBA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-compiler.d.ts","sourceRoot":"","sources":["../src/runtime-compiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AASH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime-compiler.d.ts","sourceRoot":"","sources":["../src/runtime-compiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AASH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAK5E,OAAO,EAML,KAAK,mBAAmB,EAExB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EAGpC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAuB,aAAa,EAAE,MAAM,YAAY,CAAC;AAErE,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,8BAA8B,GAC/B,MAAM,mBAAmB,CAAC;AAE3B,wEAAwE;AACxE,MAAM,WAAW,6BAA6B;IAC5C,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,qEAAqE;IACrE,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,2BAA2B;IAC1C,oEAAoE;IACpE,OAAO,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAClD;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,CAChB,MAAM,EAAE,6BAA6B,KAClC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,iBAAiB,CAAC;IACxB,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,qEAAqE;IACrE,SAAS,EAAE,aAAa,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACtD;AAsUD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,2BAA2B,EACpC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,8BAA8B,CAAC,CAYzC;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,qBAAqB,CAAC,CA0HhC;AA0BD,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,iBAAiB,CAAC;IACtB,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,CAsC9B"}
|
package/dist/scheduler.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,wBAAwB,CAAC;AAGhC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAKL,UAAU,EAEX,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAI5B,KAAK,mBAAmB,EAGxB,KAAK,OAAO,EAEZ,KAAK,UAAU,EAEhB,MAAM,0BAA0B,CAAC;AAMlC,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
|
package/dist/scheduler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { buildReportDefinition } from "./compiler.js";
|
|
1
|
+
import { c as tenantColumn, n as buildReportDefinition } from "./chunks/compiler-IUB2YNgM.js";
|
|
2
2
|
import { REPORT_SCHEDULER_TABLES, REPORT_SCHEDULES_TABLE, assertReportTablesReady, scopeKeyForTenant } from "./state.js";
|
|
3
|
-
import { refreshReport
|
|
3
|
+
import { refreshReport } from "./refresh.js";
|
|
4
4
|
import { GlobalInterceptors, ObjectRegistry, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
|
|
5
5
|
import { TenantScoped, getTenantId, tenantId } from "@happyvertical/smrt-tenancy";
|
|
6
6
|
import { validateColumnName } from "@happyvertical/sql";
|
package/dist/scheduler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.js","names":["tenantId","error"],"sources":["../src/scheduler.ts"],"sourcesContent":["import './__smrt-register__.js';\n\nimport { 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 redactErrorForPersistence,\n redactErrorMessage,\n type SmrtJob,\n SmrtJobCollection,\n type TaskRunner,\n validateCronExpression,\n} from '@happyvertical/smrt-jobs';\nimport {\n getTenantId,\n TenantScoped,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport {\n type DatabaseInterface,\n type SqlAdapterType,\n validateColumnName,\n} from '@happyvertical/sql';\nimport { buildReportDefinition } from './compiler.js';\nimport { refreshReport, tenantColumn } 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 let tenantIds = args.tenantIds;\n // Only a signed global schedule can discover scopes. Tenant-bound jobs\n // (including older queued static schedules) retain their single scope.\n if (trigger === 'schedule' && jobTenantId === null) {\n const definition = await buildReportDefinition(reportCtor);\n if (definition.refresh?.tenantFanout === 'discover') {\n if (tenantIds !== undefined) {\n throw new Error(\n 'Discovered report refresh cannot also specify tenantIds',\n );\n }\n const sourceTenant = await tenantColumn(definition.sourceClassName);\n const reportTenant = await tenantColumn(definition.reportClassName);\n if (!sourceTenant || !reportTenant) {\n throw new Error(\n 'Tenant discovery requires tenant columns on the source and report',\n );\n }\n const column = validateColumnName(sourceTenant);\n const table = validateColumnName(definition.sourceTable);\n const materializedName = ObjectRegistry.getTableName(\n definition.reportClassName,\n );\n if (!materializedName)\n throw new Error('Report table is not registered');\n const materializedTable = validateColumnName(materializedName);\n const materializedTenant = validateColumnName(reportTenant);\n // Retain scopes with cached rows after their final source row disappears,\n // so a rebuild can clear stale groups without touching the global slice.\n const result = await this.db.query(\n `SELECT CAST(${column} AS TEXT) AS tenant_id FROM ${table} WHERE ${column} IS NOT NULL\n UNION\n SELECT CAST(${materializedTenant} AS TEXT) AS tenant_id FROM ${materializedTable} WHERE ${materializedTenant} IS NOT NULL\n ORDER BY tenant_id`,\n );\n tenantIds = result.rows.map((row) => {\n if (\n typeof row.tenant_id !== 'string' ||\n row.tenant_id.trim().length === 0\n ) {\n throw new Error('Invalid tenant id discovered in report source');\n }\n return row.tenant_id;\n });\n // An empty array otherwise means a global refresh in refreshReport.\n if (tenantIds.length === 0) {\n return {\n rowCount: 0,\n refreshedAt: new Date(),\n mode,\n tenantId: null,\n changedGroupCount: 0,\n tenantResults: [],\n };\n }\n }\n }\n return refreshReport(reportCtor, {\n db: this.db,\n mode,\n trigger,\n tenantId: jobTenantId,\n 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 =\n refresh.tenantFanout === true\n ? options.tenantIds\n : [null as string | null];\n if (\n refresh.tenantFanout === true &&\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 const values = {\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 const columns = Object.keys(values);\n await options.db.query(\n `INSERT INTO ${REPORT_SCHEDULES_TABLE} (${columns.join(', ')})\n VALUES (${columns.map((_, index) => `$${index + 1}`).join(', ')})\n ON CONFLICT (report_class, scope_key, cron, mode) DO NOTHING`,\n ...Object.values(values),\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 private readonly taskBindings = new WeakMap<object, () => void>();\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 /** Bind terminal report-job events once; call the returned function on shutdown. */\n bindTaskRunner(taskRunner: Pick<TaskRunner, 'on' | 'off'>): () => void {\n if (!this.db)\n throw new Error(\n 'ReportScheduleRunner not initialized. Call initialize() first.',\n );\n const existing = this.taskBindings.get(taskRunner);\n if (existing) return existing;\n const complete = (job: SmrtJob, success: boolean, error?: Error) => {\n if (\n job.objectType !== canonicalClassName(SmrtReportRefreshTask) ||\n job.method !== 'run'\n )\n return;\n const args = job.args;\n if (!args || typeof args !== 'object' || Array.isArray(args)) return;\n // The compatibility _scheduleId is unsigned; only the signed id may\n // identify the schedule whose counters this event is allowed to change.\n const scheduleId = args.scheduleId;\n if (typeof scheduleId !== 'string' || !scheduleId) return;\n try {\n assertReportRefreshJobIntegrity(args);\n if ((args.tenantId ?? null) !== (job.tenantId ?? null)) {\n throw new Error('Invalid report refresh completion tenant');\n }\n } catch (error) {\n this.emit(\n 'runner:error',\n error instanceof Error ? error : new Error(String(error)),\n );\n return;\n }\n void this.handleJobCompletion(scheduleId, success, error?.message).catch(\n (failure) => {\n this.emit(\n 'runner:error',\n failure instanceof Error ? failure : new Error(String(failure)),\n );\n },\n );\n };\n const completed = (job: SmrtJob) => complete(job, true);\n const failed = (job: SmrtJob, error: Error) => complete(job, false, error);\n taskRunner.on('job:completed', completed);\n taskRunner.on('job:failed', failed);\n const unbind = () => {\n if (this.taskBindings.get(taskRunner) !== unbind) return;\n taskRunner.off('job:completed', completed);\n taskRunner.off('job:failed', failed);\n this.taskBindings.delete(taskRunner);\n };\n this.taskBindings.set(taskRunner, unbind);\n return unbind;\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 = redactErrorMessage(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 CAST(id AS TEXT) AS id, CAST(tenant_id AS TEXT) AS tenant_id,\n report_class, cron, mode, trigger, queue, priority, timeout\n 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 redactErrorForPersistence(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","error"],"sources":["../src/scheduler.ts"],"sourcesContent":["import './__smrt-register__.js';\n\nimport { 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 redactErrorForPersistence,\n redactErrorMessage,\n type SmrtJob,\n SmrtJobCollection,\n type TaskRunner,\n validateCronExpression,\n} from '@happyvertical/smrt-jobs';\nimport {\n getTenantId,\n TenantScoped,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport {\n type DatabaseInterface,\n type SqlAdapterType,\n validateColumnName,\n} from '@happyvertical/sql';\nimport { buildReportDefinition } from './compiler.js';\nimport { refreshReport } from './refresh.js';\nimport { tenantColumn } from './registry-columns.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 let tenantIds = args.tenantIds;\n // Only a signed global schedule can discover scopes. Tenant-bound jobs\n // (including older queued static schedules) retain their single scope.\n if (trigger === 'schedule' && jobTenantId === null) {\n const definition = await buildReportDefinition(reportCtor);\n if (definition.refresh?.tenantFanout === 'discover') {\n if (tenantIds !== undefined) {\n throw new Error(\n 'Discovered report refresh cannot also specify tenantIds',\n );\n }\n const sourceTenant = await tenantColumn(definition.sourceClassName);\n const reportTenant = await tenantColumn(definition.reportClassName);\n if (!sourceTenant || !reportTenant) {\n throw new Error(\n 'Tenant discovery requires tenant columns on the source and report',\n );\n }\n const column = validateColumnName(sourceTenant);\n const table = validateColumnName(definition.sourceTable);\n const materializedName = ObjectRegistry.getTableName(\n definition.reportClassName,\n );\n if (!materializedName)\n throw new Error('Report table is not registered');\n const materializedTable = validateColumnName(materializedName);\n const materializedTenant = validateColumnName(reportTenant);\n // Retain scopes with cached rows after their final source row disappears,\n // so a rebuild can clear stale groups without touching the global slice.\n const result = await this.db.query(\n `SELECT CAST(${column} AS TEXT) AS tenant_id FROM ${table} WHERE ${column} IS NOT NULL\n UNION\n SELECT CAST(${materializedTenant} AS TEXT) AS tenant_id FROM ${materializedTable} WHERE ${materializedTenant} IS NOT NULL\n ORDER BY tenant_id`,\n );\n tenantIds = result.rows.map((row) => {\n if (\n typeof row.tenant_id !== 'string' ||\n row.tenant_id.trim().length === 0\n ) {\n throw new Error('Invalid tenant id discovered in report source');\n }\n return row.tenant_id;\n });\n // An empty array otherwise means a global refresh in refreshReport.\n if (tenantIds.length === 0) {\n return {\n rowCount: 0,\n refreshedAt: new Date(),\n mode,\n tenantId: null,\n changedGroupCount: 0,\n tenantResults: [],\n };\n }\n }\n }\n return refreshReport(reportCtor, {\n db: this.db,\n mode,\n trigger,\n tenantId: jobTenantId,\n 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 =\n refresh.tenantFanout === true\n ? options.tenantIds\n : [null as string | null];\n if (\n refresh.tenantFanout === true &&\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 const values = {\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 const columns = Object.keys(values);\n await options.db.query(\n `INSERT INTO ${REPORT_SCHEDULES_TABLE} (${columns.join(', ')})\n VALUES (${columns.map((_, index) => `$${index + 1}`).join(', ')})\n ON CONFLICT (report_class, scope_key, cron, mode) DO NOTHING`,\n ...Object.values(values),\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 private readonly taskBindings = new WeakMap<object, () => void>();\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 /** Bind terminal report-job events once; call the returned function on shutdown. */\n bindTaskRunner(taskRunner: Pick<TaskRunner, 'on' | 'off'>): () => void {\n if (!this.db)\n throw new Error(\n 'ReportScheduleRunner not initialized. Call initialize() first.',\n );\n const existing = this.taskBindings.get(taskRunner);\n if (existing) return existing;\n const complete = (job: SmrtJob, success: boolean, error?: Error) => {\n if (\n job.objectType !== canonicalClassName(SmrtReportRefreshTask) ||\n job.method !== 'run'\n )\n return;\n const args = job.args;\n if (!args || typeof args !== 'object' || Array.isArray(args)) return;\n // The compatibility _scheduleId is unsigned; only the signed id may\n // identify the schedule whose counters this event is allowed to change.\n const scheduleId = args.scheduleId;\n if (typeof scheduleId !== 'string' || !scheduleId) return;\n try {\n assertReportRefreshJobIntegrity(args);\n if ((args.tenantId ?? null) !== (job.tenantId ?? null)) {\n throw new Error('Invalid report refresh completion tenant');\n }\n } catch (error) {\n this.emit(\n 'runner:error',\n error instanceof Error ? error : new Error(String(error)),\n );\n return;\n }\n void this.handleJobCompletion(scheduleId, success, error?.message).catch(\n (failure) => {\n this.emit(\n 'runner:error',\n failure instanceof Error ? failure : new Error(String(failure)),\n );\n },\n );\n };\n const completed = (job: SmrtJob) => complete(job, true);\n const failed = (job: SmrtJob, error: Error) => complete(job, false, error);\n taskRunner.on('job:completed', completed);\n taskRunner.on('job:failed', failed);\n const unbind = () => {\n if (this.taskBindings.get(taskRunner) !== unbind) return;\n taskRunner.off('job:completed', completed);\n taskRunner.off('job:failed', failed);\n this.taskBindings.delete(taskRunner);\n };\n this.taskBindings.set(taskRunner, unbind);\n return unbind;\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 = redactErrorMessage(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 CAST(id AS TEXT) AS id, CAST(tenant_id AS TEXT) AS tenant_id,\n report_class, cron, mode, trigger, queue, priority, timeout\n 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 redactErrorForPersistence(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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"}
|