@happyvertical/smrt-reports 0.37.2 → 0.37.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"refresh.js","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\nfunction 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\nasync 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"],"names":["runId"],"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 {\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\nfunction 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\nasync 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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"}