@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.
- package/dist/aggregate.js +1 -5
- package/dist/compiler.js +138 -185
- package/dist/compiler.js.map +1 -1
- package/dist/decorators.js +53 -68
- package/dist/decorators.js.map +1 -1
- package/dist/index.js +80 -152
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/refresh.js +423 -667
- package/dist/refresh.js.map +1 -1
- package/dist/scheduler.js +330 -395
- package/dist/scheduler.js.map +1 -1
- package/dist/smrt-knowledge.json +7 -7
- package/dist/state.js +305 -305
- package/dist/state.js.map +1 -1
- package/dist/types.js +0 -2
- package/package.json +9 -9
- package/dist/aggregate.js.map +0 -1
- package/dist/types.js.map +0 -1
package/dist/aggregate.js
CHANGED
package/dist/compiler.js
CHANGED
|
@@ -1,218 +1,171 @@
|
|
|
1
|
+
import { getRuntimeReportOptions } from "./decorators.js";
|
|
1
2
|
import { ObjectRegistry } from "@happyvertical/smrt-core";
|
|
2
3
|
import { toSnakeCase } from "@happyvertical/smrt-core/utils";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
//#region src/compiler.ts
|
|
5
|
+
var REPORT_BASE_FIELDS = /* @__PURE__ */ new Set([
|
|
6
|
+
"id",
|
|
7
|
+
"slug",
|
|
8
|
+
"context",
|
|
9
|
+
"created_at",
|
|
10
|
+
"updated_at",
|
|
11
|
+
"createdAt",
|
|
12
|
+
"updatedAt",
|
|
13
|
+
"refreshedAt"
|
|
13
14
|
]);
|
|
14
15
|
function sourceClassName(source) {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
if (typeof source === "string") return source;
|
|
17
|
+
return source.name;
|
|
17
18
|
}
|
|
18
19
|
function reportOptionsFromRegistry(reportCtor) {
|
|
19
|
-
|
|
20
|
-
return registered?.config?.report;
|
|
20
|
+
return (ObjectRegistry.getClassByConstructor(reportCtor) ?? ObjectRegistry.getClass(reportCtor.name))?.config?.report;
|
|
21
21
|
}
|
|
22
22
|
function getReportOptions(reportCtor) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
`${reportCtor.name} is missing @report({ source }) metadata. Decorate the report class with @report({ source: SourceClass }).`
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
return options;
|
|
23
|
+
const options = getRuntimeReportOptions(reportCtor) ?? reportOptionsFromRegistry(reportCtor);
|
|
24
|
+
if (!options?.source) throw new Error(`${reportCtor.name} is missing @report({ source }) metadata. Decorate the report class with @report({ source: SourceClass }).`);
|
|
25
|
+
return options;
|
|
30
26
|
}
|
|
31
27
|
function getRegisteredClassName(ctor) {
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
const registered = ObjectRegistry.getClassByConstructor(ctor) ?? ObjectRegistry.getClass(ctor.name);
|
|
29
|
+
return registered?.qualifiedName ?? registered?.name ?? ctor.name;
|
|
34
30
|
}
|
|
35
31
|
function resolveTableName(className) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
`No SMRT table registered for ${className}. Ensure the class is decorated with @smrt() and its manifest is loaded.`
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
return tableName;
|
|
32
|
+
const tableName = ObjectRegistry.getTableName(className);
|
|
33
|
+
if (!tableName) throw new Error(`No SMRT table registered for ${className}. Ensure the class is decorated with @smrt() and its manifest is loaded.`);
|
|
34
|
+
return tableName;
|
|
43
35
|
}
|
|
44
36
|
function parseWhereKey(key) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return { field: trimmed, suffix: "" };
|
|
37
|
+
const trimmed = key.trim();
|
|
38
|
+
const operators = [
|
|
39
|
+
" is not null",
|
|
40
|
+
" is null",
|
|
41
|
+
" not in",
|
|
42
|
+
" like",
|
|
43
|
+
" in",
|
|
44
|
+
" !=",
|
|
45
|
+
" >=",
|
|
46
|
+
" <=",
|
|
47
|
+
" >",
|
|
48
|
+
" <",
|
|
49
|
+
" ="
|
|
50
|
+
];
|
|
51
|
+
const lower = trimmed.toLowerCase();
|
|
52
|
+
for (const operator of operators) if (lower.endsWith(operator)) return {
|
|
53
|
+
field: trimmed.slice(0, -operator.length).trim(),
|
|
54
|
+
suffix: operator
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
field: trimmed,
|
|
58
|
+
suffix: ""
|
|
59
|
+
};
|
|
69
60
|
}
|
|
70
61
|
function normalizeConditionKey(key, mapField) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
const { field, suffix } = parseWhereKey(key);
|
|
63
|
+
const mapped = mapField(field);
|
|
64
|
+
return suffix ? `${mapped}${suffix}` : mapped;
|
|
74
65
|
}
|
|
75
66
|
function normalizeWhere(where, mapField) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
(andGroup) => andGroup.map(
|
|
80
|
-
(condition) => Object.fromEntries(
|
|
81
|
-
Object.entries(condition).map(([key, value]) => [
|
|
82
|
-
normalizeConditionKey(key, mapField),
|
|
83
|
-
value
|
|
84
|
-
])
|
|
85
|
-
)
|
|
86
|
-
)
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
return Object.fromEntries(
|
|
90
|
-
Object.entries(where).map(([key, value]) => [
|
|
91
|
-
normalizeConditionKey(key, mapField),
|
|
92
|
-
value
|
|
93
|
-
])
|
|
94
|
-
);
|
|
67
|
+
if (!where) return void 0;
|
|
68
|
+
if (Array.isArray(where)) return where.map((andGroup) => andGroup.map((condition) => Object.fromEntries(Object.entries(condition).map(([key, value]) => [normalizeConditionKey(key, mapField), value]))));
|
|
69
|
+
return Object.fromEntries(Object.entries(where).map(([key, value]) => [normalizeConditionKey(key, mapField), value]));
|
|
95
70
|
}
|
|
96
71
|
function readReportMetadata(field) {
|
|
97
|
-
|
|
72
|
+
return field?._meta?.__report ?? field?.__report;
|
|
98
73
|
}
|
|
99
74
|
function isReportSystemField(fieldName, field) {
|
|
100
|
-
|
|
75
|
+
return REPORT_BASE_FIELDS.has(fieldName) || field?._meta?.__smrtSystemField || field?._meta?.__tenancy?.isTenantIdField;
|
|
101
76
|
}
|
|
102
77
|
async function buildReportDefinition(reportCtor) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
where: options.where,
|
|
132
|
-
having: options.having,
|
|
133
|
-
refresh: options.refresh
|
|
134
|
-
};
|
|
78
|
+
const options = getReportOptions(reportCtor);
|
|
79
|
+
const reportClassName = getRegisteredClassName(reportCtor);
|
|
80
|
+
const sourceName = sourceClassName(options.source);
|
|
81
|
+
const sourceTable = resolveTableName(sourceName);
|
|
82
|
+
const fields = await ObjectRegistry.getAllFields(reportClassName);
|
|
83
|
+
const reportFields = [];
|
|
84
|
+
for (const [fieldName, field] of fields.entries()) {
|
|
85
|
+
const report = readReportMetadata(field);
|
|
86
|
+
if (!report) {
|
|
87
|
+
if (!isReportSystemField(fieldName, field) && !field?.transient) throw new Error(`Report field ${reportCtor.name}.${fieldName} is neither a grouping key nor an aggregate. Add @groupBy(), a time-bucket decorator, or an aggregate decorator.`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
reportFields.push({
|
|
91
|
+
fieldName,
|
|
92
|
+
columnName: toSnakeCase(fieldName),
|
|
93
|
+
type: field.type,
|
|
94
|
+
report
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
reportClassName,
|
|
99
|
+
sourceClassName: sourceName,
|
|
100
|
+
sourceTable,
|
|
101
|
+
fields: reportFields,
|
|
102
|
+
where: options.where,
|
|
103
|
+
having: options.having,
|
|
104
|
+
refresh: options.refresh
|
|
105
|
+
};
|
|
135
106
|
}
|
|
136
107
|
function sourceColumn(field) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
if (report.kind === "aggregate") {
|
|
148
|
-
if (!report.column) return "";
|
|
149
|
-
return toSnakeCase(report.column);
|
|
150
|
-
}
|
|
151
|
-
return "";
|
|
108
|
+
const report = field.report;
|
|
109
|
+
if (!report) throw new Error(`Report field ${field.fieldName} is missing metadata`);
|
|
110
|
+
if (report.kind === "group") return toSnakeCase(report.sourceColumn ?? field.fieldName);
|
|
111
|
+
if (report.kind === "bucket") return toSnakeCase(report.sourceColumn);
|
|
112
|
+
if (report.kind === "aggregate") {
|
|
113
|
+
if (!report.column) return "";
|
|
114
|
+
return toSnakeCase(report.column);
|
|
115
|
+
}
|
|
116
|
+
return "";
|
|
152
117
|
}
|
|
153
118
|
function getReportGroupingColumns(definition) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
).map((field) => field.columnName ?? toSnakeCase(field.fieldName));
|
|
157
|
-
return columns.length > 0 ? columns : ["id"];
|
|
119
|
+
const columns = definition.fields.filter((field) => field.report?.kind === "group" || field.report?.kind === "bucket").map((field) => field.columnName ?? toSnakeCase(field.fieldName));
|
|
120
|
+
return columns.length > 0 ? columns : ["id"];
|
|
158
121
|
}
|
|
159
122
|
function compileReportDefinition(definition) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
groupBy,
|
|
202
|
-
where: normalizeWhere(definition.where, (field) => toSnakeCase(field)),
|
|
203
|
-
having: normalizeWhere(
|
|
204
|
-
definition.having,
|
|
205
|
-
(field) => reportFieldToColumn.get(field) ?? toSnakeCase(field)
|
|
206
|
-
)
|
|
207
|
-
};
|
|
123
|
+
const select = [];
|
|
124
|
+
const groupBy = [];
|
|
125
|
+
const reportFieldToColumn = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const field of definition.fields) {
|
|
127
|
+
const outputColumn = field.columnName ?? toSnakeCase(field.fieldName);
|
|
128
|
+
reportFieldToColumn.set(field.fieldName, outputColumn);
|
|
129
|
+
reportFieldToColumn.set(outputColumn, outputColumn);
|
|
130
|
+
const report = field.report;
|
|
131
|
+
if (!report) continue;
|
|
132
|
+
if (report.kind === "group") {
|
|
133
|
+
select.push({
|
|
134
|
+
column: sourceColumn(field),
|
|
135
|
+
as: outputColumn
|
|
136
|
+
});
|
|
137
|
+
groupBy.push(outputColumn);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (report.kind === "bucket") {
|
|
141
|
+
select.push({
|
|
142
|
+
bucket: report.unit,
|
|
143
|
+
column: sourceColumn(field),
|
|
144
|
+
as: outputColumn
|
|
145
|
+
});
|
|
146
|
+
groupBy.push(outputColumn);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
select.push({
|
|
150
|
+
fn: report.fn,
|
|
151
|
+
column: report.column ? sourceColumn(field) : void 0,
|
|
152
|
+
as: outputColumn,
|
|
153
|
+
distinct: report.distinct
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (select.length === 0) throw new Error(`${definition.reportClassName} has no report fields. Add @groupBy(), a time-bucket decorator, or an aggregate decorator.`);
|
|
157
|
+
return {
|
|
158
|
+
from: definition.sourceTable,
|
|
159
|
+
select,
|
|
160
|
+
groupBy,
|
|
161
|
+
where: normalizeWhere(definition.where, (field) => toSnakeCase(field)),
|
|
162
|
+
having: normalizeWhere(definition.having, (field) => reportFieldToColumn.get(field) ?? toSnakeCase(field))
|
|
163
|
+
};
|
|
208
164
|
}
|
|
209
165
|
async function compileReportSpec(reportCtor) {
|
|
210
|
-
|
|
166
|
+
return compileReportDefinition(await buildReportDefinition(reportCtor));
|
|
211
167
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
getReportGroupingColumns
|
|
217
|
-
};
|
|
218
|
-
//# sourceMappingURL=compiler.js.map
|
|
168
|
+
//#endregion
|
|
169
|
+
export { buildReportDefinition, compileReportDefinition, compileReportSpec, getReportGroupingColumns };
|
|
170
|
+
|
|
171
|
+
//# sourceMappingURL=compiler.js.map
|
package/dist/compiler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.js","sources":["../src/compiler.ts"],"sourcesContent":["import { ObjectRegistry, type SmrtObject } from '@happyvertical/smrt-core';\nimport { toSnakeCase } from '@happyvertical/smrt-core/utils';\nimport type { WhereClause } from '@happyvertical/sql';\nimport { getRuntimeReportOptions } from './decorators.js';\nimport type {\n AggregateSelectExpr,\n AggregateSpec,\n ReportDefinition,\n ReportFieldDefinition,\n ReportFieldMetadata,\n ReportOptions,\n ReportSource,\n} from './types.js';\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\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\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 reportFields.push({\n fieldName,\n columnName: toSnakeCase(fieldName),\n type: field.type,\n report,\n });\n }\n\n return {\n reportClassName,\n sourceClassName: sourceName,\n sourceTable,\n fields: reportFields,\n where: options.where,\n having: options.having,\n refresh: options.refresh,\n };\n}\n\nfunction sourceColumn(field: ReportFieldDefinition): 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 toSnakeCase(report.sourceColumn ?? field.fieldName);\n }\n if (report.kind === 'bucket') {\n return toSnakeCase(report.sourceColumn);\n }\n if (report.kind === 'aggregate') {\n if (!report.column) return '';\n return toSnakeCase(report.column);\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\nexport function compileReportDefinition(\n definition: ReportDefinition,\n): AggregateSpec {\n const select: AggregateSelectExpr[] = [];\n const groupBy: string[] = [];\n const reportFieldToColumn = new Map<string, string>();\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 column: sourceColumn(field),\n 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),\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) : undefined,\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 return {\n from: definition.sourceTable,\n select,\n groupBy,\n where: normalizeWhere(definition.where, (field) => toSnakeCase(field)),\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"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compiler.js","names":[],"sources":["../src/compiler.ts"],"sourcesContent":["import { ObjectRegistry, type SmrtObject } from '@happyvertical/smrt-core';\nimport { toSnakeCase } from '@happyvertical/smrt-core/utils';\nimport type { WhereClause } from '@happyvertical/sql';\nimport { getRuntimeReportOptions } from './decorators.js';\nimport type {\n AggregateSelectExpr,\n AggregateSpec,\n ReportDefinition,\n ReportFieldDefinition,\n ReportFieldMetadata,\n ReportOptions,\n ReportSource,\n} from './types.js';\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\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\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 reportFields.push({\n fieldName,\n columnName: toSnakeCase(fieldName),\n type: field.type,\n report,\n });\n }\n\n return {\n reportClassName,\n sourceClassName: sourceName,\n sourceTable,\n fields: reportFields,\n where: options.where,\n having: options.having,\n refresh: options.refresh,\n };\n}\n\nfunction sourceColumn(field: ReportFieldDefinition): 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 toSnakeCase(report.sourceColumn ?? field.fieldName);\n }\n if (report.kind === 'bucket') {\n return toSnakeCase(report.sourceColumn);\n }\n if (report.kind === 'aggregate') {\n if (!report.column) return '';\n return toSnakeCase(report.column);\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\nexport function compileReportDefinition(\n definition: ReportDefinition,\n): AggregateSpec {\n const select: AggregateSelectExpr[] = [];\n const groupBy: string[] = [];\n const reportFieldToColumn = new Map<string, string>();\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 column: sourceColumn(field),\n 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),\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) : undefined,\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 return {\n from: definition.sourceTable,\n select,\n groupBy,\n where: normalizeWhere(definition.where, (field) => toSnakeCase(field)),\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":"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"}
|
package/dist/decorators.js
CHANGED
|
@@ -1,84 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { ObjectRegistry, registerCompatibleFieldDecorator } from "@happyvertical/smrt-core";
|
|
2
|
+
//#region src/decorators.ts
|
|
3
|
+
var reportOptionsByConstructor = /* @__PURE__ */ new WeakMap();
|
|
3
4
|
function report(options) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
return (ctor) => {
|
|
6
|
+
reportOptionsByConstructor.set(ctor, options);
|
|
7
|
+
return ctor;
|
|
8
|
+
};
|
|
8
9
|
}
|
|
9
10
|
function getRuntimeReportOptions(ctor) {
|
|
10
|
-
|
|
11
|
+
return reportOptionsByConstructor.get(ctor);
|
|
11
12
|
}
|
|
12
13
|
function registerReportField(metadata) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
ObjectRegistry.registerFieldDecorator(className, propertyKey, {
|
|
19
|
-
__report: metadata
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
);
|
|
23
|
-
});
|
|
14
|
+
return ((targetOrValue, propertyKeyOrContext) => {
|
|
15
|
+
registerCompatibleFieldDecorator(targetOrValue, propertyKeyOrContext, (className, propertyKey) => {
|
|
16
|
+
ObjectRegistry.registerFieldDecorator(className, propertyKey, { __report: metadata });
|
|
17
|
+
});
|
|
18
|
+
});
|
|
24
19
|
}
|
|
25
20
|
function groupBy(sourceColumn) {
|
|
26
|
-
|
|
21
|
+
return registerReportField({
|
|
22
|
+
kind: "group",
|
|
23
|
+
sourceColumn
|
|
24
|
+
});
|
|
27
25
|
}
|
|
28
26
|
function bucket(unit, sourceColumn) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
return registerReportField({
|
|
28
|
+
kind: "bucket",
|
|
29
|
+
unit,
|
|
30
|
+
sourceColumn
|
|
31
|
+
});
|
|
34
32
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
var minute = (sourceColumn) => bucket("minute", sourceColumn);
|
|
34
|
+
var hour = (sourceColumn) => bucket("hour", sourceColumn);
|
|
35
|
+
var day = (sourceColumn) => bucket("day", sourceColumn);
|
|
36
|
+
var week = (sourceColumn) => bucket("week", sourceColumn);
|
|
37
|
+
var month = (sourceColumn) => bucket("month", sourceColumn);
|
|
38
|
+
var quarter = (sourceColumn) => bucket("quarter", sourceColumn);
|
|
39
|
+
var year = (sourceColumn) => bucket("year", sourceColumn);
|
|
42
40
|
function aggregate(options) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
return registerReportField({
|
|
42
|
+
kind: "aggregate",
|
|
43
|
+
fn: options.fn,
|
|
44
|
+
column: options.column,
|
|
45
|
+
distinct: options.distinct
|
|
46
|
+
});
|
|
49
47
|
}
|
|
50
48
|
function aggregateDecorator(fn) {
|
|
51
|
-
|
|
49
|
+
return (column, options = {}) => aggregate({
|
|
50
|
+
fn,
|
|
51
|
+
column,
|
|
52
|
+
distinct: options.distinct
|
|
53
|
+
});
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
var sum = aggregateDecorator("sum");
|
|
56
|
+
var avg = aggregateDecorator("avg");
|
|
57
|
+
var min = aggregateDecorator("min");
|
|
58
|
+
var max = aggregateDecorator("max");
|
|
57
59
|
function count(columnOrOptions, options = {}) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
distinct: resolvedOptions.distinct
|
|
64
|
-
});
|
|
60
|
+
return aggregate({
|
|
61
|
+
fn: "count",
|
|
62
|
+
column: typeof columnOrOptions === "string" ? columnOrOptions : void 0,
|
|
63
|
+
distinct: (typeof columnOrOptions === "object" ? columnOrOptions : options).distinct
|
|
64
|
+
});
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
day,
|
|
71
|
-
getRuntimeReportOptions,
|
|
72
|
-
groupBy,
|
|
73
|
-
hour,
|
|
74
|
-
max,
|
|
75
|
-
min,
|
|
76
|
-
minute,
|
|
77
|
-
month,
|
|
78
|
-
quarter,
|
|
79
|
-
report,
|
|
80
|
-
sum,
|
|
81
|
-
week,
|
|
82
|
-
year
|
|
83
|
-
};
|
|
84
|
-
//# sourceMappingURL=decorators.js.map
|
|
66
|
+
//#endregion
|
|
67
|
+
export { aggregate, avg, count, day, getRuntimeReportOptions, groupBy, hour, max, min, minute, month, quarter, report, sum, week, year };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=decorators.js.map
|
package/dist/decorators.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.js","sources":["../src/decorators.ts"],"sourcesContent":["import {\n ObjectRegistry,\n registerCompatibleFieldDecorator,\n} from '@happyvertical/smrt-core';\nimport type {\n ReportAggregateFieldMetadata,\n ReportAggregateFn,\n ReportBucketFieldMetadata,\n ReportFieldMetadata,\n ReportOptions,\n ReportTimeBucketUnit,\n} from './types.js';\n\nconst reportOptionsByConstructor = new WeakMap<Function, ReportOptions>();\n\nexport function report(options: ReportOptions) {\n return <T extends abstract new (...args: any[]) => any>(ctor: T): T => {\n reportOptionsByConstructor.set(ctor, options);\n return ctor;\n };\n}\n\nexport function getRuntimeReportOptions(\n ctor: Function,\n): ReportOptions | undefined {\n return reportOptionsByConstructor.get(ctor);\n}\n\nfunction registerReportField(metadata: ReportFieldMetadata) {\n return ((targetOrValue: any, propertyKeyOrContext: any) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey) => {\n ObjectRegistry.registerFieldDecorator(className, propertyKey, {\n __report: metadata,\n });\n },\n );\n }) as PropertyDecorator;\n}\n\nexport function groupBy(sourceColumn?: string): PropertyDecorator {\n return registerReportField({ kind: 'group', sourceColumn });\n}\n\nfunction bucket(\n unit: ReportTimeBucketUnit,\n sourceColumn: string,\n): PropertyDecorator {\n return registerReportField({\n kind: 'bucket',\n unit,\n sourceColumn,\n } satisfies ReportBucketFieldMetadata);\n}\n\nexport const minute = (sourceColumn: string) => bucket('minute', sourceColumn);\nexport const hour = (sourceColumn: string) => bucket('hour', sourceColumn);\nexport const day = (sourceColumn: string) => bucket('day', sourceColumn);\nexport const week = (sourceColumn: string) => bucket('week', sourceColumn);\nexport const month = (sourceColumn: string) => bucket('month', sourceColumn);\nexport const quarter = (sourceColumn: string) =>\n bucket('quarter', sourceColumn);\nexport const year = (sourceColumn: string) => bucket('year', sourceColumn);\n\nexport interface AggregateDecoratorOptions {\n fn: ReportAggregateFn;\n column?: string;\n distinct?: boolean;\n}\n\nexport function aggregate(\n options: AggregateDecoratorOptions,\n): PropertyDecorator {\n return registerReportField({\n kind: 'aggregate',\n fn: options.fn,\n column: options.column,\n distinct: options.distinct,\n } satisfies ReportAggregateFieldMetadata);\n}\n\nfunction aggregateDecorator(fn: ReportAggregateFn) {\n return (column?: string, options: { distinct?: boolean } = {}) =>\n aggregate({ fn, column, distinct: options.distinct });\n}\n\nexport const sum = aggregateDecorator('sum');\nexport const avg = aggregateDecorator('avg');\nexport const min = aggregateDecorator('min');\nexport const max = aggregateDecorator('max');\n\nexport function count(): PropertyDecorator;\nexport function count(\n column: string,\n options?: { distinct?: boolean },\n): PropertyDecorator;\nexport function count(options: { distinct?: boolean }): PropertyDecorator;\nexport function count(\n columnOrOptions?: string | { distinct?: boolean },\n options: { distinct?: boolean } = {},\n): PropertyDecorator {\n const column =\n typeof columnOrOptions === 'string' ? columnOrOptions : undefined;\n const resolvedOptions =\n typeof columnOrOptions === 'object' ? columnOrOptions : options;\n return aggregate({\n fn: 'count',\n column,\n distinct: resolvedOptions.distinct,\n });\n}\n"],"
|
|
1
|
+
{"version":3,"file":"decorators.js","names":[],"sources":["../src/decorators.ts"],"sourcesContent":["import {\n ObjectRegistry,\n registerCompatibleFieldDecorator,\n} from '@happyvertical/smrt-core';\nimport type {\n ReportAggregateFieldMetadata,\n ReportAggregateFn,\n ReportBucketFieldMetadata,\n ReportFieldMetadata,\n ReportOptions,\n ReportTimeBucketUnit,\n} from './types.js';\n\nconst reportOptionsByConstructor = new WeakMap<Function, ReportOptions>();\n\nexport function report(options: ReportOptions) {\n return <T extends abstract new (...args: any[]) => any>(ctor: T): T => {\n reportOptionsByConstructor.set(ctor, options);\n return ctor;\n };\n}\n\nexport function getRuntimeReportOptions(\n ctor: Function,\n): ReportOptions | undefined {\n return reportOptionsByConstructor.get(ctor);\n}\n\nfunction registerReportField(metadata: ReportFieldMetadata) {\n return ((targetOrValue: any, propertyKeyOrContext: any) => {\n registerCompatibleFieldDecorator(\n targetOrValue,\n propertyKeyOrContext,\n (className, propertyKey) => {\n ObjectRegistry.registerFieldDecorator(className, propertyKey, {\n __report: metadata,\n });\n },\n );\n }) as PropertyDecorator;\n}\n\nexport function groupBy(sourceColumn?: string): PropertyDecorator {\n return registerReportField({ kind: 'group', sourceColumn });\n}\n\nfunction bucket(\n unit: ReportTimeBucketUnit,\n sourceColumn: string,\n): PropertyDecorator {\n return registerReportField({\n kind: 'bucket',\n unit,\n sourceColumn,\n } satisfies ReportBucketFieldMetadata);\n}\n\nexport const minute = (sourceColumn: string) => bucket('minute', sourceColumn);\nexport const hour = (sourceColumn: string) => bucket('hour', sourceColumn);\nexport const day = (sourceColumn: string) => bucket('day', sourceColumn);\nexport const week = (sourceColumn: string) => bucket('week', sourceColumn);\nexport const month = (sourceColumn: string) => bucket('month', sourceColumn);\nexport const quarter = (sourceColumn: string) =>\n bucket('quarter', sourceColumn);\nexport const year = (sourceColumn: string) => bucket('year', sourceColumn);\n\nexport interface AggregateDecoratorOptions {\n fn: ReportAggregateFn;\n column?: string;\n distinct?: boolean;\n}\n\nexport function aggregate(\n options: AggregateDecoratorOptions,\n): PropertyDecorator {\n return registerReportField({\n kind: 'aggregate',\n fn: options.fn,\n column: options.column,\n distinct: options.distinct,\n } satisfies ReportAggregateFieldMetadata);\n}\n\nfunction aggregateDecorator(fn: ReportAggregateFn) {\n return (column?: string, options: { distinct?: boolean } = {}) =>\n aggregate({ fn, column, distinct: options.distinct });\n}\n\nexport const sum = aggregateDecorator('sum');\nexport const avg = aggregateDecorator('avg');\nexport const min = aggregateDecorator('min');\nexport const max = aggregateDecorator('max');\n\nexport function count(): PropertyDecorator;\nexport function count(\n column: string,\n options?: { distinct?: boolean },\n): PropertyDecorator;\nexport function count(options: { distinct?: boolean }): PropertyDecorator;\nexport function count(\n columnOrOptions?: string | { distinct?: boolean },\n options: { distinct?: boolean } = {},\n): PropertyDecorator {\n const column =\n typeof columnOrOptions === 'string' ? columnOrOptions : undefined;\n const resolvedOptions =\n typeof columnOrOptions === 'object' ? columnOrOptions : options;\n return aggregate({\n fn: 'count',\n column,\n distinct: resolvedOptions.distinct,\n });\n}\n"],"mappings":";;AAaA,IAAM,6CAA6B,IAAI,QAAiC;AAEjE,SAAS,OAAO,SAAwB;CAC7C,QAAwD,SAAe;EACrE,2BAA2B,IAAI,MAAM,OAAO;EAC5C,OAAO;CACT;AACF;AAEO,SAAS,wBACd,MAC2B;CAC3B,OAAO,2BAA2B,IAAI,IAAI;AAC5C;AAEA,SAAS,oBAAoB,UAA+B;CAC1D,SAAS,eAAoB,yBAA8B;EACzD,iCACE,eACA,uBACC,WAAW,gBAAgB;GAC1B,eAAe,uBAAuB,WAAW,aAAa,EAC5D,UAAU,SACZ,CAAC;EACH,CACF;CACF;AACF;AAEO,SAAS,QAAQ,cAA0C;CAChE,OAAO,oBAAoB;EAAE,MAAM;EAAS;CAAa,CAAC;AAC5D;AAEA,SAAS,OACP,MACA,cACmB;CACnB,OAAO,oBAAoB;EACzB,MAAM;EACN;EACA;CACF,CAAqC;AACvC;AAEO,IAAM,UAAU,iBAAyB,OAAO,UAAU,YAAY;AACtE,IAAM,QAAQ,iBAAyB,OAAO,QAAQ,YAAY;AAClE,IAAM,OAAO,iBAAyB,OAAO,OAAO,YAAY;AAChE,IAAM,QAAQ,iBAAyB,OAAO,QAAQ,YAAY;AAClE,IAAM,SAAS,iBAAyB,OAAO,SAAS,YAAY;AACpE,IAAM,WAAW,iBACtB,OAAO,WAAW,YAAY;AACzB,IAAM,QAAQ,iBAAyB,OAAO,QAAQ,YAAY;AAQlE,SAAS,UACd,SACmB;CACnB,OAAO,oBAAoB;EACzB,MAAM;EACN,IAAI,QAAQ;EACZ,QAAQ,QAAQ;EAChB,UAAU,QAAQ;CACpB,CAAwC;AAC1C;AAEA,SAAS,mBAAmB,IAAuB;CACjD,QAAQ,QAAiB,UAAkC,CAAC,MAC1D,UAAU;EAAE;EAAI;EAAQ,UAAU,QAAQ;CAAS,CAAC;AACxD;AAEO,IAAM,MAAM,mBAAmB,KAAK;AACpC,IAAM,MAAM,mBAAmB,KAAK;AACpC,IAAM,MAAM,mBAAmB,KAAK;AACpC,IAAM,MAAM,mBAAmB,KAAK;AAQpC,SAAS,MACd,iBACA,UAAkC,CAAC,GAChB;CAKnB,OAAO,UAAU;EACf,IAAI;EACJ,QALA,OAAO,oBAAoB,WAAW,kBAAkB,KAAA;EAMxD,WAJA,OAAO,oBAAoB,WAAW,kBAAkB,QAAA,CAI9B;CAC5B,CAAC;AACH"}
|