@dyrected/core 2.5.14 → 2.5.16

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 (48) hide show
  1. package/dist/app-B2tg7Djj.d.cts +1575 -0
  2. package/dist/app-B2tg7Djj.d.ts +1575 -0
  3. package/dist/app-Bh4_Opv0.d.cts +1522 -0
  4. package/dist/app-Bh4_Opv0.d.ts +1522 -0
  5. package/dist/app-Bv9gaDAN.d.cts +561 -0
  6. package/dist/app-Bv9gaDAN.d.ts +561 -0
  7. package/dist/app-BvG3bRc8.d.cts +419 -0
  8. package/dist/app-BvG3bRc8.d.ts +419 -0
  9. package/dist/app-C3B9N1KR.d.cts +1522 -0
  10. package/dist/app-C3B9N1KR.d.ts +1522 -0
  11. package/dist/app-DDJJa0ep.d.cts +1621 -0
  12. package/dist/app-DDJJa0ep.d.ts +1621 -0
  13. package/dist/app-DO1s9YW1.d.cts +1621 -0
  14. package/dist/app-DO1s9YW1.d.ts +1621 -0
  15. package/dist/app-DTP3-9PJ.d.cts +561 -0
  16. package/dist/app-DTP3-9PJ.d.ts +561 -0
  17. package/dist/app-DbKDGYTI.d.cts +566 -0
  18. package/dist/app-DbKDGYTI.d.ts +566 -0
  19. package/dist/app-DqRO-CMi.d.cts +1457 -0
  20. package/dist/app-DqRO-CMi.d.ts +1457 -0
  21. package/dist/app-DvaFpOtj.d.cts +398 -0
  22. package/dist/app-DvaFpOtj.d.ts +398 -0
  23. package/dist/app-FGzip4XM.d.cts +1563 -0
  24. package/dist/app-FGzip4XM.d.ts +1563 -0
  25. package/dist/app-T0alZAE0.d.cts +383 -0
  26. package/dist/app-T0alZAE0.d.ts +383 -0
  27. package/dist/app-oQt5-9MU.d.cts +1560 -0
  28. package/dist/app-oQt5-9MU.d.ts +1560 -0
  29. package/dist/app-rZj1VFer.d.cts +1621 -0
  30. package/dist/app-rZj1VFer.d.ts +1621 -0
  31. package/dist/app-wo82JRHl.d.cts +445 -0
  32. package/dist/app-wo82JRHl.d.ts +445 -0
  33. package/dist/chunk-23URSKPI.js +2371 -0
  34. package/dist/chunk-2JMA3M5S.js +2475 -0
  35. package/dist/chunk-3FZEUK36.js +2470 -0
  36. package/dist/chunk-DOJHZ7XN.js +2394 -0
  37. package/dist/chunk-PKNFV7KE.js +2469 -0
  38. package/dist/chunk-UBTRANFX.js +2476 -0
  39. package/dist/chunk-W6KURRMW.js +2471 -0
  40. package/dist/index.cjs +457 -48
  41. package/dist/index.d.cts +117 -8
  42. package/dist/index.d.ts +117 -8
  43. package/dist/index.js +9 -3
  44. package/dist/server.cjs +449 -46
  45. package/dist/server.d.cts +57 -15
  46. package/dist/server.d.ts +57 -15
  47. package/dist/server.js +1 -1
  48. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -34,11 +34,14 @@ __export(index_exports, {
34
34
  defineCollection: () => defineCollection,
35
35
  defineConfig: () => defineConfig,
36
36
  defineGlobal: () => defineGlobal,
37
+ executeFieldAfterRead: () => executeFieldAfterRead,
38
+ executeFieldBeforeChange: () => executeFieldBeforeChange,
37
39
  generateAIPrompt: () => generateAIPrompt,
38
40
  generateFreshSetupPrompt: () => generateFreshSetupPrompt,
39
41
  normalizeConfig: () => normalizeConfig,
40
42
  parseMongoWhere: () => parseMongoWhere,
41
- parseSqlWhere: () => parseSqlWhere
43
+ parseSqlWhere: () => parseSqlWhere,
44
+ runCollectionHooks: () => runCollectionHooks
42
45
  });
43
46
  module.exports = __toCommonJS(index_exports);
44
47
 
@@ -1166,6 +1169,150 @@ function parseMongoWhere(where) {
1166
1169
  return buildClause(where);
1167
1170
  }
1168
1171
 
1172
+ // src/utils/hooks.ts
1173
+ async function runCollectionHooks(hooks, args) {
1174
+ if (!hooks || hooks.length === 0) {
1175
+ return args.data ?? args.doc ?? void 0;
1176
+ }
1177
+ let currentPayload = args.data ?? args.doc ?? void 0;
1178
+ for (const hook of hooks) {
1179
+ const result = await hook({
1180
+ ...args,
1181
+ data: args.data !== void 0 ? currentPayload : void 0,
1182
+ doc: args.doc !== void 0 ? currentPayload : void 0
1183
+ });
1184
+ if (result !== void 0) {
1185
+ currentPayload = result;
1186
+ }
1187
+ }
1188
+ return currentPayload;
1189
+ }
1190
+ async function executeFieldBeforeChange(fields, data, originalDoc, user) {
1191
+ if (!data || typeof data !== "object") return data;
1192
+ const result = { ...data };
1193
+ for (const field of fields) {
1194
+ if (!field.name) continue;
1195
+ const value = result[field.name];
1196
+ const origValue = originalDoc?.[field.name];
1197
+ let updatedValue = value;
1198
+ if (field.hooks?.beforeChange) {
1199
+ for (const hook of field.hooks.beforeChange) {
1200
+ updatedValue = await hook({
1201
+ value: updatedValue,
1202
+ originalDoc: originalDoc ?? void 0,
1203
+ data: result,
1204
+ user
1205
+ });
1206
+ }
1207
+ result[field.name] = updatedValue;
1208
+ }
1209
+ if (updatedValue !== void 0 && updatedValue !== null) {
1210
+ if (field.type === "object" && field.fields) {
1211
+ result[field.name] = await executeFieldBeforeChange(
1212
+ field.fields,
1213
+ updatedValue,
1214
+ origValue,
1215
+ user
1216
+ );
1217
+ } else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
1218
+ const arrayResult = [];
1219
+ for (let i = 0; i < updatedValue.length; i++) {
1220
+ const item = updatedValue[i];
1221
+ const origItem = Array.isArray(origValue) ? origValue[i] : null;
1222
+ arrayResult.push(
1223
+ await executeFieldBeforeChange(field.fields, item, origItem, user)
1224
+ );
1225
+ }
1226
+ result[field.name] = arrayResult;
1227
+ } else if (field.type === "blocks" && field.blocks && Array.isArray(updatedValue)) {
1228
+ const blocksResult = [];
1229
+ for (let i = 0; i < updatedValue.length; i++) {
1230
+ const blockData = updatedValue[i];
1231
+ const origBlock = Array.isArray(origValue) ? origValue[i] : null;
1232
+ const blockConfig = field.blocks.find(
1233
+ (b) => b.slug === blockData.blockType
1234
+ );
1235
+ if (blockConfig) {
1236
+ blocksResult.push(
1237
+ await executeFieldBeforeChange(
1238
+ blockConfig.fields,
1239
+ blockData,
1240
+ origBlock,
1241
+ user
1242
+ )
1243
+ );
1244
+ } else {
1245
+ blocksResult.push(blockData);
1246
+ }
1247
+ }
1248
+ result[field.name] = blocksResult;
1249
+ }
1250
+ }
1251
+ }
1252
+ return result;
1253
+ }
1254
+ async function executeFieldAfterRead(fields, doc, user) {
1255
+ if (!doc || typeof doc !== "object") return doc;
1256
+ const result = { ...doc };
1257
+ for (const field of fields) {
1258
+ if (!field.name) continue;
1259
+ const value = result[field.name];
1260
+ let updatedValue = value;
1261
+ if (field.hooks?.afterRead) {
1262
+ for (const hook of field.hooks.afterRead) {
1263
+ updatedValue = await hook({
1264
+ value: updatedValue,
1265
+ doc: result,
1266
+ user
1267
+ });
1268
+ }
1269
+ result[field.name] = updatedValue;
1270
+ }
1271
+ if (updatedValue !== void 0 && updatedValue !== null) {
1272
+ if (field.type === "object" && field.fields) {
1273
+ result[field.name] = await executeFieldAfterRead(
1274
+ field.fields,
1275
+ updatedValue,
1276
+ user
1277
+ );
1278
+ } else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
1279
+ const arrayResult = [];
1280
+ for (const item of updatedValue) {
1281
+ arrayResult.push(
1282
+ await executeFieldAfterRead(
1283
+ field.fields,
1284
+ item,
1285
+ user
1286
+ )
1287
+ );
1288
+ }
1289
+ result[field.name] = arrayResult;
1290
+ } else if (field.type === "blocks" && field.blocks && Array.isArray(updatedValue)) {
1291
+ const blocksResult = [];
1292
+ for (const blockData of updatedValue) {
1293
+ const typedBlock = blockData;
1294
+ const blockConfig = field.blocks.find(
1295
+ (b) => b.slug === typedBlock.blockType
1296
+ );
1297
+ if (blockConfig) {
1298
+ blocksResult.push(
1299
+ await executeFieldAfterRead(
1300
+ blockConfig.fields,
1301
+ typedBlock,
1302
+ user
1303
+ )
1304
+ );
1305
+ } else {
1306
+ blocksResult.push(blockData);
1307
+ }
1308
+ }
1309
+ result[field.name] = blocksResult;
1310
+ }
1311
+ }
1312
+ }
1313
+ return result;
1314
+ }
1315
+
1169
1316
  // src/app.ts
1170
1317
  var import_hono = require("hono");
1171
1318
  var import_cors = require("hono/cors");
@@ -1408,6 +1555,7 @@ var CollectionController = class {
1408
1555
  const page = Number(c.req.query("page")) || 1;
1409
1556
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 2;
1410
1557
  const sort = c.req.query("sort") || void 0;
1558
+ const user = c.get("user");
1411
1559
  let where = void 0;
1412
1560
  const whereRaw = c.req.query("where");
1413
1561
  if (whereRaw) {
@@ -1416,6 +1564,14 @@ var CollectionController = class {
1416
1564
  } catch {
1417
1565
  }
1418
1566
  }
1567
+ const beforeReadResult = await runCollectionHooks(this.collection.hooks?.beforeRead, {
1568
+ req: c.req,
1569
+ query: where,
1570
+ user
1571
+ });
1572
+ if (beforeReadResult !== void 0) {
1573
+ where = beforeReadResult;
1574
+ }
1419
1575
  let result = await db.find({
1420
1576
  collection: this.collection.slug,
1421
1577
  limit,
@@ -1437,6 +1593,17 @@ var CollectionController = class {
1437
1593
  });
1438
1594
  }
1439
1595
  result.docs = result.docs.map((doc) => DefaultsService.apply(this.collection.fields, doc));
1596
+ const processedDocs = [];
1597
+ for (const doc of result.docs) {
1598
+ const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
1599
+ doc,
1600
+ req: c.req,
1601
+ user
1602
+ });
1603
+ const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user);
1604
+ processedDocs.push(docWithFieldHooks);
1605
+ }
1606
+ result.docs = processedDocs;
1440
1607
  if (depth > 0) {
1441
1608
  const populationService = new PopulationService(db, config.collections);
1442
1609
  result = await populationService.populateResult(result, this.collection.fields, depth);
@@ -1449,21 +1616,28 @@ var CollectionController = class {
1449
1616
  if (!db) return c.json({ message: "Database not configured" }, 500);
1450
1617
  const id = c.req.param("id");
1451
1618
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
1619
+ const user = c.get("user");
1452
1620
  if (!id) return c.json({ message: "Missing ID" }, 400);
1453
1621
  const doc = await db.findOne({ collection: this.collection.slug, id });
1454
1622
  if (!doc) return c.json({ message: "Not Found" }, 404);
1455
1623
  const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
1456
- if (depth > 0 && docWithDefaults) {
1624
+ const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
1625
+ doc: docWithDefaults,
1626
+ req: c.req,
1627
+ user
1628
+ });
1629
+ const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user);
1630
+ if (depth > 0 && docWithFieldHooks) {
1457
1631
  const populationService = new PopulationService(db, config.collections);
1458
1632
  const populatedDoc = await populationService.populate({
1459
- data: docWithDefaults,
1633
+ data: docWithFieldHooks,
1460
1634
  fields: this.collection.fields,
1461
1635
  currentDepth: 0,
1462
1636
  maxDepth: depth
1463
1637
  });
1464
1638
  return c.json(populatedDoc);
1465
1639
  }
1466
- return c.json(docWithDefaults);
1640
+ return c.json(docWithFieldHooks);
1467
1641
  }
1468
1642
  async create(c) {
1469
1643
  const config = c.get("config");
@@ -1476,7 +1650,7 @@ var CollectionController = class {
1476
1650
  const body = await c.req.json();
1477
1651
  const user = c.get("user");
1478
1652
  const now = (/* @__PURE__ */ new Date()).toISOString();
1479
- const data = {
1653
+ let data = {
1480
1654
  ...body,
1481
1655
  createdAt: now,
1482
1656
  updatedAt: now,
@@ -1486,6 +1660,13 @@ var CollectionController = class {
1486
1660
  if (this.collection.auth && data.password) {
1487
1661
  data.password = await hashPassword(data.password);
1488
1662
  }
1663
+ data = await executeFieldBeforeChange(this.collection.fields, data, null, user);
1664
+ data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
1665
+ data,
1666
+ req: c.req,
1667
+ user,
1668
+ operation: "create"
1669
+ });
1489
1670
  const doc = await db.create({ collection: this.collection.slug, data });
1490
1671
  if (this.collection.audit && db) {
1491
1672
  AuditService.log(db, {
@@ -1497,7 +1678,19 @@ var CollectionController = class {
1497
1678
  after: doc
1498
1679
  });
1499
1680
  }
1500
- return c.json(doc, 201);
1681
+ await runCollectionHooks(this.collection.hooks?.afterChange, {
1682
+ doc,
1683
+ user,
1684
+ req: c.req,
1685
+ operation: "create"
1686
+ });
1687
+ const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
1688
+ doc,
1689
+ req: c.req,
1690
+ user
1691
+ });
1692
+ const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
1693
+ return c.json(finalDoc, 201);
1501
1694
  }
1502
1695
  async upload(c) {
1503
1696
  const config = c.get("config");
@@ -1524,18 +1717,38 @@ var CollectionController = class {
1524
1717
  });
1525
1718
  const user = c.get("user");
1526
1719
  const now = (/* @__PURE__ */ new Date()).toISOString();
1720
+ let data = {
1721
+ ...otherData,
1722
+ ...fileData,
1723
+ createdAt: now,
1724
+ updatedAt: now,
1725
+ createdBy: user?.sub ?? null,
1726
+ updatedBy: user?.sub ?? null
1727
+ };
1728
+ data = await executeFieldBeforeChange(this.collection.fields, data, null, user);
1729
+ data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
1730
+ data,
1731
+ req: c.req,
1732
+ user,
1733
+ operation: "create"
1734
+ });
1527
1735
  const doc = await config.db.create({
1528
1736
  collection: this.collection.slug,
1529
- data: {
1530
- ...otherData,
1531
- ...fileData,
1532
- createdAt: now,
1533
- updatedAt: now,
1534
- createdBy: user?.sub ?? null,
1535
- updatedBy: user?.sub ?? null
1536
- }
1737
+ data
1537
1738
  });
1538
- return c.json(doc, 201);
1739
+ await runCollectionHooks(this.collection.hooks?.afterChange, {
1740
+ doc,
1741
+ user,
1742
+ req: c.req,
1743
+ operation: "create"
1744
+ });
1745
+ const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
1746
+ doc,
1747
+ req: c.req,
1748
+ user
1749
+ });
1750
+ const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
1751
+ return c.json(finalDoc, 201);
1539
1752
  }
1540
1753
  async update(c) {
1541
1754
  const config = c.get("config");
@@ -1545,7 +1758,7 @@ var CollectionController = class {
1545
1758
  if (!id) return c.json({ message: "Missing ID" }, 400);
1546
1759
  const body = await c.req.json();
1547
1760
  const user = c.get("user");
1548
- const data = { ...body };
1761
+ let data = { ...body };
1549
1762
  if (this.collection.auth) {
1550
1763
  delete data.password;
1551
1764
  delete data.oldPassword;
@@ -1555,10 +1768,20 @@ var CollectionController = class {
1555
1768
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1556
1769
  updatedBy: user?.sub ?? null
1557
1770
  });
1771
+ const originalDoc = await db.findOne({ collection: this.collection.slug, id });
1772
+ if (!originalDoc) return c.json({ message: "Not Found" }, 404);
1558
1773
  let before = null;
1559
1774
  if (this.collection.audit) {
1560
- before = await db.findOne({ collection: this.collection.slug, id });
1561
- }
1775
+ before = originalDoc;
1776
+ }
1777
+ data = await executeFieldBeforeChange(this.collection.fields, data, originalDoc, user);
1778
+ data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
1779
+ data,
1780
+ doc: originalDoc,
1781
+ req: c.req,
1782
+ user,
1783
+ operation: "update"
1784
+ });
1562
1785
  const doc = await db.update({ collection: this.collection.slug, id, data });
1563
1786
  if (this.collection.audit && db) {
1564
1787
  AuditService.log(db, {
@@ -1570,7 +1793,20 @@ var CollectionController = class {
1570
1793
  after: doc
1571
1794
  });
1572
1795
  }
1573
- return c.json(doc);
1796
+ await runCollectionHooks(this.collection.hooks?.afterChange, {
1797
+ doc,
1798
+ previousDoc: originalDoc,
1799
+ user,
1800
+ req: c.req,
1801
+ operation: "update"
1802
+ });
1803
+ const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
1804
+ doc,
1805
+ req: c.req,
1806
+ user
1807
+ });
1808
+ const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
1809
+ return c.json(finalDoc);
1574
1810
  }
1575
1811
  /**
1576
1812
  * POST /api/collections/:slug/:id/change-password
@@ -1650,10 +1886,18 @@ var CollectionController = class {
1650
1886
  const id = c.req.param("id");
1651
1887
  if (!id) return c.json({ message: "Missing ID" }, 400);
1652
1888
  const user = c.get("user");
1889
+ const doc = await db.findOne({ collection: this.collection.slug, id });
1890
+ if (!doc) return c.json({ message: "Not Found" }, 404);
1653
1891
  let before = null;
1654
1892
  if (this.collection.audit) {
1655
- before = await db.findOne({ collection: this.collection.slug, id });
1893
+ before = doc;
1656
1894
  }
1895
+ await runCollectionHooks(this.collection.hooks?.beforeDelete, {
1896
+ id,
1897
+ doc,
1898
+ user,
1899
+ req: c.req
1900
+ });
1657
1901
  await db.delete({ collection: this.collection.slug, id });
1658
1902
  if (this.collection.audit && db) {
1659
1903
  AuditService.log(db, {
@@ -1665,6 +1909,12 @@ var CollectionController = class {
1665
1909
  after: null
1666
1910
  });
1667
1911
  }
1912
+ await runCollectionHooks(this.collection.hooks?.afterDelete, {
1913
+ id,
1914
+ doc,
1915
+ user,
1916
+ req: c.req
1917
+ });
1668
1918
  return c.json({ message: "Deleted" });
1669
1919
  }
1670
1920
  async deleteMany(c) {
@@ -1689,10 +1939,21 @@ var CollectionController = class {
1689
1939
  const failed = [];
1690
1940
  for (const id of ids) {
1691
1941
  try {
1942
+ const doc = await db.findOne({ collection: this.collection.slug, id });
1943
+ if (!doc) {
1944
+ failed.push({ id, error: "Not Found" });
1945
+ continue;
1946
+ }
1692
1947
  let before = null;
1693
1948
  if (this.collection.audit) {
1694
- before = await db.findOne({ collection: this.collection.slug, id });
1949
+ before = doc;
1695
1950
  }
1951
+ await runCollectionHooks(this.collection.hooks?.beforeDelete, {
1952
+ id,
1953
+ doc,
1954
+ user,
1955
+ req: c.req
1956
+ });
1696
1957
  await db.delete({ collection: this.collection.slug, id });
1697
1958
  deleted.push(id);
1698
1959
  if (this.collection.audit) {
@@ -1705,6 +1966,12 @@ var CollectionController = class {
1705
1966
  after: null
1706
1967
  });
1707
1968
  }
1969
+ await runCollectionHooks(this.collection.hooks?.afterDelete, {
1970
+ id,
1971
+ doc,
1972
+ user,
1973
+ req: c.req
1974
+ });
1708
1975
  } catch (err) {
1709
1976
  failed.push({ id, error: err?.message ?? "Unknown error" });
1710
1977
  }
@@ -1749,6 +2016,16 @@ var GlobalController = class {
1749
2016
  const db = config.db;
1750
2017
  if (!db) return c.json({ message: "Database not configured" }, 500);
1751
2018
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
2019
+ const user = c.get("user");
2020
+ let query = void 0;
2021
+ const beforeReadResult = await runCollectionHooks(this.global.hooks?.beforeRead, {
2022
+ req: c.req,
2023
+ query,
2024
+ user
2025
+ });
2026
+ if (beforeReadResult !== void 0) {
2027
+ query = beforeReadResult;
2028
+ }
1752
2029
  let data = await db.getGlobal({ slug: this.global.slug });
1753
2030
  const isEmpty = !data || Object.keys(data).length === 0;
1754
2031
  if (isEmpty && this.global.initialData) {
@@ -1757,24 +2034,53 @@ var GlobalController = class {
1757
2034
  data = this.global.initialData;
1758
2035
  }
1759
2036
  const dataWithDefaults = DefaultsService.apply(this.global.fields, data);
1760
- if (depth > 0 && dataWithDefaults) {
2037
+ const docWithCollectionHooks = await runCollectionHooks(this.global.hooks?.afterRead, {
2038
+ doc: dataWithDefaults,
2039
+ req: c.req,
2040
+ user
2041
+ });
2042
+ const docWithFieldHooks = await executeFieldAfterRead(this.global.fields, docWithCollectionHooks, user);
2043
+ if (depth > 0 && docWithFieldHooks) {
1761
2044
  const populationService = new PopulationService(db, config.collections);
1762
2045
  const populatedData = await populationService.populate({
1763
- data: dataWithDefaults,
2046
+ data: docWithFieldHooks,
1764
2047
  fields: this.global.fields,
1765
2048
  currentDepth: 0,
1766
2049
  maxDepth: depth
1767
2050
  });
1768
2051
  return c.json(populatedData);
1769
2052
  }
1770
- return c.json(dataWithDefaults);
2053
+ return c.json(docWithFieldHooks);
1771
2054
  }
1772
2055
  async update(c) {
1773
2056
  const db = c.get("config").db;
1774
2057
  if (!db) return c.json({ message: "Database not configured" }, 500);
1775
2058
  const body = await c.req.json();
1776
- const data = await db.updateGlobal({ slug: this.global.slug, data: body });
1777
- return c.json(data);
2059
+ const user = c.get("user");
2060
+ const originalDoc = await db.getGlobal({ slug: this.global.slug }) || {};
2061
+ let data = await executeFieldBeforeChange(this.global.fields, body, originalDoc, user);
2062
+ data = await runCollectionHooks(this.global.hooks?.beforeChange, {
2063
+ data,
2064
+ doc: originalDoc,
2065
+ req: c.req,
2066
+ user,
2067
+ operation: "update"
2068
+ });
2069
+ const updated = await db.updateGlobal({ slug: this.global.slug, data });
2070
+ await runCollectionHooks(this.global.hooks?.afterChange, {
2071
+ doc: updated,
2072
+ previousDoc: originalDoc,
2073
+ user,
2074
+ req: c.req,
2075
+ operation: "update"
2076
+ });
2077
+ const readDoc = await runCollectionHooks(this.global.hooks?.afterRead, {
2078
+ doc: updated,
2079
+ req: c.req,
2080
+ user
2081
+ });
2082
+ const finalDoc = await executeFieldAfterRead(this.global.fields, readDoc, user);
2083
+ return c.json(finalDoc);
1778
2084
  }
1779
2085
  async seed(c) {
1780
2086
  const config = c.get("config");
@@ -2683,12 +2989,13 @@ function fieldToSchema(field) {
2683
2989
  schema = { type: "string", format: "date-time" };
2684
2990
  break;
2685
2991
  case "select":
2686
- schema = { type: "string", enum: field.options?.map((o) => typeof o === "string" ? o : o.value) };
2992
+ case "radio":
2993
+ schema = { type: "string", enum: Array.isArray(field.options) ? field.options.map((o) => typeof o === "string" ? o : o.value) : void 0 };
2687
2994
  break;
2688
2995
  case "multiSelect":
2689
2996
  schema = {
2690
2997
  type: "array",
2691
- items: { type: "string", enum: field.options?.map((o) => typeof o === "string" ? o : o.value) }
2998
+ items: { type: "string", enum: Array.isArray(field.options) ? field.options.map((o) => typeof o === "string" ? o : o.value) : void 0 }
2692
2999
  };
2693
3000
  break;
2694
3001
  case "relationship":
@@ -2812,6 +3119,49 @@ function accessGate(target, action) {
2812
3119
  await next();
2813
3120
  };
2814
3121
  }
3122
+ async function checkAccess(access, accessArgs) {
3123
+ if (access === void 0 || access === null) return true;
3124
+ if (typeof access === "function") {
3125
+ try {
3126
+ const result = await access(accessArgs);
3127
+ return typeof result === "boolean" ? result : !!result;
3128
+ } catch (err) {
3129
+ console.error("[dyrected/core] Functional access check failed:", err);
3130
+ return false;
3131
+ }
3132
+ }
3133
+ if (typeof access === "string" || typeof access === "boolean") {
3134
+ return evaluateAccess(access, accessArgs);
3135
+ }
3136
+ return true;
3137
+ }
3138
+ function serializeFieldForApi(f) {
3139
+ if (!f) return f;
3140
+ const serialized = { ...f };
3141
+ if (serialized.admin?.hooks) {
3142
+ const hooks = { ...serialized.admin.hooks };
3143
+ if (typeof hooks.onChange === "function") {
3144
+ hooks.onChange = hooks.onChange.toString();
3145
+ }
3146
+ if (typeof hooks.options === "function") {
3147
+ hooks.options = hooks.options.toString();
3148
+ }
3149
+ serialized.admin = { ...serialized.admin, hooks };
3150
+ }
3151
+ if (typeof serialized.options === "function" || serialized.options && typeof serialized.options === "object" && "resolve" in serialized.options) {
3152
+ serialized.options = { _dynamic: true };
3153
+ }
3154
+ if (serialized.fields) {
3155
+ serialized.fields = serialized.fields.map(serializeFieldForApi);
3156
+ }
3157
+ if (serialized.blocks) {
3158
+ serialized.blocks = serialized.blocks.map((b) => ({
3159
+ ...b,
3160
+ fields: b.fields?.map(serializeFieldForApi)
3161
+ }));
3162
+ }
3163
+ return serialized;
3164
+ }
2815
3165
  function registerRoutes(app, config) {
2816
3166
  app.get("/api/schemas", optionalAuth(), async (c) => {
2817
3167
  const siteId = c.req.header("X-Site-Id");
@@ -2827,26 +3177,10 @@ function registerRoutes(app, config) {
2827
3177
  }
2828
3178
  const user = c.get("user");
2829
3179
  const accessArgs = { user, req: c.req, doc: null };
2830
- const resolveAccess = async (access) => {
2831
- if (access === void 0 || access === null) return true;
2832
- if (typeof access === "function") {
2833
- try {
2834
- const result = await access(accessArgs);
2835
- return typeof result === "boolean" ? result : !!result;
2836
- } catch (err) {
2837
- console.error("[dyrected/core] Functional access check failed:", err);
2838
- return false;
2839
- }
2840
- }
2841
- if (typeof access === "string" || typeof access === "boolean") {
2842
- return evaluateAccess(access, accessArgs);
2843
- }
2844
- return true;
2845
- };
2846
3180
  const serializeAccess = async (access) => {
2847
3181
  if (typeof access === "string") return access;
2848
3182
  if (typeof access === "boolean") return access;
2849
- return resolveAccess(access);
3183
+ return checkAccess(access, accessArgs);
2850
3184
  };
2851
3185
  const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
2852
3186
  slug: col.slug,
@@ -2857,7 +3191,7 @@ function registerRoutes(app, config) {
2857
3191
  update: await serializeAccess(col.access?.update),
2858
3192
  delete: await serializeAccess(col.access?.delete)
2859
3193
  },
2860
- fields: await Promise.all(col.fields.map(async (f) => ({
3194
+ fields: await Promise.all(col.fields.map(serializeFieldForApi).map(async (f) => ({
2861
3195
  name: f.name,
2862
3196
  type: f.type,
2863
3197
  label: f.label,
@@ -2885,7 +3219,7 @@ function registerRoutes(app, config) {
2885
3219
  read: await serializeAccess(glb.access?.read),
2886
3220
  update: await serializeAccess(glb.access?.update)
2887
3221
  },
2888
- fields: await Promise.all(glb.fields.map(async (f) => ({
3222
+ fields: await Promise.all(glb.fields.map(serializeFieldForApi).map(async (f) => ({
2889
3223
  name: f.name,
2890
3224
  type: f.type,
2891
3225
  label: f.label,
@@ -2910,6 +3244,78 @@ function registerRoutes(app, config) {
2910
3244
  admin: config.admin || {}
2911
3245
  });
2912
3246
  });
3247
+ app.get("/api/dyrected/options/:collection/:field", optionalAuth(), async (c) => {
3248
+ const { collection: colSlug, field: fieldName } = c.req.param();
3249
+ const siteId = c.req.header("X-Site-Id");
3250
+ let collections = [...config.collections];
3251
+ if (siteId && config.onSchemaFetch) {
3252
+ const dynamic = await config.onSchemaFetch(siteId);
3253
+ if (dynamic.collections) collections = [...collections, ...dynamic.collections];
3254
+ }
3255
+ const user = c.get("user");
3256
+ let collection = collections.find((col) => col.slug === colSlug);
3257
+ let field;
3258
+ if (collection) {
3259
+ const accessExpr = collection.access?.read;
3260
+ if (accessExpr !== void 0 && accessExpr !== null) {
3261
+ const accessArgs = { user, req: c.req, doc: null };
3262
+ const allowed = await checkAccess(accessExpr, accessArgs);
3263
+ if (!allowed) {
3264
+ return c.json({ error: true, message: `Access denied: read on ${colSlug}` }, 403);
3265
+ }
3266
+ }
3267
+ field = collection.fields.find((f) => f.name === fieldName);
3268
+ } else {
3269
+ let globals = [...config.globals];
3270
+ if (siteId && config.onSchemaFetch) {
3271
+ const dynamic = await config.onSchemaFetch(siteId);
3272
+ if (dynamic.globals) globals = [...globals, ...dynamic.globals];
3273
+ }
3274
+ const glb = globals.find((g) => g.slug === colSlug);
3275
+ if (!glb) {
3276
+ return c.json({ error: true, message: `${colSlug} not found as collection or global` }, 404);
3277
+ }
3278
+ const accessExpr = glb.access?.read;
3279
+ if (accessExpr !== void 0 && accessExpr !== null) {
3280
+ const accessArgs = { user, req: c.req, doc: null };
3281
+ const allowed = await checkAccess(accessExpr, accessArgs);
3282
+ if (!allowed) {
3283
+ return c.json({ error: true, message: `Access denied: read on global ${colSlug}` }, 403);
3284
+ }
3285
+ }
3286
+ field = glb.fields.find((f) => f.name === fieldName);
3287
+ }
3288
+ if (!field) {
3289
+ return c.json({ error: true, message: `Field ${fieldName} not found in ${colSlug}` }, 404);
3290
+ }
3291
+ let resolver;
3292
+ if (typeof field.options === "function") {
3293
+ resolver = field.options;
3294
+ } else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
3295
+ resolver = field.options.resolve;
3296
+ }
3297
+ if (!resolver) {
3298
+ return c.json({ error: true, message: `Field ${fieldName} in ${colSlug} is not dynamic` }, 400);
3299
+ }
3300
+ try {
3301
+ const db = c.get("db") || config.db;
3302
+ const queryParams = c.req.query();
3303
+ const reqContext = {
3304
+ query: queryParams,
3305
+ headers: c.req.header(),
3306
+ raw: c.req.raw
3307
+ };
3308
+ const result = await resolver({
3309
+ db,
3310
+ user,
3311
+ req: reqContext
3312
+ });
3313
+ return c.json(result);
3314
+ } catch (err) {
3315
+ console.error(`[dyrected/core] Failed to resolve dynamic options for field ${fieldName}:`, err);
3316
+ return c.json({ error: true, message: err.message || "Failed to resolve dynamic options" }, 500);
3317
+ }
3318
+ });
2913
3319
  app.get("/api/openapi.json", (c) => {
2914
3320
  return c.json(generateOpenApi(config));
2915
3321
  });
@@ -3097,9 +3503,12 @@ function defineConfig(config) {
3097
3503
  defineCollection,
3098
3504
  defineConfig,
3099
3505
  defineGlobal,
3506
+ executeFieldAfterRead,
3507
+ executeFieldBeforeChange,
3100
3508
  generateAIPrompt,
3101
3509
  generateFreshSetupPrompt,
3102
3510
  normalizeConfig,
3103
3511
  parseMongoWhere,
3104
- parseSqlWhere
3512
+ parseSqlWhere,
3513
+ runCollectionHooks
3105
3514
  });