@happyvertical/smrt-reports 0.57.35 → 0.57.37

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 CHANGED
@@ -39,6 +39,11 @@ component tests. `./playground` supplies each screen's preview.
39
39
  totals, and dimension facets. At source-query compilation,
40
40
  dimension and bucket filters compile to `WHERE`, aggregate-measure filters
41
41
  compile to `HAVING`, and mixed `OR`/`NOT` filter scopes fail closed.
42
+ - A column whose materialized schema column is neither `notNull` nor the
43
+ primary key also declares `isNull`/`isNotNull` (#4448), compiled to
44
+ `IS NULL`/`IS NOT NULL`. Report `ne`/`notIn` stay plain `<>` and exclude
45
+ nulls. The null tests are left out of `reportDefinitionFingerprint()`, so
46
+ views stored before them keep their binding.
42
47
  - `id` is the only row identity. It must be a non-empty persisted string and is
43
48
  never replaced by a display index or page position.
44
49
  - The descriptor is an exposure boundary. Sensitive/secret fields, fields with
@@ -122,7 +127,7 @@ See [core recipes](../core/agents/recipes.md).
122
127
 
123
128
  - Report cache tables are normal `@smrt()` tables. Runtime refresh must not create schema.
124
129
  - Store report metadata under field `_meta.__report`; scanner and runtime decorators must stay aligned.
125
- - Joins, row expressions and filtered measures (#3903) compile to the SDK's `AggregateSpec` (`fromAlias`, `joins`, `expr`, `filter`); expressions are literal data nodes because the scanner reads decorator arguments statically. Qualify every source column with `src` once a report joins (refresh filters do), put the tenant match in the join's `ON`, and joined reports rebuild (an incremental request is run as a rebuild), and drilldown refuses joined or computed group keys.
130
+ - Joins, row expressions and filtered measures (#3903) compile to the SDK's `AggregateSpec` (`fromAlias`, `joins`, `expr`, `filter`); expressions are literal data nodes because the scanner reads decorator arguments statically. Qualify every source column with `src` once a report joins (refresh filters do), put the tenant match in the join's `ON`, and joined reports rebuild (an incremental request is run as a rebuild), and drilldown refuses joined or computed group keys. A join key (or tenant pair) that is TEXT against UUID in the merged migration schema (text reference to a uuid id, #4441) compares as `asText` on both sides on every dialect but SQLite; `compileReportDefinition(definition, adapterType)` needs the real adapter for that, and same-typed keys stay uncast.
126
131
  - Keep SQL generation portable. Use the SDK `buildAggregate()`/`bucketExpr()` helpers for time buckets and `$N` placeholders.
127
132
  - Do not add a local aggregate SQL builder here; the implementation lives in `@happyvertical/sql`.
128
133
  - Refresh runtime tables are schema-managed. Runtime refresh must fail clearly when `_smrt_report_runs`, `_smrt_report_watermarks`, or `_smrt_report_locks` have not been migrated.
package/README.md CHANGED
@@ -106,6 +106,18 @@ class LabourByWorkOrder extends SmrtReport {
106
106
  `inner`. Aliases are lower-case identifiers and not `src`. Reference a joined
107
107
  field as `alias.field` in `@groupBy`, the aggregate and bucket decorators,
108
108
  expressions and `where`.
109
+ - **Key types.** `on` and `to` may be the same type (uuid = uuid, text = text),
110
+ compared as they are so their indexes serve the join, or a text field against a
111
+ uuid one, such as a polymorphic `workRefId` string pointing at a uuid `id`.
112
+ When one column is text and the other uuid in the schema, the join compares both as text
113
+ on PostgreSQL and DuckDB (which would otherwise refuse `text = uuid` or fail on
114
+ a non-uuid value), so a text value that is not a uuid simply matches nothing:
115
+ NULL on a left join, dropped on an inner one. A uuid compares in its canonical
116
+ lower-case form, so a reference stored in upper case does not match. SQLite
117
+ stores both as TEXT and compares them uncast. The cast column cannot use its
118
+ index; for a large table prefer a uuid-typed reference (`@foreignKey()` or
119
+ `@crossPackageRef()`). The same rule applies to the tenant columns. Other
120
+ type pairs (an integer against a decimal, say) are compared as they are.
109
121
  - **Tenancy is built into the join.** When the related model is tenant-scoped the
110
122
  join also matches `tenant_id` between the two rows, so an id that points at
111
123
  another tenant's row never matches. A tenant-scoped join from a source with no
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,37 @@ async function tenantColumn(className) {
15
15
  for (const [fieldName, field] of fields.entries()) if (field?._meta?.__tenancy?.isTenantIdField) return toSnakeCase(fieldName);
16
16
  return await fieldColumn(className, "tenantId");
17
17
  }
18
+ function columnTypeReader() {
19
+ let tables;
20
+ const merged = () => {
21
+ if (tables === void 0) try {
22
+ tables = ObjectRegistry.getAllSchemasAsDefinitions();
23
+ } catch {
24
+ tables = null;
25
+ }
26
+ return tables;
27
+ };
28
+ const upper = (type) => typeof type === "string" ? type.toUpperCase() : null;
29
+ return (className, column) => {
30
+ const table = ObjectRegistry.getTableName(className);
31
+ const fromTable = table ? upper(merged()?.[table]?.columns?.[column]?.type) : null;
32
+ if (fromTable) return fromTable;
33
+ const names = [className];
34
+ if (ObjectRegistry.getTableStrategy(className) === "sti") {
35
+ const base = ObjectRegistry.getSTIBase(className);
36
+ if (base) names.push(base);
37
+ }
38
+ for (const name of names) {
39
+ const type = upper(ObjectRegistry.getSchema(name)?.columns?.[column]?.type);
40
+ if (type) return type;
41
+ }
42
+ return null;
43
+ };
44
+ }
45
+ function compareAsText(types, left, right) {
46
+ const pair = [types(left.className, left.column), types(right.className, right.column)];
47
+ return pair.includes("UUID") && pair.includes("TEXT");
48
+ }
18
49
  function fieldPolicy(field) {
19
50
  const meta = field?._meta ?? {};
20
51
  const sensitivity = [field?.sensitivity, meta.sensitivity].find((value) => typeof value === "string");
@@ -103,6 +134,7 @@ async function resolveJoins(reportName, sourceName, joins) {
103
134
  const resolved = [];
104
135
  if (!joins) return resolved;
105
136
  const sourceTenant = await tenantColumn(sourceName);
137
+ const columnType = columnTypeReader();
106
138
  for (const [alias, join] of Object.entries(joins)) {
107
139
  if (!JOIN_ALIAS.test(alias) || alias === "src" || alias.length > 32) throw new Error(`${reportName} join alias '${alias}' must be lower-case letters, digits and underscores, start with a letter, and not be 'src'`);
108
140
  if (!join?.source || typeof join.on !== "string") throw new Error(`${reportName} join '${alias}' needs a source and an on field`);
@@ -117,6 +149,20 @@ async function resolveJoins(reportName, sourceName, joins) {
117
149
  const joinedTenant = await tenantColumn(className);
118
150
  if (joinedTenant && !sourceTenant) throw new Error(`${reportName} join '${alias}': ${className} is tenant-scoped but ${sourceName} has no tenant field to match it with`);
119
151
  const softDelete = await fieldColumn(className, "deletedAt");
152
+ const keyAsText = compareAsText(columnType, {
153
+ className: sourceName,
154
+ column: onColumn
155
+ }, {
156
+ className,
157
+ column: targetColumn
158
+ });
159
+ const tenantAsText = joinedTenant && sourceTenant ? compareAsText(columnType, {
160
+ className: sourceName,
161
+ column: sourceTenant
162
+ }, {
163
+ className,
164
+ column: joinedTenant
165
+ }) : false;
120
166
  resolved.push({
121
167
  alias,
122
168
  className,
@@ -124,9 +170,11 @@ async function resolveJoins(reportName, sourceName, joins) {
124
170
  type: join.type ?? "left",
125
171
  sourceColumn: onColumn,
126
172
  targetColumn,
173
+ ...keyAsText ? { keyAsText: true } : {},
127
174
  ...joinedTenant && sourceTenant ? { tenantColumns: {
128
175
  source: sourceTenant,
129
- joined: joinedTenant
176
+ joined: joinedTenant,
177
+ ...tenantAsText ? { asText: true } : {}
130
178
  } } : {},
131
179
  ...softDelete ? { softDeleteColumn: softDelete } : {}
132
180
  });
@@ -253,17 +301,25 @@ function getReportGroupingColumns(definition) {
253
301
  const columns = definition.fields.filter((field) => field.report?.kind === "group" || field.report?.kind === "bucket").map((field) => field.columnName ?? toSnakeCase(field.fieldName));
254
302
  return columns.length > 0 ? columns : ["id"];
255
303
  }
256
- function joinCondition(join) {
257
- const conditions = [{
304
+ function joinCondition(join, adapterType) {
305
+ const castable = adapterType !== "sqlite";
306
+ const equal = (left, right, asText) => asText && castable ? {
258
307
  op: "eq",
259
- left: { col: `src.${join.sourceColumn}` },
260
- right: { col: `${join.alias}.${join.targetColumn}` }
261
- }];
262
- if (join.tenantColumns) conditions.push({
308
+ left: {
309
+ op: "asText",
310
+ operand: { col: left }
311
+ },
312
+ right: {
313
+ op: "asText",
314
+ operand: { col: right }
315
+ }
316
+ } : {
263
317
  op: "eq",
264
- left: { col: `src.${join.tenantColumns.source}` },
265
- right: { col: `${join.alias}.${join.tenantColumns.joined}` }
266
- });
318
+ left: { col: left },
319
+ right: { col: right }
320
+ };
321
+ const conditions = [equal(`src.${join.sourceColumn}`, `${join.alias}.${join.targetColumn}`, join.keyAsText)];
322
+ if (join.tenantColumns) conditions.push(equal(`src.${join.tenantColumns.source}`, `${join.alias}.${join.tenantColumns.joined}`, join.tenantColumns.asText));
267
323
  if (join.softDeleteColumn) conditions.push({
268
324
  op: "isNull",
269
325
  operand: { col: `${join.alias}.${join.softDeleteColumn}` }
@@ -273,7 +329,7 @@ function joinCondition(join) {
273
329
  operands: conditions
274
330
  };
275
331
  }
276
- function compileReportDefinition(definition) {
332
+ function compileReportDefinition(definition, adapterType) {
277
333
  const select = [];
278
334
  const groupBy = [];
279
335
  const reportFieldToColumn = /* @__PURE__ */ new Map();
@@ -323,7 +379,7 @@ function compileReportDefinition(definition) {
323
379
  table: join.table,
324
380
  alias: join.alias,
325
381
  type: join.type,
326
- on: joinCondition(join)
382
+ on: joinCondition(join, adapterType)
327
383
  }))
328
384
  } : {},
329
385
  select,
@@ -338,4 +394,4 @@ async function compileReportSpec(reportCtor) {
338
394
  //#endregion
339
395
  export { getReportGroupingColumns as a, tenantColumn as c, compileReportSpec as i, buildReportDefinition as n, fieldColumn as o, compileReportDefinition as r, fieldPolicy as s, REPORT_SOURCE_ALIAS as t };
340
396
 
341
- //# sourceMappingURL=compiler-IUB2YNgM.js.map
397
+ //# sourceMappingURL=compiler-CqTiU1S6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler-CqTiU1S6.js","names":[],"sources":["../../src/registry-columns.ts","../../src/compiler.ts"],"sourcesContent":["import { ObjectRegistry } from '@happyvertical/smrt-core';\nimport { toSnakeCase } from '@happyvertical/smrt-core/utils';\n\nexport async 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\ntype SchemaTables = Record<\n string,\n { columns?: Record<string, { type?: unknown }> }\n>;\n\n/**\n * Reads column types for one report build. The tables come from the same\n * merged assembly migration uses (manifest columns plus runtime overlays such\n * as `tenantScoped`'s tenant_id), built at most once per build. If assembling\n * them fails for a reason elsewhere in the registry, types fall back to each\n * class's own built schema, and a column neither describes is unknown.\n */\nexport function columnTypeReader(): (\n className: string,\n column: string,\n) => string | null {\n let tables: SchemaTables | null | undefined;\n const merged = (): SchemaTables | null => {\n if (tables === undefined) {\n try {\n tables = ObjectRegistry.getAllSchemasAsDefinitions() as SchemaTables;\n } catch {\n tables = null;\n }\n }\n return tables;\n };\n const upper = (type: unknown) =>\n typeof type === 'string' ? type.toUpperCase() : null;\n\n return (className, column) => {\n const table = ObjectRegistry.getTableName(className);\n const fromTable = table\n ? upper(merged()?.[table]?.columns?.[column]?.type)\n : null;\n if (fromTable) return fromTable;\n // An STI child's columns live on its base table's schema.\n const names = [className];\n if (ObjectRegistry.getTableStrategy(className) === 'sti') {\n const base = ObjectRegistry.getSTIBase(className);\n if (base) names.push(base);\n }\n for (const name of names) {\n const type = upper(\n ObjectRegistry.getSchema(name)?.columns?.[column]?.type,\n );\n if (type) return type;\n }\n return null;\n };\n}\n\n/**\n * Whether two key columns must be compared as text: one is UUID and the other\n * TEXT (a text reference against a uuid id, which Postgres has no operator\n * for). Every other pair, same-typed, undescribed or another mismatch,\n * compares as it is, so indexes still serve the join and numeric keys are\n * never compared by their text form.\n */\nexport function compareAsText(\n types: (className: string, column: string) => string | null,\n left: { className: string; column: string },\n right: { className: string; column: string },\n): boolean {\n const pair = [\n types(left.className, left.column),\n types(right.className, right.column),\n ];\n return pair.includes('UUID') && pair.includes('TEXT');\n}\n\n/** The registry field properties that decide who may read a column. */\nexport interface PolicyField {\n sensitive?: boolean;\n readPermission?: string;\n sensitivity?: unknown;\n _meta?: Record<string, unknown>;\n}\n\nexport function fieldPolicy(field: PolicyField | undefined): {\n sensitive: boolean;\n readPermission?: string;\n} {\n const meta = field?._meta ?? {};\n const sensitivity = [field?.sensitivity, meta.sensitivity].find(\n (value): value is string => typeof value === 'string',\n );\n return {\n sensitive:\n field?.sensitive === true ||\n meta.sensitive === true ||\n sensitivity === 'sensitive' ||\n sensitivity === 'secret',\n readPermission:\n typeof field?.readPermission === 'string'\n ? field.readPermission\n : typeof meta.readPermission === 'string'\n ? meta.readPermission\n : undefined,\n };\n}\n","import { ObjectRegistry, type SmrtObject } from '@happyvertical/smrt-core';\nimport { toSnakeCase } from '@happyvertical/smrt-core/utils';\nimport type {\n AggregateExpression,\n SqlAdapterType,\n WhereClause,\n} from '@happyvertical/sql';\nimport { getRuntimeReportOptions } from './decorators.js';\nimport {\n columnTypeReader,\n compareAsText,\n fieldColumn,\n fieldPolicy,\n tenantColumn,\n} from './registry-columns.js';\nimport type {\n AggregateSelectExpr,\n AggregateSpec,\n ReportDefinition,\n ReportFieldDefinition,\n ReportFieldMetadata,\n ReportJoinDefinition,\n ReportOperand,\n ReportOptions,\n ReportRelatedFieldPolicy,\n ReportSource,\n} from './types.js';\n\n/** The report source's alias in a query that joins other tables. */\nexport const REPORT_SOURCE_ALIAS = 'src';\n\n/** Lower-case, so snake-casing a column reference leaves the alias alone. */\nconst JOIN_ALIAS = /^[a-z][a-z0-9_]*$/;\n\nconst REPORT_BASE_FIELDS = new Set([\n 'id',\n 'slug',\n 'context',\n 'created_at',\n 'updated_at',\n 'createdAt',\n 'updatedAt',\n 'refreshedAt',\n]);\n\nfunction isConstructor(\n value: ReportSource,\n): value is new (\n ...args: any[]\n) => SmrtObject {\n return typeof value === 'function';\n}\n\nfunction sourceClassName(source: ReportSource): string {\n if (typeof source === 'string') return source;\n return source.name;\n}\n\nfunction reportOptionsFromRegistry(\n reportCtor: new (...args: any[]) => SmrtObject,\n): ReportOptions | undefined {\n const registered =\n ObjectRegistry.getClassByConstructor(reportCtor) ??\n ObjectRegistry.getClass(reportCtor.name);\n return (registered?.config as { report?: ReportOptions } | undefined)?.report;\n}\n\nfunction getReportOptions(\n reportCtor: new (...args: any[]) => SmrtObject,\n): ReportOptions {\n const options =\n getRuntimeReportOptions(reportCtor) ??\n reportOptionsFromRegistry(reportCtor);\n if (!options?.source) {\n throw new Error(\n `${reportCtor.name} is missing @report({ source }) metadata. ` +\n `Decorate the report class with @report({ source: SourceClass }).`,\n );\n }\n return options;\n}\n\nfunction getRegisteredClassName(\n ctor: new (...args: any[]) => SmrtObject,\n): string {\n const registered =\n ObjectRegistry.getClassByConstructor(ctor) ??\n ObjectRegistry.getClass(ctor.name);\n return registered?.qualifiedName ?? registered?.name ?? ctor.name;\n}\n\nfunction resolveTableName(className: string): string {\n const tableName = ObjectRegistry.getTableName(className);\n if (!tableName) {\n throw new Error(\n `No SMRT table registered for ${className}. ` +\n `Ensure the class is decorated with @smrt() and its manifest is loaded.`,\n );\n }\n return tableName;\n}\n\nfunction parseWhereKey(key: string): { field: string; suffix: string } {\n const trimmed = key.trim();\n const operators = [\n ' is not null',\n ' is null',\n ' not in',\n ' like',\n ' in',\n ' !=',\n ' >=',\n ' <=',\n ' >',\n ' <',\n ' =',\n ];\n const lower = trimmed.toLowerCase();\n for (const operator of operators) {\n if (lower.endsWith(operator)) {\n return {\n field: trimmed.slice(0, -operator.length).trim(),\n suffix: operator,\n };\n }\n }\n return { field: trimmed, suffix: '' };\n}\n\nfunction normalizeConditionKey(\n key: string,\n mapField: (field: string) => string,\n): string {\n const { field, suffix } = parseWhereKey(key);\n const mapped = mapField(field);\n return suffix ? `${mapped}${suffix}` : mapped;\n}\n\nfunction normalizeWhere(\n where: WhereClause | undefined,\n mapField: (field: string) => string,\n): WhereClause | undefined {\n if (!where) return undefined;\n\n if (Array.isArray(where)) {\n return where.map((andGroup) =>\n andGroup.map((condition: Record<string, any>) =>\n Object.fromEntries(\n Object.entries(condition).map(([key, value]) => [\n normalizeConditionKey(key, mapField),\n value,\n ]),\n ),\n ),\n );\n }\n\n return Object.fromEntries(\n Object.entries(where).map(([key, value]) => [\n normalizeConditionKey(key, mapField),\n value,\n ]),\n );\n}\n\nfunction readReportMetadata(field: any): ReportFieldMetadata | undefined {\n return field?._meta?.__report ?? field?.__report;\n}\n\nfunction isReportSystemField(fieldName: string, field: any): boolean {\n return (\n REPORT_BASE_FIELDS.has(fieldName) ||\n field?._meta?.__smrtSystemField ||\n field?._meta?.__tenancy?.isTenantIdField\n );\n}\n\nasync function resolveJoins(\n reportName: string,\n sourceName: string,\n joins: ReportOptions['joins'],\n): Promise<ReportJoinDefinition[]> {\n const resolved: ReportJoinDefinition[] = [];\n if (!joins) return resolved;\n const sourceTenant = await tenantColumn(sourceName);\n const columnType = columnTypeReader();\n\n for (const [alias, join] of Object.entries(joins)) {\n if (\n !JOIN_ALIAS.test(alias) ||\n alias === REPORT_SOURCE_ALIAS ||\n alias.length > 32\n ) {\n throw new Error(\n `${reportName} join alias '${alias}' must be lower-case letters, digits and underscores, start with a letter, and not be '${REPORT_SOURCE_ALIAS}'`,\n );\n }\n if (!join?.source || typeof join.on !== 'string') {\n throw new Error(\n `${reportName} join '${alias}' needs a source and an on field`,\n );\n }\n if (\n join.type !== undefined &&\n join.type !== 'inner' &&\n join.type !== 'left'\n ) {\n throw new Error(\n `${reportName} join '${alias}' type must be 'inner' or 'left'`,\n );\n }\n const className = sourceClassName(join.source);\n const table = resolveTableName(className);\n const onColumn = await fieldColumn(sourceName, join.on);\n if (!onColumn) {\n throw new Error(\n `${reportName} join '${alias}': ${sourceName} has no field '${join.on}'`,\n );\n }\n const to = join.to ?? 'id';\n const targetColumn = to === 'id' ? 'id' : await fieldColumn(className, to);\n if (!targetColumn) {\n throw new Error(\n `${reportName} join '${alias}': ${className} has no field '${to}'`,\n );\n }\n\n // A tenant-scoped related model is matched within the source row's tenant,\n // so ids that collide across tenants never join. A source with no tenant\n // cannot say which tenant's related rows to match.\n const joinedTenant = await tenantColumn(className);\n if (joinedTenant && !sourceTenant) {\n throw new Error(\n `${reportName} join '${alias}': ${className} is tenant-scoped but ${sourceName} has no tenant field to match it with`,\n );\n }\n const softDelete = await fieldColumn(className, 'deletedAt');\n // Keys of different schema types (a text reference to a uuid id) compare\n // as text: Postgres has no text = uuid operator, and a text value that is\n // not a uuid then matches nothing instead of failing the rebuild.\n const keyAsText = compareAsText(\n columnType,\n { className: sourceName, column: onColumn },\n { className, column: targetColumn },\n );\n const tenantAsText =\n joinedTenant && sourceTenant\n ? compareAsText(\n columnType,\n { className: sourceName, column: sourceTenant },\n { className, column: joinedTenant },\n )\n : false;\n resolved.push({\n alias,\n className,\n table,\n type: join.type ?? 'left',\n sourceColumn: onColumn,\n targetColumn,\n ...(keyAsText ? { keyAsText: true } : {}),\n ...(joinedTenant && sourceTenant\n ? {\n tenantColumns: {\n source: sourceTenant,\n joined: joinedTenant,\n ...(tenantAsText ? { asText: true } : {}),\n },\n }\n : {}),\n ...(softDelete ? { softDeleteColumn: softDelete } : {}),\n });\n }\n return resolved;\n}\n\nfunction operandColumns(node: unknown, found: string[] = []): string[] {\n if (!node || typeof node !== 'object') return found;\n const record = node as Record<string, unknown>;\n if (typeof record.col === 'string') found.push(record.col);\n for (const key of ['left', 'right', 'later', 'earlier', 'operand']) {\n operandColumns(record[key], found);\n }\n if (Array.isArray(record.operands)) {\n for (const operand of record.operands) operandColumns(operand, found);\n }\n return found;\n}\n\n/** Every model field a report field reads, as written (`status`, `wo.title`). */\nfunction referencedColumns(\n fieldName: string,\n report: ReportFieldMetadata,\n): string[] {\n if (report.kind === 'group') {\n return report.expr\n ? operandColumns(report.expr)\n : [report.sourceColumn ?? fieldName];\n }\n if (report.kind === 'bucket') return [report.sourceColumn];\n return [\n ...(report.column ? [report.column] : []),\n ...operandColumns(report.expr),\n ...operandColumns(report.filter),\n ];\n}\n\nasync function relatedPolicies(\n reportName: string,\n fieldName: string,\n report: ReportFieldMetadata,\n joins: ReportJoinDefinition[],\n): Promise<ReportRelatedFieldPolicy[]> {\n const policies: ReportRelatedFieldPolicy[] = [];\n for (const ref of referencedColumns(fieldName, report)) {\n if (!ref.includes('.')) continue;\n const parts = ref.split('.');\n const join =\n parts.length === 2 ? joins.find((j) => j.alias === parts[0]) : undefined;\n if (!join) {\n throw new Error(\n `${reportName}.${fieldName} reads '${ref}', which is not a column of a declared join`,\n );\n }\n const related = parts[1];\n const registryFields = await ObjectRegistry.getAllFields(join.className);\n const key = toSnakeCase(related);\n const found = [...registryFields.entries()].find(\n ([name]) => name === related || toSnakeCase(name) === key,\n );\n if (!found && key !== 'id') {\n throw new Error(\n `${reportName}.${fieldName} reads '${ref}', but ${join.className} has no field '${related}'`,\n );\n }\n const policy = fieldPolicy(found?.[1] as Parameters<typeof fieldPolicy>[0]);\n policies.push({\n alias: join.alias,\n fieldName: related,\n sensitive: policy.sensitive,\n ...(policy.readPermission\n ? { readPermission: policy.readPermission }\n : {}),\n });\n }\n return policies;\n}\n\nexport async function buildReportDefinition(\n reportCtor: new (...args: any[]) => SmrtObject,\n): Promise<ReportDefinition> {\n const options = getReportOptions(reportCtor);\n const reportClassName = getRegisteredClassName(reportCtor);\n const sourceName = sourceClassName(options.source);\n const sourceTable = resolveTableName(sourceName);\n const fields = await ObjectRegistry.getAllFields(reportClassName);\n const joins = await resolveJoins(reportCtor.name, sourceName, options.joins);\n if (joins.length > 0 && options.refresh?.mode === 'incremental') {\n throw new Error(\n `${reportCtor.name} joins ${joins.map((join) => join.className).join(', ')} and cannot refresh incrementally: ` +\n `a change to a joined row does not move the source watermark. Use mode: 'rebuild'.`,\n );\n }\n\n const reportFields: ReportFieldDefinition[] = [];\n for (const [fieldName, field] of fields.entries()) {\n const report = readReportMetadata(field);\n if (!report) {\n if (!isReportSystemField(fieldName, field) && !field?.transient) {\n throw new Error(\n `Report field ${reportCtor.name}.${fieldName} is neither a grouping key nor an aggregate. ` +\n `Add @groupBy(), a time-bucket decorator, or an aggregate decorator.`,\n );\n }\n continue;\n }\n\n const related = await relatedPolicies(\n reportCtor.name,\n fieldName,\n report,\n joins,\n );\n reportFields.push({\n fieldName,\n columnName: toSnakeCase(fieldName),\n type: field.type,\n report,\n ...(related.length > 0 ? { related } : {}),\n });\n }\n\n return {\n reportClassName,\n sourceClassName: sourceName,\n sourceTable,\n ...(joins.length > 0 ? { joins } : {}),\n fields: reportFields,\n where: options.where,\n having: options.having,\n refresh: options.refresh,\n };\n}\n\n/**\n * A model field as a query column. Without joins it is the bare column, as\n * before. With joins every column is qualified: a bare field by the source\n * alias, `alias.field` by its join, so a column the source and a joined table\n * share (`tenant_id`, `deleted_at`) is never ambiguous.\n */\nfunction columnFor(\n ref: string,\n definition: Pick<ReportDefinition, 'joins'>,\n): string {\n const joins = definition.joins ?? [];\n if (!ref.includes('.')) {\n const column = toSnakeCase(ref);\n return joins.length > 0 ? `${REPORT_SOURCE_ALIAS}.${column}` : column;\n }\n const parts = ref.split('.');\n if (parts.length !== 2 || !joins.some((join) => join.alias === parts[0])) {\n throw new Error(\n `Report column '${ref}' is not a column of a declared join`,\n );\n }\n return `${parts[0]}.${toSnakeCase(parts[1])}`;\n}\n\nfunction mapOperand(\n node: ReportOperand,\n mapColumn: (ref: string) => string,\n): ReportOperand {\n if (!node || typeof node !== 'object') return node;\n const record = node as Record<string, unknown>;\n if (typeof record.col === 'string') return { col: mapColumn(record.col) };\n const out: Record<string, unknown> = { ...record };\n for (const key of ['left', 'right', 'later', 'earlier', 'operand']) {\n if (record[key] !== undefined) {\n out[key] = mapOperand(record[key] as ReportOperand, mapColumn);\n }\n }\n if (Array.isArray(record.operands)) {\n out.operands = record.operands.map((operand) =>\n mapOperand(operand as ReportOperand, mapColumn),\n );\n }\n return out as ReportOperand;\n}\n\nfunction sourceColumn(\n field: ReportFieldDefinition,\n definition: Pick<ReportDefinition, 'joins'>,\n): string {\n const report = field.report;\n if (!report) {\n throw new Error(`Report field ${field.fieldName} is missing metadata`);\n }\n if (report.kind === 'group') {\n return columnFor(report.sourceColumn ?? field.fieldName, definition);\n }\n if (report.kind === 'bucket') {\n return columnFor(report.sourceColumn, definition);\n }\n if (report.kind === 'aggregate') {\n if (!report.column) return '';\n return columnFor(report.column, definition);\n }\n return '';\n}\n\nexport function getReportGroupingColumns(\n definition: Pick<ReportDefinition, 'fields'>,\n): string[] {\n const columns = 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 return columns.length > 0 ? columns : ['id'];\n}\n\n/** The join condition: the key, the tenant match, and no soft-deleted related row. */\nfunction joinCondition(\n join: ReportJoinDefinition,\n adapterType: SqlAdapterType | undefined,\n): AggregateExpression {\n // SQLite stores a uuid as TEXT, so its keys already compare; a cast there\n // would only stop the joined id's index serving the join.\n const castable = adapterType !== 'sqlite';\n const equal = (\n left: string,\n right: string,\n asText: boolean | undefined,\n ): AggregateExpression =>\n asText && castable\n ? {\n op: 'eq',\n left: { op: 'asText', operand: { col: left } },\n right: { op: 'asText', operand: { col: right } },\n }\n : { op: 'eq', left: { col: left }, right: { col: right } };\n const conditions: AggregateExpression[] = [\n equal(\n `${REPORT_SOURCE_ALIAS}.${join.sourceColumn}`,\n `${join.alias}.${join.targetColumn}`,\n join.keyAsText,\n ),\n ];\n if (join.tenantColumns) {\n conditions.push(\n equal(\n `${REPORT_SOURCE_ALIAS}.${join.tenantColumns.source}`,\n `${join.alias}.${join.tenantColumns.joined}`,\n join.tenantColumns.asText,\n ),\n );\n }\n if (join.softDeleteColumn) {\n conditions.push({\n op: 'isNull',\n operand: { col: `${join.alias}.${join.softDeleteColumn}` },\n });\n }\n return conditions.length === 1\n ? conditions[0]\n : { op: 'and', operands: conditions };\n}\n\n/**\n * Compile a report definition to an aggregate spec. `adapterType` is the\n * database the spec will run on: a join whose key columns have different\n * schema types (a text reference to a uuid id) compares them as text, except\n * on SQLite, where both are already TEXT. Omitted, the join casts.\n */\nexport function compileReportDefinition(\n definition: ReportDefinition,\n adapterType?: SqlAdapterType,\n): AggregateSpec {\n const select: AggregateSelectExpr[] = [];\n const groupBy: string[] = [];\n const reportFieldToColumn = new Map<string, string>();\n const mapColumn = (ref: string) => columnFor(ref, definition);\n\n for (const field of definition.fields) {\n const outputColumn = field.columnName ?? toSnakeCase(field.fieldName);\n reportFieldToColumn.set(field.fieldName, outputColumn);\n reportFieldToColumn.set(outputColumn, outputColumn);\n\n const report = field.report;\n if (!report) continue;\n\n if (report.kind === 'group') {\n select.push(\n report.expr\n ? { expr: mapOperand(report.expr, mapColumn), as: outputColumn }\n : { column: sourceColumn(field, definition), as: outputColumn },\n );\n groupBy.push(outputColumn);\n continue;\n }\n\n if (report.kind === 'bucket') {\n select.push({\n bucket: report.unit,\n column: sourceColumn(field, definition),\n as: outputColumn,\n });\n groupBy.push(outputColumn);\n continue;\n }\n\n select.push({\n fn: report.fn,\n column: report.column ? sourceColumn(field, definition) : undefined,\n ...(report.expr ? { expr: mapOperand(report.expr, mapColumn) } : {}),\n ...(report.filter\n ? { filter: mapOperand(report.filter, mapColumn) }\n : {}),\n as: outputColumn,\n distinct: report.distinct,\n });\n }\n\n if (select.length === 0) {\n throw new Error(\n `${definition.reportClassName} has no report fields. ` +\n `Add @groupBy(), a time-bucket decorator, or an aggregate decorator.`,\n );\n }\n\n const joins = definition.joins ?? [];\n return {\n from: definition.sourceTable,\n ...(joins.length > 0\n ? {\n fromAlias: REPORT_SOURCE_ALIAS,\n joins: joins.map((join) => ({\n table: join.table,\n alias: join.alias,\n type: join.type,\n on: joinCondition(join, adapterType),\n })),\n }\n : {}),\n select,\n groupBy,\n where: normalizeWhere(definition.where, mapColumn),\n having: normalizeWhere(\n definition.having,\n (field) => reportFieldToColumn.get(field) ?? toSnakeCase(field),\n ),\n };\n}\n\nexport async function compileReportSpec(\n reportCtor: new (...args: any[]) => SmrtObject,\n): Promise<AggregateSpec> {\n return compileReportDefinition(await buildReportDefinition(reportCtor));\n}\n"],"mappings":";;;;AAGA,eAAsB,YACpB,WACA,WACwB;CACxB,MAAM,SAAS,MAAM,eAAe,aAAa,SAAS;CAC1D,MAAM,kBAAkB,YAAY,SAAS;CAE7C,KAAA,MAAW,CAAC,cAAc,OAAO,QAAQ,GACvC,IAAI,cAAc,aAAa,YAAY,SAAS,MAAM,iBACxD,OAAO;CAIX,OAAO;AACT;AAEA,eAAsB,aAAa,WAA2C;CAE5E,MAAM,kBADa,eAAe,SAAS,SACnB,CAAA,EAAY,oBAAoB;CACxD,IAAI,iBACF,OAAO,YAAY,eAAe;CAGpC,MAAM,SAAS,MAAM,eAAe,aAAa,SAAS;CAC1D,KAAA,MAAW,CAAC,WAAW,UAAU,OAAO,QAAQ,GAC9C,IAAI,OAAO,OAAO,WAAW,iBAC3B,OAAO,YAAY,SAAS;CAKhC,OAAO,MADqB,YAAY,WAAW,UAAU;AAE/D;AAcO,SAAS,mBAGG;CACjB,IAAI;CACJ,MAAM,eAAoC;EACxC,IAAI,WAAW,KAAA,GACb,IAAI;GACF,SAAS,eAAe,2BAA2B;EACrD,QAAQ;GACN,SAAS;EACX;EAEF,OAAO;CACT;CACA,MAAM,SAAS,SACb,OAAO,SAAS,WAAW,KAAK,YAAY,IAAI;CAElD,QAAQ,WAAW,WAAW;EAC5B,MAAM,QAAQ,eAAe,aAAa,SAAS;EACnD,MAAM,YAAY,QACd,MAAM,OAAO,CAAA,GAAI,MAAK,EAAG,UAAU,OAAM,EAAG,IAAI,IAChD;EACJ,IAAI,WAAW,OAAO;EAEtB,MAAM,QAAQ,CAAC,SAAS;EACxB,IAAI,eAAe,iBAAiB,SAAS,MAAM,OAAO;GACxD,MAAM,OAAO,eAAe,WAAW,SAAS;GAChD,IAAI,MAAM,MAAM,KAAK,IAAI;EAC3B;EACA,KAAA,MAAW,QAAQ,OAAO;GACxB,MAAM,OAAO,MACX,eAAe,UAAU,IAAI,CAAA,EAAG,UAAU,OAAM,EAAG,IACrD;GACA,IAAI,MAAM,OAAO;EACnB;EACA,OAAO;CACT;AACF;AASO,SAAS,cACd,OACA,MACA,OACS;CACT,MAAM,OAAO,CACX,MAAM,KAAK,WAAW,KAAK,MAAM,GACjC,MAAM,MAAM,WAAW,MAAM,MAAM,CACrC;CACA,OAAO,KAAK,SAAS,MAAM,KAAK,KAAK,SAAS,MAAM;AACtD;AAUO,SAAS,YAAY,OAG1B;CACA,MAAM,OAAO,OAAO,SAAS,CAAC;CAC9B,MAAM,cAAc,CAAC,OAAO,aAAa,KAAK,WAAW,CAAA,CAAE,MACxD,UAA2B,OAAO,UAAU,QAC/C;CACA,OAAO;EACL,WACE,OAAO,cAAc,QACrB,KAAK,cAAc,QACnB,gBAAgB,eAChB,gBAAgB;EAClB,gBACE,OAAO,OAAO,mBAAmB,WAC7B,MAAM,iBACN,OAAO,KAAK,mBAAmB,WAC7B,KAAK,iBACL,KAAA;CACV;AACF;;;AC5GO,IAAM,sBAAsB;AAGnC,IAAM,aAAa;AAEnB,IAAM,qCAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAUD,SAAS,gBAAgB,QAA8B;CACrD,IAAI,OAAO,WAAW,UAAU,OAAO;CACvC,OAAO,OAAO;AAChB;AAEA,SAAS,0BACP,YAC2B;CAI3B,QAFE,eAAe,sBAAsB,UAAU,KAC/C,eAAe,SAAS,WAAW,IAAI,EAAA,EACrB,QAAmD;AACzE;AAEA,SAAS,iBACP,YACe;CACf,MAAM,UACJ,wBAAwB,UAAU,KAClC,0BAA0B,UAAU;CACtC,IAAI,CAAC,SAAS,QACZ,MAAM,IAAI,MACR,GAAG,WAAW,KAAI,2GAEpB;CAEF,OAAO;AACT;AAEA,SAAS,uBACP,MACQ;CACR,MAAM,aACJ,eAAe,sBAAsB,IAAI,KACzC,eAAe,SAAS,KAAK,IAAI;CACnC,OAAO,YAAY,iBAAiB,YAAY,QAAQ,KAAK;AAC/D;AAEA,SAAS,iBAAiB,WAA2B;CACnD,MAAM,YAAY,eAAe,aAAa,SAAS;CACvD,IAAI,CAAC,WACH,MAAM,IAAI,MACR,gCAAgC,UAAS,yEAE3C;CAEF,OAAO;AACT;AAEA,SAAS,cAAc,KAAgD;CACrE,MAAM,UAAU,IAAI,KAAK;CACzB,MAAM,YAAY;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,MAAM,QAAQ,QAAQ,YAAY;CAClC,KAAA,MAAW,YAAY,WACrB,IAAI,MAAM,SAAS,QAAQ,GACzB,OAAO;EACL,OAAO,QAAQ,MAAM,GAAG,CAAC,SAAS,MAAM,CAAA,CAAE,KAAK;EAC/C,QAAQ;CACV;CAGJ,OAAO;EAAE,OAAO;EAAS,QAAQ;CAAG;AACtC;AAEA,SAAS,sBACP,KACA,UACQ;CACR,MAAM,EAAE,OAAO,WAAW,cAAc,GAAG;CAC3C,MAAM,SAAS,SAAS,KAAK;CAC7B,OAAO,SAAS,GAAG,SAAS,WAAW;AACzC;AAEA,SAAS,eACP,OACA,UACyB;CACzB,IAAI,CAAC,OAAO,OAAO,KAAA;CAEnB,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,aAChB,SAAS,KAAK,cACZ,OAAO,YACL,OAAO,QAAQ,SAAS,CAAA,CAAE,KAAK,CAAC,KAAK,WAAW,CAC9C,sBAAsB,KAAK,QAAQ,GACnC,KACF,CAAC,CACH,CACF,CACF;CAGF,OAAO,OAAO,YACZ,OAAO,QAAQ,KAAK,CAAA,CAAE,KAAK,CAAC,KAAK,WAAW,CAC1C,sBAAsB,KAAK,QAAQ,GACnC,KACF,CAAC,CACH;AACF;AAEA,SAAS,mBAAmB,OAA6C;CACvE,OAAO,OAAO,OAAO,YAAY,OAAO;AAC1C;AAEA,SAAS,oBAAoB,WAAmB,OAAqB;CACnE,OACE,mBAAmB,IAAI,SAAS,KAChC,OAAO,OAAO,qBACd,OAAO,OAAO,WAAW;AAE7B;AAEA,eAAe,aACb,YACA,YACA,OACiC;CACjC,MAAM,WAAmC,CAAC;CAC1C,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,eAAe,MAAM,aAAa,UAAU;CAClD,MAAM,aAAa,iBAAiB;CAEpC,KAAA,MAAW,CAAC,OAAO,SAAS,OAAO,QAAQ,KAAK,GAAG;EACjD,IACE,CAAC,WAAW,KAAK,KAAK,KACtB,UAAA,SACA,MAAM,SAAS,IAEf,MAAM,IAAI,MACR,GAAG,WAAU,eAAgB,MAAK,4FACpC;EAEF,IAAI,CAAC,MAAM,UAAU,OAAO,KAAK,OAAO,UACtC,MAAM,IAAI,MACR,GAAG,WAAU,SAAU,MAAK,iCAC9B;EAEF,IACE,KAAK,SAAS,KAAA,KACd,KAAK,SAAS,WACd,KAAK,SAAS,QAEd,MAAM,IAAI,MACR,GAAG,WAAU,SAAU,MAAK,iCAC9B;EAEF,MAAM,YAAY,gBAAgB,KAAK,MAAM;EAC7C,MAAM,QAAQ,iBAAiB,SAAS;EACxC,MAAM,WAAW,MAAM,YAAY,YAAY,KAAK,EAAE;EACtD,IAAI,CAAC,UACH,MAAM,IAAI,MACR,GAAG,WAAU,SAAU,MAAK,KAAM,WAAU,iBAAkB,KAAK,GAAE,EACvE;EAEF,MAAM,KAAK,KAAK,MAAM;EACtB,MAAM,eAAe,OAAO,OAAO,OAAO,MAAM,YAAY,WAAW,EAAE;EACzE,IAAI,CAAC,cACH,MAAM,IAAI,MACR,GAAG,WAAU,SAAU,MAAK,KAAM,UAAS,iBAAkB,GAAE,EACjE;EAMF,MAAM,eAAe,MAAM,aAAa,SAAS;EACjD,IAAI,gBAAgB,CAAC,cACnB,MAAM,IAAI,MACR,GAAG,WAAU,SAAU,MAAK,KAAM,UAAS,wBAAyB,WAAU,sCAChF;EAEF,MAAM,aAAa,MAAM,YAAY,WAAW,WAAW;EAI3D,MAAM,YAAY,cAChB,YACA;GAAE,WAAW;GAAY,QAAQ;EAAS,GAC1C;GAAE;GAAW,QAAQ;EAAa,CACpC;EACA,MAAM,eACJ,gBAAgB,eACZ,cACE,YACA;GAAE,WAAW;GAAY,QAAQ;EAAa,GAC9C;GAAE;GAAW,QAAQ;EAAa,CACpC,IACA;EACN,SAAS,KAAK;GACZ;GACA;GACA;GACA,MAAM,KAAK,QAAQ;GACnB,cAAc;GACd;GACA,GAAI,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;GACvC,GAAI,gBAAgB,eAChB,EACE,eAAe;IACb,QAAQ;IACR,QAAQ;IACR,GAAI,eAAe,EAAE,QAAQ,KAAK,IAAI,CAAC;GACzC,EACF,IACA,CAAC;GACL,GAAI,aAAa,EAAE,kBAAkB,WAAW,IAAI,CAAC;EACvD,CAAC;CACH;CACA,OAAO;AACT;AAEA,SAAS,eAAe,MAAe,QAAkB,CAAC,GAAa;CACrE,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO;CAC9C,MAAM,SAAS;CACf,IAAI,OAAO,OAAO,QAAQ,UAAU,MAAM,KAAK,OAAO,GAAG;CACzD,KAAA,MAAW,OAAO;EAAC;EAAQ;EAAS;EAAS;EAAW;CAAS,GAC/D,eAAe,OAAO,MAAM,KAAK;CAEnC,IAAI,MAAM,QAAQ,OAAO,QAAQ,GAC/B,KAAA,MAAW,WAAW,OAAO,UAAU,eAAe,SAAS,KAAK;CAEtE,OAAO;AACT;AAGA,SAAS,kBACP,WACA,QACU;CACV,IAAI,OAAO,SAAS,SAClB,OAAO,OAAO,OACV,eAAe,OAAO,IAAI,IAC1B,CAAC,OAAO,gBAAgB,SAAS;CAEvC,IAAI,OAAO,SAAS,UAAU,OAAO,CAAC,OAAO,YAAY;CACzD,OAAO;EACL,GAAI,OAAO,SAAS,CAAC,OAAO,MAAM,IAAI,CAAC;EACvC,GAAG,eAAe,OAAO,IAAI;EAC7B,GAAG,eAAe,OAAO,MAAM;CACjC;AACF;AAEA,eAAe,gBACb,YACA,WACA,QACA,OACqC;CACrC,MAAM,WAAuC,CAAC;CAC9C,KAAA,MAAW,OAAO,kBAAkB,WAAW,MAAM,GAAG;EACtD,IAAI,CAAC,IAAI,SAAS,GAAG,GAAG;EACxB,MAAM,QAAQ,IAAI,MAAM,GAAG;EAC3B,MAAM,OACJ,MAAM,WAAW,IAAI,MAAM,MAAM,MAAM,EAAE,UAAU,MAAM,EAAE,IAAI,KAAA;EACjE,IAAI,CAAC,MACH,MAAM,IAAI,MACR,GAAG,WAAU,GAAI,UAAS,UAAW,IAAG,4CAC1C;EAEF,MAAM,UAAU,MAAM;EACtB,MAAM,iBAAiB,MAAM,eAAe,aAAa,KAAK,SAAS;EACvE,MAAM,MAAM,YAAY,OAAO;EAC/B,MAAM,QAAQ,CAAC,GAAG,eAAe,QAAQ,CAAC,CAAA,CAAE,MACzC,CAAC,UAAU,SAAS,WAAW,YAAY,IAAI,MAAM,GACxD;EACA,IAAI,CAAC,SAAS,QAAQ,MACpB,MAAM,IAAI,MACR,GAAG,WAAU,GAAI,UAAS,UAAW,IAAG,SAAU,KAAK,UAAS,iBAAkB,QAAO,EAC3F;EAEF,MAAM,SAAS,YAAY,QAAQ,EAAuC;EAC1E,SAAS,KAAK;GACZ,OAAO,KAAK;GACZ,WAAW;GACX,WAAW,OAAO;GAClB,GAAI,OAAO,iBACP,EAAE,gBAAgB,OAAO,eAAe,IACxC,CAAC;EACP,CAAC;CACH;CACA,OAAO;AACT;AAEA,eAAsB,sBACpB,YAC2B;CAC3B,MAAM,UAAU,iBAAiB,UAAU;CAC3C,MAAM,kBAAkB,uBAAuB,UAAU;CACzD,MAAM,aAAa,gBAAgB,QAAQ,MAAM;CACjD,MAAM,cAAc,iBAAiB,UAAU;CAC/C,MAAM,SAAS,MAAM,eAAe,aAAa,eAAe;CAChE,MAAM,QAAQ,MAAM,aAAa,WAAW,MAAM,YAAY,QAAQ,KAAK;CAC3E,IAAI,MAAM,SAAS,KAAK,QAAQ,SAAS,SAAS,eAChD,MAAM,IAAI,MACR,GAAG,WAAW,KAAI,SAAU,MAAM,KAAK,SAAS,KAAK,SAAS,CAAA,CAAE,KAAK,IAAI,EAAC,qHAE5E;CAGF,MAAM,eAAwC,CAAC;CAC/C,KAAA,MAAW,CAAC,WAAW,UAAU,OAAO,QAAQ,GAAG;EACjD,MAAM,SAAS,mBAAmB,KAAK;EACvC,IAAI,CAAC,QAAQ;GACX,IAAI,CAAC,oBAAoB,WAAW,KAAK,KAAK,CAAC,OAAO,WACpD,MAAM,IAAI,MACR,gBAAgB,WAAW,KAAI,GAAI,UAAS,iHAE9C;GAEF;EACF;EAEA,MAAM,UAAU,MAAM,gBACpB,WAAW,MACX,WACA,QACA,KACF;EACA,aAAa,KAAK;GAChB;GACA,YAAY,YAAY,SAAS;GACjC,MAAM,MAAM;GACZ;GACA,GAAI,QAAQ,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;EAC1C,CAAC;CACH;CAEA,OAAO;EACL;EACA,iBAAiB;EACjB;EACA,GAAI,MAAM,SAAS,IAAI,EAAE,MAAM,IAAI,CAAC;EACpC,QAAQ;EACR,OAAO,QAAQ;EACf,QAAQ,QAAQ;EAChB,SAAS,QAAQ;CACnB;AACF;AAQA,SAAS,UACP,KACA,YACQ;CACR,MAAM,QAAQ,WAAW,SAAS,CAAC;CACnC,IAAI,CAAC,IAAI,SAAS,GAAG,GAAG;EACtB,MAAM,SAAS,YAAY,GAAG;EAC9B,OAAO,MAAM,SAAS,IAAI,OAA0B,WAAW;CACjE;CACA,MAAM,QAAQ,IAAI,MAAM,GAAG;CAC3B,IAAI,MAAM,WAAW,KAAK,CAAC,MAAM,MAAM,SAAS,KAAK,UAAU,MAAM,EAAE,GACrE,MAAM,IAAI,MACR,kBAAkB,IAAG,qCACvB;CAEF,OAAO,GAAG,MAAM,GAAE,GAAI,YAAY,MAAM,EAAE;AAC5C;AAEA,SAAS,WACP,MACA,WACe;CACf,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO;CAC9C,MAAM,SAAS;CACf,IAAI,OAAO,OAAO,QAAQ,UAAU,OAAO,EAAE,KAAK,UAAU,OAAO,GAAG,EAAE;CACxE,MAAM,MAA+B,EAAE,GAAG,OAAO;CACjD,KAAA,MAAW,OAAO;EAAC;EAAQ;EAAS;EAAS;EAAW;CAAS,GAC/D,IAAI,OAAO,SAAS,KAAA,GAClB,IAAI,OAAO,WAAW,OAAO,MAAuB,SAAS;CAGjE,IAAI,MAAM,QAAQ,OAAO,QAAQ,GAC/B,IAAI,WAAW,OAAO,SAAS,KAAK,YAClC,WAAW,SAA0B,SAAS,CAChD;CAEF,OAAO;AACT;AAEA,SAAS,aACP,OACA,YACQ;CACR,MAAM,SAAS,MAAM;CACrB,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,gBAAgB,MAAM,UAAS,qBAAsB;CAEvE,IAAI,OAAO,SAAS,SAClB,OAAO,UAAU,OAAO,gBAAgB,MAAM,WAAW,UAAU;CAErE,IAAI,OAAO,SAAS,UAClB,OAAO,UAAU,OAAO,cAAc,UAAU;CAElD,IAAI,OAAO,SAAS,aAAa;EAC/B,IAAI,CAAC,OAAO,QAAQ,OAAO;EAC3B,OAAO,UAAU,OAAO,QAAQ,UAAU;CAC5C;CACA,OAAO;AACT;AAEO,SAAS,yBACd,YACU;CACV,MAAM,UAAU,WAAW,OACxB,QACE,UACC,MAAM,QAAQ,SAAS,WAAW,MAAM,QAAQ,SAAS,QAC7D,CAAA,CACC,KAAK,UAAU,MAAM,cAAc,YAAY,MAAM,SAAS,CAAC;CAClE,OAAO,QAAQ,SAAS,IAAI,UAAU,CAAC,IAAI;AAC7C;AAGA,SAAS,cACP,MACA,aACqB;CAGrB,MAAM,WAAW,gBAAgB;CACjC,MAAM,SACJ,MACA,OACA,WAEA,UAAU,WACN;EACE,IAAI;EACJ,MAAM;GAAE,IAAI;GAAU,SAAS,EAAE,KAAK,KAAK;EAAE;EAC7C,OAAO;GAAE,IAAI;GAAU,SAAS,EAAE,KAAK,MAAM;EAAE;CACjD,IACA;EAAE,IAAI;EAAM,MAAM,EAAE,KAAK,KAAK;EAAG,OAAO,EAAE,KAAK,MAAM;CAAE;CAC7D,MAAM,aAAoC,CACxC,MACE,OAA0B,KAAK,gBAC/B,GAAG,KAAK,MAAK,GAAI,KAAK,gBACtB,KAAK,SACP,CACF;CACA,IAAI,KAAK,eACP,WAAW,KACT,MACE,OAA0B,KAAK,cAAc,UAC7C,GAAG,KAAK,MAAK,GAAI,KAAK,cAAc,UACpC,KAAK,cAAc,MACrB,CACF;CAEF,IAAI,KAAK,kBACP,WAAW,KAAK;EACd,IAAI;EACJ,SAAS,EAAE,KAAK,GAAG,KAAK,MAAK,GAAI,KAAK,mBAAmB;CAC3D,CAAC;CAEH,OAAO,WAAW,WAAW,IACzB,WAAW,KACX;EAAE,IAAI;EAAO,UAAU;CAAW;AACxC;AAQO,SAAS,wBACd,YACA,aACe;CACf,MAAM,SAAgC,CAAC;CACvC,MAAM,UAAoB,CAAC;CAC3B,MAAM,sCAAsB,IAAI,IAAoB;CACpD,MAAM,aAAa,QAAgB,UAAU,KAAK,UAAU;CAE5D,KAAA,MAAW,SAAS,WAAW,QAAQ;EACrC,MAAM,eAAe,MAAM,cAAc,YAAY,MAAM,SAAS;EACpE,oBAAoB,IAAI,MAAM,WAAW,YAAY;EACrD,oBAAoB,IAAI,cAAc,YAAY;EAElD,MAAM,SAAS,MAAM;EACrB,IAAI,CAAC,QAAQ;EAEb,IAAI,OAAO,SAAS,SAAS;GAC3B,OAAO,KACL,OAAO,OACH;IAAE,MAAM,WAAW,OAAO,MAAM,SAAS;IAAG,IAAI;GAAa,IAC7D;IAAE,QAAQ,aAAa,OAAO,UAAU;IAAG,IAAI;GAAa,CAClE;GACA,QAAQ,KAAK,YAAY;GACzB;EACF;EAEA,IAAI,OAAO,SAAS,UAAU;GAC5B,OAAO,KAAK;IACV,QAAQ,OAAO;IACf,QAAQ,aAAa,OAAO,UAAU;IACtC,IAAI;GACN,CAAC;GACD,QAAQ,KAAK,YAAY;GACzB;EACF;EAEA,OAAO,KAAK;GACV,IAAI,OAAO;GACX,QAAQ,OAAO,SAAS,aAAa,OAAO,UAAU,IAAI,KAAA;GAC1D,GAAI,OAAO,OAAO,EAAE,MAAM,WAAW,OAAO,MAAM,SAAS,EAAE,IAAI,CAAC;GAClE,GAAI,OAAO,SACP,EAAE,QAAQ,WAAW,OAAO,QAAQ,SAAS,EAAE,IAC/C,CAAC;GACL,IAAI;GACJ,UAAU,OAAO;EACnB,CAAC;CACH;CAEA,IAAI,OAAO,WAAW,GACpB,MAAM,IAAI,MACR,GAAG,WAAW,gBAAe,2FAE/B;CAGF,MAAM,QAAQ,WAAW,SAAS,CAAC;CACnC,OAAO;EACL,MAAM,WAAW;EACjB,GAAI,MAAM,SAAS,IACf;GACE,WAAA;GACA,OAAO,MAAM,KAAK,UAAU;IAC1B,OAAO,KAAK;IACZ,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,IAAI,cAAc,MAAM,WAAW;GACrC,EAAE;EACJ,IACA,CAAC;EACL;EACA;EACA,OAAO,eAAe,WAAW,OAAO,SAAS;EACjD,QAAQ,eACN,WAAW,SACV,UAAU,oBAAoB,IAAI,KAAK,KAAK,YAAY,KAAK,CAChE;CACF;AACF;AAEA,eAAsB,kBACpB,YACwB;CACxB,OAAO,wBAAwB,MAAM,sBAAsB,UAAU,CAAC;AACxE"}
@@ -1,9 +1,16 @@
1
1
  import { type SmrtObject } from '@happyvertical/smrt-core';
2
+ import type { SqlAdapterType } from '@happyvertical/sql';
2
3
  import type { AggregateSpec, ReportDefinition } from './types.js';
3
4
  /** The report source's alias in a query that joins other tables. */
4
5
  export declare const REPORT_SOURCE_ALIAS = "src";
5
6
  export declare function buildReportDefinition(reportCtor: new (...args: any[]) => SmrtObject): Promise<ReportDefinition>;
6
7
  export declare function getReportGroupingColumns(definition: Pick<ReportDefinition, 'fields'>): string[];
7
- export declare function compileReportDefinition(definition: ReportDefinition): AggregateSpec;
8
+ /**
9
+ * Compile a report definition to an aggregate spec. `adapterType` is the
10
+ * database the spec will run on: a join whose key columns have different
11
+ * schema types (a text reference to a uuid id) compares them as text, except
12
+ * on SQLite, where both are already TEXT. Omitted, the join casts.
13
+ */
14
+ export declare function compileReportDefinition(definition: ReportDefinition, adapterType?: SqlAdapterType): AggregateSpec;
8
15
  export declare function compileReportSpec(reportCtor: new (...args: any[]) => SmrtObject): Promise<AggregateSpec>;
9
16
  //# sourceMappingURL=compiler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAK3E,OAAO,KAAK,EAEV,aAAa,EACb,gBAAgB,EAQjB,MAAM,YAAY,CAAC;AAEpB,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAuSzC,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,GAC7C,OAAO,CAAC,gBAAgB,CAAC,CAoD3B;AAoED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,GAC3C,MAAM,EAAE,CAQV;AA6BD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,gBAAgB,GAC3B,aAAa,CA2Ef;AAED,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,GAC7C,OAAO,CAAC,aAAa,CAAC,CAExB"}
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3E,OAAO,KAAK,EAEV,cAAc,EAEf,MAAM,oBAAoB,CAAC;AAS5B,OAAO,KAAK,EAEV,aAAa,EACb,gBAAgB,EAQjB,MAAM,YAAY,CAAC;AAEpB,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AA+TzC,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,GAC7C,OAAO,CAAC,gBAAgB,CAAC,CAoD3B;AAoED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,GAC3C,MAAM,EAAE,CAQV;AAiDD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,gBAAgB,EAC5B,WAAW,CAAC,EAAE,cAAc,GAC3B,aAAa,CA2Ef;AAED,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,GAC7C,OAAO,CAAC,aAAa,CAAC,CAExB"}
package/dist/compiler.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as getReportGroupingColumns, i as compileReportSpec, n as buildReportDefinition, r as compileReportDefinition, t as REPORT_SOURCE_ALIAS } from "./chunks/compiler-IUB2YNgM.js";
1
+ import { a as getReportGroupingColumns, i as compileReportSpec, n as buildReportDefinition, r as compileReportDefinition, t as REPORT_SOURCE_ALIAS } from "./chunks/compiler-CqTiU1S6.js";
2
2
  export { REPORT_SOURCE_ALIAS, buildReportDefinition, compileReportDefinition, compileReportSpec, getReportGroupingColumns };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { aggregate, avg, count, day, getRuntimeReportOptions, groupBy, hour, max, min, minute, month, quarter, report, sum, week, year } from "./decorators.js";
2
- import { a as getReportGroupingColumns, c as tenantColumn, i as compileReportSpec, n as buildReportDefinition, r as compileReportDefinition, s as fieldPolicy } from "./chunks/compiler-IUB2YNgM.js";
2
+ import { a as getReportGroupingColumns, c as tenantColumn, i as compileReportSpec, n as buildReportDefinition, r as compileReportDefinition, s as fieldPolicy } from "./chunks/compiler-CqTiU1S6.js";
3
3
  import { REPORT_LOCKS_TABLE, REPORT_REFRESH_TASKS_TABLE, REPORT_RUNS_TABLE, REPORT_RUNTIME_TABLES, REPORT_SCHEDULER_TABLES, REPORT_SCHEDULES_TABLE, REPORT_WATERMARKS_TABLE, SmrtReportLock, SmrtReportLockCollection, SmrtReportRun, SmrtReportRunCollection, SmrtReportSchedule, SmrtReportScheduleCollection, SmrtReportWatermark, SmrtReportWatermarkCollection, assertReportTablesReady, scopeKeyForTenant } from "./state.js";
4
4
  import { adapterTypeFromDb, refreshReport, reportRowIdentity } from "./refresh.js";
5
5
  import { ReportScheduleRunner, SmrtPrincipalReportRefreshTask, SmrtReportRefreshTask, enqueueReportRefresh, ensureReportRefreshSchedules, registerReportRefreshExecutionAuthorityHost, registerReportRefreshInterceptor, registerReportRefreshJobIntegritySigner } from "./scheduler.js";
@@ -275,7 +275,7 @@ function isColumnBackedField(field) {
275
275
  "manyToMany"
276
276
  ].includes(field.type ?? "");
277
277
  }
278
- function reportColumn(field, registryField, joins = []) {
278
+ function reportColumn(field, registryField, joins = [], nullableColumns = /* @__PURE__ */ new Set()) {
279
279
  const policy = fieldPolicy(registryField);
280
280
  const relatedRestricted = (field.related ?? []).some((related) => related.sensitive || related.readPermission);
281
281
  if (policy.sensitive || policy.readPermission || relatedRestricted || !isColumnBackedField(registryField)) return;
@@ -283,7 +283,8 @@ function reportColumn(field, registryField, joins = []) {
283
283
  const report = field.report;
284
284
  if (!report) return void 0;
285
285
  const type = queryType(field.type ?? registryField?.type);
286
- const filterOperators = filterOperatorsFor(type);
286
+ const valueOperators = filterOperatorsFor(type);
287
+ const filterOperators = valueOperators && nullableColumns.has(id) ? [...valueOperators, ...NULL_TEST_OPERATORS] : valueOperators;
287
288
  const base = {
288
289
  id,
289
290
  fieldName: field.fieldName,
@@ -349,6 +350,13 @@ function toQueryField(column) {
349
350
  ...column.filterOperators === void 0 ? {} : { filterOperators: [...column.filterOperators] }
350
351
  };
351
352
  }
353
+ var NULL_TEST_OPERATORS = ["isNotNull", "isNull"];
354
+ function nullableReportColumns(reportClassName) {
355
+ const columns = ObjectRegistry.getSchema(reportClassName)?.columns ?? {};
356
+ const nullable = /* @__PURE__ */ new Set();
357
+ for (const [name, column] of Object.entries(columns)) if (column.notNull !== true && column.primaryKey !== true) nullable.add(name);
358
+ return nullable;
359
+ }
352
360
  function filterOperatorsFor(type) {
353
361
  switch (type) {
354
362
  case "string": return [
@@ -569,7 +577,8 @@ async function buildReportAdapterDescriptor(reportCtor, options = {}) {
569
577
  const registered = registeredClass(reportCtor);
570
578
  const fields = await ObjectRegistry.getAllFields(definition.reportClassName);
571
579
  const tenantField = configuredTenantField(registered?.tenantScopedConfig?.field);
572
- const columns = [identityColumn(), ...definition.fields.map((field) => reportColumn(field, fields.get(field.fieldName), definition.joins)).filter((field) => Boolean(field))].sort((left, right) => left.id.localeCompare(right.id));
580
+ const nullableColumns = nullableReportColumns(definition.reportClassName);
581
+ const columns = [identityColumn(), ...definition.fields.map((field) => reportColumn(field, fields.get(field.fieldName), definition.joins, nullableColumns)).filter((field) => Boolean(field))].sort((left, right) => left.id.localeCompare(right.id));
573
582
  const schema = {
574
583
  version: 1,
575
584
  identityField: "id",
@@ -674,12 +683,16 @@ function inverseOperator(operator) {
674
683
  case "lte": return "gt";
675
684
  case "in": return "notIn";
676
685
  case "notIn": return "in";
686
+ case "isNull": return "isNotNull";
687
+ case "isNotNull": return "isNull";
677
688
  case "like": return reportFilterError("Report filters do not support negating a LIKE predicate");
678
689
  }
679
690
  }
680
691
  function collectionCondition(field, operator, value) {
681
692
  const keyFor = (suffix2) => suffix2 ? `${field} ${suffix2}` : field;
682
693
  const scalar = (key, scalarValue) => [[{ [key]: scalarValue }]];
694
+ if (operator === "isNull") return scalar(field, null);
695
+ if (operator === "isNotNull") return scalar(keyFor("!="), null);
683
696
  if (operator === "in") {
684
697
  const values = value;
685
698
  const nonNull = values.filter((entry) => entry !== null);
@@ -2018,6 +2031,9 @@ function stable(value) {
2018
2031
  const object = plainObject(value, "Report surface value");
2019
2032
  return "{" + Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stable(object[key])}`).join(",") + "}";
2020
2033
  }
2034
+ function withoutNullTests(operators) {
2035
+ return operators.filter((operator) => operator !== "isNull" && operator !== "isNotNull");
2036
+ }
2021
2037
  function reportDefinitionFingerprint(descriptor) {
2022
2038
  const definition = {
2023
2039
  resourceId: descriptor.resourceId,
@@ -2035,7 +2051,7 @@ function reportDefinitionFingerprint(descriptor) {
2035
2051
  projectable: column.projectable === true,
2036
2052
  sortable: column.sortable === true,
2037
2053
  facetable: column.facetable === true,
2038
- filterOperators: column.filterOperators ?? [],
2054
+ filterOperators: withoutNullTests(column.filterOperators ?? []),
2039
2055
  capabilities: [...column.capabilities].sort(),
2040
2056
  sensitivity: column.sensitivity ?? null,
2041
2057
  sourceColumn: column.sourceColumn ?? null,
@@ -2044,7 +2060,13 @@ function reportDefinitionFingerprint(descriptor) {
2044
2060
  distinct: column.distinct ?? null,
2045
2061
  ...column.semantics ? { semantics: column.semantics } : {}
2046
2062
  })),
2047
- schema: descriptor.schema,
2063
+ schema: {
2064
+ ...descriptor.schema,
2065
+ fields: descriptor.schema.fields.map((field) => field.filterOperators ? {
2066
+ ...field,
2067
+ filterOperators: withoutNullTests(field.filterOperators)
2068
+ } : field)
2069
+ },
2048
2070
  ...descriptor.semantics ? { semantics: descriptor.semantics } : {}
2049
2071
  };
2050
2072
  return "rv1_" + createHash("sha256").update(stable(definition), "utf8").digest("base64url");