@objectstack/service-analytics 10.0.0 → 10.3.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.d.cts CHANGED
@@ -221,6 +221,14 @@ interface AnalyticsServiceConfig {
221
221
  * `StrategyContext.coerceTemporalFilterValue` for the full rationale.
222
222
  */
223
223
  coerceTemporalFilterValue?: (objectName: string, fieldName: string, value: unknown) => unknown;
224
+ /**
225
+ * ADR-0062 D6 — report whether an object is federated (external datasource).
226
+ * Threaded into the StrategyContext so `NativeSQLStrategy` declines external
227
+ * objects (which it would otherwise query against the wrong physical table),
228
+ * routing them to the driver-correct ObjectQL aggregate path instead. See
229
+ * `StrategyContext.isExternalObject`.
230
+ */
231
+ isExternalObject?: (objectName: string) => boolean;
224
232
  /**
225
233
  * ADR-0021 — optional object-graph resolver used when compiling datasets:
226
234
  * `(baseObject, relationshipName) => relatedObjectName | undefined`. When
package/dist/index.d.ts CHANGED
@@ -221,6 +221,14 @@ interface AnalyticsServiceConfig {
221
221
  * `StrategyContext.coerceTemporalFilterValue` for the full rationale.
222
222
  */
223
223
  coerceTemporalFilterValue?: (objectName: string, fieldName: string, value: unknown) => unknown;
224
+ /**
225
+ * ADR-0062 D6 — report whether an object is federated (external datasource).
226
+ * Threaded into the StrategyContext so `NativeSQLStrategy` declines external
227
+ * objects (which it would otherwise query against the wrong physical table),
228
+ * routing them to the driver-correct ObjectQL aggregate path instead. See
229
+ * `StrategyContext.isExternalObject`.
230
+ */
231
+ isExternalObject?: (objectName: string) => boolean;
224
232
  /**
225
233
  * ADR-0021 — optional object-graph resolver used when compiling datasets:
226
234
  * `(baseObject, relationshipName) => relatedObjectName | undefined`. When
package/dist/index.js CHANGED
@@ -320,6 +320,17 @@ var NativeSQLStrategy = class {
320
320
  canHandle(query, ctx) {
321
321
  if (!query.cube) return false;
322
322
  if (query.timeDimensions?.some((td) => !!td.granularity)) return false;
323
+ if (typeof ctx.isExternalObject === "function") {
324
+ const cube = ctx.getCube(query.cube);
325
+ if (cube) {
326
+ if (ctx.isExternalObject(this.extractObjectName(cube))) return false;
327
+ const joinTargets = cube.joins ? Object.values(cube.joins) : [];
328
+ for (const j of joinTargets) {
329
+ const joinedObject = j?.name;
330
+ if (joinedObject && ctx.isExternalObject(joinedObject)) return false;
331
+ }
332
+ }
333
+ }
323
334
  const caps = ctx.queryCapabilities(query.cube);
324
335
  return caps.nativeSql && typeof ctx.executeRawSql === "function";
325
336
  }
@@ -1477,7 +1488,8 @@ var AnalyticsService = class {
1477
1488
  // Prefer a compiled dataset's declared relationships (D-C join allowlist);
1478
1489
  // fall back to any explicitly-configured provider for legacy cubes.
1479
1490
  getAllowedRelationships: (cubeName) => this.datasetRegistry.get(cubeName)?.allowedRelationships ?? config.getAllowedRelationships?.(cubeName),
1480
- coerceTemporalFilterValue: config.coerceTemporalFilterValue
1491
+ coerceTemporalFilterValue: config.coerceTemporalFilterValue,
1492
+ isExternalObject: config.isExternalObject
1481
1493
  };
1482
1494
  const builtIn = [
1483
1495
  new NativeSQLStrategy(),
@@ -1745,9 +1757,10 @@ var AnalyticsService = class {
1745
1757
  if (!cube) {
1746
1758
  cube = this.inferCubeFromQuery(query);
1747
1759
  this.cubeRegistry.register(cube);
1748
- this.logger.warn(
1749
- `[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.`
1750
- );
1760
+ const isScalarMetric = (query.dimensions?.length ?? 0) === 0 && (query.timeDimensions?.length ?? 0) === 0;
1761
+ 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.`;
1762
+ if (isScalarMetric) this.logger.debug(message);
1763
+ else this.logger.warn(message);
1751
1764
  return;
1752
1765
  }
1753
1766
  const stripPrefix = (m) => m.includes(".") ? m.split(".").slice(1).join(".") : m;
@@ -2082,6 +2095,13 @@ var AnalyticsServicePlugin = class {
2082
2095
  const f = dataEngine()?.getObject?.(object)?.fields?.[field];
2083
2096
  return f ? { type: f.type, defaultCurrency: f.currencyConfig?.defaultCurrency } : void 0;
2084
2097
  },
2098
+ // ADR-0062 D6 — a federated object carries an `external` block (ADR-0015).
2099
+ // Reported so NativeSQLStrategy declines it (its hand-compiled FROM would
2100
+ // hit the wrong physical table) and the driver-correct ObjectQL path runs.
2101
+ isExternalObject: (objectName) => {
2102
+ const obj = dataEngine()?.getObject?.(objectName);
2103
+ return !!(obj && obj.external != null);
2104
+ },
2085
2105
  draftRowsResolver
2086
2106
  };
2087
2107
  if (autoBridgedReadScope) {