@objectstack/service-analytics 10.2.0 → 11.0.0
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/LICENSE +202 -93
- package/dist/index.cjs +106 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -12
- package/dist/index.d.ts +32 -12
- package/dist/index.js +106 -54
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -63,8 +63,9 @@ interface CompiledDataset {
|
|
|
63
63
|
/** The Cube the dataset compiles to (consumed by the strategy chain). */
|
|
64
64
|
cube: Cube;
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* Every join alias the dataset may use — each declared `include` path AND its
|
|
67
|
+
* intermediate prefixes (ADR-0071). The join allowlist (D-C): the
|
|
68
|
+
* NativeSQLStrategy rejects any join alias not in this set.
|
|
68
69
|
*/
|
|
69
70
|
allowedRelationships: Set<string>;
|
|
70
71
|
/** Derived measures, computed post-aggregation by the executor (Q1). */
|
|
@@ -75,12 +76,26 @@ interface CompiledDataset {
|
|
|
75
76
|
measureFilters: Record<string, FilterCondition>;
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
|
-
*
|
|
79
|
-
* name
|
|
80
|
-
*
|
|
81
|
-
* the relationship name equals the related table name).
|
|
79
|
+
* The related object reached by traversing a relationship: its logical object
|
|
80
|
+
* name (used to resolve the NEXT hop in a multi-hop chain — ADR-0071) and its
|
|
81
|
+
* physical table name (the join target).
|
|
82
82
|
*/
|
|
83
|
-
|
|
83
|
+
interface RelationshipTarget {
|
|
84
|
+
object: string;
|
|
85
|
+
table: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolves a relationship name on a base object to the related object/table,
|
|
89
|
+
* using the runtime's object graph. Optional: when omitted the compiler trusts
|
|
90
|
+
* the declared `include` names (the NativeSQLStrategy convention assumes the
|
|
91
|
+
* relationship name equals the related table name).
|
|
92
|
+
*
|
|
93
|
+
* May return a bare table-name `string` (legacy single-hop: object name is
|
|
94
|
+
* assumed equal to the table) or a {@link RelationshipTarget} (required to
|
|
95
|
+
* traverse further along a multi-hop path, where object differs from table for
|
|
96
|
+
* namespaced objects).
|
|
97
|
+
*/
|
|
98
|
+
type RelationshipResolver = (baseObject: string, relationshipName: string) => string | RelationshipTarget | undefined;
|
|
84
99
|
declare function compileDataset(dataset: Dataset, resolver?: RelationshipResolver): CompiledDataset;
|
|
85
100
|
|
|
86
101
|
/**
|
|
@@ -554,14 +569,19 @@ declare class NativeSQLStrategy implements AnalyticsStrategy {
|
|
|
554
569
|
* responsible for isolation — see contract note).
|
|
555
570
|
*/
|
|
556
571
|
private applyReadScope;
|
|
572
|
+
/** SQL-safe join alias for a relationship path (dots → `__`); single-segment
|
|
573
|
+
* paths are unchanged. Mirrors the dataset compiler's `cube.joins` keying so
|
|
574
|
+
* alias, allowlist, and per-hop RLS all agree on one valid identifier. */
|
|
575
|
+
private joinAlias;
|
|
557
576
|
/**
|
|
558
577
|
* Resolve a dimension/measure/filter SQL expression that may reference a
|
|
559
578
|
* related table via dot notation (e.g. `account.industry`).
|
|
560
579
|
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
580
|
+
* A dotted `sql` is a relationship PATH (ADR-0071 multi-hop): every segment
|
|
581
|
+
* but the last is a to-one relationship hop, the last is the column. Each hop
|
|
582
|
+
* synthesises a `LEFT JOIN` aliased by its full path prefix, chained
|
|
583
|
+
* parent→child. The convention (matching the auto-cube generator and
|
|
584
|
+
* ObjectStack object schemas) for a single hop is:
|
|
565
585
|
*
|
|
566
586
|
* <parentTable>.<lookupField> = <lookupField>.id
|
|
567
587
|
*
|
|
@@ -656,4 +676,4 @@ declare class ObjectQLStrategy implements AnalyticsStrategy {
|
|
|
656
676
|
private buildFieldMeta;
|
|
657
677
|
}
|
|
658
678
|
|
|
659
|
-
export { AnalyticsService, type AnalyticsServiceConfig, AnalyticsServicePlugin, type AnalyticsServicePluginOptions, type CompareTo, type CompiledDataset, CubeRegistry, DatasetExecutor, type DerivedMeasureSpec, type DimensionLabelDeps, type FieldMetaLite, NativeSQLStrategy, ObjectQLStrategy, type RelationshipResolver, combineFilters, compileDataset, compileScopedFilterToSql, evaluateDerivedMeasures, mergeByDimensions, pickDisplayField, resolveDimensionLabels, shiftRange };
|
|
679
|
+
export { AnalyticsService, type AnalyticsServiceConfig, AnalyticsServicePlugin, type AnalyticsServicePluginOptions, type CompareTo, type CompiledDataset, CubeRegistry, DatasetExecutor, type DerivedMeasureSpec, type DimensionLabelDeps, type FieldMetaLite, NativeSQLStrategy, ObjectQLStrategy, type RelationshipResolver, type RelationshipTarget, combineFilters, compileDataset, compileScopedFilterToSql, evaluateDerivedMeasures, mergeByDimensions, pickDisplayField, resolveDimensionLabels, shiftRange };
|
package/dist/index.d.ts
CHANGED
|
@@ -63,8 +63,9 @@ interface CompiledDataset {
|
|
|
63
63
|
/** The Cube the dataset compiles to (consumed by the strategy chain). */
|
|
64
64
|
cube: Cube;
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* Every join alias the dataset may use — each declared `include` path AND its
|
|
67
|
+
* intermediate prefixes (ADR-0071). The join allowlist (D-C): the
|
|
68
|
+
* NativeSQLStrategy rejects any join alias not in this set.
|
|
68
69
|
*/
|
|
69
70
|
allowedRelationships: Set<string>;
|
|
70
71
|
/** Derived measures, computed post-aggregation by the executor (Q1). */
|
|
@@ -75,12 +76,26 @@ interface CompiledDataset {
|
|
|
75
76
|
measureFilters: Record<string, FilterCondition>;
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
|
-
*
|
|
79
|
-
* name
|
|
80
|
-
*
|
|
81
|
-
* the relationship name equals the related table name).
|
|
79
|
+
* The related object reached by traversing a relationship: its logical object
|
|
80
|
+
* name (used to resolve the NEXT hop in a multi-hop chain — ADR-0071) and its
|
|
81
|
+
* physical table name (the join target).
|
|
82
82
|
*/
|
|
83
|
-
|
|
83
|
+
interface RelationshipTarget {
|
|
84
|
+
object: string;
|
|
85
|
+
table: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolves a relationship name on a base object to the related object/table,
|
|
89
|
+
* using the runtime's object graph. Optional: when omitted the compiler trusts
|
|
90
|
+
* the declared `include` names (the NativeSQLStrategy convention assumes the
|
|
91
|
+
* relationship name equals the related table name).
|
|
92
|
+
*
|
|
93
|
+
* May return a bare table-name `string` (legacy single-hop: object name is
|
|
94
|
+
* assumed equal to the table) or a {@link RelationshipTarget} (required to
|
|
95
|
+
* traverse further along a multi-hop path, where object differs from table for
|
|
96
|
+
* namespaced objects).
|
|
97
|
+
*/
|
|
98
|
+
type RelationshipResolver = (baseObject: string, relationshipName: string) => string | RelationshipTarget | undefined;
|
|
84
99
|
declare function compileDataset(dataset: Dataset, resolver?: RelationshipResolver): CompiledDataset;
|
|
85
100
|
|
|
86
101
|
/**
|
|
@@ -554,14 +569,19 @@ declare class NativeSQLStrategy implements AnalyticsStrategy {
|
|
|
554
569
|
* responsible for isolation — see contract note).
|
|
555
570
|
*/
|
|
556
571
|
private applyReadScope;
|
|
572
|
+
/** SQL-safe join alias for a relationship path (dots → `__`); single-segment
|
|
573
|
+
* paths are unchanged. Mirrors the dataset compiler's `cube.joins` keying so
|
|
574
|
+
* alias, allowlist, and per-hop RLS all agree on one valid identifier. */
|
|
575
|
+
private joinAlias;
|
|
557
576
|
/**
|
|
558
577
|
* Resolve a dimension/measure/filter SQL expression that may reference a
|
|
559
578
|
* related table via dot notation (e.g. `account.industry`).
|
|
560
579
|
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
580
|
+
* A dotted `sql` is a relationship PATH (ADR-0071 multi-hop): every segment
|
|
581
|
+
* but the last is a to-one relationship hop, the last is the column. Each hop
|
|
582
|
+
* synthesises a `LEFT JOIN` aliased by its full path prefix, chained
|
|
583
|
+
* parent→child. The convention (matching the auto-cube generator and
|
|
584
|
+
* ObjectStack object schemas) for a single hop is:
|
|
565
585
|
*
|
|
566
586
|
* <parentTable>.<lookupField> = <lookupField>.id
|
|
567
587
|
*
|
|
@@ -656,4 +676,4 @@ declare class ObjectQLStrategy implements AnalyticsStrategy {
|
|
|
656
676
|
private buildFieldMeta;
|
|
657
677
|
}
|
|
658
678
|
|
|
659
|
-
export { AnalyticsService, type AnalyticsServiceConfig, AnalyticsServicePlugin, type AnalyticsServicePluginOptions, type CompareTo, type CompiledDataset, CubeRegistry, DatasetExecutor, type DerivedMeasureSpec, type DimensionLabelDeps, type FieldMetaLite, NativeSQLStrategy, ObjectQLStrategy, type RelationshipResolver, combineFilters, compileDataset, compileScopedFilterToSql, evaluateDerivedMeasures, mergeByDimensions, pickDisplayField, resolveDimensionLabels, shiftRange };
|
|
679
|
+
export { AnalyticsService, type AnalyticsServiceConfig, AnalyticsServicePlugin, type AnalyticsServicePluginOptions, type CompareTo, type CompiledDataset, CubeRegistry, DatasetExecutor, type DerivedMeasureSpec, type DimensionLabelDeps, type FieldMetaLite, NativeSQLStrategy, ObjectQLStrategy, type RelationshipResolver, type RelationshipTarget, combineFilters, compileDataset, compileScopedFilterToSql, evaluateDerivedMeasures, mergeByDimensions, pickDisplayField, resolveDimensionLabels, shiftRange };
|
package/dist/index.js
CHANGED
|
@@ -132,7 +132,7 @@ var MONGO_TO_CUBE_OP = {
|
|
|
132
132
|
};
|
|
133
133
|
function stringifyForCube(v) {
|
|
134
134
|
if (v == null) return "";
|
|
135
|
-
if (typeof v === "boolean") return v ? "
|
|
135
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
136
136
|
if (v instanceof Date) return v.toISOString();
|
|
137
137
|
if (typeof v === "object") return JSON.stringify(v);
|
|
138
138
|
return String(v);
|
|
@@ -187,19 +187,24 @@ function normalizeAnalyticsFilters(query) {
|
|
|
187
187
|
}
|
|
188
188
|
return out;
|
|
189
189
|
}
|
|
190
|
+
function recoverNumber(s) {
|
|
191
|
+
if (/^-?\d+(\.\d+)?$/.test(s)) {
|
|
192
|
+
const n = Number(s);
|
|
193
|
+
if (Number.isFinite(n)) return n;
|
|
194
|
+
}
|
|
195
|
+
return void 0;
|
|
196
|
+
}
|
|
190
197
|
function coerceFilterValueForSql(s) {
|
|
191
198
|
if (s === "true") return 1;
|
|
192
199
|
if (s === "false") return 0;
|
|
193
200
|
if (s === "null") return null;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
return s;
|
|
201
|
+
return recoverNumber(s) ?? s;
|
|
202
|
+
}
|
|
203
|
+
function coerceFilterValueForObjectQL(s) {
|
|
204
|
+
if (s === "true") return true;
|
|
205
|
+
if (s === "false") return false;
|
|
206
|
+
if (s === "null") return null;
|
|
207
|
+
return recoverNumber(s) ?? s;
|
|
203
208
|
}
|
|
204
209
|
|
|
205
210
|
// src/read-scope-sql.ts
|
|
@@ -451,14 +456,21 @@ var NativeSQLStrategy = class {
|
|
|
451
456
|
});
|
|
452
457
|
whereClauses.push(`(${rendered})`);
|
|
453
458
|
}
|
|
459
|
+
/** SQL-safe join alias for a relationship path (dots → `__`); single-segment
|
|
460
|
+
* paths are unchanged. Mirrors the dataset compiler's `cube.joins` keying so
|
|
461
|
+
* alias, allowlist, and per-hop RLS all agree on one valid identifier. */
|
|
462
|
+
joinAlias(path) {
|
|
463
|
+
return path.replace(/\./g, "__");
|
|
464
|
+
}
|
|
454
465
|
/**
|
|
455
466
|
* Resolve a dimension/measure/filter SQL expression that may reference a
|
|
456
467
|
* related table via dot notation (e.g. `account.industry`).
|
|
457
468
|
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
469
|
+
* A dotted `sql` is a relationship PATH (ADR-0071 multi-hop): every segment
|
|
470
|
+
* but the last is a to-one relationship hop, the last is the column. Each hop
|
|
471
|
+
* synthesises a `LEFT JOIN` aliased by its full path prefix, chained
|
|
472
|
+
* parent→child. The convention (matching the auto-cube generator and
|
|
473
|
+
* ObjectStack object schemas) for a single hop is:
|
|
462
474
|
*
|
|
463
475
|
* <parentTable>.<lookupField> = <lookupField>.id
|
|
464
476
|
*
|
|
@@ -470,19 +482,33 @@ var NativeSQLStrategy = class {
|
|
|
470
482
|
* Pure column references (no dot) are returned as-is.
|
|
471
483
|
*/
|
|
472
484
|
qualifyAndRegisterJoin(rawSql, parentTable, joins, cube) {
|
|
473
|
-
if (!rawSql.includes("."))
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
485
|
+
if (!rawSql.includes(".")) {
|
|
486
|
+
const canJoin = !!cube?.joins && Object.keys(cube.joins).length > 0;
|
|
487
|
+
if (canJoin && /^[A-Za-z_][A-Za-z0-9_]*$/.test(rawSql)) {
|
|
488
|
+
return `"${parentTable}"."${rawSql}"`;
|
|
489
|
+
}
|
|
490
|
+
return rawSql;
|
|
491
|
+
}
|
|
492
|
+
const segments = rawSql.split(".");
|
|
493
|
+
const column = segments[segments.length - 1];
|
|
494
|
+
const hops = segments.slice(0, -1);
|
|
495
|
+
if (hops.length === 0 || !column) return rawSql;
|
|
496
|
+
let parentAlias = parentTable;
|
|
497
|
+
let prefix = "";
|
|
498
|
+
for (const seg of hops) {
|
|
499
|
+
prefix = prefix ? `${prefix}.${seg}` : seg;
|
|
500
|
+
const alias = this.joinAlias(prefix);
|
|
501
|
+
if (!joins.has(alias)) {
|
|
502
|
+
const joinTable = cube?.joins?.[alias]?.name ?? alias;
|
|
503
|
+
const tableRef = joinTable === alias ? `"${alias}"` : `"${joinTable}" "${alias}"`;
|
|
504
|
+
joins.set(
|
|
505
|
+
alias,
|
|
506
|
+
`LEFT JOIN ${tableRef} ON "${parentAlias}"."${seg}" = "${alias}"."id"`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
parentAlias = alias;
|
|
484
510
|
}
|
|
485
|
-
return `"${
|
|
511
|
+
return `"${parentAlias}"."${column}"`;
|
|
486
512
|
}
|
|
487
513
|
/**
|
|
488
514
|
* Resolve a member reference (dimension, measure, or filter field) to its
|
|
@@ -568,9 +594,11 @@ var NativeSQLStrategy = class {
|
|
|
568
594
|
const measure = dim ? void 0 : this.lookupMember(cube, member, "measure");
|
|
569
595
|
const rawSql = dim?.sql ?? measure?.sql ?? (member.includes(".") ? member.split(".").slice(1).join(".") : member);
|
|
570
596
|
if (rawSql.includes(".")) {
|
|
571
|
-
const
|
|
572
|
-
const
|
|
573
|
-
|
|
597
|
+
const segments = rawSql.split(".");
|
|
598
|
+
const field = segments[segments.length - 1];
|
|
599
|
+
const relPath = segments.slice(0, -1).join(".");
|
|
600
|
+
const object = cube.joins?.[this.joinAlias(relPath)]?.name ?? relPath;
|
|
601
|
+
return { object, field };
|
|
574
602
|
}
|
|
575
603
|
return { object: baseTable, field: rawSql };
|
|
576
604
|
}
|
|
@@ -815,8 +843,8 @@ var ObjectQLStrategy = class {
|
|
|
815
843
|
if (operator === "set") return { $ne: null };
|
|
816
844
|
if (operator === "notSet") return null;
|
|
817
845
|
if (!values || values.length === 0) return void 0;
|
|
818
|
-
const v0 =
|
|
819
|
-
const all = values.map(
|
|
846
|
+
const v0 = coerceFilterValueForObjectQL(values[0]);
|
|
847
|
+
const all = values.map(coerceFilterValueForObjectQL);
|
|
820
848
|
switch (operator) {
|
|
821
849
|
case "equals":
|
|
822
850
|
return v0;
|
|
@@ -863,6 +891,9 @@ var ObjectQLStrategy = class {
|
|
|
863
891
|
// src/dataset-compiler.ts
|
|
864
892
|
var UNSUPPORTED_AGGREGATES = /* @__PURE__ */ new Set(["array_agg", "string_agg"]);
|
|
865
893
|
function aggregateToMetricType(m) {
|
|
894
|
+
if (!m.aggregate) {
|
|
895
|
+
throw new Error(`[dataset-compiler] non-derived measure "${m.name}" has no aggregate`);
|
|
896
|
+
}
|
|
866
897
|
if (UNSUPPORTED_AGGREGATES.has(m.aggregate)) {
|
|
867
898
|
throw new Error(
|
|
868
899
|
`[dataset-compiler] measure "${m.name}" uses aggregate "${m.aggregate}" which is not supported by the v1 dataset runtime (supported: count, sum, avg, min, max, count_distinct).`
|
|
@@ -886,36 +917,56 @@ function dimensionType(d) {
|
|
|
886
917
|
return "string";
|
|
887
918
|
}
|
|
888
919
|
}
|
|
889
|
-
function
|
|
890
|
-
const idx = field.
|
|
920
|
+
function fieldRelationshipPath(field) {
|
|
921
|
+
const idx = field.lastIndexOf(".");
|
|
891
922
|
return idx > 0 ? field.slice(0, idx) : null;
|
|
892
923
|
}
|
|
924
|
+
var MAX_JOIN_HOPS = 3;
|
|
925
|
+
var joinAlias = (path) => path.replace(/\./g, "__");
|
|
893
926
|
function compileDataset(dataset, resolver) {
|
|
894
927
|
const include = dataset.include ?? [];
|
|
895
|
-
const
|
|
928
|
+
const resolveHop = (fromObject, rel) => {
|
|
929
|
+
if (!resolver) return { object: rel, table: rel };
|
|
930
|
+
const resolved = resolver(fromObject, rel);
|
|
931
|
+
if (!resolved) {
|
|
932
|
+
throw new Error(
|
|
933
|
+
`[dataset-compiler] dataset "${dataset.name}" includes relationship "${rel}" which does not exist on object "${fromObject}".`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
return typeof resolved === "string" ? { object: resolved, table: resolved } : resolved;
|
|
937
|
+
};
|
|
896
938
|
const joins = {};
|
|
897
|
-
for (const
|
|
898
|
-
|
|
899
|
-
if (
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
939
|
+
for (const path of include) {
|
|
940
|
+
const segments = path.split(".");
|
|
941
|
+
if (segments.length > MAX_JOIN_HOPS) {
|
|
942
|
+
throw new Error(
|
|
943
|
+
`[dataset-compiler] dataset "${dataset.name}" include path "${path}" exceeds the ${MAX_JOIN_HOPS}-hop limit (${segments.length} hops). Deeper traversal is not supported.`
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
let fromObject = dataset.object;
|
|
947
|
+
let parentAlias = dataset.object;
|
|
948
|
+
let prefix = "";
|
|
949
|
+
for (const seg of segments) {
|
|
950
|
+
prefix = prefix ? `${prefix}.${seg}` : seg;
|
|
951
|
+
const target = resolveHop(fromObject, seg);
|
|
952
|
+
const alias = joinAlias(prefix);
|
|
953
|
+
if (!joins[alias]) {
|
|
954
|
+
joins[alias] = {
|
|
955
|
+
name: target.table,
|
|
956
|
+
relationship: "many_to_one",
|
|
957
|
+
sql: `${parentAlias}.${seg} = ${prefix}.id`
|
|
958
|
+
};
|
|
905
959
|
}
|
|
906
|
-
|
|
960
|
+
fromObject = target.object;
|
|
961
|
+
parentAlias = prefix;
|
|
907
962
|
}
|
|
908
|
-
joins[rel] = {
|
|
909
|
-
name: targetTable,
|
|
910
|
-
relationship: "many_to_one",
|
|
911
|
-
sql: `${dataset.object}.${rel} = ${rel}.id`
|
|
912
|
-
};
|
|
913
963
|
}
|
|
964
|
+
const allowedRelationships = new Set(Object.keys(joins));
|
|
914
965
|
const assertDeclared = (field, ownerKind, ownerName) => {
|
|
915
|
-
const
|
|
916
|
-
if (
|
|
966
|
+
const relPath = fieldRelationshipPath(field);
|
|
967
|
+
if (relPath && !joins[joinAlias(relPath)]) {
|
|
917
968
|
throw new Error(
|
|
918
|
-
`[dataset-compiler] ${ownerKind} "${ownerName}" references relationship "${
|
|
969
|
+
`[dataset-compiler] ${ownerKind} "${ownerName}" references relationship path "${relPath}" via "${field}", but "${relPath}" is not declared in the dataset's \`include\`. Only fields along a declared relationship path are joinable.`
|
|
919
970
|
);
|
|
920
971
|
}
|
|
921
972
|
};
|
|
@@ -1757,9 +1808,10 @@ var AnalyticsService = class {
|
|
|
1757
1808
|
if (!cube) {
|
|
1758
1809
|
cube = this.inferCubeFromQuery(query);
|
|
1759
1810
|
this.cubeRegistry.register(cube);
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
);
|
|
1811
|
+
const isScalarMetric = (query.dimensions?.length ?? 0) === 0 && (query.timeDimensions?.length ?? 0) === 0;
|
|
1812
|
+
const message = `[Analytics] No cube registered for "${name}"; auto-inferred a minimal cube (sql="${name}", measures=${Object.keys(cube.measures).join(",") || "(none)"}, dimensions=${Object.keys(cube.dimensions).join(",") || "(none)"}). Define an explicit Cube in your stack for full control.`;
|
|
1813
|
+
if (isScalarMetric) this.logger.debug(message);
|
|
1814
|
+
else this.logger.warn(message);
|
|
1763
1815
|
return;
|
|
1764
1816
|
}
|
|
1765
1817
|
const stripPrefix = (m) => m.includes(".") ? m.split(".").slice(1).join(".") : m;
|