@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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/plugin-approvals@9.0.1 build /home/runner/work/framework/framework/packages/plugins/plugin-approvals
2
+ > @objectstack/plugin-approvals@9.2.0 build /home/runner/work/framework/framework/packages/plugins/plugin-approvals
3
3
  > tsup --config ../../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- CJS dist/index.js 64.93 KB
14
- CJS dist/index.js.map 113.45 KB
15
- CJS ⚡️ Build success in 189ms
16
- ESM dist/index.mjs 63.58 KB
17
- ESM dist/index.mjs.map 112.27 KB
18
- ESM ⚡️ Build success in 190ms
13
+ ESM dist/index.mjs 67.42 KB
14
+ ESM dist/index.mjs.map 119.54 KB
15
+ ESM ⚡️ Build success in 167ms
16
+ CJS dist/index.js 68.76 KB
17
+ CJS dist/index.js.map 120.72 KB
18
+ CJS ⚡️ Build success in 169ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 24542ms
21
- DTS dist/index.d.mts 334.31 KB
22
- DTS dist/index.d.ts 334.31 KB
20
+ DTS ⚡️ Build success in 24561ms
21
+ DTS dist/index.d.mts 334.84 KB
22
+ DTS dist/index.d.ts 334.84 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @objectstack/plugin-approvals
2
2
 
3
+ ## 9.2.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [2f57b75]
8
+ - Updated dependencies [2f57b75]
9
+ - @objectstack/spec@9.2.0
10
+ - @objectstack/core@9.2.0
11
+ - @objectstack/formula@9.2.0
12
+ - @objectstack/metadata-core@9.2.0
13
+ - @objectstack/platform-objects@9.2.0
14
+
15
+ ## 9.1.0
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [b9062c9]
20
+ - @objectstack/spec@9.1.0
21
+ - @objectstack/core@9.1.0
22
+ - @objectstack/formula@9.1.0
23
+ - @objectstack/metadata-core@9.1.0
24
+ - @objectstack/platform-objects@9.1.0
25
+
3
26
  ## 9.0.1
4
27
 
5
28
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -7550,10 +7550,21 @@ declare class ApprovalService implements IApprovalService {
7550
7550
  private resolveDisplayField;
7551
7551
  private static pickTitle;
7552
7552
  /**
7553
- * Attach inbox display fields (`record_title`, `submitter_name`) to rows.
7554
- * Batched: one query per distinct target object plus one `sys_user` lookup.
7555
- * Best-effort — a deleted record falls back to the payload snapshot, and a
7556
- * lookup failure leaves the field unset rather than failing the list.
7553
+ * Batch-resolve `sys_user` display names for identifiers that may be user
7554
+ * ids or emails. Best-effort failures leave entries unresolved.
7555
+ */
7556
+ private resolveUserNames;
7557
+ /** Lookup-typed fields (key + referenced object) of an object's schema. */
7558
+ private resolveLookupFields;
7559
+ /**
7560
+ * Attach inbox display fields to rows so clients never render a raw
7561
+ * identifier: `record_title`, `submitter_name`, `object_label`,
7562
+ * `pending_approver_names` (user-id approvers), and `payload_display`
7563
+ * (lookup foreign keys in the snapshot → referenced record titles).
7564
+ * Batched: one query per distinct object (target + referenced) plus one
7565
+ * `sys_user` lookup. Best-effort — a deleted record falls back to the
7566
+ * payload snapshot, and any failure leaves the field unset rather than
7567
+ * failing the list.
7557
7568
  */
7558
7569
  private enrichRows;
7559
7570
  listRequests(filter: {
package/dist/index.d.ts CHANGED
@@ -7550,10 +7550,21 @@ declare class ApprovalService implements IApprovalService {
7550
7550
  private resolveDisplayField;
7551
7551
  private static pickTitle;
7552
7552
  /**
7553
- * Attach inbox display fields (`record_title`, `submitter_name`) to rows.
7554
- * Batched: one query per distinct target object plus one `sys_user` lookup.
7555
- * Best-effort — a deleted record falls back to the payload snapshot, and a
7556
- * lookup failure leaves the field unset rather than failing the list.
7553
+ * Batch-resolve `sys_user` display names for identifiers that may be user
7554
+ * ids or emails. Best-effort failures leave entries unresolved.
7555
+ */
7556
+ private resolveUserNames;
7557
+ /** Lookup-typed fields (key + referenced object) of an object's schema. */
7558
+ private resolveLookupFields;
7559
+ /**
7560
+ * Attach inbox display fields to rows so clients never render a raw
7561
+ * identifier: `record_title`, `submitter_name`, `object_label`,
7562
+ * `pending_approver_names` (user-id approvers), and `payload_display`
7563
+ * (lookup foreign keys in the snapshot → referenced record titles).
7564
+ * Batched: one query per distinct object (target + referenced) plus one
7565
+ * `sys_user` lookup. Best-effort — a deleted record falls back to the
7566
+ * payload snapshot, and any failure leaves the field unset rather than
7567
+ * failing the list.
7557
7568
  */
7558
7569
  private enrichRows;
7559
7570
  listRequests(filter: {
package/dist/index.js CHANGED
@@ -1464,10 +1464,67 @@ var ApprovalService = class _ApprovalService {
1464
1464
  return void 0;
1465
1465
  }
1466
1466
  /**
1467
- * Attach inbox display fields (`record_title`, `submitter_name`) to rows.
1468
- * Batched: one query per distinct target object plus one `sys_user` lookup.
1469
- * Best-effort — a deleted record falls back to the payload snapshot, and a
1470
- * lookup failure leaves the field unset rather than failing the list.
1467
+ * Batch-resolve `sys_user` display names for identifiers that may be user
1468
+ * ids or emails. Best-effort failures leave entries unresolved.
1469
+ */
1470
+ async resolveUserNames(identifiers) {
1471
+ const names = /* @__PURE__ */ new Map();
1472
+ const targets = Array.from(new Set(identifiers.filter(Boolean)));
1473
+ if (!targets.length) return names;
1474
+ try {
1475
+ const users = await this.engine.find("sys_user", {
1476
+ where: { id: { $in: targets } },
1477
+ fields: ["id", "name", "email"],
1478
+ limit: targets.length,
1479
+ context: SYSTEM_CTX
1480
+ });
1481
+ for (const u of users ?? []) {
1482
+ if (u?.id && (u.name || u.email)) names.set(String(u.id), String(u.name ?? u.email));
1483
+ }
1484
+ } catch {
1485
+ }
1486
+ const unresolvedEmails = targets.filter((t) => !names.has(t) && t.includes("@"));
1487
+ if (unresolvedEmails.length) {
1488
+ try {
1489
+ const users = await this.engine.find("sys_user", {
1490
+ where: { email: { $in: unresolvedEmails } },
1491
+ fields: ["email", "name"],
1492
+ limit: unresolvedEmails.length,
1493
+ context: SYSTEM_CTX
1494
+ });
1495
+ for (const u of users ?? []) {
1496
+ if (u?.email && u.name) names.set(String(u.email), String(u.name));
1497
+ }
1498
+ } catch {
1499
+ }
1500
+ }
1501
+ return names;
1502
+ }
1503
+ /** Lookup-typed fields (key + referenced object) of an object's schema. */
1504
+ resolveLookupFields(object) {
1505
+ try {
1506
+ const schema = this.engine.getSchema?.(object);
1507
+ const fields = schema?.fields ?? {};
1508
+ const out = [];
1509
+ for (const [key, f] of Object.entries(fields)) {
1510
+ if ((f?.type === "lookup" || f?.type === "master_detail") && f?.reference) {
1511
+ out.push({ key, reference: String(f.reference) });
1512
+ }
1513
+ }
1514
+ return out;
1515
+ } catch {
1516
+ return [];
1517
+ }
1518
+ }
1519
+ /**
1520
+ * Attach inbox display fields to rows so clients never render a raw
1521
+ * identifier: `record_title`, `submitter_name`, `object_label`,
1522
+ * `pending_approver_names` (user-id approvers), and `payload_display`
1523
+ * (lookup foreign keys in the snapshot → referenced record titles).
1524
+ * Batched: one query per distinct object (target + referenced) plus one
1525
+ * `sys_user` lookup. Best-effort — a deleted record falls back to the
1526
+ * payload snapshot, and any failure leaves the field unset rather than
1527
+ * failing the list.
1471
1528
  */
1472
1529
  async enrichRows(rows) {
1473
1530
  if (!rows.length) return;
@@ -1482,7 +1539,13 @@ var ApprovalService = class _ApprovalService {
1482
1539
  set.add(r.record_id);
1483
1540
  }
1484
1541
  const titles = /* @__PURE__ */ new Map();
1542
+ const objectLabels = /* @__PURE__ */ new Map();
1485
1543
  for (const [object, idSet] of byObject) {
1544
+ try {
1545
+ const schema = this.engine.getSchema?.(object);
1546
+ if (schema?.label) objectLabels.set(object, String(schema.label));
1547
+ } catch {
1548
+ }
1486
1549
  const ids = Array.from(idSet);
1487
1550
  const displayField = this.resolveDisplayField(object);
1488
1551
  try {
@@ -1493,47 +1556,81 @@ var ApprovalService = class _ApprovalService {
1493
1556
  });
1494
1557
  for (const rec of recs ?? []) {
1495
1558
  const title = _ApprovalService.pickTitle(rec, displayField);
1496
- if (rec?.id && title) titles.set(`${object}\0${rec.id}`, title);
1559
+ if (rec?.id && title) titles.set(`${object} ${rec.id}`, title);
1497
1560
  }
1498
1561
  } catch {
1499
1562
  }
1500
1563
  }
1501
- const submitters = Array.from(new Set(rows.map((r) => r.submitter_id).filter(Boolean)));
1502
- const names = /* @__PURE__ */ new Map();
1503
- if (submitters.length) {
1564
+ const lookupFieldsByObject = /* @__PURE__ */ new Map();
1565
+ for (const object of byObject.keys()) {
1566
+ const lookups = this.resolveLookupFields(object);
1567
+ if (lookups.length) lookupFieldsByObject.set(object, lookups);
1568
+ }
1569
+ const refIds = /* @__PURE__ */ new Map();
1570
+ for (const r of rows) {
1571
+ const lookups = lookupFieldsByObject.get(r.object_name);
1572
+ const payload = r.payload;
1573
+ if (!lookups || !payload || typeof payload !== "object") continue;
1574
+ for (const { key, reference } of lookups) {
1575
+ const v = payload[key];
1576
+ if (v == null || typeof v === "object" || !String(v).trim()) continue;
1577
+ let set = refIds.get(reference);
1578
+ if (!set) {
1579
+ set = /* @__PURE__ */ new Set();
1580
+ refIds.set(reference, set);
1581
+ }
1582
+ set.add(String(v));
1583
+ }
1584
+ }
1585
+ const refTitles = /* @__PURE__ */ new Map();
1586
+ for (const [object, idSet] of refIds) {
1587
+ const ids = Array.from(idSet);
1588
+ const displayField = this.resolveDisplayField(object);
1504
1589
  try {
1505
- const users = await this.engine.find("sys_user", {
1506
- where: { id: { $in: submitters } },
1507
- fields: ["id", "name", "email"],
1508
- limit: submitters.length,
1590
+ const recs = await this.engine.find(object, {
1591
+ where: { id: { $in: ids } },
1592
+ limit: ids.length,
1509
1593
  context: SYSTEM_CTX
1510
1594
  });
1511
- for (const u of users ?? []) {
1512
- if (u?.id && (u.name || u.email)) names.set(String(u.id), String(u.name ?? u.email));
1595
+ for (const rec of recs ?? []) {
1596
+ const title = _ApprovalService.pickTitle(rec, displayField);
1597
+ if (rec?.id && title) refTitles.set(`${object} ${rec.id}`, title);
1513
1598
  }
1514
1599
  } catch {
1515
1600
  }
1516
- const unresolvedEmails = submitters.filter((s) => !names.has(s) && s.includes("@"));
1517
- if (unresolvedEmails.length) {
1518
- try {
1519
- const users = await this.engine.find("sys_user", {
1520
- where: { email: { $in: unresolvedEmails } },
1521
- fields: ["email", "name"],
1522
- limit: unresolvedEmails.length,
1523
- context: SYSTEM_CTX
1524
- });
1525
- for (const u of users ?? []) {
1526
- if (u?.email && u.name) names.set(String(u.email), String(u.name));
1527
- }
1528
- } catch {
1529
- }
1601
+ }
1602
+ const userIdentifiers = [];
1603
+ for (const r of rows) {
1604
+ userIdentifiers.push(r.submitter_id);
1605
+ for (const a of r.pending_approvers ?? []) {
1606
+ if (a && !a.includes(":")) userIdentifiers.push(a);
1530
1607
  }
1531
1608
  }
1609
+ const names = await this.resolveUserNames(userIdentifiers);
1532
1610
  for (const r of rows) {
1533
- const title = titles.get(`${r.object_name}\0${r.record_id}`) ?? _ApprovalService.pickTitle(r.payload, void 0);
1611
+ const title = titles.get(`${r.object_name} ${r.record_id}`) ?? _ApprovalService.pickTitle(r.payload, void 0);
1534
1612
  if (title) r.record_title = title;
1535
1613
  const name = r.submitter_id ? names.get(String(r.submitter_id)) : void 0;
1536
1614
  if (name) r.submitter_name = name;
1615
+ const label = objectLabels.get(r.object_name);
1616
+ if (label) r.object_label = label;
1617
+ const approverNames = {};
1618
+ for (const a of r.pending_approvers ?? []) {
1619
+ const n = names.get(String(a));
1620
+ if (n) approverNames[a] = n;
1621
+ }
1622
+ if (Object.keys(approverNames).length) r.pending_approver_names = approverNames;
1623
+ const lookups = lookupFieldsByObject.get(r.object_name);
1624
+ if (lookups && r.payload && typeof r.payload === "object") {
1625
+ const display = {};
1626
+ for (const { key, reference } of lookups) {
1627
+ const v = r.payload[key];
1628
+ if (v == null) continue;
1629
+ const t = refTitles.get(`${reference} ${String(v)}`);
1630
+ if (t) display[key] = t;
1631
+ }
1632
+ if (Object.keys(display).length) r.payload_display = display;
1633
+ }
1537
1634
  }
1538
1635
  }
1539
1636
  // ── Read API ─────────────────────────────────────────────────
@@ -1592,7 +1689,15 @@ var ApprovalService = class _ApprovalService {
1592
1689
  orderBy: [{ field: "created_at", direction: "asc" }],
1593
1690
  context: SYSTEM_CTX
1594
1691
  });
1595
- return Array.isArray(rows) ? rows.map(rowFromAction) : [];
1692
+ const actions = Array.isArray(rows) ? rows.map(rowFromAction) : [];
1693
+ const names = await this.resolveUserNames(
1694
+ actions.map((a) => a.actor_id).filter((id) => id && !id.includes(":"))
1695
+ );
1696
+ for (const a of actions) {
1697
+ const n = a.actor_id ? names.get(String(a.actor_id)) : void 0;
1698
+ if (n) a.actor_name = n;
1699
+ }
1700
+ return actions;
1596
1701
  }
1597
1702
  };
1598
1703