@objectstack/plugin-approvals 9.0.1 → 9.2.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.mjs CHANGED
@@ -1443,10 +1443,67 @@ var ApprovalService = class _ApprovalService {
1443
1443
  return void 0;
1444
1444
  }
1445
1445
  /**
1446
- * Attach inbox display fields (`record_title`, `submitter_name`) to rows.
1447
- * Batched: one query per distinct target object plus one `sys_user` lookup.
1448
- * Best-effort — a deleted record falls back to the payload snapshot, and a
1449
- * lookup failure leaves the field unset rather than failing the list.
1446
+ * Batch-resolve `sys_user` display names for identifiers that may be user
1447
+ * ids or emails. Best-effort failures leave entries unresolved.
1448
+ */
1449
+ async resolveUserNames(identifiers) {
1450
+ const names = /* @__PURE__ */ new Map();
1451
+ const targets = Array.from(new Set(identifiers.filter(Boolean)));
1452
+ if (!targets.length) return names;
1453
+ try {
1454
+ const users = await this.engine.find("sys_user", {
1455
+ where: { id: { $in: targets } },
1456
+ fields: ["id", "name", "email"],
1457
+ limit: targets.length,
1458
+ context: SYSTEM_CTX
1459
+ });
1460
+ for (const u of users ?? []) {
1461
+ if (u?.id && (u.name || u.email)) names.set(String(u.id), String(u.name ?? u.email));
1462
+ }
1463
+ } catch {
1464
+ }
1465
+ const unresolvedEmails = targets.filter((t) => !names.has(t) && t.includes("@"));
1466
+ if (unresolvedEmails.length) {
1467
+ try {
1468
+ const users = await this.engine.find("sys_user", {
1469
+ where: { email: { $in: unresolvedEmails } },
1470
+ fields: ["email", "name"],
1471
+ limit: unresolvedEmails.length,
1472
+ context: SYSTEM_CTX
1473
+ });
1474
+ for (const u of users ?? []) {
1475
+ if (u?.email && u.name) names.set(String(u.email), String(u.name));
1476
+ }
1477
+ } catch {
1478
+ }
1479
+ }
1480
+ return names;
1481
+ }
1482
+ /** Lookup-typed fields (key + referenced object) of an object's schema. */
1483
+ resolveLookupFields(object) {
1484
+ try {
1485
+ const schema = this.engine.getSchema?.(object);
1486
+ const fields = schema?.fields ?? {};
1487
+ const out = [];
1488
+ for (const [key, f] of Object.entries(fields)) {
1489
+ if ((f?.type === "lookup" || f?.type === "master_detail") && f?.reference) {
1490
+ out.push({ key, reference: String(f.reference) });
1491
+ }
1492
+ }
1493
+ return out;
1494
+ } catch {
1495
+ return [];
1496
+ }
1497
+ }
1498
+ /**
1499
+ * Attach inbox display fields to rows so clients never render a raw
1500
+ * identifier: `record_title`, `submitter_name`, `object_label`,
1501
+ * `pending_approver_names` (user-id approvers), and `payload_display`
1502
+ * (lookup foreign keys in the snapshot → referenced record titles).
1503
+ * Batched: one query per distinct object (target + referenced) plus one
1504
+ * `sys_user` lookup. Best-effort — a deleted record falls back to the
1505
+ * payload snapshot, and any failure leaves the field unset rather than
1506
+ * failing the list.
1450
1507
  */
1451
1508
  async enrichRows(rows) {
1452
1509
  if (!rows.length) return;
@@ -1461,7 +1518,13 @@ var ApprovalService = class _ApprovalService {
1461
1518
  set.add(r.record_id);
1462
1519
  }
1463
1520
  const titles = /* @__PURE__ */ new Map();
1521
+ const objectLabels = /* @__PURE__ */ new Map();
1464
1522
  for (const [object, idSet] of byObject) {
1523
+ try {
1524
+ const schema = this.engine.getSchema?.(object);
1525
+ if (schema?.label) objectLabels.set(object, String(schema.label));
1526
+ } catch {
1527
+ }
1465
1528
  const ids = Array.from(idSet);
1466
1529
  const displayField = this.resolveDisplayField(object);
1467
1530
  try {
@@ -1472,47 +1535,81 @@ var ApprovalService = class _ApprovalService {
1472
1535
  });
1473
1536
  for (const rec of recs ?? []) {
1474
1537
  const title = _ApprovalService.pickTitle(rec, displayField);
1475
- if (rec?.id && title) titles.set(`${object}\0${rec.id}`, title);
1538
+ if (rec?.id && title) titles.set(`${object} ${rec.id}`, title);
1476
1539
  }
1477
1540
  } catch {
1478
1541
  }
1479
1542
  }
1480
- const submitters = Array.from(new Set(rows.map((r) => r.submitter_id).filter(Boolean)));
1481
- const names = /* @__PURE__ */ new Map();
1482
- if (submitters.length) {
1543
+ const lookupFieldsByObject = /* @__PURE__ */ new Map();
1544
+ for (const object of byObject.keys()) {
1545
+ const lookups = this.resolveLookupFields(object);
1546
+ if (lookups.length) lookupFieldsByObject.set(object, lookups);
1547
+ }
1548
+ const refIds = /* @__PURE__ */ new Map();
1549
+ for (const r of rows) {
1550
+ const lookups = lookupFieldsByObject.get(r.object_name);
1551
+ const payload = r.payload;
1552
+ if (!lookups || !payload || typeof payload !== "object") continue;
1553
+ for (const { key, reference } of lookups) {
1554
+ const v = payload[key];
1555
+ if (v == null || typeof v === "object" || !String(v).trim()) continue;
1556
+ let set = refIds.get(reference);
1557
+ if (!set) {
1558
+ set = /* @__PURE__ */ new Set();
1559
+ refIds.set(reference, set);
1560
+ }
1561
+ set.add(String(v));
1562
+ }
1563
+ }
1564
+ const refTitles = /* @__PURE__ */ new Map();
1565
+ for (const [object, idSet] of refIds) {
1566
+ const ids = Array.from(idSet);
1567
+ const displayField = this.resolveDisplayField(object);
1483
1568
  try {
1484
- const users = await this.engine.find("sys_user", {
1485
- where: { id: { $in: submitters } },
1486
- fields: ["id", "name", "email"],
1487
- limit: submitters.length,
1569
+ const recs = await this.engine.find(object, {
1570
+ where: { id: { $in: ids } },
1571
+ limit: ids.length,
1488
1572
  context: SYSTEM_CTX
1489
1573
  });
1490
- for (const u of users ?? []) {
1491
- if (u?.id && (u.name || u.email)) names.set(String(u.id), String(u.name ?? u.email));
1574
+ for (const rec of recs ?? []) {
1575
+ const title = _ApprovalService.pickTitle(rec, displayField);
1576
+ if (rec?.id && title) refTitles.set(`${object} ${rec.id}`, title);
1492
1577
  }
1493
1578
  } catch {
1494
1579
  }
1495
- const unresolvedEmails = submitters.filter((s) => !names.has(s) && s.includes("@"));
1496
- if (unresolvedEmails.length) {
1497
- try {
1498
- const users = await this.engine.find("sys_user", {
1499
- where: { email: { $in: unresolvedEmails } },
1500
- fields: ["email", "name"],
1501
- limit: unresolvedEmails.length,
1502
- context: SYSTEM_CTX
1503
- });
1504
- for (const u of users ?? []) {
1505
- if (u?.email && u.name) names.set(String(u.email), String(u.name));
1506
- }
1507
- } catch {
1508
- }
1580
+ }
1581
+ const userIdentifiers = [];
1582
+ for (const r of rows) {
1583
+ userIdentifiers.push(r.submitter_id);
1584
+ for (const a of r.pending_approvers ?? []) {
1585
+ if (a && !a.includes(":")) userIdentifiers.push(a);
1509
1586
  }
1510
1587
  }
1588
+ const names = await this.resolveUserNames(userIdentifiers);
1511
1589
  for (const r of rows) {
1512
- const title = titles.get(`${r.object_name}\0${r.record_id}`) ?? _ApprovalService.pickTitle(r.payload, void 0);
1590
+ const title = titles.get(`${r.object_name} ${r.record_id}`) ?? _ApprovalService.pickTitle(r.payload, void 0);
1513
1591
  if (title) r.record_title = title;
1514
1592
  const name = r.submitter_id ? names.get(String(r.submitter_id)) : void 0;
1515
1593
  if (name) r.submitter_name = name;
1594
+ const label = objectLabels.get(r.object_name);
1595
+ if (label) r.object_label = label;
1596
+ const approverNames = {};
1597
+ for (const a of r.pending_approvers ?? []) {
1598
+ const n = names.get(String(a));
1599
+ if (n) approverNames[a] = n;
1600
+ }
1601
+ if (Object.keys(approverNames).length) r.pending_approver_names = approverNames;
1602
+ const lookups = lookupFieldsByObject.get(r.object_name);
1603
+ if (lookups && r.payload && typeof r.payload === "object") {
1604
+ const display = {};
1605
+ for (const { key, reference } of lookups) {
1606
+ const v = r.payload[key];
1607
+ if (v == null) continue;
1608
+ const t = refTitles.get(`${reference} ${String(v)}`);
1609
+ if (t) display[key] = t;
1610
+ }
1611
+ if (Object.keys(display).length) r.payload_display = display;
1612
+ }
1516
1613
  }
1517
1614
  }
1518
1615
  // ── Read API ─────────────────────────────────────────────────
@@ -1571,7 +1668,15 @@ var ApprovalService = class _ApprovalService {
1571
1668
  orderBy: [{ field: "created_at", direction: "asc" }],
1572
1669
  context: SYSTEM_CTX
1573
1670
  });
1574
- return Array.isArray(rows) ? rows.map(rowFromAction) : [];
1671
+ const actions = Array.isArray(rows) ? rows.map(rowFromAction) : [];
1672
+ const names = await this.resolveUserNames(
1673
+ actions.map((a) => a.actor_id).filter((id) => id && !id.includes(":"))
1674
+ );
1675
+ for (const a of actions) {
1676
+ const n = a.actor_id ? names.get(String(a.actor_id)) : void 0;
1677
+ if (n) a.actor_name = n;
1678
+ }
1679
+ return actions;
1575
1680
  }
1576
1681
  };
1577
1682