@objectstack/service-analytics 8.0.0 → 8.0.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 +51 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +51 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1176,13 +1176,60 @@ var AnalyticsService = class {
|
|
|
1176
1176
|
* current request's ExecutionContext (ADR-0021 D-C). The strategy then sees a
|
|
1177
1177
|
* `getReadScope(objectName)` that already knows the active tenant.
|
|
1178
1178
|
*/
|
|
1179
|
-
callCtx(context) {
|
|
1179
|
+
async callCtx(query, context) {
|
|
1180
1180
|
if (!this.readScopeProvider) return this.baseCtx;
|
|
1181
|
+
const scopes = await this.resolveReadScopes(query, context);
|
|
1181
1182
|
return {
|
|
1182
1183
|
...this.baseCtx,
|
|
1183
|
-
getReadScope: (objectName) =>
|
|
1184
|
+
getReadScope: (objectName) => scopes.get(objectName) ?? null
|
|
1184
1185
|
};
|
|
1185
1186
|
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Resolve the read scope (tenant + RLS `FilterCondition`) for the base object
|
|
1189
|
+
* AND every joined object of the query's cube, keyed by object name. This is
|
|
1190
|
+
* the async pre-pass that lets the synchronous strategy enforce scoping even
|
|
1191
|
+
* when the provider (security `getReadFilter`) resolves asynchronously.
|
|
1192
|
+
*
|
|
1193
|
+
* The object set is `cube.sql` (base) plus every `cube.joins[*].name` — a
|
|
1194
|
+
* SUPERSET of what the strategy actually scans (the strategy only joins along
|
|
1195
|
+
* declared relationships), so no scanned object is ever left unscoped.
|
|
1196
|
+
*
|
|
1197
|
+
* Fail-closed: if the provider throws for an object, the whole query is
|
|
1198
|
+
* rejected rather than emitting SQL with that object unscoped.
|
|
1199
|
+
*/
|
|
1200
|
+
async resolveReadScopes(query, context) {
|
|
1201
|
+
const map = /* @__PURE__ */ new Map();
|
|
1202
|
+
const provider = this.readScopeProvider;
|
|
1203
|
+
if (!provider || !query.cube) return map;
|
|
1204
|
+
const cube = this.cubeRegistry.get(query.cube);
|
|
1205
|
+
if (!cube) return map;
|
|
1206
|
+
const objects = /* @__PURE__ */ new Set();
|
|
1207
|
+
if (typeof cube.sql === "string" && cube.sql.trim()) {
|
|
1208
|
+
objects.add(cube.sql.trim());
|
|
1209
|
+
}
|
|
1210
|
+
const joins = cube.joins;
|
|
1211
|
+
if (joins) {
|
|
1212
|
+
for (const [alias, j] of Object.entries(joins)) {
|
|
1213
|
+
objects.add(j?.name ?? alias);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
for (const object of objects) {
|
|
1217
|
+
let filter;
|
|
1218
|
+
try {
|
|
1219
|
+
filter = await provider(object, context);
|
|
1220
|
+
} catch (e) {
|
|
1221
|
+
this.logger.error?.(
|
|
1222
|
+
`[Analytics] read-scope resolution failed for object "${object}" \u2014 rejecting query (fail-closed, ADR-0021 D-C)`,
|
|
1223
|
+
e instanceof Error ? e : new Error(String(e))
|
|
1224
|
+
);
|
|
1225
|
+
throw new Error(
|
|
1226
|
+
`[Analytics] read-scope resolution failed for "${object}"; query denied (fail-closed).`
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
if (filter != null) map.set(object, filter);
|
|
1230
|
+
}
|
|
1231
|
+
return map;
|
|
1232
|
+
}
|
|
1186
1233
|
/**
|
|
1187
1234
|
* Execute an analytical query by delegating to the first capable strategy.
|
|
1188
1235
|
*/
|
|
@@ -1191,7 +1238,7 @@ var AnalyticsService = class {
|
|
|
1191
1238
|
throw new Error("Cube name is required in analytics query");
|
|
1192
1239
|
}
|
|
1193
1240
|
this.ensureCube(query);
|
|
1194
|
-
const ctx = this.callCtx(context);
|
|
1241
|
+
const ctx = await this.callCtx(query, context);
|
|
1195
1242
|
const strategy = this.resolveStrategy(query, ctx);
|
|
1196
1243
|
this.logger.debug(`[Analytics] Query on cube "${query.cube}" \u2192 ${strategy.name}`);
|
|
1197
1244
|
return strategy.execute(query, ctx);
|
|
@@ -1246,7 +1293,7 @@ var AnalyticsService = class {
|
|
|
1246
1293
|
throw new Error("Cube name is required for SQL generation");
|
|
1247
1294
|
}
|
|
1248
1295
|
this.ensureCube(query);
|
|
1249
|
-
const ctx = this.callCtx(context);
|
|
1296
|
+
const ctx = await this.callCtx(query, context);
|
|
1250
1297
|
const strategy = this.resolveStrategy(query, ctx);
|
|
1251
1298
|
this.logger.debug(`[Analytics] generateSql on cube "${query.cube}" \u2192 ${strategy.name}`);
|
|
1252
1299
|
return strategy.generateSql(query, ctx);
|