@happyvertical/smrt-reports 0.37.2 → 0.37.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,162 +1,90 @@
1
- import { validateColumnName } from "@happyvertical/sql";
2
- import { bucketExpr, buildAggregate } from "@happyvertical/sql";
3
- import { buildReportDefinition } from "./compiler.js";
4
- import { compileReportDefinition, compileReportSpec, getReportGroupingColumns } from "./compiler.js";
1
+ import { bucketExpr, buildAggregate } from "./aggregate.js";
5
2
  import { aggregate, avg, count, day, getRuntimeReportOptions, groupBy, hour, max, min, minute, month, quarter, report, sum, week, year } from "./decorators.js";
6
- import { refreshReport } from "./refresh.js";
7
- import { reportRowIdentity } from "./refresh.js";
8
- import { SmrtObject, SmrtCollection, ObjectRegistry } from "@happyvertical/smrt-core";
3
+ import { buildReportDefinition, compileReportDefinition, compileReportSpec, getReportGroupingColumns } from "./compiler.js";
4
+ 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";
5
+ import { refreshReport, reportRowIdentity } from "./refresh.js";
6
+ import { ReportScheduleRunner, SmrtReportRefreshTask, enqueueReportRefresh, ensureReportRefreshSchedules, registerReportRefreshInterceptor } from "./scheduler.js";
7
+ import { validateColumnName } from "@happyvertical/sql";
8
+ import { ObjectRegistry, SmrtCollection, SmrtObject } from "@happyvertical/smrt-core";
9
9
  import { toSnakeCase } from "@happyvertical/smrt-core/utils";
10
10
  import { getTenantId } from "@happyvertical/smrt-tenancy";
11
- import { ReportScheduleRunner, SmrtReportRefreshTask, enqueueReportRefresh, ensureReportRefreshSchedules, registerReportRefreshInterceptor } from "./scheduler.js";
12
- 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";
11
+ //#region src/report.ts
13
12
  function registryColumnName(fieldName, field) {
14
- return validateColumnName(
15
- field?.columnName ?? field?._meta?.columnName ?? toSnakeCase(fieldName)
16
- );
13
+ return validateColumnName(field?.columnName ?? field?._meta?.columnName ?? toSnakeCase(fieldName));
17
14
  }
18
15
  function findFieldColumn(fields, fieldName) {
19
- const direct = fields.get(fieldName);
20
- if (direct) return registryColumnName(fieldName, direct);
21
- const requestedColumn = toSnakeCase(fieldName);
22
- for (const [name, field] of fields.entries()) {
23
- if (toSnakeCase(name) === requestedColumn) {
24
- return registryColumnName(name, field);
25
- }
26
- }
27
- return validateColumnName(requestedColumn);
16
+ const direct = fields.get(fieldName);
17
+ if (direct) return registryColumnName(fieldName, direct);
18
+ const requestedColumn = toSnakeCase(fieldName);
19
+ for (const [name, field] of fields.entries()) if (toSnakeCase(name) === requestedColumn) return registryColumnName(name, field);
20
+ return validateColumnName(requestedColumn);
28
21
  }
29
22
  function findTenantColumn(fields, configuredField) {
30
- if (configuredField) {
31
- return registryColumnName(configuredField, fields.get(configuredField));
32
- }
33
- for (const [fieldName, field] of fields.entries()) {
34
- if (fieldName === "tenantId" || field?._meta?.__tenancy?.isTenantIdField) {
35
- return registryColumnName(fieldName, field);
36
- }
37
- }
38
- return null;
39
- }
40
- class SmrtReport extends SmrtObject {
41
- static _isReportBase = true;
42
- refreshedAt = null;
43
- constructor(options = {}) {
44
- super(options);
45
- if (options.refreshedAt !== void 0) {
46
- this.refreshedAt = options.refreshedAt instanceof Date ? options.refreshedAt : options.refreshedAt ? new Date(options.refreshedAt) : null;
47
- }
48
- }
49
- isStale(ttlMs) {
50
- if (!this.refreshedAt) return true;
51
- if (ttlMs === void 0) return false;
52
- return Date.now() - this.refreshedAt.getTime() > ttlMs;
53
- }
54
- async refresh(options = {}) {
55
- const result = await refreshReport(this.constructor, {
56
- ...options,
57
- db: this.db
58
- });
59
- this.refreshedAt = result.refreshedAt;
60
- return result;
61
- }
23
+ if (configuredField) return registryColumnName(configuredField, fields.get(configuredField));
24
+ for (const [fieldName, field] of fields.entries()) if (fieldName === "tenantId" || field?._meta?.__tenancy?.isTenantIdField) return registryColumnName(fieldName, field);
25
+ return null;
62
26
  }
63
- class SmrtReportCollection extends SmrtCollection {
64
- async refreshIfStale() {
65
- const reportCtor = this.getItemClass();
66
- const definition = await buildReportDefinition(reportCtor);
67
- const refresh = definition.refresh;
68
- if (!refresh?.ttl || refresh.manual) return;
69
- const registered = ObjectRegistry.getClassByConstructor(reportCtor) ?? ObjectRegistry.getClass(reportCtor.name);
70
- const reportClass = registered?.qualifiedName ?? registered?.name ?? reportCtor.name;
71
- const tableName = ObjectRegistry.getTableName(reportClass);
72
- if (!tableName) return;
73
- const fields = await ObjectRegistry.getAllFields(reportClass);
74
- const safeTableName = validateColumnName(tableName);
75
- const refreshedAtColumn = findFieldColumn(fields, "refreshedAt");
76
- const tenantColumn = findTenantColumn(
77
- fields,
78
- registered?.tenantScopedConfig?.field
79
- );
80
- const tenantId = getTenantId() ?? null;
81
- const result = tenantColumn ? await this.db.query(
82
- `SELECT MAX(${refreshedAtColumn}) AS refreshed_at FROM ${safeTableName} WHERE ${tenantColumn} ${tenantId ? "= ?" : "IS NULL"}`,
83
- ...tenantId ? [tenantId] : []
84
- ) : await this.db.query(
85
- `SELECT MAX(${refreshedAtColumn}) AS refreshed_at FROM ${safeTableName}`
86
- );
87
- const refreshedAt = result.rows[0]?.refreshed_at ? new Date(result.rows[0].refreshed_at) : null;
88
- const stale = !refreshedAt || Date.now() - refreshedAt.getTime() > refresh.ttl;
89
- if (!stale) return;
90
- await refreshReport(reportCtor, {
91
- db: this.db,
92
- mode: refresh.mode ?? "rebuild",
93
- trigger: "ttl",
94
- tenantId
95
- });
96
- }
97
- async refresh(options = {}) {
98
- return refreshReport(this.getItemClass(), {
99
- ...options,
100
- db: this.db
101
- });
102
- }
103
- async list(options = {}) {
104
- await this.refreshIfStale();
105
- return super.list(options);
106
- }
107
- async get(filter, options = {}) {
108
- await this.refreshIfStale();
109
- return super.get(filter, options);
110
- }
111
- }
112
- export {
113
- REPORT_LOCKS_TABLE,
114
- REPORT_REFRESH_TASKS_TABLE,
115
- REPORT_RUNS_TABLE,
116
- REPORT_RUNTIME_TABLES,
117
- REPORT_SCHEDULER_TABLES,
118
- REPORT_SCHEDULES_TABLE,
119
- REPORT_WATERMARKS_TABLE,
120
- ReportScheduleRunner,
121
- SmrtReport,
122
- SmrtReportCollection,
123
- SmrtReportLock,
124
- SmrtReportLockCollection,
125
- SmrtReportRefreshTask,
126
- SmrtReportRun,
127
- SmrtReportRunCollection,
128
- SmrtReportSchedule,
129
- SmrtReportScheduleCollection,
130
- SmrtReportWatermark,
131
- SmrtReportWatermarkCollection,
132
- aggregate,
133
- assertReportTablesReady,
134
- avg,
135
- bucketExpr,
136
- buildAggregate,
137
- buildReportDefinition,
138
- compileReportDefinition,
139
- compileReportSpec,
140
- count,
141
- day,
142
- enqueueReportRefresh,
143
- ensureReportRefreshSchedules,
144
- getReportGroupingColumns,
145
- getRuntimeReportOptions,
146
- groupBy,
147
- hour,
148
- max,
149
- min,
150
- minute,
151
- month,
152
- quarter,
153
- refreshReport,
154
- registerReportRefreshInterceptor,
155
- report,
156
- reportRowIdentity,
157
- scopeKeyForTenant,
158
- sum,
159
- week,
160
- year
27
+ var SmrtReport = class extends SmrtObject {
28
+ static _isReportBase = true;
29
+ refreshedAt = null;
30
+ constructor(options = {}) {
31
+ super(options);
32
+ if (options.refreshedAt !== void 0) this.refreshedAt = options.refreshedAt instanceof Date ? options.refreshedAt : options.refreshedAt ? new Date(options.refreshedAt) : null;
33
+ }
34
+ isStale(ttlMs) {
35
+ if (!this.refreshedAt) return true;
36
+ if (ttlMs === void 0) return false;
37
+ return Date.now() - this.refreshedAt.getTime() > ttlMs;
38
+ }
39
+ async refresh(options = {}) {
40
+ const result = await refreshReport(this.constructor, {
41
+ ...options,
42
+ db: this.db
43
+ });
44
+ this.refreshedAt = result.refreshedAt;
45
+ return result;
46
+ }
47
+ };
48
+ var SmrtReportCollection = class extends SmrtCollection {
49
+ async refreshIfStale() {
50
+ const reportCtor = this.getItemClass();
51
+ const refresh = (await buildReportDefinition(reportCtor)).refresh;
52
+ if (!refresh?.ttl || refresh.manual) return;
53
+ const registered = ObjectRegistry.getClassByConstructor(reportCtor) ?? ObjectRegistry.getClass(reportCtor.name);
54
+ const reportClass = registered?.qualifiedName ?? registered?.name ?? reportCtor.name;
55
+ const tableName = ObjectRegistry.getTableName(reportClass);
56
+ if (!tableName) return;
57
+ const fields = await ObjectRegistry.getAllFields(reportClass);
58
+ const safeTableName = validateColumnName(tableName);
59
+ const refreshedAtColumn = findFieldColumn(fields, "refreshedAt");
60
+ const tenantColumn = findTenantColumn(fields, registered?.tenantScopedConfig?.field);
61
+ const tenantId = getTenantId() ?? null;
62
+ const result = tenantColumn ? await this.db.query(`SELECT MAX(${refreshedAtColumn}) AS refreshed_at FROM ${safeTableName} WHERE ${tenantColumn} ${tenantId ? "= ?" : "IS NULL"}`, ...tenantId ? [tenantId] : []) : await this.db.query(`SELECT MAX(${refreshedAtColumn}) AS refreshed_at FROM ${safeTableName}`);
63
+ const refreshedAt = result.rows[0]?.refreshed_at ? new Date(result.rows[0].refreshed_at) : null;
64
+ if (!(!refreshedAt || Date.now() - refreshedAt.getTime() > refresh.ttl)) return;
65
+ await refreshReport(reportCtor, {
66
+ db: this.db,
67
+ mode: refresh.mode ?? "rebuild",
68
+ trigger: "ttl",
69
+ tenantId
70
+ });
71
+ }
72
+ async refresh(options = {}) {
73
+ return refreshReport(this.getItemClass(), {
74
+ ...options,
75
+ db: this.db
76
+ });
77
+ }
78
+ async list(options = {}) {
79
+ await this.refreshIfStale();
80
+ return super.list(options);
81
+ }
82
+ async get(filter, options = {}) {
83
+ await this.refreshIfStale();
84
+ return super.get(filter, options);
85
+ }
161
86
  };
162
- //# sourceMappingURL=index.js.map
87
+ //#endregion
88
+ export { REPORT_LOCKS_TABLE, REPORT_REFRESH_TASKS_TABLE, REPORT_RUNS_TABLE, REPORT_RUNTIME_TABLES, REPORT_SCHEDULER_TABLES, REPORT_SCHEDULES_TABLE, REPORT_WATERMARKS_TABLE, ReportScheduleRunner, SmrtReport, SmrtReportCollection, SmrtReportLock, SmrtReportLockCollection, SmrtReportRefreshTask, SmrtReportRun, SmrtReportRunCollection, SmrtReportSchedule, SmrtReportScheduleCollection, SmrtReportWatermark, SmrtReportWatermarkCollection, aggregate, assertReportTablesReady, avg, bucketExpr, buildAggregate, buildReportDefinition, compileReportDefinition, compileReportSpec, count, day, enqueueReportRefresh, ensureReportRefreshSchedules, getReportGroupingColumns, getRuntimeReportOptions, groupBy, hour, max, min, minute, month, quarter, refreshReport, registerReportRefreshInterceptor, report, reportRowIdentity, scopeKeyForTenant, sum, week, year };
89
+
90
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/report.ts"],"sourcesContent":["import {\n ObjectRegistry,\n SmrtCollection,\n SmrtObject,\n type SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport { toSnakeCase } from '@happyvertical/smrt-core/utils';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport { validateColumnName } from '@happyvertical/sql';\nimport { buildReportDefinition } from './compiler.js';\nimport { refreshReport } from './refresh.js';\nimport type { ReportRefreshOptions, ReportRefreshResult } from './types.js';\n\ntype RegistryField = {\n columnName?: string;\n _meta?: {\n columnName?: string;\n __tenancy?: { isTenantIdField?: boolean };\n };\n};\n\nfunction registryColumnName(fieldName: string, field?: RegistryField): string {\n return validateColumnName(\n field?.columnName ?? field?._meta?.columnName ?? toSnakeCase(fieldName),\n );\n}\n\nfunction findFieldColumn(\n fields: Map<string, RegistryField>,\n fieldName: string,\n): string {\n const direct = fields.get(fieldName);\n if (direct) return registryColumnName(fieldName, direct);\n const requestedColumn = toSnakeCase(fieldName);\n for (const [name, field] of fields.entries()) {\n if (toSnakeCase(name) === requestedColumn) {\n return registryColumnName(name, field);\n }\n }\n return validateColumnName(requestedColumn);\n}\n\nfunction findTenantColumn(\n fields: Map<string, RegistryField>,\n configuredField?: string,\n): string | null {\n if (configuredField) {\n return registryColumnName(configuredField, fields.get(configuredField));\n }\n for (const [fieldName, field] of fields.entries()) {\n if (fieldName === 'tenantId' || field?._meta?.__tenancy?.isTenantIdField) {\n return registryColumnName(fieldName, field);\n }\n }\n return null;\n}\n\nexport class SmrtReport extends SmrtObject {\n static readonly _isReportBase = true as const;\n\n refreshedAt: Date | null = null;\n\n constructor(options: SmrtObjectOptions = {}) {\n super(options);\n if (options.refreshedAt !== undefined) {\n this.refreshedAt =\n options.refreshedAt instanceof Date\n ? options.refreshedAt\n : options.refreshedAt\n ? new Date(options.refreshedAt)\n : null;\n }\n }\n\n isStale(ttlMs?: number): boolean {\n if (!this.refreshedAt) return true;\n if (ttlMs === undefined) return false;\n return Date.now() - this.refreshedAt.getTime() > ttlMs;\n }\n\n async refresh(\n options: Omit<ReportRefreshOptions, 'db'> = {},\n ): Promise<ReportRefreshResult> {\n const result = await refreshReport(this.constructor as typeof SmrtReport, {\n ...options,\n db: this.db,\n });\n this.refreshedAt = result.refreshedAt;\n return result;\n }\n}\n\nexport class SmrtReportCollection<\n ModelType extends SmrtReport,\n> extends SmrtCollection<ModelType> {\n private async refreshIfStale(): Promise<void> {\n const reportCtor = this.getItemClass();\n const definition = await buildReportDefinition(reportCtor);\n const refresh = definition.refresh;\n if (!refresh?.ttl || refresh.manual) return;\n\n const registered =\n ObjectRegistry.getClassByConstructor(reportCtor) ??\n ObjectRegistry.getClass(reportCtor.name);\n const reportClass =\n registered?.qualifiedName ?? registered?.name ?? reportCtor.name;\n const tableName = ObjectRegistry.getTableName(reportClass);\n if (!tableName) return;\n\n const fields = (await ObjectRegistry.getAllFields(reportClass)) as Map<\n string,\n RegistryField\n >;\n const safeTableName = validateColumnName(tableName);\n const refreshedAtColumn = findFieldColumn(fields, 'refreshedAt');\n const tenantColumn = findTenantColumn(\n fields,\n registered?.tenantScopedConfig?.field,\n );\n const tenantId = getTenantId() ?? null;\n const result = tenantColumn\n ? await this.db.query(\n `SELECT MAX(${refreshedAtColumn}) AS refreshed_at FROM ${safeTableName} WHERE ${tenantColumn} ${tenantId ? '= ?' : 'IS NULL'}`,\n ...(tenantId ? [tenantId] : []),\n )\n : await this.db.query(\n `SELECT MAX(${refreshedAtColumn}) AS refreshed_at FROM ${safeTableName}`,\n );\n const refreshedAt = result.rows[0]?.refreshed_at\n ? new Date(result.rows[0].refreshed_at as string)\n : null;\n const stale =\n !refreshedAt || Date.now() - refreshedAt.getTime() > refresh.ttl;\n if (!stale) return;\n\n await refreshReport(reportCtor, {\n db: this.db,\n mode: refresh.mode ?? 'rebuild',\n trigger: 'ttl',\n tenantId,\n });\n }\n\n async refresh(\n options: Omit<ReportRefreshOptions, 'db'> = {},\n ): Promise<ReportRefreshResult> {\n return refreshReport(this.getItemClass(), {\n ...options,\n db: this.db,\n });\n }\n\n override async list(\n options: Parameters<SmrtCollection<ModelType>['list']>[0] = {},\n ): Promise<ModelType[]> {\n await this.refreshIfStale();\n return super.list(options);\n }\n\n override async get(\n filter: Parameters<SmrtCollection<ModelType>['get']>[0],\n options: Parameters<SmrtCollection<ModelType>['get']>[1] = {},\n ): Promise<ModelType | null> {\n await this.refreshIfStale();\n return super.get(filter, options);\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/report.ts"],"sourcesContent":["import {\n ObjectRegistry,\n SmrtCollection,\n SmrtObject,\n type SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport { toSnakeCase } from '@happyvertical/smrt-core/utils';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport { validateColumnName } from '@happyvertical/sql';\nimport { buildReportDefinition } from './compiler.js';\nimport { refreshReport } from './refresh.js';\nimport type { ReportRefreshOptions, ReportRefreshResult } from './types.js';\n\ntype RegistryField = {\n columnName?: string;\n _meta?: {\n columnName?: string;\n __tenancy?: { isTenantIdField?: boolean };\n };\n};\n\nfunction registryColumnName(fieldName: string, field?: RegistryField): string {\n return validateColumnName(\n field?.columnName ?? field?._meta?.columnName ?? toSnakeCase(fieldName),\n );\n}\n\nfunction findFieldColumn(\n fields: Map<string, RegistryField>,\n fieldName: string,\n): string {\n const direct = fields.get(fieldName);\n if (direct) return registryColumnName(fieldName, direct);\n const requestedColumn = toSnakeCase(fieldName);\n for (const [name, field] of fields.entries()) {\n if (toSnakeCase(name) === requestedColumn) {\n return registryColumnName(name, field);\n }\n }\n return validateColumnName(requestedColumn);\n}\n\nfunction findTenantColumn(\n fields: Map<string, RegistryField>,\n configuredField?: string,\n): string | null {\n if (configuredField) {\n return registryColumnName(configuredField, fields.get(configuredField));\n }\n for (const [fieldName, field] of fields.entries()) {\n if (fieldName === 'tenantId' || field?._meta?.__tenancy?.isTenantIdField) {\n return registryColumnName(fieldName, field);\n }\n }\n return null;\n}\n\nexport class SmrtReport extends SmrtObject {\n static readonly _isReportBase = true as const;\n\n refreshedAt: Date | null = null;\n\n constructor(options: SmrtObjectOptions = {}) {\n super(options);\n if (options.refreshedAt !== undefined) {\n this.refreshedAt =\n options.refreshedAt instanceof Date\n ? options.refreshedAt\n : options.refreshedAt\n ? new Date(options.refreshedAt)\n : null;\n }\n }\n\n isStale(ttlMs?: number): boolean {\n if (!this.refreshedAt) return true;\n if (ttlMs === undefined) return false;\n return Date.now() - this.refreshedAt.getTime() > ttlMs;\n }\n\n async refresh(\n options: Omit<ReportRefreshOptions, 'db'> = {},\n ): Promise<ReportRefreshResult> {\n const result = await refreshReport(this.constructor as typeof SmrtReport, {\n ...options,\n db: this.db,\n });\n this.refreshedAt = result.refreshedAt;\n return result;\n }\n}\n\nexport class SmrtReportCollection<\n ModelType extends SmrtReport,\n> extends SmrtCollection<ModelType> {\n private async refreshIfStale(): Promise<void> {\n const reportCtor = this.getItemClass();\n const definition = await buildReportDefinition(reportCtor);\n const refresh = definition.refresh;\n if (!refresh?.ttl || refresh.manual) return;\n\n const registered =\n ObjectRegistry.getClassByConstructor(reportCtor) ??\n ObjectRegistry.getClass(reportCtor.name);\n const reportClass =\n registered?.qualifiedName ?? registered?.name ?? reportCtor.name;\n const tableName = ObjectRegistry.getTableName(reportClass);\n if (!tableName) return;\n\n const fields = (await ObjectRegistry.getAllFields(reportClass)) as Map<\n string,\n RegistryField\n >;\n const safeTableName = validateColumnName(tableName);\n const refreshedAtColumn = findFieldColumn(fields, 'refreshedAt');\n const tenantColumn = findTenantColumn(\n fields,\n registered?.tenantScopedConfig?.field,\n );\n const tenantId = getTenantId() ?? null;\n const result = tenantColumn\n ? await this.db.query(\n `SELECT MAX(${refreshedAtColumn}) AS refreshed_at FROM ${safeTableName} WHERE ${tenantColumn} ${tenantId ? '= ?' : 'IS NULL'}`,\n ...(tenantId ? [tenantId] : []),\n )\n : await this.db.query(\n `SELECT MAX(${refreshedAtColumn}) AS refreshed_at FROM ${safeTableName}`,\n );\n const refreshedAt = result.rows[0]?.refreshed_at\n ? new Date(result.rows[0].refreshed_at as string)\n : null;\n const stale =\n !refreshedAt || Date.now() - refreshedAt.getTime() > refresh.ttl;\n if (!stale) return;\n\n await refreshReport(reportCtor, {\n db: this.db,\n mode: refresh.mode ?? 'rebuild',\n trigger: 'ttl',\n tenantId,\n });\n }\n\n async refresh(\n options: Omit<ReportRefreshOptions, 'db'> = {},\n ): Promise<ReportRefreshResult> {\n return refreshReport(this.getItemClass(), {\n ...options,\n db: this.db,\n });\n }\n\n override async list(\n options: Parameters<SmrtCollection<ModelType>['list']>[0] = {},\n ): Promise<ModelType[]> {\n await this.refreshIfStale();\n return super.list(options);\n }\n\n override async get(\n filter: Parameters<SmrtCollection<ModelType>['get']>[0],\n options: Parameters<SmrtCollection<ModelType>['get']>[1] = {},\n ): Promise<ModelType | null> {\n await this.refreshIfStale();\n return super.get(filter, options);\n }\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1782873344784,
3
+ "timestamp": 1782943594711,
4
4
  "packageName": "@happyvertical/smrt-reports",
5
- "packageVersion": "0.37.2",
5
+ "packageVersion": "0.37.4",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-reports:SmrtReport": {
8
8
  "name": "smrtreport",