@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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +23 -0
- package/dist/index.d.mts +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +135 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +135 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/approval-service.test.ts +31 -0
- package/src/approval-service.ts +134 -29
package/dist/index.mjs
CHANGED
|
@@ -1443,10 +1443,67 @@ var ApprovalService = class _ApprovalService {
|
|
|
1443
1443
|
return void 0;
|
|
1444
1444
|
}
|
|
1445
1445
|
/**
|
|
1446
|
-
*
|
|
1447
|
-
*
|
|
1448
|
-
|
|
1449
|
-
|
|
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}
|
|
1538
|
+
if (rec?.id && title) titles.set(`${object} ${rec.id}`, title);
|
|
1476
1539
|
}
|
|
1477
1540
|
} catch {
|
|
1478
1541
|
}
|
|
1479
1542
|
}
|
|
1480
|
-
const
|
|
1481
|
-
const
|
|
1482
|
-
|
|
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
|
|
1485
|
-
where: { id: { $in:
|
|
1486
|
-
|
|
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
|
|
1491
|
-
|
|
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
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
|