@objectstack/service-analytics 9.8.0 → 9.9.1
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 +31 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +29 -16
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -381,6 +381,8 @@ interface AnalyticsServicePluginOptions {
|
|
|
381
381
|
alias: string;
|
|
382
382
|
}>;
|
|
383
383
|
filter?: Record<string, unknown>;
|
|
384
|
+
/** Reference timezone (IANA) for date bucketing — ADR-0053 Phase 2. */
|
|
385
|
+
timezone?: string;
|
|
384
386
|
}) => Promise<Record<string, unknown>[]>;
|
|
385
387
|
/**
|
|
386
388
|
* ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). The
|
package/dist/index.d.ts
CHANGED
|
@@ -381,6 +381,8 @@ interface AnalyticsServicePluginOptions {
|
|
|
381
381
|
alias: string;
|
|
382
382
|
}>;
|
|
383
383
|
filter?: Record<string, unknown>;
|
|
384
|
+
/** Reference timezone (IANA) for date bucketing — ADR-0053 Phase 2. */
|
|
385
|
+
timezone?: string;
|
|
384
386
|
}) => Promise<Record<string, unknown>[]>;
|
|
385
387
|
/**
|
|
386
388
|
* ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). The
|
package/dist/index.js
CHANGED
|
@@ -636,7 +636,11 @@ var ObjectQLStrategy = class {
|
|
|
636
636
|
// contract types groupBy as string[]; the cast carries the richer shape.
|
|
637
637
|
groupBy: groupBy.length > 0 ? groupBy : void 0,
|
|
638
638
|
aggregations: aggregations.length > 0 ? aggregations : void 0,
|
|
639
|
-
filter: Object.keys(filter).length > 0 ? filter : void 0
|
|
639
|
+
filter: Object.keys(filter).length > 0 ? filter : void 0,
|
|
640
|
+
// ADR-0053 Phase 2 (D2): forward the reference tz so date buckets resolve
|
|
641
|
+
// on that zone's calendar days. A non-UTC zone makes the engine bucket
|
|
642
|
+
// in-memory (uniform across drivers); UTC/unset keeps the DB fast path.
|
|
643
|
+
timezone: query.timezone
|
|
640
644
|
});
|
|
641
645
|
const mappedRows = rows.map((row) => {
|
|
642
646
|
const mapped = {};
|
|
@@ -1035,7 +1039,8 @@ var DatasetExecutor = class {
|
|
|
1035
1039
|
measures: unfiltered,
|
|
1036
1040
|
dimensions,
|
|
1037
1041
|
where: baseFilter,
|
|
1038
|
-
selection
|
|
1042
|
+
selection,
|
|
1043
|
+
contextTimezone: context?.timezone
|
|
1039
1044
|
}), context);
|
|
1040
1045
|
} else {
|
|
1041
1046
|
result = { rows: [], fields: [] };
|
|
@@ -1046,7 +1051,8 @@ var DatasetExecutor = class {
|
|
|
1046
1051
|
measures: [m],
|
|
1047
1052
|
dimensions,
|
|
1048
1053
|
where: mFilter,
|
|
1049
|
-
selection
|
|
1054
|
+
selection,
|
|
1055
|
+
contextTimezone: context?.timezone
|
|
1050
1056
|
}), context);
|
|
1051
1057
|
result.rows = mergeByDimensions(result.rows, sub.rows, dimensions, [m]);
|
|
1052
1058
|
result.fields.push({ name: m, type: "number" });
|
|
@@ -1070,7 +1076,9 @@ var DatasetExecutor = class {
|
|
|
1070
1076
|
cube: compiled.cube.name,
|
|
1071
1077
|
measures: opts.measures,
|
|
1072
1078
|
dimensions: opts.dimensions,
|
|
1073
|
-
|
|
1079
|
+
// Precedence: explicit selection tz → request's reference tz
|
|
1080
|
+
// (ExecutionContext.timezone, ADR-0053 Phase 2) → UTC.
|
|
1081
|
+
timezone: opts.selection.timezone ?? opts.contextTimezone ?? "UTC"
|
|
1074
1082
|
};
|
|
1075
1083
|
if (opts.where) q.where = opts.where;
|
|
1076
1084
|
const selTimeDims = opts.selection.timeDimensions ?? [];
|
|
@@ -1108,7 +1116,7 @@ var DatasetExecutor = class {
|
|
|
1108
1116
|
dimensions,
|
|
1109
1117
|
where: baseFilter,
|
|
1110
1118
|
timeDimensions: shiftedTd,
|
|
1111
|
-
timezone: selection.timezone ?? "UTC"
|
|
1119
|
+
timezone: selection.timezone ?? context?.timezone ?? "UTC"
|
|
1112
1120
|
}, context);
|
|
1113
1121
|
return sub.rows.map((row) => {
|
|
1114
1122
|
const out = {};
|
|
@@ -1221,6 +1229,7 @@ function pickDisplayField(fields) {
|
|
|
1221
1229
|
}
|
|
1222
1230
|
|
|
1223
1231
|
// src/preview-evaluator.ts
|
|
1232
|
+
import { calendarPartsInTzOrUtc } from "@objectstack/core";
|
|
1224
1233
|
function compare(a, b) {
|
|
1225
1234
|
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
1226
1235
|
return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;
|
|
@@ -1268,23 +1277,23 @@ function matchesWhere(row, where) {
|
|
|
1268
1277
|
}
|
|
1269
1278
|
return true;
|
|
1270
1279
|
}
|
|
1271
|
-
function bucketDate(value, granularity) {
|
|
1280
|
+
function bucketDate(value, granularity, timezone) {
|
|
1272
1281
|
const d = new Date(String(value));
|
|
1273
1282
|
if (Number.isNaN(d.getTime())) return null;
|
|
1274
|
-
const y = d
|
|
1275
|
-
const m = `${
|
|
1276
|
-
const day = `${
|
|
1283
|
+
const { year: y, month, day: dayNum } = calendarPartsInTzOrUtc(d, timezone);
|
|
1284
|
+
const m = `${month}`.padStart(2, "0");
|
|
1285
|
+
const day = `${dayNum}`.padStart(2, "0");
|
|
1277
1286
|
switch (granularity) {
|
|
1278
1287
|
case "year":
|
|
1279
1288
|
return `${y}`;
|
|
1280
1289
|
case "quarter":
|
|
1281
|
-
return `${y}-Q${Math.floor(
|
|
1290
|
+
return `${y}-Q${Math.floor((month - 1) / 3) + 1}`;
|
|
1282
1291
|
case "month":
|
|
1283
1292
|
return `${y}-${m}`;
|
|
1284
1293
|
case "week": {
|
|
1285
|
-
const monday = new Date(
|
|
1286
|
-
const dow = (
|
|
1287
|
-
monday.setUTCDate(
|
|
1294
|
+
const monday = new Date(Date.UTC(y, month - 1, dayNum));
|
|
1295
|
+
const dow = (monday.getUTCDay() + 6) % 7;
|
|
1296
|
+
monday.setUTCDate(monday.getUTCDate() - dow);
|
|
1288
1297
|
return monday.toISOString().slice(0, 10);
|
|
1289
1298
|
}
|
|
1290
1299
|
case "day":
|
|
@@ -1329,6 +1338,7 @@ function evaluateAnalyticsQueryOverRows(query, cube, rows) {
|
|
|
1329
1338
|
});
|
|
1330
1339
|
}
|
|
1331
1340
|
const dimensions = query.dimensions ?? [];
|
|
1341
|
+
const timezone = query.timezone;
|
|
1332
1342
|
const granByDim = new Map(timeDims.filter((t) => t.granularity).map((t) => [t.dimension, t.granularity]));
|
|
1333
1343
|
const keyOf = (r) => {
|
|
1334
1344
|
const values = {};
|
|
@@ -1337,7 +1347,7 @@ function evaluateAnalyticsQueryOverRows(query, cube, rows) {
|
|
|
1337
1347
|
const field = String(dim?.sql ?? name);
|
|
1338
1348
|
const raw = r[field];
|
|
1339
1349
|
const gran = granByDim.get(name) ?? (dim?.type === "time" && dim.granularities?.length === 1 ? String(dim.granularities[0]) : void 0);
|
|
1340
|
-
values[name] = gran ? bucketDate(raw, gran) : raw ?? null;
|
|
1350
|
+
values[name] = gran ? bucketDate(raw, gran, timezone) : raw ?? null;
|
|
1341
1351
|
}
|
|
1342
1352
|
return { key: JSON.stringify(values), values };
|
|
1343
1353
|
};
|
|
@@ -1818,7 +1828,7 @@ var AnalyticsServicePlugin = class {
|
|
|
1818
1828
|
'[Analytics] No "data" service registered yet at init; will retry per-query. Register ObjectQLPlugin or pass executeAggregate.'
|
|
1819
1829
|
);
|
|
1820
1830
|
}
|
|
1821
|
-
executeAggregate = async (objectName, { groupBy, aggregations, filter }) => {
|
|
1831
|
+
executeAggregate = async (objectName, { groupBy, aggregations, filter, timezone }) => {
|
|
1822
1832
|
const engine = tryGetDataEngine();
|
|
1823
1833
|
if (!engine) {
|
|
1824
1834
|
throw new Error(
|
|
@@ -1832,7 +1842,10 @@ var AnalyticsServicePlugin = class {
|
|
|
1832
1842
|
function: a.method,
|
|
1833
1843
|
field: a.field,
|
|
1834
1844
|
alias: a.alias
|
|
1835
|
-
}))
|
|
1845
|
+
})),
|
|
1846
|
+
// ADR-0053 Phase 2: thread the reference tz so date buckets resolve on
|
|
1847
|
+
// that zone's calendar days (engine buckets in-memory when non-UTC).
|
|
1848
|
+
timezone
|
|
1836
1849
|
});
|
|
1837
1850
|
return rows;
|
|
1838
1851
|
};
|