@objectstack/service-analytics 17.0.0 → 17.1.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/dist/index.cjs CHANGED
@@ -41,9 +41,9 @@ __export(index_exports, {
41
41
  module.exports = __toCommonJS(index_exports);
42
42
 
43
43
  // src/analytics-service.ts
44
- var import_data5 = require("@objectstack/spec/data");
44
+ var import_data6 = require("@objectstack/spec/data");
45
45
  var import_ui2 = require("@objectstack/spec/ui");
46
- var import_core5 = require("@objectstack/core");
46
+ var import_core6 = require("@objectstack/core");
47
47
  var import_types = require("@objectstack/types");
48
48
 
49
49
  // src/cube-registry.ts
@@ -162,21 +162,18 @@ var CubeRegistry = class {
162
162
  };
163
163
 
164
164
  // src/strategies/filter-normalizer.ts
165
- var import_data = require("@objectstack/spec/data");
165
+ var import_data2 = require("@objectstack/spec/data");
166
166
  var import_api = require("@objectstack/spec/api");
167
167
 
168
168
  // src/comparand-shape.ts
169
+ var import_core = require("@objectstack/core");
170
+ var import_data = require("@objectstack/spec/data");
169
171
  function isBindableComparand(value) {
170
- if (value === null || value === void 0) return true;
171
- const kind = typeof value;
172
- if (kind === "string" || kind === "number" || kind === "bigint" || kind === "boolean") return true;
173
- return value instanceof Date || ArrayBuffer.isView(value);
172
+ if (value === void 0) return true;
173
+ return (0, import_data.isAcceptedFilterComparand)(value) || ArrayBuffer.isView(value);
174
174
  }
175
175
  function isRenderableTextComparand(value) {
176
- if (value === null || value === void 0) return true;
177
- const kind = typeof value;
178
- if (kind === "string" || kind === "number" || kind === "bigint" || kind === "boolean") return true;
179
- return value instanceof Date;
176
+ return value === void 0 || (0, import_data.isAcceptedFilterComparand)(value);
180
177
  }
181
178
  function isFieldReference(value) {
182
179
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
@@ -212,6 +209,51 @@ function findIn(node, field) {
212
209
  }
213
210
  return null;
214
211
  }
212
+ function findUninterpretableTemporalMember(filter, kindOf) {
213
+ return findUninterpretableIn(filter, "", kindOf);
214
+ }
215
+ function findUninterpretableIn(node, field, kindOf) {
216
+ if (!node || typeof node !== "object") return null;
217
+ if (Array.isArray(node)) {
218
+ for (const child of node) {
219
+ const hit = findUninterpretableIn(child, field, kindOf);
220
+ if (hit) return hit;
221
+ }
222
+ return null;
223
+ }
224
+ if (node instanceof Date || ArrayBuffer.isView(node)) return null;
225
+ if (isFieldReference(node)) return null;
226
+ for (const [key, value] of Object.entries(node)) {
227
+ const scope = key.startsWith("$") ? field : key;
228
+ const kind = scope ? kindOf(scope) : null;
229
+ if (kind) {
230
+ const hit2 = judgeTemporalLiterals(value, scope, kind);
231
+ if (hit2) return hit2;
232
+ continue;
233
+ }
234
+ const hit = findUninterpretableIn(value, scope, kindOf);
235
+ if (hit) return hit;
236
+ }
237
+ return null;
238
+ }
239
+ function judgeTemporalLiterals(value, field, kind) {
240
+ if (Array.isArray(value)) {
241
+ for (const member of value) {
242
+ const hit = judgeTemporalLiterals(member, field, kind);
243
+ if (hit) return hit;
244
+ }
245
+ return null;
246
+ }
247
+ if (value && typeof value === "object") {
248
+ if (value instanceof Date || ArrayBuffer.isView(value) || isFieldReference(value)) return null;
249
+ for (const nested of Object.values(value)) {
250
+ const hit = judgeTemporalLiterals(nested, field, kind);
251
+ if (hit) return hit;
252
+ }
253
+ return null;
254
+ }
255
+ return (0, import_core.isUninterpretableTemporalComparand)(kind, value) ? { field, kind, value } : null;
256
+ }
215
257
  var TEXT_PATTERN_OPERATORS = /* @__PURE__ */ new Set([
216
258
  "$contains",
217
259
  "$notContains",
@@ -229,7 +271,7 @@ function shapePreview(value) {
229
271
  }
230
272
  }
231
273
  function unrenderableTextComparandMessage(op, field, value) {
232
- return `"${op}" on "${field}" matches against the TEXT of a pattern, but its comparand is ${Array.isArray(value) ? "an array" : "an object"} (${shapePreview(value)}). filter.zod.ts declares it a string (StringOperatorSchema); a string, number, boolean, null or Date is accepted. Refusing rather than stringifying it: String({}) is "[object Object]", so the pattern that ran would be one nobody wrote \u2014 and a row storing that literal text matches it.`;
274
+ return `"${op}" on "${field}" matches against the TEXT of a pattern, but its comparand is ${Array.isArray(value) ? "an array" : "an object"} (${shapePreview(value)}). filter.zod.ts declares it a string (StringOperatorSchema); ${import_data.ACCEPTED_FILTER_COMPARAND_TYPES_SENTENCE} is accepted. Refusing rather than stringifying it: String({}) is "[object Object]", so the pattern that ran would be one nobody wrote \u2014 and a row storing that literal text matches it.`;
233
275
  }
234
276
  function fieldReferenceComparandMessage(op, field, ref, position) {
235
277
  return `"${op}" on "${field}"${position ? ` (${position})` : ""} compares against the field reference { "$field": "${ref}" }, which this compiler does not lower into a column-to-column comparison. Refusing rather than binding it: the reference object used to become the BOUND VALUE of the comparison, so the emitted predicate compared "${field}" against the reference itself \u2014 a value no row can hold \u2014 and a read scope built from it answered the wrong row set with nothing to read. \u26A0\uFE0F This is NOT the platform declining the rule. @objectstack/spec declares this shape (FieldReferenceSchema), @objectstack/formula resolves it per record in memory, driver-sql / driver-sqlite-wasm compile it to a same-table column comparison for the six scalar operators since #5222, and since the 2026-08-12 ruling on #7598 the analytics native-SQL strategy DECLINES such a query so it routes to the ObjectQL engine path and runs there \u2014 the driver enforcing declared-only enumeration, the tenant-isolation ban and the comparison class with metadata it owns. What refuses here is this SQL lowering, whose only remaining caller is the /analytics/sql display echo; it has no faithful rendering of the predicate the engine path actually runs, and half-rendering one would describe a query that returns different rows. Run the query itself (/analytics/query) to get its rows (#7598).`;
@@ -238,7 +280,7 @@ function fieldReferenceBetweenBoundMessage(op, field, ref, index) {
238
280
  return `"${op}" on "${field}" has the field reference { "$field": "${ref}" } at index ${index} of its [min, max] bounds. A range BOUND may not be a field reference on any backend: driver-sql and driver-sqlite-wasm refuse both endpoints (#5222), @objectstack/formula does not resolve a reference inside a list either \u2014 it orders the bounds against the raw reference object, which no value compares meaningfully to \u2014 and @objectstack/spec no longer declares the position at all (#7596 removed FieldReferenceSchema from the $between endpoint union, ADR-0049 declared = enforced). Refusing rather than lowering it: this compiler splits $between into its two bounds, so the reference would arrive at the driver under a "$gte" / "$lte" the author never wrote \u2014 a position the SQL drivers DO compile \u2014 and the range would quietly succeed here while the identical filter is refused everywhere else. Use a literal bound, or spell the comparison you meant as a scalar one ({ "${field}": { "$gte": { "$field": "${ref}" } } }), which IS served \u2014 on the ObjectQL engine path, where the driver enforces the #5222 rulings (#7598).`;
239
281
  }
240
282
  function unbindableListMemberMessage(op, field, value, index) {
241
- return `"${op}" on "${field}" has a value at index ${index} of its list that cannot be bound as a SQL parameter: ${shapePreview(value)}. Every member of an $in/$nin/$between list is a comparand in its own right \u2014 use a string, number, boolean, null, Date or binary value. Refusing rather than binding it: the member can equal no stored value, so the list silently loses that entry (and a $nin loses the exclusion the caller wrote).`;
283
+ return `"${op}" on "${field}" has a value at index ${index} of its list that cannot be bound as a SQL parameter: ${shapePreview(value)}. Every member of an $in/$nin/$between list is a comparand in its own right \u2014 use ${import_data.ACCEPTED_FILTER_COMPARAND_TYPES_SENTENCE} (or a binary value). Refusing rather than binding it: the member can equal no stored value, so the list silently loses that entry (and a $nin loses the exclusion the caller wrote).`;
242
284
  }
243
285
 
244
286
  // src/strategies/filter-normalizer.ts
@@ -586,7 +628,7 @@ function nullSafeNegationOperand(node) {
586
628
  }
587
629
  function filterArrayNotLowerableError(where) {
588
630
  return invalidFilterError(
589
- `[analytics] received a 'where' array that is not a filter: ${JSON.stringify(where)}. A filter array is a comparison [field, operator, value], a logical node ["and"|"or", ...conditions], or a list of those \u2014 it is INPUT-ONLY sugar (spec 'FilterArray'), lowered to a FilterCondition by @objectstack/spec parseFilterAST() at every door, this one included (#5158/#5334). This value cannot be lowered, and an unapplied filter would have charted the UNFILTERED dataset. Recognised operators: ${[...import_data.VALID_AST_OPERATORS].sort().join(", ")}. Infix joins ([condA, "or", condB]) are NOT one of the shapes \u2014 write the prefix form ["or", condA, condB].`
631
+ `[analytics] received a 'where' array that is not a filter: ${JSON.stringify(where)}. A filter array is a comparison [field, operator, value], a logical node ["and"|"or", ...conditions], or a list of those \u2014 it is INPUT-ONLY sugar (spec 'FilterArray'), lowered to a FilterCondition by @objectstack/spec parseFilterAST() at every door, this one included (#5158/#5334). This value cannot be lowered, and an unapplied filter would have charted the UNFILTERED dataset. Recognised operators: ${[...import_data2.VALID_AST_OPERATORS].sort().join(", ")}. Infix joins ([condA, "or", condB]) are NOT one of the shapes \u2014 write the prefix form ["or", condA, condB].`
590
632
  );
591
633
  }
592
634
  function lowerAnalyticsWhere(query) {
@@ -595,8 +637,8 @@ function lowerAnalyticsWhere(query) {
595
637
  if (!where || typeof where !== "object") return null;
596
638
  if (Array.isArray(where)) {
597
639
  if (where.length === 0) return null;
598
- if (!(0, import_data.isFilterAST)(where)) throw filterArrayNotLowerableError(where);
599
- const condition = (0, import_data.parseFilterAST)(where);
640
+ if (!(0, import_data2.isFilterAST)(where)) throw filterArrayNotLowerableError(where);
641
+ const condition = (0, import_data2.parseFilterAST)(where);
600
642
  if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
601
643
  throw invalidFilterError(
602
644
  `[analytics] filter array ${JSON.stringify(where)} passed isFilterAST() but parseFilterAST() lowered it to ${JSON.stringify(condition)}. Refusing rather than charting the dataset unfiltered (#5158/#5334).`
@@ -1019,7 +1061,7 @@ function invalidMemberError(message, meta) {
1019
1061
  }
1020
1062
 
1021
1063
  // src/strategies/native-sql-strategy.ts
1022
- var import_core = require("@objectstack/core");
1064
+ var import_core2 = require("@objectstack/core");
1023
1065
  var AGGREGATE_SQL = {
1024
1066
  "count": () => "COUNT(*)",
1025
1067
  "sum": (col) => `SUM(${col})`,
@@ -1051,9 +1093,51 @@ var NativeSQLStrategy = class {
1051
1093
  }
1052
1094
  }
1053
1095
  if (this.carriesCrossFieldComparison(query, ctx)) return false;
1096
+ if (this.carriesUninterpretableTemporalComparand(query, ctx)) return false;
1054
1097
  const caps = ctx.queryCapabilities(query.cube);
1055
1098
  return caps.nativeSql && typeof ctx.executeRawSql === "function";
1056
1099
  }
1100
+ /**
1101
+ * [#8690] Does the query's `where` compare a declared TIME dimension against
1102
+ * a value no temporal storage rule can read? See the ruling at
1103
+ * {@link canHandle}.
1104
+ *
1105
+ * The classification comes from the CUBE, the only metadata this package has:
1106
+ * a dimension declares `type: 'time'` (compiled from the dataset dimension's
1107
+ * `type: 'date'`), and {@link lookupMember} is the same resolution every other
1108
+ * member lookup in this strategy uses, so "the member the gate classified"
1109
+ * and "the member the compiler emits" cannot drift apart.
1110
+ *
1111
+ * A `time` dimension is read with the DATETIME rule — the permissive one of
1112
+ * the three. That is the right direction because this is a routing decision,
1113
+ * not a verdict: the engine door re-judges with the field's real declared
1114
+ * type and has the final say, so under-classifying an exotic spelling merely
1115
+ * leaves today's behaviour, while over-classifying would silently move a
1116
+ * working dashboard off the fast path. The comparands this card measured
1117
+ * (`last_30_days`, `not-a-date-at-all`) are unreadable under all three rules,
1118
+ * so the decline fires for them whichever backing type the dimension has.
1119
+ *
1120
+ * `lowerAnalyticsWhere` rather than `query.where` raw, so the authored ARRAY
1121
+ * sugar is seen after `parseFilterAST` has lowered it; a THROW from that
1122
+ * lowering is not this gate's to answer — the filter is malformed either way
1123
+ * and `normalizeAnalyticsFilterTree` refuses it a moment later with the
1124
+ * message and envelope it has always had.
1125
+ */
1126
+ carriesUninterpretableTemporalComparand(query, ctx) {
1127
+ const cube = query.cube ? ctx.getCube(query.cube) : void 0;
1128
+ if (!cube) return false;
1129
+ let where = null;
1130
+ try {
1131
+ where = lowerAnalyticsWhere(query);
1132
+ } catch {
1133
+ return false;
1134
+ }
1135
+ if (!where) return false;
1136
+ return findUninterpretableTemporalMember(
1137
+ where,
1138
+ (member) => this.lookupMember(cube, member, "dimension")?.type === "time" ? "datetime" : null
1139
+ ) !== null;
1140
+ }
1057
1141
  /**
1058
1142
  * [#7598] Does serving this query require the cross-field capability this
1059
1143
  * strategy declines? See the ruling recorded at {@link canHandle}.
@@ -1176,7 +1260,7 @@ var NativeSQLStrategy = class {
1176
1260
  if (range.length === 2) {
1177
1261
  const td2 = this.resolveStorageTarget(cube, td.dimension, tableName);
1178
1262
  const column = this.temporalColumn(ctx, td2, colExpr);
1179
- const nextDay = (0, import_core.nextUtcCalendarDay)(range[1]);
1263
+ const nextDay = (0, import_core2.nextUtcCalendarDay)(range[1]);
1180
1264
  params.push(this.coerceTemporal(ctx, td2, range[0]));
1181
1265
  const lower = `${column} >= $${params.length}`;
1182
1266
  if (nextDay != null) {
@@ -1546,7 +1630,7 @@ var NativeSQLStrategy = class {
1546
1630
  return `${rawCol} ${sqlOp} ${patternRef} ESCAPE $${params.length}`;
1547
1631
  }
1548
1632
  if (operator === "lte") {
1549
- const nextDay = (0, import_core.nextUtcCalendarDay)(values[0]);
1633
+ const nextDay = (0, import_core2.nextUtcCalendarDay)(values[0]);
1550
1634
  if (nextDay != null) {
1551
1635
  params.push(this.coerceTemporal(ctx, target, nextDay));
1552
1636
  return `${this.temporalColumn(ctx, target, rawCol)} < $${params.length}`;
@@ -1576,8 +1660,8 @@ var NativeSQLStrategy = class {
1576
1660
  };
1577
1661
 
1578
1662
  // src/strategies/objectql-strategy.ts
1579
- var import_data2 = require("@objectstack/spec/data");
1580
- var import_core2 = require("@objectstack/core");
1663
+ var import_data3 = require("@objectstack/spec/data");
1664
+ var import_core3 = require("@objectstack/core");
1581
1665
 
1582
1666
  // src/strategies/cross-object-rebucket.ts
1583
1667
  var RECOMBINABLE_METHODS = /* @__PURE__ */ new Set([
@@ -1822,7 +1906,7 @@ var ObjectQLStrategy = class {
1822
1906
  );
1823
1907
  if (filterClause) whereParts.push(filterClause);
1824
1908
  for (const { field, bounds } of this.dateRangeBounds(cube, query)) {
1825
- const nextDay = (0, import_core2.nextUtcCalendarDay)(bounds.$lte);
1909
+ const nextDay = (0, import_core3.nextUtcCalendarDay)(bounds.$lte);
1826
1910
  params.push(bounds.$gte, nextDay ?? bounds.$lte);
1827
1911
  whereParts.push(
1828
1912
  `(${field} >= $${params.length - 1} AND ${field} ${nextDay ? "<" : "<="} $${params.length})`
@@ -1873,11 +1957,11 @@ var ObjectQLStrategy = class {
1873
1957
  * predicate. `$and` makes that structurally impossible.
1874
1958
  */
1875
1959
  withReadScope(objectName, filter, ctx) {
1876
- const userFilter = Object.keys(filter).length > 0 ? (0, import_data2.markFilterSubtreeProvenance)(filter, "author") : void 0;
1960
+ const userFilter = Object.keys(filter).length > 0 ? (0, import_data3.markFilterSubtreeProvenance)(filter, "author") : void 0;
1877
1961
  if (typeof ctx.getReadScope !== "function") return userFilter;
1878
1962
  const scope = ctx.getReadScope(objectName);
1879
1963
  if (scope === void 0 || scope === null) return userFilter;
1880
- const scopeFilter = (0, import_data2.markFilterSubtreeProvenance)(scope, "policy");
1964
+ const scopeFilter = (0, import_data3.markFilterSubtreeProvenance)(scope, "policy");
1881
1965
  if (!userFilter) return scopeFilter;
1882
1966
  return { $and: [userFilter, scopeFilter] };
1883
1967
  }
@@ -2051,7 +2135,7 @@ var ObjectQLStrategy = class {
2051
2135
  if (fkValues.length === 0 || typeof ctx.executeAggregate !== "function") return map;
2052
2136
  const idFilter = { id: { $in: fkValues } };
2053
2137
  const scope = typeof ctx.getReadScope === "function" ? ctx.getReadScope(refObject) : null;
2054
- if (scope != null) (0, import_data2.markFilterSubtreeProvenance)(scope, "policy");
2138
+ if (scope != null) (0, import_data3.markFilterSubtreeProvenance)(scope, "policy");
2055
2139
  const filter = scope != null ? { $and: [idFilter, scope] } : idFilter;
2056
2140
  const rows = await ctx.executeAggregate(refObject, {
2057
2141
  groupBy: ["id", attr],
@@ -2543,10 +2627,10 @@ var ObjectQLStrategy = class {
2543
2627
  };
2544
2628
 
2545
2629
  // src/dataset-compiler.ts
2546
- var import_data3 = require("@objectstack/spec/data");
2630
+ var import_data4 = require("@objectstack/spec/data");
2547
2631
  var import_ui = require("@objectstack/spec/ui");
2548
2632
  var UNSUPPORTED_AGGREGATES = /* @__PURE__ */ new Set();
2549
- var SUPPORTED_AGGREGATES = import_data3.AggregationFunction.options.filter((a) => !UNSUPPORTED_AGGREGATES.has(a));
2633
+ var SUPPORTED_AGGREGATES = import_data4.AggregationFunction.options.filter((a) => !UNSUPPORTED_AGGREGATES.has(a));
2550
2634
  function aggregateToMetricType(m) {
2551
2635
  if (!m.aggregate) {
2552
2636
  throw new Error(`[dataset-compiler] non-derived measure "${m.name}" has no aggregate`);
@@ -2708,11 +2792,11 @@ function compileDataset(dataset, resolver, options) {
2708
2792
  }
2709
2793
 
2710
2794
  // src/dataset-executor.ts
2711
- var import_data4 = require("@objectstack/spec/data");
2712
- var import_core3 = require("@objectstack/core");
2795
+ var import_data5 = require("@objectstack/spec/data");
2796
+ var import_core4 = require("@objectstack/core");
2713
2797
  function resolveSelectionTokens(compiled, selection, context) {
2714
- const tokenCtx = (0, import_core3.filterTokenContextFrom)(context, /* @__PURE__ */ new Date());
2715
- const resolve = (v) => (0, import_core3.resolveFilterTokens)(v, tokenCtx);
2798
+ const tokenCtx = (0, import_core4.filterTokenContextFrom)(context, /* @__PURE__ */ new Date());
2799
+ const resolve = (v) => (0, import_core4.resolveFilterTokens)(v, tokenCtx);
2716
2800
  const filter = resolve(compiled.filter);
2717
2801
  const measureFilters = resolve(compiled.measureFilters);
2718
2802
  const runtimeFilter = resolve(selection.runtimeFilter);
@@ -2748,7 +2832,7 @@ function evaluateDerivedMeasures(rows, derived) {
2748
2832
  }
2749
2833
  function fillEmptyGroups(rows, columnAggregates) {
2750
2834
  for (const [column, aggregate2] of Object.entries(columnAggregates)) {
2751
- const empty = (0, import_data4.emptyGroupValueFor)(aggregate2);
2835
+ const empty = (0, import_data5.emptyGroupValueFor)(aggregate2);
2752
2836
  if (empty === void 0) continue;
2753
2837
  for (const row of rows) if (row[column] == null) row[column] = empty;
2754
2838
  }
@@ -2948,7 +3032,7 @@ function bucketKeyAtOrdinal(ordinal, granularity) {
2948
3032
  }
2949
3033
  function alignedCompareBucketKey(key, granularity, kind, currentRange, shiftedRange) {
2950
3034
  if (typeof key !== "string" || key.length === 0) return null;
2951
- const span = (0, import_core3.bucketKeyToCalendarRange)(key, granularity);
3035
+ const span = (0, import_core4.bucketKeyToCalendarRange)(key, granularity);
2952
3036
  if (!span) return null;
2953
3037
  const targetOrdinal = kind === "previousYear" ? bucketOrdinalOfDay(shiftYear(span.start, 1), granularity) : bucketOrdinalOfDay(span.start, granularity) + (bucketOrdinalOfDay(currentRange[0], granularity) - bucketOrdinalOfDay(shiftedRange[0], granularity));
2954
3038
  const first = bucketOrdinalOfDay(currentRange[0], granularity);
@@ -3412,18 +3496,18 @@ function pickDisplayField(fields) {
3412
3496
  }
3413
3497
 
3414
3498
  // src/preview-evaluator.ts
3415
- var import_core4 = require("@objectstack/core");
3499
+ var import_core5 = require("@objectstack/core");
3416
3500
  function compare(a, b) {
3417
3501
  if (typeof a === "number" && typeof b === "number") return a - b;
3418
3502
  if (a instanceof Date || b instanceof Date) {
3419
- const ai = (0, import_core4.utcInstantMs)(a);
3420
- const bi = (0, import_core4.utcInstantMs)(b);
3503
+ const ai = (0, import_core5.utcInstantMs)(a);
3504
+ const bi = (0, import_core5.utcInstantMs)(b);
3421
3505
  if (ai !== null && bi !== null) return ai - bi;
3422
3506
  }
3423
3507
  return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;
3424
3508
  }
3425
3509
  function lteBound(value, bound) {
3426
- const nextDay = (0, import_core4.nextUtcCalendarDay)(bound);
3510
+ const nextDay = (0, import_core5.nextUtcCalendarDay)(bound);
3427
3511
  if (nextDay != null) return compare(value, nextDay) < 0;
3428
3512
  return compare(value, bound) <= 0;
3429
3513
  }
@@ -3481,7 +3565,7 @@ function matchesWhere(row, where) {
3481
3565
  function bucketDate(value, granularity, timezone) {
3482
3566
  const d = new Date(String(value));
3483
3567
  if (Number.isNaN(d.getTime())) return null;
3484
- const { year: y, month, day: dayNum } = (0, import_core4.calendarPartsInTzOrUtc)(d, timezone);
3568
+ const { year: y, month, day: dayNum } = (0, import_core5.calendarPartsInTzOrUtc)(d, timezone);
3485
3569
  const m = `${month}`.padStart(2, "0");
3486
3570
  const day = `${dayNum}`.padStart(2, "0");
3487
3571
  switch (granularity) {
@@ -3535,7 +3619,7 @@ function evaluateAnalyticsQueryOverRows(query, cube, rows) {
3535
3619
  const [start, end] = Array.isArray(td.dateRange) ? td.dateRange : [td.dateRange, td.dateRange];
3536
3620
  filtered = filtered.filter((r) => {
3537
3621
  const v = String(r[field] ?? "");
3538
- const nextDay = (0, import_core4.nextUtcCalendarDay)(end);
3622
+ const nextDay = (0, import_core5.nextUtcCalendarDay)(end);
3539
3623
  const inUpper = nextDay != null ? v < nextDay : v <= `${end}~`;
3540
3624
  return v >= String(start) && inUpper;
3541
3625
  });
@@ -3669,7 +3753,7 @@ var AnalyticsService = class {
3669
3753
  this.datasetRegistry = /* @__PURE__ */ new Map();
3670
3754
  /** [#3867] One-shot flag for the {@link assertInferableCube} stand-down warning. */
3671
3755
  this.warnedNoObjectRegistry = false;
3672
- this.logger = config.logger || (0, import_core5.createLogger)({ level: "info", format: "pretty" });
3756
+ this.logger = config.logger || (0, import_core6.createLogger)({ level: "info", format: "pretty" });
3673
3757
  this.cubeRegistry = new CubeRegistry();
3674
3758
  if (config.cubes) {
3675
3759
  this.cubeRegistry.registerAll(config.cubes);
@@ -3683,7 +3767,7 @@ var AnalyticsService = class {
3683
3767
  this.getObjectFieldNames = config.getObjectFieldNames;
3684
3768
  this.getObjectDatasource = config.getObjectDatasource;
3685
3769
  this.isExternalObject = config.isExternalObject;
3686
- this.debugSql = config.debugSql ?? (0, import_core5.getEnv)("NODE_ENV") === "development";
3770
+ this.debugSql = config.debugSql ?? (0, import_core6.getEnv)("NODE_ENV") === "development";
3687
3771
  if (config.datasets) {
3688
3772
  for (const ds of config.datasets) {
3689
3773
  try {
@@ -3948,11 +4032,11 @@ var AnalyticsService = class {
3948
4032
  else if (rangeTz === "UTC") rangeDims.push({ d, granularity, instant: false });
3949
4033
  }
3950
4034
  if (rangeDims.length && result.rows.length) {
3951
- const bound = (ymd, instant) => instant ? new Date((0, import_core5.zonedDateStartToUtcMs)(ymd, rangeTz)).toISOString() : ymd;
4035
+ const bound = (ymd, instant) => instant ? new Date((0, import_core6.zonedDateStartToUtcMs)(ymd, rangeTz)).toISOString() : ymd;
3952
4036
  result.drillRanges = result.rows.map((row) => {
3953
4037
  const ranges = {};
3954
4038
  for (const { d, granularity, instant } of rangeDims) {
3955
- const cal = (0, import_core5.bucketKeyToCalendarRange)(row[d.name], granularity);
4039
+ const cal = (0, import_core6.bucketKeyToCalendarRange)(row[d.name], granularity);
3956
4040
  if (cal) {
3957
4041
  ranges[d.name] = { field: d.field, gte: bound(cal.start, instant), lt: bound(cal.end, instant) };
3958
4042
  }
@@ -4003,7 +4087,7 @@ var AnalyticsService = class {
4003
4087
  }
4004
4088
  }
4005
4089
  if (f.percentScale == null) {
4006
- f.percentScale = m.derived?.op === "ratio" ? "fraction" : (0, import_data5.percentScaleOf)(meta);
4090
+ f.percentScale = m.derived?.op === "ratio" ? "fraction" : (0, import_data6.percentScaleOf)(meta);
4007
4091
  }
4008
4092
  }
4009
4093
  }