@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/dist/index.cjs +262 -357
  3. package/dist/index.d.cts +385 -448
  4. package/dist/index.d.ts +385 -448
  5. package/dist/index.js +258 -350
  6. package/package.json +5 -6
  7. package/dist/tsconfig.tsbuildinfo +0 -1
  8. package/src/aggregate-capability.test.ts +0 -177
  9. package/src/apiAccessDenied.test.ts +0 -233
  10. package/src/appAccessProbe.test.ts +0 -147
  11. package/src/bulkDelete.test.ts +0 -75
  12. package/src/bulkUpdate.test.ts +0 -93
  13. package/src/cache/MetadataCache.test.ts +0 -477
  14. package/src/cache/MetadataCache.ts +0 -289
  15. package/src/cloud-surface-retired-4152.pin.test.ts +0 -171
  16. package/src/connection.test.ts +0 -188
  17. package/src/contracts.ts +0 -115
  18. package/src/deleteView.homes.test.ts +0 -389
  19. package/src/deleteViewContract.types.test.ts +0 -246
  20. package/src/errors.test.ts +0 -426
  21. package/src/errors.ts +0 -293
  22. package/src/exportDownload.test.ts +0 -141
  23. package/src/filter-entry-translation.test.ts +0 -360
  24. package/src/filter-operator-ast-parity.test.ts +0 -135
  25. package/src/getObjectSchema.test.ts +0 -145
  26. package/src/httpFailureLogging.test.ts +0 -109
  27. package/src/importJob.test.ts +0 -71
  28. package/src/index.ts +0 -4442
  29. package/src/integration.ts +0 -200
  30. package/src/listViewOverrides.test.ts +0 -241
  31. package/src/listViews.test.ts +0 -175
  32. package/src/metadata-client.get-envelope.test.ts +0 -171
  33. package/src/metadata-client.saveAdvisories.test.ts +0 -226
  34. package/src/metadata-client.test.ts +0 -303
  35. package/src/metadata-client.ts +0 -1046
  36. package/src/occ.test.ts +0 -101
  37. package/src/onMutation.test.ts +0 -589
  38. package/src/onSaveAdvisory.test.ts +0 -311
  39. package/src/onWriteWarning.test.ts +0 -273
  40. package/src/orderby-serialization.test.ts +0 -115
  41. package/src/queryDataset.test.ts +0 -543
  42. package/src/searchAll.test.ts +0 -120
  43. package/src/security.ts +0 -243
  44. package/src/spec-symbol-batch6.test.ts +0 -326
  45. package/src/studio.ts +0 -152
  46. package/src/updateView.draft.test.ts +0 -242
  47. package/src/userState.test.ts +0 -315
  48. package/src/userState.ts +0 -238
  49. package/src/v3-compat.test.ts +0 -229
  50. package/src/validation-error.test.ts +0 -94
  51. package/src/viewCacheInvalidation.pin.test.ts +0 -260
  52. 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 (Phase 3a). Returns
299
- * `code` (the artifact / fallback default), `overlay` (the saved
300
- * customisation, if any), and `effective` (what the runtime sees).
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 pkg = options.packageId ? `&package=${encodeURIComponent(options.packageId)}` : "";
304
- const url = `${this.base}/${encodeURIComponent(type)}/${encodeURIComponent(name)}?layers=true${pkg}`;
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 === "VALIDATION_FAILED" || status === 400) return { kind: "rejected", code, message };
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) => console.debug(message, meta ?? ""),
1745
- info: (message, meta) => console.info(message, meta ?? ""),
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 definition for an object.
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
- * Symmetric counterpart to {@link getView}: writes the view to the
3372
- * server metadata store via `client.meta.saveItem`, then invalidates
3373
- * the matching cache entry so the next {@link getView} reflects the
3374
- * new payload. Returns the persisted item when the server echoes it,
3375
- * otherwise undefined.
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
- * Used by ObjectView for "live" toolbar persistence (density,
3378
- * column widths, sort, etc.) and by the View Config Panel for
3379
- * explicit saves.
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 = { ...config || {}, object: config?.object || objectName, name: viewId };
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
- return !(viewKind && FORM_FAMILY.has(viewKind));
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("apps", appId);
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/apps` is filtered per session server-side
3714
- * (`filterAppForUser`), so an app withheld by `requiredPermissions` and an app
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("pages", pageId);
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 detail = "";
3823
+ let errBody;
3934
3824
  try {
3935
- const errBody = await res.json();
3936
- detail = errBody?.message || errBody?.error || JSON.stringify(errBody);
3825
+ errBody = await res.json();
3937
3826
  } catch {
3938
3827
  }
3939
- if (res.status === 501 || res.status === 404) {
3940
- throw new AnalyticsNotInstalledError("POST /analytics/dataset/query", detail || void 0);
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
- SecurityManager,
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
  };