@object-ui/data-objectstack 17.5.0 → 17.6.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/CHANGELOG.md +219 -0
- package/dist/index.cjs +262 -357
- package/dist/index.d.cts +385 -448
- package/dist/index.d.ts +385 -448
- package/dist/index.js +258 -350
- package/package.json +5 -6
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/aggregate-capability.test.ts +0 -177
- package/src/apiAccessDenied.test.ts +0 -233
- package/src/appAccessProbe.test.ts +0 -147
- package/src/bulkDelete.test.ts +0 -75
- package/src/bulkUpdate.test.ts +0 -93
- package/src/cache/MetadataCache.test.ts +0 -477
- package/src/cache/MetadataCache.ts +0 -289
- package/src/cloud-surface-retired-4152.pin.test.ts +0 -171
- package/src/connection.test.ts +0 -188
- package/src/contracts.ts +0 -115
- package/src/deleteView.homes.test.ts +0 -389
- package/src/deleteViewContract.types.test.ts +0 -246
- package/src/errors.test.ts +0 -426
- package/src/errors.ts +0 -293
- package/src/exportDownload.test.ts +0 -141
- package/src/filter-entry-translation.test.ts +0 -360
- package/src/filter-operator-ast-parity.test.ts +0 -135
- package/src/getObjectSchema.test.ts +0 -145
- package/src/httpFailureLogging.test.ts +0 -109
- package/src/importJob.test.ts +0 -71
- package/src/index.ts +0 -4442
- package/src/integration.ts +0 -200
- package/src/listViewOverrides.test.ts +0 -241
- package/src/listViews.test.ts +0 -175
- package/src/metadata-client.get-envelope.test.ts +0 -171
- package/src/metadata-client.saveAdvisories.test.ts +0 -226
- package/src/metadata-client.test.ts +0 -303
- package/src/metadata-client.ts +0 -1046
- package/src/occ.test.ts +0 -101
- package/src/onMutation.test.ts +0 -589
- package/src/onSaveAdvisory.test.ts +0 -311
- package/src/onWriteWarning.test.ts +0 -273
- package/src/orderby-serialization.test.ts +0 -115
- package/src/queryDataset.test.ts +0 -543
- package/src/searchAll.test.ts +0 -120
- package/src/security.ts +0 -243
- package/src/spec-symbol-batch6.test.ts +0 -326
- package/src/studio.ts +0 -152
- package/src/updateView.draft.test.ts +0 -242
- package/src/userState.test.ts +0 -315
- package/src/userState.ts +0 -238
- package/src/v3-compat.test.ts +0 -229
- package/src/validation-error.test.ts +0 -94
- package/src/viewCacheInvalidation.pin.test.ts +0 -260
- package/src/viewInvalidationSeam.pin.test.ts +0 -162
package/dist/index.js
CHANGED
|
@@ -246,7 +246,6 @@ var MetadataClient = class _MetadataClient {
|
|
|
246
246
|
"Content-Type": "application/json"
|
|
247
247
|
};
|
|
248
248
|
if (options.ifMatch) headers["If-Match"] = options.ifMatch;
|
|
249
|
-
if (options.actor) headers["X-Actor"] = options.actor;
|
|
250
249
|
const res = await this.fetchImpl(url, {
|
|
251
250
|
method: "PUT",
|
|
252
251
|
headers,
|
|
@@ -295,13 +294,39 @@ var MetadataClient = class _MetadataClient {
|
|
|
295
294
|
return inner;
|
|
296
295
|
}
|
|
297
296
|
/**
|
|
298
|
-
* Get the 3-state layered view of a metadata item (
|
|
299
|
-
*
|
|
300
|
-
*
|
|
297
|
+
* Get the 3-state layered view of a metadata item: `code` (the packaged
|
|
298
|
+
* artifact baseline), `overlay` (the tenant customisation row alone) and
|
|
299
|
+
* `effective` (the merged value the runtime sees).
|
|
300
|
+
*
|
|
301
|
+
* Reads **`GET /meta/:type/:name/layers`** — the path the framework declares
|
|
302
|
+
* for this projection, with a response schema of its own
|
|
303
|
+
* (`GetMetaItemLayeredResponseSchema`, objectstack#5882 ruling B). It used to
|
|
304
|
+
* be reached by hanging a `layers` flag on the ordinary item read, which made
|
|
305
|
+
* one route answer two unrelated representations while `packages/spec`
|
|
306
|
+
* declared only one of them. That spelling still answers this same body
|
|
307
|
+
* inside its deprecation window (the response carries RFC 9745
|
|
308
|
+
* `Deprecation: true` and an RFC 8288 `Link: rel="successor-version"` back to
|
|
309
|
+
* this path) and is scheduled for removal upstream, so nothing here may
|
|
310
|
+
* depend on it — the repo-wide ratchet is
|
|
311
|
+
* `scripts/__tests__/layered-read-declared-path-4016.test.ts`.
|
|
312
|
+
*
|
|
313
|
+
* The request is built here rather than delegated to `@objectstack/client`
|
|
314
|
+
* because the SDK expresses no layered read in EITHER spelling: the
|
|
315
|
+
* framework's REST route ledger records this route as `server-only`,
|
|
316
|
+
* "consumed by objectui over plain HTTP", and whether the SDK should express
|
|
317
|
+
* it is an open upstream product call.
|
|
318
|
+
*
|
|
319
|
+
* One behaviour delta rides along with the path, and it is the server's
|
|
320
|
+
* choice rather than ours: the retired flag FELL THROUGH to the plain item
|
|
321
|
+
* read on a backend whose protocol implementation had no layered support,
|
|
322
|
+
* answering the `{ type, name, item }` envelope. A dedicated path refuses to
|
|
323
|
+
* answer a different resource under this one's declared shape, so it returns
|
|
324
|
+
* 501 `NOT_IMPLEMENTED` instead — which surfaces here as a thrown error
|
|
325
|
+
* rather than a view with `code` and `overlay` silently blank.
|
|
301
326
|
*/
|
|
302
327
|
async layered(type, name, options = {}) {
|
|
303
|
-
const
|
|
304
|
-
const url = `${this.base}/${encodeURIComponent(type)}/${encodeURIComponent(name)}
|
|
328
|
+
const qs = options.packageId ? `?package=${encodeURIComponent(options.packageId)}` : "";
|
|
329
|
+
const url = `${this.base}/${encodeURIComponent(type)}/${encodeURIComponent(name)}/layers${qs}`;
|
|
305
330
|
const res = await this.fetchImpl(url, { method: "GET", headers: this.headers, cache: "no-store" });
|
|
306
331
|
if (res.status === 404) {
|
|
307
332
|
return { code: null, overlay: null, overlayScope: null, effective: null };
|
|
@@ -391,7 +416,6 @@ var MetadataClient = class _MetadataClient {
|
|
|
391
416
|
const url = `${this.base}/${encodeURIComponent(type)}/${encodeURIComponent(name)}${qs}`;
|
|
392
417
|
const headers = { ...this.headers };
|
|
393
418
|
if (options.ifMatch) headers["If-Match"] = options.ifMatch;
|
|
394
|
-
if (options.actor) headers["X-Actor"] = options.actor;
|
|
395
419
|
const res = await this.fetchImpl(url, { method: "DELETE", headers });
|
|
396
420
|
if (!res.ok) throw await parseError(res);
|
|
397
421
|
return await res.json();
|
|
@@ -409,7 +433,6 @@ var MetadataClient = class _MetadataClient {
|
|
|
409
433
|
...this.headers,
|
|
410
434
|
"Content-Type": "application/json"
|
|
411
435
|
};
|
|
412
|
-
if (options.actor) headers["X-Actor"] = options.actor;
|
|
413
436
|
const res = await this.fetchImpl(url, {
|
|
414
437
|
method: "POST",
|
|
415
438
|
headers,
|
|
@@ -432,7 +455,6 @@ var MetadataClient = class _MetadataClient {
|
|
|
432
455
|
...this.headers,
|
|
433
456
|
"Content-Type": "application/json"
|
|
434
457
|
};
|
|
435
|
-
if (options.actor) headers["X-Actor"] = options.actor;
|
|
436
458
|
const res = await this.fetchImpl(url, {
|
|
437
459
|
method: "POST",
|
|
438
460
|
headers,
|
|
@@ -1037,52 +1059,6 @@ function isErrorType(error, errorClass) {
|
|
|
1037
1059
|
return error instanceof errorClass;
|
|
1038
1060
|
}
|
|
1039
1061
|
|
|
1040
|
-
// src/contracts.ts
|
|
1041
|
-
function validatePluginContract(contract) {
|
|
1042
|
-
const errors = [];
|
|
1043
|
-
const warnings = [];
|
|
1044
|
-
if (!contract.name || contract.name.trim().length === 0) {
|
|
1045
|
-
errors.push({ field: "name", message: "Plugin name is required", code: "MISSING_NAME" });
|
|
1046
|
-
}
|
|
1047
|
-
if (!contract.version || !/^\d+\.\d+\.\d+/.test(contract.version)) {
|
|
1048
|
-
errors.push({ field: "version", message: "Valid semver version is required", code: "INVALID_VERSION" });
|
|
1049
|
-
}
|
|
1050
|
-
if (!contract.exports || contract.exports.length === 0) {
|
|
1051
|
-
errors.push({ field: "exports", message: "At least one export is required", code: "NO_EXPORTS" });
|
|
1052
|
-
}
|
|
1053
|
-
if (contract.exports) {
|
|
1054
|
-
const validTypes = ["component", "hook", "utility", "provider"];
|
|
1055
|
-
for (const exp of contract.exports) {
|
|
1056
|
-
if (!exp.name) {
|
|
1057
|
-
errors.push({ field: "exports.name", message: "Export name is required", code: "MISSING_EXPORT_NAME" });
|
|
1058
|
-
}
|
|
1059
|
-
if (!validTypes.includes(exp.type)) {
|
|
1060
|
-
errors.push({ field: "exports.type", message: `Invalid export type: ${exp.type}`, code: "INVALID_EXPORT_TYPE" });
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
if (!contract.permissions || contract.permissions.length === 0) {
|
|
1065
|
-
warnings.push("No permissions declared \u2014 plugin will have minimal access");
|
|
1066
|
-
}
|
|
1067
|
-
return { valid: errors.length === 0, errors, warnings };
|
|
1068
|
-
}
|
|
1069
|
-
function generateContractManifest(contract) {
|
|
1070
|
-
return {
|
|
1071
|
-
$schema: "https://objectui.org/schemas/plugin-contract-v1.json",
|
|
1072
|
-
name: contract.name,
|
|
1073
|
-
version: contract.version,
|
|
1074
|
-
peerDependencies: contract.peerDependencies ?? {},
|
|
1075
|
-
exports: contract.exports.map((exp) => ({
|
|
1076
|
-
name: exp.name,
|
|
1077
|
-
type: exp.type,
|
|
1078
|
-
description: exp.description ?? ""
|
|
1079
|
-
})),
|
|
1080
|
-
permissions: contract.permissions ?? [],
|
|
1081
|
-
api: contract.api ?? {},
|
|
1082
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
1062
|
// src/userState.ts
|
|
1087
1063
|
var DEFAULT_RESOURCE = "sys_user_preference";
|
|
1088
1064
|
function createObjectStackUserStateAdapter(options) {
|
|
@@ -1185,267 +1161,6 @@ function createObjectStackUserStateAdapter(options) {
|
|
|
1185
1161
|
};
|
|
1186
1162
|
}
|
|
1187
1163
|
|
|
1188
|
-
// src/integration.ts
|
|
1189
|
-
var IntegrationManager = class {
|
|
1190
|
-
constructor() {
|
|
1191
|
-
__publicField(this, "integrations", /* @__PURE__ */ new Map());
|
|
1192
|
-
}
|
|
1193
|
-
/**
|
|
1194
|
-
* Register a new integration.
|
|
1195
|
-
*/
|
|
1196
|
-
register(id, config) {
|
|
1197
|
-
this.integrations.set(id, config);
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* Remove an integration.
|
|
1201
|
-
*/
|
|
1202
|
-
unregister(id) {
|
|
1203
|
-
this.integrations.delete(id);
|
|
1204
|
-
}
|
|
1205
|
-
/**
|
|
1206
|
-
* Get all registered integrations.
|
|
1207
|
-
*/
|
|
1208
|
-
getAll() {
|
|
1209
|
-
return new Map(this.integrations);
|
|
1210
|
-
}
|
|
1211
|
-
/**
|
|
1212
|
-
* Get integrations that match a specific event.
|
|
1213
|
-
*/
|
|
1214
|
-
getForEvent(event) {
|
|
1215
|
-
return Array.from(this.integrations.values()).filter(
|
|
1216
|
-
(integration) => integration.enabled && integration.triggers?.some((t) => t.event === event)
|
|
1217
|
-
);
|
|
1218
|
-
}
|
|
1219
|
-
/**
|
|
1220
|
-
* Dispatch an event to all matching integrations.
|
|
1221
|
-
* Returns results for each integration.
|
|
1222
|
-
*/
|
|
1223
|
-
async dispatch(event, payload) {
|
|
1224
|
-
const matching = this.getForEvent(event);
|
|
1225
|
-
const results = [];
|
|
1226
|
-
for (const [id, integration] of this.integrations) {
|
|
1227
|
-
if (!matching.includes(integration)) continue;
|
|
1228
|
-
try {
|
|
1229
|
-
await this.send(integration, payload);
|
|
1230
|
-
results.push({ id, success: true });
|
|
1231
|
-
} catch (err) {
|
|
1232
|
-
results.push({ id, success: false, error: err.message });
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
return results;
|
|
1236
|
-
}
|
|
1237
|
-
/**
|
|
1238
|
-
* Send payload to a specific integration.
|
|
1239
|
-
*/
|
|
1240
|
-
async send(integration, payload) {
|
|
1241
|
-
switch (integration.provider) {
|
|
1242
|
-
case "webhook": {
|
|
1243
|
-
const cfg = integration.config;
|
|
1244
|
-
const url = cfg.url;
|
|
1245
|
-
try {
|
|
1246
|
-
const parsed = new URL(url);
|
|
1247
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1248
|
-
throw new Error(`Invalid URL protocol: ${parsed.protocol}`);
|
|
1249
|
-
}
|
|
1250
|
-
} catch (e) {
|
|
1251
|
-
if (e instanceof TypeError) {
|
|
1252
|
-
throw new Error(`Invalid webhook URL: ${url}`);
|
|
1253
|
-
}
|
|
1254
|
-
throw e;
|
|
1255
|
-
}
|
|
1256
|
-
await fetch(url, {
|
|
1257
|
-
method: cfg.method,
|
|
1258
|
-
headers: {
|
|
1259
|
-
"Content-Type": "application/json",
|
|
1260
|
-
...cfg.headers
|
|
1261
|
-
},
|
|
1262
|
-
body: JSON.stringify(payload)
|
|
1263
|
-
});
|
|
1264
|
-
break;
|
|
1265
|
-
}
|
|
1266
|
-
case "slack": {
|
|
1267
|
-
const cfg = integration.config;
|
|
1268
|
-
const url = cfg.webhookUrl;
|
|
1269
|
-
try {
|
|
1270
|
-
const parsed = new URL(url);
|
|
1271
|
-
if (parsed.protocol !== "https:") {
|
|
1272
|
-
throw new Error(`Invalid Slack webhook URL protocol: ${parsed.protocol}`);
|
|
1273
|
-
}
|
|
1274
|
-
} catch (e) {
|
|
1275
|
-
if (e instanceof TypeError) {
|
|
1276
|
-
throw new Error(`Invalid Slack webhook URL: ${url}`);
|
|
1277
|
-
}
|
|
1278
|
-
throw e;
|
|
1279
|
-
}
|
|
1280
|
-
await fetch(url, {
|
|
1281
|
-
method: "POST",
|
|
1282
|
-
headers: { "Content-Type": "application/json" },
|
|
1283
|
-
body: JSON.stringify({
|
|
1284
|
-
channel: cfg.channel,
|
|
1285
|
-
username: cfg.username,
|
|
1286
|
-
icon_emoji: cfg.iconEmoji,
|
|
1287
|
-
text: JSON.stringify(payload)
|
|
1288
|
-
})
|
|
1289
|
-
});
|
|
1290
|
-
break;
|
|
1291
|
-
}
|
|
1292
|
-
// Email and other providers would require server-side implementation
|
|
1293
|
-
default:
|
|
1294
|
-
break;
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
};
|
|
1298
|
-
|
|
1299
|
-
// src/security.ts
|
|
1300
|
-
var SecurityManager = class {
|
|
1301
|
-
constructor(policy = {}) {
|
|
1302
|
-
__publicField(this, "policy");
|
|
1303
|
-
__publicField(this, "auditLog", []);
|
|
1304
|
-
this.policy = policy;
|
|
1305
|
-
}
|
|
1306
|
-
/**
|
|
1307
|
-
* Generate a CSP header string from the configuration.
|
|
1308
|
-
*/
|
|
1309
|
-
generateCSPHeader() {
|
|
1310
|
-
const csp = this.policy.csp;
|
|
1311
|
-
if (!csp) return "";
|
|
1312
|
-
const directives = [];
|
|
1313
|
-
if (csp.scriptSrc?.length) directives.push(`script-src ${csp.scriptSrc.join(" ")}`);
|
|
1314
|
-
if (csp.styleSrc?.length) directives.push(`style-src ${csp.styleSrc.join(" ")}`);
|
|
1315
|
-
if (csp.imgSrc?.length) directives.push(`img-src ${csp.imgSrc.join(" ")}`);
|
|
1316
|
-
if (csp.connectSrc?.length) directives.push(`connect-src ${csp.connectSrc.join(" ")}`);
|
|
1317
|
-
if (csp.fontSrc?.length) directives.push(`font-src ${csp.fontSrc.join(" ")}`);
|
|
1318
|
-
if (csp.frameSrc?.length) directives.push(`frame-src ${csp.frameSrc.join(" ")}`);
|
|
1319
|
-
if (csp.reportUri) directives.push(`report-uri ${csp.reportUri}`);
|
|
1320
|
-
return directives.join("; ");
|
|
1321
|
-
}
|
|
1322
|
-
/**
|
|
1323
|
-
* Record an audit log entry.
|
|
1324
|
-
*/
|
|
1325
|
-
recordAudit(entry) {
|
|
1326
|
-
if (!this.policy.auditLog?.enabled) return;
|
|
1327
|
-
if (this.policy.auditLog.events && !this.policy.auditLog.events.includes(entry.event)) return;
|
|
1328
|
-
const fullEntry = {
|
|
1329
|
-
...entry,
|
|
1330
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1331
|
-
};
|
|
1332
|
-
this.auditLog.push(fullEntry);
|
|
1333
|
-
const dest = this.policy.auditLog.destination ?? "console";
|
|
1334
|
-
if (dest === "console" || dest === "both") {
|
|
1335
|
-
console.info("[AUDIT]", fullEntry.event, fullEntry.userId, fullEntry.resource ?? "");
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
/**
|
|
1339
|
-
* Get audit log entries.
|
|
1340
|
-
*/
|
|
1341
|
-
getAuditLog(filter) {
|
|
1342
|
-
let entries = [...this.auditLog];
|
|
1343
|
-
if (filter?.event) entries = entries.filter((e) => e.event === filter.event);
|
|
1344
|
-
if (filter?.userId) entries = entries.filter((e) => e.userId === filter.userId);
|
|
1345
|
-
if (filter?.since) entries = entries.filter((e) => e.timestamp >= filter.since);
|
|
1346
|
-
return entries;
|
|
1347
|
-
}
|
|
1348
|
-
/**
|
|
1349
|
-
* Apply data masking to a record.
|
|
1350
|
-
*/
|
|
1351
|
-
maskRecord(record, userRoles = []) {
|
|
1352
|
-
if (!this.policy.dataMasking?.rules?.length) return record;
|
|
1353
|
-
const masked = { ...record };
|
|
1354
|
-
const maskChar = this.policy.dataMasking.maskChar ?? "*";
|
|
1355
|
-
for (const rule of this.policy.dataMasking.rules) {
|
|
1356
|
-
if (!(rule.field in masked) || masked[rule.field] == null) continue;
|
|
1357
|
-
if (rule.exemptRoles?.some((role) => userRoles.includes(role))) continue;
|
|
1358
|
-
const value = String(masked[rule.field]);
|
|
1359
|
-
switch (rule.strategy) {
|
|
1360
|
-
case "full":
|
|
1361
|
-
masked[rule.field] = maskChar.repeat(value.length);
|
|
1362
|
-
break;
|
|
1363
|
-
case "partial": {
|
|
1364
|
-
const visible = rule.visibleChars ?? 4;
|
|
1365
|
-
if (value.length <= visible) {
|
|
1366
|
-
masked[rule.field] = maskChar.repeat(value.length);
|
|
1367
|
-
} else {
|
|
1368
|
-
masked[rule.field] = value.slice(0, visible) + maskChar.repeat(value.length - visible);
|
|
1369
|
-
}
|
|
1370
|
-
break;
|
|
1371
|
-
}
|
|
1372
|
-
case "hash":
|
|
1373
|
-
masked[rule.field] = `[HASHED:${simpleHash(value)}]`;
|
|
1374
|
-
break;
|
|
1375
|
-
case "redact":
|
|
1376
|
-
masked[rule.field] = "[REDACTED]";
|
|
1377
|
-
break;
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
return masked;
|
|
1381
|
-
}
|
|
1382
|
-
/**
|
|
1383
|
-
* Update the security policy.
|
|
1384
|
-
*/
|
|
1385
|
-
updatePolicy(policy) {
|
|
1386
|
-
this.policy = { ...this.policy, ...policy };
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Get current security policy.
|
|
1390
|
-
*/
|
|
1391
|
-
getPolicy() {
|
|
1392
|
-
return { ...this.policy };
|
|
1393
|
-
}
|
|
1394
|
-
};
|
|
1395
|
-
function simpleHash(str) {
|
|
1396
|
-
let hash = 0;
|
|
1397
|
-
for (let i = 0; i < str.length; i++) {
|
|
1398
|
-
const char = str.charCodeAt(i);
|
|
1399
|
-
hash = (hash << 5) - hash + char;
|
|
1400
|
-
hash = hash & hash;
|
|
1401
|
-
}
|
|
1402
|
-
return Math.abs(hash).toString(36);
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
// src/studio.ts
|
|
1406
|
-
function createDefaultCanvasConfig(overrides) {
|
|
1407
|
-
return {
|
|
1408
|
-
width: 1200,
|
|
1409
|
-
height: 800,
|
|
1410
|
-
background: "grid",
|
|
1411
|
-
gridSize: 8,
|
|
1412
|
-
snapToGrid: true,
|
|
1413
|
-
zoom: {
|
|
1414
|
-
min: 0.25,
|
|
1415
|
-
max: 3,
|
|
1416
|
-
step: 0.1,
|
|
1417
|
-
current: 1
|
|
1418
|
-
},
|
|
1419
|
-
panOffset: { x: 0, y: 0 },
|
|
1420
|
-
showMinimap: false,
|
|
1421
|
-
minimapPosition: "bottom-right",
|
|
1422
|
-
...overrides
|
|
1423
|
-
};
|
|
1424
|
-
}
|
|
1425
|
-
function snapToGrid(x, y, gridSize) {
|
|
1426
|
-
return {
|
|
1427
|
-
x: Math.round(x / gridSize) * gridSize,
|
|
1428
|
-
y: Math.round(y / gridSize) * gridSize
|
|
1429
|
-
};
|
|
1430
|
-
}
|
|
1431
|
-
function calculateAutoLayout(items, canvasWidth, padding = 40, gap = 40) {
|
|
1432
|
-
const positions = [];
|
|
1433
|
-
let currentX = padding;
|
|
1434
|
-
let currentY = padding;
|
|
1435
|
-
let rowMaxHeight = 0;
|
|
1436
|
-
for (const item of items) {
|
|
1437
|
-
if (currentX + item.width + padding > canvasWidth) {
|
|
1438
|
-
currentX = padding;
|
|
1439
|
-
currentY += rowMaxHeight + gap;
|
|
1440
|
-
rowMaxHeight = 0;
|
|
1441
|
-
}
|
|
1442
|
-
positions.push({ id: item.id, x: currentX, y: currentY });
|
|
1443
|
-
currentX += item.width + gap;
|
|
1444
|
-
rowMaxHeight = Math.max(rowMaxHeight, item.height);
|
|
1445
|
-
}
|
|
1446
|
-
return positions;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
1164
|
// src/index.ts
|
|
1450
1165
|
var FILTER_OPERATOR_ALIASES = {
|
|
1451
1166
|
equals: "=",
|
|
@@ -1473,6 +1188,15 @@ var FILTER_OPERATOR_ALIASES = {
|
|
|
1473
1188
|
notin: "nin",
|
|
1474
1189
|
nin: "nin",
|
|
1475
1190
|
contains: "contains",
|
|
1191
|
+
// Case-insensitive contains. A canonical `VIEW_FILTER_OPERATORS` member that
|
|
1192
|
+
// arrived with `@objectstack/spec` 17.1.0 (objectui#5328), and an IDENTITY row
|
|
1193
|
+
// like `contains` above: `icontains` is itself a member of
|
|
1194
|
+
// `VALID_AST_OPERATORS`, so the spelling the author writes is the spelling the
|
|
1195
|
+
// AST takes — nothing is translated and no case-sensitivity is lost.
|
|
1196
|
+
// Declared rather than left to the `?? op` fall-through on the rule this
|
|
1197
|
+
// table's test states: the AST gate accepting a spelling is not the driver
|
|
1198
|
+
// compiling it into a WHERE clause.
|
|
1199
|
+
icontains: "icontains",
|
|
1476
1200
|
not_contains: "notcontains",
|
|
1477
1201
|
notcontains: "notcontains",
|
|
1478
1202
|
starts_with: "startswith",
|
|
@@ -1633,21 +1357,83 @@ function isAppPermissionDeniedError(error) {
|
|
|
1633
1357
|
return errorCodeIs(error, APP_PERMISSION_DENIED_CODE);
|
|
1634
1358
|
}
|
|
1635
1359
|
var AnalyticsNotInstalledError = class extends Error {
|
|
1636
|
-
constructor(surface, detail) {
|
|
1360
|
+
constructor(surface, detail, serverCode) {
|
|
1637
1361
|
super(
|
|
1638
1362
|
`Analytics capability is not installed on this deployment \u2014 ${surface} is unavailable. Install @objectstack/service-analytics and mount AnalyticsServicePlugin to enable it.` + (detail ? ` (server said: ${detail})` : "")
|
|
1639
1363
|
);
|
|
1640
1364
|
__publicField(this, "code", "ANALYTICS_NOT_INSTALLED");
|
|
1641
1365
|
/** The surface that was unavailable, for the message a host renders. */
|
|
1642
1366
|
__publicField(this, "surface");
|
|
1367
|
+
/**
|
|
1368
|
+
* The server's own ADR-0112 code — the field this branch was chosen BY, when
|
|
1369
|
+
* a producer named one (`NOT_IMPLEMENTED` from the mounted route with no
|
|
1370
|
+
* service behind it, `ROUTE_NOT_FOUND` from the dispatcher when the route is
|
|
1371
|
+
* not mounted at all). Absent for the no-code residual, where a bare
|
|
1372
|
+
* transport 404/501 is all there was to read.
|
|
1373
|
+
*
|
|
1374
|
+
* Additive (objectui#5663): carried so a reader can audit that the headline
|
|
1375
|
+
* and the quoted `detail` below came off the same answer.
|
|
1376
|
+
*/
|
|
1377
|
+
__publicField(this, "serverCode");
|
|
1643
1378
|
this.name = "AnalyticsNotInstalledError";
|
|
1644
1379
|
this.surface = surface;
|
|
1380
|
+
this.serverCode = serverCode;
|
|
1645
1381
|
}
|
|
1646
1382
|
};
|
|
1647
1383
|
function isAnalyticsNotInstalledError(error) {
|
|
1648
1384
|
if (!error || typeof error !== "object") return false;
|
|
1649
1385
|
return error.code === "ANALYTICS_NOT_INSTALLED";
|
|
1650
1386
|
}
|
|
1387
|
+
var AnalyticsDatasetNotFoundError = class extends Error {
|
|
1388
|
+
constructor(opts) {
|
|
1389
|
+
const subject = opts.datasetName ? `Dataset "${opts.datasetName}"` : "The requested dataset";
|
|
1390
|
+
super(
|
|
1391
|
+
`${subject} is not defined in this environment \u2014 the app that defines it is missing or out of date. Analytics itself is installed and answering, so the fix is to upgrade the installed app, not to install a server plugin.` + (opts.serverMessage ? ` (server said: ${opts.serverMessage})` : "")
|
|
1392
|
+
);
|
|
1393
|
+
__publicField(this, "code", "ANALYTICS_DATASET_NOT_FOUND");
|
|
1394
|
+
/**
|
|
1395
|
+
* The dataset this query asked for, taken from the REQUEST rather than parsed
|
|
1396
|
+
* back out of the server's prose: the request is what we know for certain,
|
|
1397
|
+
* and re-reading the name out of a message would make the headline depend on
|
|
1398
|
+
* that message's WORDING — the coupling framework#5367 spent a whole issue
|
|
1399
|
+
* removing from the producing route.
|
|
1400
|
+
*/
|
|
1401
|
+
__publicField(this, "datasetName");
|
|
1402
|
+
/** The server's own ADR-0112 code — the field this branch was chosen BY. */
|
|
1403
|
+
__publicField(this, "serverCode");
|
|
1404
|
+
/** The server's own message, quoted verbatim in the parenthetical. */
|
|
1405
|
+
__publicField(this, "serverMessage");
|
|
1406
|
+
this.name = "AnalyticsDatasetNotFoundError";
|
|
1407
|
+
this.datasetName = opts.datasetName;
|
|
1408
|
+
this.serverCode = opts.serverCode;
|
|
1409
|
+
this.serverMessage = opts.serverMessage;
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
var AnalyticsUnauthenticatedError = class extends Error {
|
|
1413
|
+
constructor(opts = {}) {
|
|
1414
|
+
super(
|
|
1415
|
+
"Analytics query refused: this session is not authenticated. Sign in again and retry \u2014 the request was refused before it ran, so it says nothing about whether the analytics capability is installed." + (opts.serverMessage ? ` (server said: ${opts.serverMessage})` : "")
|
|
1416
|
+
);
|
|
1417
|
+
__publicField(this, "code", "ANALYTICS_UNAUTHENTICATED");
|
|
1418
|
+
/** The server's own ADR-0112 code — the field this branch was chosen BY. */
|
|
1419
|
+
__publicField(this, "serverCode");
|
|
1420
|
+
/** The server's own message, quoted verbatim in the parenthetical. */
|
|
1421
|
+
__publicField(this, "serverMessage");
|
|
1422
|
+
this.name = "AnalyticsUnauthenticatedError";
|
|
1423
|
+
this.serverCode = opts.serverCode;
|
|
1424
|
+
this.serverMessage = opts.serverMessage;
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
function readAnalyticsErrorEnvelope(body) {
|
|
1428
|
+
if (!body || typeof body !== "object") return {};
|
|
1429
|
+
const flat = body;
|
|
1430
|
+
const wrapped = flat.error && typeof flat.error === "object" ? flat.error : void 0;
|
|
1431
|
+
const source = wrapped ?? flat;
|
|
1432
|
+
const code = typeof source.code === "string" && source.code.length > 0 ? source.code : void 0;
|
|
1433
|
+
const message = typeof source.message === "string" && source.message.length > 0 ? source.message : typeof flat.error === "string" && flat.error.length > 0 ? flat.error : void 0;
|
|
1434
|
+
return { code, message };
|
|
1435
|
+
}
|
|
1436
|
+
var ANALYTICS_ABSENT_STATUSES = [404, 501];
|
|
1651
1437
|
var AnalyticsQueryRejectedError = class extends Error {
|
|
1652
1438
|
constructor(detail, serverCode) {
|
|
1653
1439
|
super(
|
|
@@ -1662,13 +1448,22 @@ var AnalyticsQueryRejectedError = class extends Error {
|
|
|
1662
1448
|
};
|
|
1663
1449
|
function classifyAnalyticsFailure(error) {
|
|
1664
1450
|
const err = error ?? {};
|
|
1665
|
-
const code = typeof err.code === "string" ? err.code : void 0;
|
|
1451
|
+
const code = typeof err.code === "string" && err.code.length > 0 ? err.code : void 0;
|
|
1666
1452
|
const message = typeof err.message === "string" ? err.message : void 0;
|
|
1667
1453
|
const status = typeof err.httpStatus === "number" ? err.httpStatus : typeof err.status === "number" ? err.status : typeof err.statusCode === "number" ? err.statusCode : void 0;
|
|
1668
|
-
if (code
|
|
1669
|
-
if (status === 404 || status === 501 || code === "NOT_IMPLEMENTED" || code === "ROUTE_NOT_FOUND") {
|
|
1454
|
+
if (errorCodeIsAnyOf({ code }, ["NOT_IMPLEMENTED", "ROUTE_NOT_FOUND"])) {
|
|
1670
1455
|
return { kind: "not-installed", code, message };
|
|
1671
1456
|
}
|
|
1457
|
+
if (errorCodeIs({ code }, "VALIDATION_FAILED")) return { kind: "rejected", code, message };
|
|
1458
|
+
if (errorCodeIs({ code }, "UNAUTHENTICATED")) return { kind: "unauthenticated", code, message };
|
|
1459
|
+
if (errorCodeIs({ code }, "CUBE_NOT_FOUND")) return { kind: "cube-not-found", code, message };
|
|
1460
|
+
if (code === void 0) {
|
|
1461
|
+
if (status !== void 0 && ANALYTICS_ABSENT_STATUSES.includes(status)) {
|
|
1462
|
+
return { kind: "not-installed", code, message };
|
|
1463
|
+
}
|
|
1464
|
+
if (status === 400) return { kind: "rejected", code, message };
|
|
1465
|
+
if (status === 401) return { kind: "unauthenticated", code, message };
|
|
1466
|
+
}
|
|
1672
1467
|
return { kind: "unknown", code, message };
|
|
1673
1468
|
}
|
|
1674
1469
|
var ConcurrentUpdateError = class extends Error {
|
|
@@ -1741,8 +1536,14 @@ function createQuietHttpLogger() {
|
|
|
1741
1536
|
return false;
|
|
1742
1537
|
};
|
|
1743
1538
|
const logger = {
|
|
1744
|
-
debug: (message, meta) =>
|
|
1745
|
-
|
|
1539
|
+
debug: (message, meta) => (
|
|
1540
|
+
// eslint-disable-next-line no-console -- objectui#4029, see comment above
|
|
1541
|
+
console.debug(message, meta ?? "")
|
|
1542
|
+
),
|
|
1543
|
+
info: (message, meta) => (
|
|
1544
|
+
// eslint-disable-next-line no-console -- objectui#4029, see comment above
|
|
1545
|
+
console.info(message, meta ?? "")
|
|
1546
|
+
),
|
|
1746
1547
|
warn: (message, meta) => console.warn(message, meta ?? ""),
|
|
1747
1548
|
error: (message, error, meta) => {
|
|
1748
1549
|
if (isExpected404(meta)) {
|
|
@@ -1755,6 +1556,7 @@ function createQuietHttpLogger() {
|
|
|
1755
1556
|
console.error(formatHttpFailureMessage(message, meta) ?? message, error ?? "", meta ?? "");
|
|
1756
1557
|
},
|
|
1757
1558
|
fatal: (message, error, meta) => console.error(formatHttpFailureMessage(message, meta) ?? message, error ?? "", meta ?? ""),
|
|
1559
|
+
// eslint-disable-next-line no-console -- objectui#4029, see comment above
|
|
1758
1560
|
log: (message, ...args) => console.log(message, ...args),
|
|
1759
1561
|
child: () => logger,
|
|
1760
1562
|
withTrace: () => logger
|
|
@@ -1797,6 +1599,44 @@ function viewItemObjectName(item) {
|
|
|
1797
1599
|
const spec = item?.list ?? item;
|
|
1798
1600
|
return spec?.data?.object ?? spec?.object ?? spec?.objectName;
|
|
1799
1601
|
}
|
|
1602
|
+
var VIEW_OVERLAY_MARKER = "_isOverride";
|
|
1603
|
+
function isLegacyOverlayRow(item, spec) {
|
|
1604
|
+
if (spec && spec.config && typeof spec.config === "object") return false;
|
|
1605
|
+
const viewKind = item?.viewKind ?? spec?.viewKind;
|
|
1606
|
+
return viewKind === "list";
|
|
1607
|
+
}
|
|
1608
|
+
function isPersonalizationOverlayRow(item, spec) {
|
|
1609
|
+
if (item?.[VIEW_OVERLAY_MARKER] === true) return true;
|
|
1610
|
+
if (spec?.[VIEW_OVERLAY_MARKER] === true) return true;
|
|
1611
|
+
return isLegacyOverlayRow(item, spec);
|
|
1612
|
+
}
|
|
1613
|
+
var VIEW_OVERLAY_OWNED_KEYS = Object.freeze([
|
|
1614
|
+
"rowHeight",
|
|
1615
|
+
"sort",
|
|
1616
|
+
"hiddenFields",
|
|
1617
|
+
"columnState",
|
|
1618
|
+
"inlineEdit"
|
|
1619
|
+
]);
|
|
1620
|
+
var VIEW_OVERLAY_IDENTITY_KEYS = Object.freeze([
|
|
1621
|
+
"name",
|
|
1622
|
+
"object",
|
|
1623
|
+
"viewKind",
|
|
1624
|
+
VIEW_OVERLAY_MARKER
|
|
1625
|
+
]);
|
|
1626
|
+
function narrowPersonalizationOverlay(row) {
|
|
1627
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return row;
|
|
1628
|
+
const item = row;
|
|
1629
|
+
const spec = item.list ?? item;
|
|
1630
|
+
if (!isPersonalizationOverlayRow(item, spec)) return row;
|
|
1631
|
+
const narrowed = {};
|
|
1632
|
+
for (const key of VIEW_OVERLAY_IDENTITY_KEYS) {
|
|
1633
|
+
if (item[key] !== void 0) narrowed[key] = item[key];
|
|
1634
|
+
}
|
|
1635
|
+
for (const key of VIEW_OVERLAY_OWNED_KEYS) {
|
|
1636
|
+
if (item[key] !== void 0) narrowed[key] = item[key];
|
|
1637
|
+
}
|
|
1638
|
+
return narrowed;
|
|
1639
|
+
}
|
|
1800
1640
|
function unwrapViewDraft(resp) {
|
|
1801
1641
|
if (!resp || typeof resp !== "object") return null;
|
|
1802
1642
|
const env = resp;
|
|
@@ -3366,25 +3206,59 @@ var ObjectStackAdapter = class {
|
|
|
3366
3206
|
}
|
|
3367
3207
|
}
|
|
3368
3208
|
/**
|
|
3369
|
-
* Persist a view
|
|
3209
|
+
* Persist a toolbar-driven view config patch — density, column widths,
|
|
3210
|
+
* sort, hidden columns, inline edit. Symmetric counterpart to
|
|
3211
|
+
* {@link getView}: writes the row to the server metadata store via
|
|
3212
|
+
* `client.meta.saveItem`, then invalidates the matching cache entry so the
|
|
3213
|
+
* next {@link getView} reflects the new payload. Returns the persisted item
|
|
3214
|
+
* when the server echoes it, otherwise undefined.
|
|
3215
|
+
*
|
|
3216
|
+
* Called from exactly ONE production site — `ObjectView`'s
|
|
3217
|
+
* `persistViewPatch`, for the toolbar toggle — but that ONE call site
|
|
3218
|
+
* fires for BOTH kinds of active tab: a code-defined **system** view (no
|
|
3219
|
+
* row of its own yet) and a genuinely user-created **saved** view (already
|
|
3220
|
+
* has a row — the toggle is editing ITS OWN definition, not laying an
|
|
3221
|
+
* overlay on top of it). Which one a given call means is NOT re-derived
|
|
3222
|
+
* here from the write's shape (objectui#4227's own lesson: shape inference
|
|
3223
|
+
* on this namespace is exactly what let a system view masquerade as
|
|
3224
|
+
* saved) — the caller already knows, via the same `isSavedViewId`
|
|
3225
|
+
* classification that gates the switcher's readonly flag and its five
|
|
3226
|
+
* mutating handlers, and passes it as {@link opts.isSavedView}.
|
|
3370
3227
|
*
|
|
3371
|
-
*
|
|
3372
|
-
*
|
|
3373
|
-
*
|
|
3374
|
-
*
|
|
3375
|
-
*
|
|
3228
|
+
* - `isSavedView` false/omitted (system-view target, the common case and
|
|
3229
|
+
* the default for backward compatibility): stamps
|
|
3230
|
+
* {@link VIEW_OVERLAY_MARKER} so {@link listViews} excludes the row —
|
|
3231
|
+
* the original objectui#4227 fix.
|
|
3232
|
+
* - `isSavedView` true: the marker is withheld. Stamping it here would
|
|
3233
|
+
* flag the saved view's OWN row as a personalization overlay, and
|
|
3234
|
+
* `listViews()` would exclude it on the very next read — the user's own
|
|
3235
|
+
* view would vanish from the switcher the moment they toggled its
|
|
3236
|
+
* density (objectui#4227 follow-up, PM review on PR #4713, measured:
|
|
3237
|
+
* `persistViewPatch` has no gate on which kind of tab is active, and
|
|
3238
|
+
* this method writes to the exact same `(type='view', name=viewId)` key
|
|
3239
|
+
* {@link createView}/the ADR-0034 `viewEnvelope` seam already used for
|
|
3240
|
+
* that view, so the write is an upsert onto the saved view's row, not a
|
|
3241
|
+
* new one).
|
|
3376
3242
|
*
|
|
3377
|
-
*
|
|
3378
|
-
*
|
|
3379
|
-
*
|
|
3243
|
+
* Per objectstack#7494's ruling, the overlay this writes (system-view
|
|
3244
|
+
* case) is ORG-WIDE shared view settings, not a per-user preference — a
|
|
3245
|
+
* true per-user scope is a parked v18 direction on the platform side.
|
|
3380
3246
|
*
|
|
3381
3247
|
* @param objectName - Object name (e.g. 'lead')
|
|
3382
3248
|
* @param viewId - View identifier (e.g. 'all_leads')
|
|
3383
3249
|
* @param config - Full view definition to persist
|
|
3250
|
+
* @param opts.isSavedView - Whether `viewId` already names a saved view
|
|
3251
|
+
* (vs. a system view being personalized for the first time). Omit /
|
|
3252
|
+
* `false` for the default overlay-marking behavior.
|
|
3384
3253
|
*/
|
|
3385
|
-
async updateViewConfig(objectName, viewId, config) {
|
|
3254
|
+
async updateViewConfig(objectName, viewId, config, opts) {
|
|
3386
3255
|
await this.connect();
|
|
3387
|
-
const merged = {
|
|
3256
|
+
const merged = {
|
|
3257
|
+
...config || {},
|
|
3258
|
+
object: config?.object || objectName,
|
|
3259
|
+
name: viewId,
|
|
3260
|
+
...opts?.isSavedView ? {} : { [VIEW_OVERLAY_MARKER]: true }
|
|
3261
|
+
};
|
|
3388
3262
|
const result = await this.client.meta.saveItem(
|
|
3389
3263
|
"view",
|
|
3390
3264
|
viewId,
|
|
@@ -3423,7 +3297,9 @@ var ObjectStackAdapter = class {
|
|
|
3423
3297
|
const spec = v.list ?? v;
|
|
3424
3298
|
if (viewItemObjectName(v) !== objectName) return false;
|
|
3425
3299
|
const viewKind = v.viewKind ?? spec?.viewKind;
|
|
3426
|
-
|
|
3300
|
+
if (viewKind && FORM_FAMILY.has(viewKind)) return false;
|
|
3301
|
+
if (isPersonalizationOverlayRow(v, spec)) return false;
|
|
3302
|
+
return true;
|
|
3427
3303
|
}).map((v) => {
|
|
3428
3304
|
const spec = v.list ?? v;
|
|
3429
3305
|
const isDraft = v._draft === true || spec?._draft === true;
|
|
@@ -3698,7 +3574,7 @@ var ObjectStackAdapter = class {
|
|
|
3698
3574
|
try {
|
|
3699
3575
|
const cacheKey = `app:${appId}`;
|
|
3700
3576
|
return await this.metadataCache.get(cacheKey, async () => {
|
|
3701
|
-
const result = await this.client.meta.getItem("
|
|
3577
|
+
const result = await this.client.meta.getItem("app", appId);
|
|
3702
3578
|
if (result && result.item) return result.item;
|
|
3703
3579
|
return result ?? null;
|
|
3704
3580
|
});
|
|
@@ -3710,8 +3586,13 @@ var ObjectStackAdapter = class {
|
|
|
3710
3586
|
* Ask the by-name meta app route WHY an app is not in this session's app list
|
|
3711
3587
|
* (objectui#4252).
|
|
3712
3588
|
*
|
|
3713
|
-
* `GET /api/v1/meta
|
|
3714
|
-
*
|
|
3589
|
+
* The app LIST is the generic metadata list route `GET /api/v1/meta/:type`,
|
|
3590
|
+
* requested with the singular type segment `app` — the same address this
|
|
3591
|
+
* method appends a name to below, and the one `MetadataProvider` reads its
|
|
3592
|
+
* items from. The server filters that list per session in `filterAppForUser`
|
|
3593
|
+
* (`packages/rest/src/rest-server.ts`, applied inside the `:type` list handler
|
|
3594
|
+
* once the type segment resolves to `app`), so an app withheld by
|
|
3595
|
+
* `requiredPermissions` and an app
|
|
3715
3596
|
* that does not exist are byte-identical there: both are simply absent. A
|
|
3716
3597
|
* console reading only that list has one fact and two conditions, and it
|
|
3717
3598
|
* renders its copy for the wrong one — "it may still be publishing" over a
|
|
@@ -3761,7 +3642,7 @@ var ObjectStackAdapter = class {
|
|
|
3761
3642
|
try {
|
|
3762
3643
|
const cacheKey = `page:${pageId}`;
|
|
3763
3644
|
return await this.metadataCache.get(cacheKey, async () => {
|
|
3764
|
-
const result = await this.client.meta.getItem("
|
|
3645
|
+
const result = await this.client.meta.getItem("page", pageId);
|
|
3765
3646
|
if (result && result.item) return result.item;
|
|
3766
3647
|
return result ?? null;
|
|
3767
3648
|
});
|
|
@@ -3851,6 +3732,15 @@ var ObjectStackAdapter = class {
|
|
|
3851
3732
|
if (failure.kind === "rejected") {
|
|
3852
3733
|
throw new AnalyticsQueryRejectedError(failure.message, failure.code);
|
|
3853
3734
|
}
|
|
3735
|
+
if (failure.kind === "unauthenticated") {
|
|
3736
|
+
throw new AnalyticsUnauthenticatedError({
|
|
3737
|
+
serverCode: failure.code,
|
|
3738
|
+
serverMessage: failure.message
|
|
3739
|
+
});
|
|
3740
|
+
}
|
|
3741
|
+
if (failure.kind === "cube-not-found") {
|
|
3742
|
+
throw e;
|
|
3743
|
+
}
|
|
3854
3744
|
if (failure.kind === "not-installed") {
|
|
3855
3745
|
this.warnAnalyticsCapabilityOnce(failure.message);
|
|
3856
3746
|
}
|
|
@@ -3930,14 +3820,35 @@ var ObjectStackAdapter = class {
|
|
|
3930
3820
|
body: JSON.stringify(requestBody)
|
|
3931
3821
|
});
|
|
3932
3822
|
if (!res.ok) {
|
|
3933
|
-
let
|
|
3823
|
+
let errBody;
|
|
3934
3824
|
try {
|
|
3935
|
-
|
|
3936
|
-
detail = errBody?.message || errBody?.error || JSON.stringify(errBody);
|
|
3825
|
+
errBody = await res.json();
|
|
3937
3826
|
} catch {
|
|
3938
3827
|
}
|
|
3939
|
-
|
|
3940
|
-
|
|
3828
|
+
const { code: serverCode, message: serverMessage } = readAnalyticsErrorEnvelope(errBody);
|
|
3829
|
+
const detail = serverMessage || (errBody === void 0 ? "" : JSON.stringify(errBody));
|
|
3830
|
+
const surface = "POST /analytics/dataset/query";
|
|
3831
|
+
const requestedDatasetName = typeof dataset === "string" ? dataset : typeof dataset?.name === "string" ? String(dataset.name) : void 0;
|
|
3832
|
+
if (errorCodeIsAnyOf({ code: serverCode }, ["NOT_IMPLEMENTED", "ROUTE_NOT_FOUND"])) {
|
|
3833
|
+
throw new AnalyticsNotInstalledError(surface, serverMessage, serverCode);
|
|
3834
|
+
}
|
|
3835
|
+
if (errorCodeIs({ code: serverCode }, "NOT_FOUND")) {
|
|
3836
|
+
throw new AnalyticsDatasetNotFoundError({
|
|
3837
|
+
datasetName: requestedDatasetName,
|
|
3838
|
+
serverCode,
|
|
3839
|
+
serverMessage
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
if (errorCodeIs({ code: serverCode }, "UNAUTHENTICATED")) {
|
|
3843
|
+
throw new AnalyticsUnauthenticatedError({ serverCode, serverMessage });
|
|
3844
|
+
}
|
|
3845
|
+
if (serverCode === void 0) {
|
|
3846
|
+
if (ANALYTICS_ABSENT_STATUSES.includes(res.status)) {
|
|
3847
|
+
throw new AnalyticsNotInstalledError(surface, detail || void 0);
|
|
3848
|
+
}
|
|
3849
|
+
if (res.status === 401) {
|
|
3850
|
+
throw new AnalyticsUnauthenticatedError({ serverMessage });
|
|
3851
|
+
}
|
|
3941
3852
|
}
|
|
3942
3853
|
throw new Error(`Dataset query failed: ${res.status} ${res.statusText}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
3943
3854
|
}
|
|
@@ -4196,31 +4107,29 @@ function createObjectStackAdapter(config) {
|
|
|
4196
4107
|
export {
|
|
4197
4108
|
API_ACCESS_DENIED_CODES,
|
|
4198
4109
|
APP_PERMISSION_DENIED_CODE,
|
|
4110
|
+
AnalyticsDatasetNotFoundError,
|
|
4199
4111
|
AnalyticsNotInstalledError,
|
|
4200
4112
|
AnalyticsQueryRejectedError,
|
|
4113
|
+
AnalyticsUnauthenticatedError,
|
|
4201
4114
|
AuthenticationError,
|
|
4202
4115
|
BulkOperationError,
|
|
4203
4116
|
ConcurrentUpdateError,
|
|
4204
4117
|
ConnectionError,
|
|
4205
4118
|
DataApiValidationError,
|
|
4206
4119
|
FILTER_OPERATOR_ALIASES,
|
|
4207
|
-
IntegrationManager,
|
|
4208
4120
|
MalformedFilterError,
|
|
4209
4121
|
MetadataClient,
|
|
4210
4122
|
MetadataNotFoundError,
|
|
4211
4123
|
ObjectStackAdapter,
|
|
4212
4124
|
ObjectStackError,
|
|
4213
|
-
|
|
4214
|
-
calculateAutoLayout,
|
|
4125
|
+
VIEW_OVERLAY_OWNED_KEYS,
|
|
4215
4126
|
classifyAnalyticsFailure,
|
|
4216
4127
|
clearSharedDiscoveryCache,
|
|
4217
|
-
createDefaultCanvasConfig,
|
|
4218
4128
|
createErrorFromResponse,
|
|
4219
4129
|
createObjectStackAdapter,
|
|
4220
4130
|
createObjectStackUserStateAdapter,
|
|
4221
4131
|
createQuietHttpLogger,
|
|
4222
4132
|
formatHttpFailureMessage,
|
|
4223
|
-
generateContractManifest,
|
|
4224
4133
|
getSharedDiscovery,
|
|
4225
4134
|
is404Error,
|
|
4226
4135
|
isAnalyticsNotInstalledError,
|
|
@@ -4230,11 +4139,10 @@ export {
|
|
|
4230
4139
|
isErrorType,
|
|
4231
4140
|
isMalformedFilterError,
|
|
4232
4141
|
isObjectStackError,
|
|
4142
|
+
narrowPersonalizationOverlay,
|
|
4233
4143
|
normaliseClientError,
|
|
4234
4144
|
readSaveAdvisories,
|
|
4235
4145
|
readTransactionalBatchCapability,
|
|
4236
4146
|
serializeOrderBy,
|
|
4237
|
-
snapToGrid,
|
|
4238
|
-
validatePluginContract,
|
|
4239
4147
|
viewItemObjectName
|
|
4240
4148
|
};
|