@dyrected/core 2.5.14 → 2.5.17
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/app-B2tg7Djj.d.cts +1575 -0
- package/dist/app-B2tg7Djj.d.ts +1575 -0
- package/dist/app-BElen1tP.d.cts +1690 -0
- package/dist/app-BElen1tP.d.ts +1690 -0
- package/dist/app-Bh4_Opv0.d.cts +1522 -0
- package/dist/app-Bh4_Opv0.d.ts +1522 -0
- package/dist/app-Bv9gaDAN.d.cts +561 -0
- package/dist/app-Bv9gaDAN.d.ts +561 -0
- package/dist/app-BvG3bRc8.d.cts +419 -0
- package/dist/app-BvG3bRc8.d.ts +419 -0
- package/dist/app-C3B9N1KR.d.cts +1522 -0
- package/dist/app-C3B9N1KR.d.ts +1522 -0
- package/dist/app-DDJJa0ep.d.cts +1621 -0
- package/dist/app-DDJJa0ep.d.ts +1621 -0
- package/dist/app-DO1s9YW1.d.cts +1621 -0
- package/dist/app-DO1s9YW1.d.ts +1621 -0
- package/dist/app-DTP3-9PJ.d.cts +561 -0
- package/dist/app-DTP3-9PJ.d.ts +561 -0
- package/dist/app-DbKDGYTI.d.cts +566 -0
- package/dist/app-DbKDGYTI.d.ts +566 -0
- package/dist/app-DqRO-CMi.d.cts +1457 -0
- package/dist/app-DqRO-CMi.d.ts +1457 -0
- package/dist/app-DvaFpOtj.d.cts +398 -0
- package/dist/app-DvaFpOtj.d.ts +398 -0
- package/dist/app-FGzip4XM.d.cts +1563 -0
- package/dist/app-FGzip4XM.d.ts +1563 -0
- package/dist/app-T0alZAE0.d.cts +383 -0
- package/dist/app-T0alZAE0.d.ts +383 -0
- package/dist/app-oQt5-9MU.d.cts +1560 -0
- package/dist/app-oQt5-9MU.d.ts +1560 -0
- package/dist/app-rZj1VFer.d.cts +1621 -0
- package/dist/app-rZj1VFer.d.ts +1621 -0
- package/dist/app-wo82JRHl.d.cts +445 -0
- package/dist/app-wo82JRHl.d.ts +445 -0
- package/dist/chunk-23URSKPI.js +2371 -0
- package/dist/chunk-2JMA3M5S.js +2475 -0
- package/dist/chunk-3FZEUK36.js +2470 -0
- package/dist/chunk-DOJHZ7XN.js +2394 -0
- package/dist/chunk-EXXOPW3I.js +2483 -0
- package/dist/chunk-PKNFV7KE.js +2469 -0
- package/dist/chunk-UBTRANFX.js +2476 -0
- package/dist/chunk-W6KURRMW.js +2471 -0
- package/dist/chunk-YNJ3YC4N.js +2483 -0
- package/dist/index.cjs +465 -48
- package/dist/index.d.cts +124 -8
- package/dist/index.d.ts +124 -8
- package/dist/index.js +9 -3
- package/dist/server.cjs +457 -46
- package/dist/server.d.cts +57 -15
- package/dist/server.d.ts +57 -15
- package/dist/server.js +1 -1
- 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,158 @@ function parseMongoWhere(where) {
|
|
|
1166
1169
|
return buildClause(where);
|
|
1167
1170
|
}
|
|
1168
1171
|
|
|
1172
|
+
// src/utils/hooks.ts
|
|
1173
|
+
async function runCollectionHooks(hooks, args, options = {}) {
|
|
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
|
+
try {
|
|
1180
|
+
const result = await hook({
|
|
1181
|
+
...args,
|
|
1182
|
+
data: args.data !== void 0 ? currentPayload : void 0,
|
|
1183
|
+
doc: args.doc !== void 0 ? currentPayload : void 0
|
|
1184
|
+
});
|
|
1185
|
+
if (result !== void 0) {
|
|
1186
|
+
currentPayload = result;
|
|
1187
|
+
}
|
|
1188
|
+
} catch (err) {
|
|
1189
|
+
if (options.isolated) {
|
|
1190
|
+
console.error("[dyrected/core] Side-effect hook failed (error isolated \u2014 DB write was successful):", err);
|
|
1191
|
+
} else {
|
|
1192
|
+
throw err;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return currentPayload;
|
|
1197
|
+
}
|
|
1198
|
+
async function executeFieldBeforeChange(fields, data, originalDoc, user) {
|
|
1199
|
+
if (!data || typeof data !== "object") return data;
|
|
1200
|
+
const result = { ...data };
|
|
1201
|
+
for (const field of fields) {
|
|
1202
|
+
if (!field.name) continue;
|
|
1203
|
+
const value = result[field.name];
|
|
1204
|
+
const origValue = originalDoc?.[field.name];
|
|
1205
|
+
let updatedValue = value;
|
|
1206
|
+
if (field.hooks?.beforeChange) {
|
|
1207
|
+
for (const hook of field.hooks.beforeChange) {
|
|
1208
|
+
updatedValue = await hook({
|
|
1209
|
+
value: updatedValue,
|
|
1210
|
+
originalDoc: originalDoc ?? void 0,
|
|
1211
|
+
data: result,
|
|
1212
|
+
user
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
result[field.name] = updatedValue;
|
|
1216
|
+
}
|
|
1217
|
+
if (updatedValue !== void 0 && updatedValue !== null) {
|
|
1218
|
+
if (field.type === "object" && field.fields) {
|
|
1219
|
+
result[field.name] = await executeFieldBeforeChange(
|
|
1220
|
+
field.fields,
|
|
1221
|
+
updatedValue,
|
|
1222
|
+
origValue,
|
|
1223
|
+
user
|
|
1224
|
+
);
|
|
1225
|
+
} else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
|
|
1226
|
+
const arrayResult = [];
|
|
1227
|
+
for (let i = 0; i < updatedValue.length; i++) {
|
|
1228
|
+
const item = updatedValue[i];
|
|
1229
|
+
const origItem = Array.isArray(origValue) ? origValue[i] : null;
|
|
1230
|
+
arrayResult.push(
|
|
1231
|
+
await executeFieldBeforeChange(field.fields, item, origItem, user)
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
result[field.name] = arrayResult;
|
|
1235
|
+
} else if (field.type === "blocks" && field.blocks && Array.isArray(updatedValue)) {
|
|
1236
|
+
const blocksResult = [];
|
|
1237
|
+
for (let i = 0; i < updatedValue.length; i++) {
|
|
1238
|
+
const blockData = updatedValue[i];
|
|
1239
|
+
const origBlock = Array.isArray(origValue) ? origValue[i] : null;
|
|
1240
|
+
const blockConfig = field.blocks.find(
|
|
1241
|
+
(b) => b.slug === blockData.blockType
|
|
1242
|
+
);
|
|
1243
|
+
if (blockConfig) {
|
|
1244
|
+
blocksResult.push(
|
|
1245
|
+
await executeFieldBeforeChange(
|
|
1246
|
+
blockConfig.fields,
|
|
1247
|
+
blockData,
|
|
1248
|
+
origBlock,
|
|
1249
|
+
user
|
|
1250
|
+
)
|
|
1251
|
+
);
|
|
1252
|
+
} else {
|
|
1253
|
+
blocksResult.push(blockData);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
result[field.name] = blocksResult;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return result;
|
|
1261
|
+
}
|
|
1262
|
+
async function executeFieldAfterRead(fields, doc, user) {
|
|
1263
|
+
if (!doc || typeof doc !== "object") return doc;
|
|
1264
|
+
const result = { ...doc };
|
|
1265
|
+
for (const field of fields) {
|
|
1266
|
+
if (!field.name) continue;
|
|
1267
|
+
const value = result[field.name];
|
|
1268
|
+
let updatedValue = value;
|
|
1269
|
+
if (field.hooks?.afterRead) {
|
|
1270
|
+
for (const hook of field.hooks.afterRead) {
|
|
1271
|
+
updatedValue = await hook({
|
|
1272
|
+
value: updatedValue,
|
|
1273
|
+
doc: result,
|
|
1274
|
+
user
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
result[field.name] = updatedValue;
|
|
1278
|
+
}
|
|
1279
|
+
if (updatedValue !== void 0 && updatedValue !== null) {
|
|
1280
|
+
if (field.type === "object" && field.fields) {
|
|
1281
|
+
result[field.name] = await executeFieldAfterRead(
|
|
1282
|
+
field.fields,
|
|
1283
|
+
updatedValue,
|
|
1284
|
+
user
|
|
1285
|
+
);
|
|
1286
|
+
} else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
|
|
1287
|
+
const arrayResult = [];
|
|
1288
|
+
for (const item of updatedValue) {
|
|
1289
|
+
arrayResult.push(
|
|
1290
|
+
await executeFieldAfterRead(
|
|
1291
|
+
field.fields,
|
|
1292
|
+
item,
|
|
1293
|
+
user
|
|
1294
|
+
)
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
result[field.name] = arrayResult;
|
|
1298
|
+
} else if (field.type === "blocks" && field.blocks && Array.isArray(updatedValue)) {
|
|
1299
|
+
const blocksResult = [];
|
|
1300
|
+
for (const blockData of updatedValue) {
|
|
1301
|
+
const typedBlock = blockData;
|
|
1302
|
+
const blockConfig = field.blocks.find(
|
|
1303
|
+
(b) => b.slug === typedBlock.blockType
|
|
1304
|
+
);
|
|
1305
|
+
if (blockConfig) {
|
|
1306
|
+
blocksResult.push(
|
|
1307
|
+
await executeFieldAfterRead(
|
|
1308
|
+
blockConfig.fields,
|
|
1309
|
+
typedBlock,
|
|
1310
|
+
user
|
|
1311
|
+
)
|
|
1312
|
+
);
|
|
1313
|
+
} else {
|
|
1314
|
+
blocksResult.push(blockData);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
result[field.name] = blocksResult;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return result;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1169
1324
|
// src/app.ts
|
|
1170
1325
|
var import_hono = require("hono");
|
|
1171
1326
|
var import_cors = require("hono/cors");
|
|
@@ -1408,6 +1563,7 @@ var CollectionController = class {
|
|
|
1408
1563
|
const page = Number(c.req.query("page")) || 1;
|
|
1409
1564
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 2;
|
|
1410
1565
|
const sort = c.req.query("sort") || void 0;
|
|
1566
|
+
const user = c.get("user");
|
|
1411
1567
|
let where = void 0;
|
|
1412
1568
|
const whereRaw = c.req.query("where");
|
|
1413
1569
|
if (whereRaw) {
|
|
@@ -1416,6 +1572,14 @@ var CollectionController = class {
|
|
|
1416
1572
|
} catch {
|
|
1417
1573
|
}
|
|
1418
1574
|
}
|
|
1575
|
+
const beforeReadResult = await runCollectionHooks(this.collection.hooks?.beforeRead, {
|
|
1576
|
+
req: c.req,
|
|
1577
|
+
query: where,
|
|
1578
|
+
user
|
|
1579
|
+
});
|
|
1580
|
+
if (beforeReadResult !== void 0) {
|
|
1581
|
+
where = beforeReadResult;
|
|
1582
|
+
}
|
|
1419
1583
|
let result = await db.find({
|
|
1420
1584
|
collection: this.collection.slug,
|
|
1421
1585
|
limit,
|
|
@@ -1437,6 +1601,17 @@ var CollectionController = class {
|
|
|
1437
1601
|
});
|
|
1438
1602
|
}
|
|
1439
1603
|
result.docs = result.docs.map((doc) => DefaultsService.apply(this.collection.fields, doc));
|
|
1604
|
+
const processedDocs = [];
|
|
1605
|
+
for (const doc of result.docs) {
|
|
1606
|
+
const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
1607
|
+
doc,
|
|
1608
|
+
req: c.req,
|
|
1609
|
+
user
|
|
1610
|
+
});
|
|
1611
|
+
const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user);
|
|
1612
|
+
processedDocs.push(docWithFieldHooks);
|
|
1613
|
+
}
|
|
1614
|
+
result.docs = processedDocs;
|
|
1440
1615
|
if (depth > 0) {
|
|
1441
1616
|
const populationService = new PopulationService(db, config.collections);
|
|
1442
1617
|
result = await populationService.populateResult(result, this.collection.fields, depth);
|
|
@@ -1449,21 +1624,28 @@ var CollectionController = class {
|
|
|
1449
1624
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
1450
1625
|
const id = c.req.param("id");
|
|
1451
1626
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
|
|
1627
|
+
const user = c.get("user");
|
|
1452
1628
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
1453
1629
|
const doc = await db.findOne({ collection: this.collection.slug, id });
|
|
1454
1630
|
if (!doc) return c.json({ message: "Not Found" }, 404);
|
|
1455
1631
|
const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
|
|
1456
|
-
|
|
1632
|
+
const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
1633
|
+
doc: docWithDefaults,
|
|
1634
|
+
req: c.req,
|
|
1635
|
+
user
|
|
1636
|
+
});
|
|
1637
|
+
const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user);
|
|
1638
|
+
if (depth > 0 && docWithFieldHooks) {
|
|
1457
1639
|
const populationService = new PopulationService(db, config.collections);
|
|
1458
1640
|
const populatedDoc = await populationService.populate({
|
|
1459
|
-
data:
|
|
1641
|
+
data: docWithFieldHooks,
|
|
1460
1642
|
fields: this.collection.fields,
|
|
1461
1643
|
currentDepth: 0,
|
|
1462
1644
|
maxDepth: depth
|
|
1463
1645
|
});
|
|
1464
1646
|
return c.json(populatedDoc);
|
|
1465
1647
|
}
|
|
1466
|
-
return c.json(
|
|
1648
|
+
return c.json(docWithFieldHooks);
|
|
1467
1649
|
}
|
|
1468
1650
|
async create(c) {
|
|
1469
1651
|
const config = c.get("config");
|
|
@@ -1476,7 +1658,7 @@ var CollectionController = class {
|
|
|
1476
1658
|
const body = await c.req.json();
|
|
1477
1659
|
const user = c.get("user");
|
|
1478
1660
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1479
|
-
|
|
1661
|
+
let data = {
|
|
1480
1662
|
...body,
|
|
1481
1663
|
createdAt: now,
|
|
1482
1664
|
updatedAt: now,
|
|
@@ -1486,6 +1668,13 @@ var CollectionController = class {
|
|
|
1486
1668
|
if (this.collection.auth && data.password) {
|
|
1487
1669
|
data.password = await hashPassword(data.password);
|
|
1488
1670
|
}
|
|
1671
|
+
data = await executeFieldBeforeChange(this.collection.fields, data, null, user);
|
|
1672
|
+
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
1673
|
+
data,
|
|
1674
|
+
req: c.req,
|
|
1675
|
+
user,
|
|
1676
|
+
operation: "create"
|
|
1677
|
+
});
|
|
1489
1678
|
const doc = await db.create({ collection: this.collection.slug, data });
|
|
1490
1679
|
if (this.collection.audit && db) {
|
|
1491
1680
|
AuditService.log(db, {
|
|
@@ -1497,7 +1686,19 @@ var CollectionController = class {
|
|
|
1497
1686
|
after: doc
|
|
1498
1687
|
});
|
|
1499
1688
|
}
|
|
1500
|
-
|
|
1689
|
+
await runCollectionHooks(this.collection.hooks?.afterChange, {
|
|
1690
|
+
doc,
|
|
1691
|
+
user,
|
|
1692
|
+
req: c.req,
|
|
1693
|
+
operation: "create"
|
|
1694
|
+
}, { isolated: true });
|
|
1695
|
+
const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
1696
|
+
doc,
|
|
1697
|
+
req: c.req,
|
|
1698
|
+
user
|
|
1699
|
+
});
|
|
1700
|
+
const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
|
|
1701
|
+
return c.json(finalDoc, 201);
|
|
1501
1702
|
}
|
|
1502
1703
|
async upload(c) {
|
|
1503
1704
|
const config = c.get("config");
|
|
@@ -1524,18 +1725,38 @@ var CollectionController = class {
|
|
|
1524
1725
|
});
|
|
1525
1726
|
const user = c.get("user");
|
|
1526
1727
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1728
|
+
let data = {
|
|
1729
|
+
...otherData,
|
|
1730
|
+
...fileData,
|
|
1731
|
+
createdAt: now,
|
|
1732
|
+
updatedAt: now,
|
|
1733
|
+
createdBy: user?.sub ?? null,
|
|
1734
|
+
updatedBy: user?.sub ?? null
|
|
1735
|
+
};
|
|
1736
|
+
data = await executeFieldBeforeChange(this.collection.fields, data, null, user);
|
|
1737
|
+
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
1738
|
+
data,
|
|
1739
|
+
req: c.req,
|
|
1740
|
+
user,
|
|
1741
|
+
operation: "create"
|
|
1742
|
+
});
|
|
1527
1743
|
const doc = await config.db.create({
|
|
1528
1744
|
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
|
-
}
|
|
1745
|
+
data
|
|
1537
1746
|
});
|
|
1538
|
-
|
|
1747
|
+
await runCollectionHooks(this.collection.hooks?.afterChange, {
|
|
1748
|
+
doc,
|
|
1749
|
+
user,
|
|
1750
|
+
req: c.req,
|
|
1751
|
+
operation: "create"
|
|
1752
|
+
}, { isolated: true });
|
|
1753
|
+
const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
1754
|
+
doc,
|
|
1755
|
+
req: c.req,
|
|
1756
|
+
user
|
|
1757
|
+
});
|
|
1758
|
+
const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
|
|
1759
|
+
return c.json(finalDoc, 201);
|
|
1539
1760
|
}
|
|
1540
1761
|
async update(c) {
|
|
1541
1762
|
const config = c.get("config");
|
|
@@ -1545,7 +1766,7 @@ var CollectionController = class {
|
|
|
1545
1766
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
1546
1767
|
const body = await c.req.json();
|
|
1547
1768
|
const user = c.get("user");
|
|
1548
|
-
|
|
1769
|
+
let data = { ...body };
|
|
1549
1770
|
if (this.collection.auth) {
|
|
1550
1771
|
delete data.password;
|
|
1551
1772
|
delete data.oldPassword;
|
|
@@ -1555,10 +1776,20 @@ var CollectionController = class {
|
|
|
1555
1776
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1556
1777
|
updatedBy: user?.sub ?? null
|
|
1557
1778
|
});
|
|
1779
|
+
const originalDoc = await db.findOne({ collection: this.collection.slug, id });
|
|
1780
|
+
if (!originalDoc) return c.json({ message: "Not Found" }, 404);
|
|
1558
1781
|
let before = null;
|
|
1559
1782
|
if (this.collection.audit) {
|
|
1560
|
-
before =
|
|
1561
|
-
}
|
|
1783
|
+
before = originalDoc;
|
|
1784
|
+
}
|
|
1785
|
+
data = await executeFieldBeforeChange(this.collection.fields, data, originalDoc, user);
|
|
1786
|
+
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
1787
|
+
data,
|
|
1788
|
+
doc: originalDoc,
|
|
1789
|
+
req: c.req,
|
|
1790
|
+
user,
|
|
1791
|
+
operation: "update"
|
|
1792
|
+
});
|
|
1562
1793
|
const doc = await db.update({ collection: this.collection.slug, id, data });
|
|
1563
1794
|
if (this.collection.audit && db) {
|
|
1564
1795
|
AuditService.log(db, {
|
|
@@ -1570,7 +1801,20 @@ var CollectionController = class {
|
|
|
1570
1801
|
after: doc
|
|
1571
1802
|
});
|
|
1572
1803
|
}
|
|
1573
|
-
|
|
1804
|
+
await runCollectionHooks(this.collection.hooks?.afterChange, {
|
|
1805
|
+
doc,
|
|
1806
|
+
previousDoc: originalDoc,
|
|
1807
|
+
user,
|
|
1808
|
+
req: c.req,
|
|
1809
|
+
operation: "update"
|
|
1810
|
+
}, { isolated: true });
|
|
1811
|
+
const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
1812
|
+
doc,
|
|
1813
|
+
req: c.req,
|
|
1814
|
+
user
|
|
1815
|
+
});
|
|
1816
|
+
const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
|
|
1817
|
+
return c.json(finalDoc);
|
|
1574
1818
|
}
|
|
1575
1819
|
/**
|
|
1576
1820
|
* POST /api/collections/:slug/:id/change-password
|
|
@@ -1650,10 +1894,18 @@ var CollectionController = class {
|
|
|
1650
1894
|
const id = c.req.param("id");
|
|
1651
1895
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
1652
1896
|
const user = c.get("user");
|
|
1897
|
+
const doc = await db.findOne({ collection: this.collection.slug, id });
|
|
1898
|
+
if (!doc) return c.json({ message: "Not Found" }, 404);
|
|
1653
1899
|
let before = null;
|
|
1654
1900
|
if (this.collection.audit) {
|
|
1655
|
-
before =
|
|
1901
|
+
before = doc;
|
|
1656
1902
|
}
|
|
1903
|
+
await runCollectionHooks(this.collection.hooks?.beforeDelete, {
|
|
1904
|
+
id,
|
|
1905
|
+
doc,
|
|
1906
|
+
user,
|
|
1907
|
+
req: c.req
|
|
1908
|
+
});
|
|
1657
1909
|
await db.delete({ collection: this.collection.slug, id });
|
|
1658
1910
|
if (this.collection.audit && db) {
|
|
1659
1911
|
AuditService.log(db, {
|
|
@@ -1665,6 +1917,12 @@ var CollectionController = class {
|
|
|
1665
1917
|
after: null
|
|
1666
1918
|
});
|
|
1667
1919
|
}
|
|
1920
|
+
await runCollectionHooks(this.collection.hooks?.afterDelete, {
|
|
1921
|
+
id,
|
|
1922
|
+
doc,
|
|
1923
|
+
user,
|
|
1924
|
+
req: c.req
|
|
1925
|
+
}, { isolated: true });
|
|
1668
1926
|
return c.json({ message: "Deleted" });
|
|
1669
1927
|
}
|
|
1670
1928
|
async deleteMany(c) {
|
|
@@ -1689,10 +1947,21 @@ var CollectionController = class {
|
|
|
1689
1947
|
const failed = [];
|
|
1690
1948
|
for (const id of ids) {
|
|
1691
1949
|
try {
|
|
1950
|
+
const doc = await db.findOne({ collection: this.collection.slug, id });
|
|
1951
|
+
if (!doc) {
|
|
1952
|
+
failed.push({ id, error: "Not Found" });
|
|
1953
|
+
continue;
|
|
1954
|
+
}
|
|
1692
1955
|
let before = null;
|
|
1693
1956
|
if (this.collection.audit) {
|
|
1694
|
-
before =
|
|
1957
|
+
before = doc;
|
|
1695
1958
|
}
|
|
1959
|
+
await runCollectionHooks(this.collection.hooks?.beforeDelete, {
|
|
1960
|
+
id,
|
|
1961
|
+
doc,
|
|
1962
|
+
user,
|
|
1963
|
+
req: c.req
|
|
1964
|
+
});
|
|
1696
1965
|
await db.delete({ collection: this.collection.slug, id });
|
|
1697
1966
|
deleted.push(id);
|
|
1698
1967
|
if (this.collection.audit) {
|
|
@@ -1705,6 +1974,12 @@ var CollectionController = class {
|
|
|
1705
1974
|
after: null
|
|
1706
1975
|
});
|
|
1707
1976
|
}
|
|
1977
|
+
await runCollectionHooks(this.collection.hooks?.afterDelete, {
|
|
1978
|
+
id,
|
|
1979
|
+
doc,
|
|
1980
|
+
user,
|
|
1981
|
+
req: c.req
|
|
1982
|
+
}, { isolated: true });
|
|
1708
1983
|
} catch (err) {
|
|
1709
1984
|
failed.push({ id, error: err?.message ?? "Unknown error" });
|
|
1710
1985
|
}
|
|
@@ -1749,6 +2024,16 @@ var GlobalController = class {
|
|
|
1749
2024
|
const db = config.db;
|
|
1750
2025
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
1751
2026
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
|
|
2027
|
+
const user = c.get("user");
|
|
2028
|
+
let query = void 0;
|
|
2029
|
+
const beforeReadResult = await runCollectionHooks(this.global.hooks?.beforeRead, {
|
|
2030
|
+
req: c.req,
|
|
2031
|
+
query,
|
|
2032
|
+
user
|
|
2033
|
+
});
|
|
2034
|
+
if (beforeReadResult !== void 0) {
|
|
2035
|
+
query = beforeReadResult;
|
|
2036
|
+
}
|
|
1752
2037
|
let data = await db.getGlobal({ slug: this.global.slug });
|
|
1753
2038
|
const isEmpty = !data || Object.keys(data).length === 0;
|
|
1754
2039
|
if (isEmpty && this.global.initialData) {
|
|
@@ -1757,24 +2042,53 @@ var GlobalController = class {
|
|
|
1757
2042
|
data = this.global.initialData;
|
|
1758
2043
|
}
|
|
1759
2044
|
const dataWithDefaults = DefaultsService.apply(this.global.fields, data);
|
|
1760
|
-
|
|
2045
|
+
const docWithCollectionHooks = await runCollectionHooks(this.global.hooks?.afterRead, {
|
|
2046
|
+
doc: dataWithDefaults,
|
|
2047
|
+
req: c.req,
|
|
2048
|
+
user
|
|
2049
|
+
});
|
|
2050
|
+
const docWithFieldHooks = await executeFieldAfterRead(this.global.fields, docWithCollectionHooks, user);
|
|
2051
|
+
if (depth > 0 && docWithFieldHooks) {
|
|
1761
2052
|
const populationService = new PopulationService(db, config.collections);
|
|
1762
2053
|
const populatedData = await populationService.populate({
|
|
1763
|
-
data:
|
|
2054
|
+
data: docWithFieldHooks,
|
|
1764
2055
|
fields: this.global.fields,
|
|
1765
2056
|
currentDepth: 0,
|
|
1766
2057
|
maxDepth: depth
|
|
1767
2058
|
});
|
|
1768
2059
|
return c.json(populatedData);
|
|
1769
2060
|
}
|
|
1770
|
-
return c.json(
|
|
2061
|
+
return c.json(docWithFieldHooks);
|
|
1771
2062
|
}
|
|
1772
2063
|
async update(c) {
|
|
1773
2064
|
const db = c.get("config").db;
|
|
1774
2065
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
1775
2066
|
const body = await c.req.json();
|
|
1776
|
-
const
|
|
1777
|
-
|
|
2067
|
+
const user = c.get("user");
|
|
2068
|
+
const originalDoc = await db.getGlobal({ slug: this.global.slug }) || {};
|
|
2069
|
+
let data = await executeFieldBeforeChange(this.global.fields, body, originalDoc, user);
|
|
2070
|
+
data = await runCollectionHooks(this.global.hooks?.beforeChange, {
|
|
2071
|
+
data,
|
|
2072
|
+
doc: originalDoc,
|
|
2073
|
+
req: c.req,
|
|
2074
|
+
user,
|
|
2075
|
+
operation: "update"
|
|
2076
|
+
});
|
|
2077
|
+
const updated = await db.updateGlobal({ slug: this.global.slug, data });
|
|
2078
|
+
await runCollectionHooks(this.global.hooks?.afterChange, {
|
|
2079
|
+
doc: updated,
|
|
2080
|
+
previousDoc: originalDoc,
|
|
2081
|
+
user,
|
|
2082
|
+
req: c.req,
|
|
2083
|
+
operation: "update"
|
|
2084
|
+
}, { isolated: true });
|
|
2085
|
+
const readDoc = await runCollectionHooks(this.global.hooks?.afterRead, {
|
|
2086
|
+
doc: updated,
|
|
2087
|
+
req: c.req,
|
|
2088
|
+
user
|
|
2089
|
+
});
|
|
2090
|
+
const finalDoc = await executeFieldAfterRead(this.global.fields, readDoc, user);
|
|
2091
|
+
return c.json(finalDoc);
|
|
1778
2092
|
}
|
|
1779
2093
|
async seed(c) {
|
|
1780
2094
|
const config = c.get("config");
|
|
@@ -2683,12 +2997,13 @@ function fieldToSchema(field) {
|
|
|
2683
2997
|
schema = { type: "string", format: "date-time" };
|
|
2684
2998
|
break;
|
|
2685
2999
|
case "select":
|
|
2686
|
-
|
|
3000
|
+
case "radio":
|
|
3001
|
+
schema = { type: "string", enum: Array.isArray(field.options) ? field.options.map((o) => typeof o === "string" ? o : o.value) : void 0 };
|
|
2687
3002
|
break;
|
|
2688
3003
|
case "multiSelect":
|
|
2689
3004
|
schema = {
|
|
2690
3005
|
type: "array",
|
|
2691
|
-
items: { type: "string", enum: field.options
|
|
3006
|
+
items: { type: "string", enum: Array.isArray(field.options) ? field.options.map((o) => typeof o === "string" ? o : o.value) : void 0 }
|
|
2692
3007
|
};
|
|
2693
3008
|
break;
|
|
2694
3009
|
case "relationship":
|
|
@@ -2812,6 +3127,49 @@ function accessGate(target, action) {
|
|
|
2812
3127
|
await next();
|
|
2813
3128
|
};
|
|
2814
3129
|
}
|
|
3130
|
+
async function checkAccess(access, accessArgs) {
|
|
3131
|
+
if (access === void 0 || access === null) return true;
|
|
3132
|
+
if (typeof access === "function") {
|
|
3133
|
+
try {
|
|
3134
|
+
const result = await access(accessArgs);
|
|
3135
|
+
return typeof result === "boolean" ? result : !!result;
|
|
3136
|
+
} catch (err) {
|
|
3137
|
+
console.error("[dyrected/core] Functional access check failed:", err);
|
|
3138
|
+
return false;
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
if (typeof access === "string" || typeof access === "boolean") {
|
|
3142
|
+
return evaluateAccess(access, accessArgs);
|
|
3143
|
+
}
|
|
3144
|
+
return true;
|
|
3145
|
+
}
|
|
3146
|
+
function serializeFieldForApi(f) {
|
|
3147
|
+
if (!f) return f;
|
|
3148
|
+
const serialized = { ...f };
|
|
3149
|
+
if (serialized.admin?.hooks) {
|
|
3150
|
+
const hooks = { ...serialized.admin.hooks };
|
|
3151
|
+
if (typeof hooks.onChange === "function") {
|
|
3152
|
+
hooks.onChange = hooks.onChange.toString();
|
|
3153
|
+
}
|
|
3154
|
+
if (typeof hooks.options === "function") {
|
|
3155
|
+
hooks.options = hooks.options.toString();
|
|
3156
|
+
}
|
|
3157
|
+
serialized.admin = { ...serialized.admin, hooks };
|
|
3158
|
+
}
|
|
3159
|
+
if (typeof serialized.options === "function" || serialized.options && typeof serialized.options === "object" && "resolve" in serialized.options) {
|
|
3160
|
+
serialized.options = { _dynamic: true };
|
|
3161
|
+
}
|
|
3162
|
+
if (serialized.fields) {
|
|
3163
|
+
serialized.fields = serialized.fields.map(serializeFieldForApi);
|
|
3164
|
+
}
|
|
3165
|
+
if (serialized.blocks) {
|
|
3166
|
+
serialized.blocks = serialized.blocks.map((b) => ({
|
|
3167
|
+
...b,
|
|
3168
|
+
fields: b.fields?.map(serializeFieldForApi)
|
|
3169
|
+
}));
|
|
3170
|
+
}
|
|
3171
|
+
return serialized;
|
|
3172
|
+
}
|
|
2815
3173
|
function registerRoutes(app, config) {
|
|
2816
3174
|
app.get("/api/schemas", optionalAuth(), async (c) => {
|
|
2817
3175
|
const siteId = c.req.header("X-Site-Id");
|
|
@@ -2827,26 +3185,10 @@ function registerRoutes(app, config) {
|
|
|
2827
3185
|
}
|
|
2828
3186
|
const user = c.get("user");
|
|
2829
3187
|
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
3188
|
const serializeAccess = async (access) => {
|
|
2847
3189
|
if (typeof access === "string") return access;
|
|
2848
3190
|
if (typeof access === "boolean") return access;
|
|
2849
|
-
return
|
|
3191
|
+
return checkAccess(access, accessArgs);
|
|
2850
3192
|
};
|
|
2851
3193
|
const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
2852
3194
|
slug: col.slug,
|
|
@@ -2857,7 +3199,7 @@ function registerRoutes(app, config) {
|
|
|
2857
3199
|
update: await serializeAccess(col.access?.update),
|
|
2858
3200
|
delete: await serializeAccess(col.access?.delete)
|
|
2859
3201
|
},
|
|
2860
|
-
fields: await Promise.all(col.fields.map(async (f) => ({
|
|
3202
|
+
fields: await Promise.all(col.fields.map(serializeFieldForApi).map(async (f) => ({
|
|
2861
3203
|
name: f.name,
|
|
2862
3204
|
type: f.type,
|
|
2863
3205
|
label: f.label,
|
|
@@ -2885,7 +3227,7 @@ function registerRoutes(app, config) {
|
|
|
2885
3227
|
read: await serializeAccess(glb.access?.read),
|
|
2886
3228
|
update: await serializeAccess(glb.access?.update)
|
|
2887
3229
|
},
|
|
2888
|
-
fields: await Promise.all(glb.fields.map(async (f) => ({
|
|
3230
|
+
fields: await Promise.all(glb.fields.map(serializeFieldForApi).map(async (f) => ({
|
|
2889
3231
|
name: f.name,
|
|
2890
3232
|
type: f.type,
|
|
2891
3233
|
label: f.label,
|
|
@@ -2910,6 +3252,78 @@ function registerRoutes(app, config) {
|
|
|
2910
3252
|
admin: config.admin || {}
|
|
2911
3253
|
});
|
|
2912
3254
|
});
|
|
3255
|
+
app.get("/api/dyrected/options/:collection/:field", optionalAuth(), async (c) => {
|
|
3256
|
+
const { collection: colSlug, field: fieldName } = c.req.param();
|
|
3257
|
+
const siteId = c.req.header("X-Site-Id");
|
|
3258
|
+
let collections = [...config.collections];
|
|
3259
|
+
if (siteId && config.onSchemaFetch) {
|
|
3260
|
+
const dynamic = await config.onSchemaFetch(siteId);
|
|
3261
|
+
if (dynamic.collections) collections = [...collections, ...dynamic.collections];
|
|
3262
|
+
}
|
|
3263
|
+
const user = c.get("user");
|
|
3264
|
+
let collection = collections.find((col) => col.slug === colSlug);
|
|
3265
|
+
let field;
|
|
3266
|
+
if (collection) {
|
|
3267
|
+
const accessExpr = collection.access?.read;
|
|
3268
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
3269
|
+
const accessArgs = { user, req: c.req, doc: null };
|
|
3270
|
+
const allowed = await checkAccess(accessExpr, accessArgs);
|
|
3271
|
+
if (!allowed) {
|
|
3272
|
+
return c.json({ error: true, message: `Access denied: read on ${colSlug}` }, 403);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
field = collection.fields.find((f) => f.name === fieldName);
|
|
3276
|
+
} else {
|
|
3277
|
+
let globals = [...config.globals];
|
|
3278
|
+
if (siteId && config.onSchemaFetch) {
|
|
3279
|
+
const dynamic = await config.onSchemaFetch(siteId);
|
|
3280
|
+
if (dynamic.globals) globals = [...globals, ...dynamic.globals];
|
|
3281
|
+
}
|
|
3282
|
+
const glb = globals.find((g) => g.slug === colSlug);
|
|
3283
|
+
if (!glb) {
|
|
3284
|
+
return c.json({ error: true, message: `${colSlug} not found as collection or global` }, 404);
|
|
3285
|
+
}
|
|
3286
|
+
const accessExpr = glb.access?.read;
|
|
3287
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
3288
|
+
const accessArgs = { user, req: c.req, doc: null };
|
|
3289
|
+
const allowed = await checkAccess(accessExpr, accessArgs);
|
|
3290
|
+
if (!allowed) {
|
|
3291
|
+
return c.json({ error: true, message: `Access denied: read on global ${colSlug}` }, 403);
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
field = glb.fields.find((f) => f.name === fieldName);
|
|
3295
|
+
}
|
|
3296
|
+
if (!field) {
|
|
3297
|
+
return c.json({ error: true, message: `Field ${fieldName} not found in ${colSlug}` }, 404);
|
|
3298
|
+
}
|
|
3299
|
+
let resolver;
|
|
3300
|
+
if (typeof field.options === "function") {
|
|
3301
|
+
resolver = field.options;
|
|
3302
|
+
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
3303
|
+
resolver = field.options.resolve;
|
|
3304
|
+
}
|
|
3305
|
+
if (!resolver) {
|
|
3306
|
+
return c.json({ error: true, message: `Field ${fieldName} in ${colSlug} is not dynamic` }, 400);
|
|
3307
|
+
}
|
|
3308
|
+
try {
|
|
3309
|
+
const db = c.get("db") || config.db;
|
|
3310
|
+
const queryParams = c.req.query();
|
|
3311
|
+
const reqContext = {
|
|
3312
|
+
query: queryParams,
|
|
3313
|
+
headers: c.req.header(),
|
|
3314
|
+
raw: c.req.raw
|
|
3315
|
+
};
|
|
3316
|
+
const result = await resolver({
|
|
3317
|
+
db,
|
|
3318
|
+
user,
|
|
3319
|
+
req: reqContext
|
|
3320
|
+
});
|
|
3321
|
+
return c.json(result);
|
|
3322
|
+
} catch (err) {
|
|
3323
|
+
console.error(`[dyrected/core] Failed to resolve dynamic options for field ${fieldName}:`, err);
|
|
3324
|
+
return c.json({ error: true, message: err.message || "Failed to resolve dynamic options" }, 500);
|
|
3325
|
+
}
|
|
3326
|
+
});
|
|
2913
3327
|
app.get("/api/openapi.json", (c) => {
|
|
2914
3328
|
return c.json(generateOpenApi(config));
|
|
2915
3329
|
});
|
|
@@ -3097,9 +3511,12 @@ function defineConfig(config) {
|
|
|
3097
3511
|
defineCollection,
|
|
3098
3512
|
defineConfig,
|
|
3099
3513
|
defineGlobal,
|
|
3514
|
+
executeFieldAfterRead,
|
|
3515
|
+
executeFieldBeforeChange,
|
|
3100
3516
|
generateAIPrompt,
|
|
3101
3517
|
generateFreshSetupPrompt,
|
|
3102
3518
|
normalizeConfig,
|
|
3103
3519
|
parseMongoWhere,
|
|
3104
|
-
parseSqlWhere
|
|
3520
|
+
parseSqlWhere,
|
|
3521
|
+
runCollectionHooks
|
|
3105
3522
|
});
|