@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/server.cjs
CHANGED
|
@@ -268,6 +268,158 @@ async function verifyPassword(plain, stored) {
|
|
|
268
268
|
return (0, import_node_crypto.timingSafeEqual)(derivedKey, storedBuffer);
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
// src/utils/hooks.ts
|
|
272
|
+
async function runCollectionHooks(hooks, args, options = {}) {
|
|
273
|
+
if (!hooks || hooks.length === 0) {
|
|
274
|
+
return args.data ?? args.doc ?? void 0;
|
|
275
|
+
}
|
|
276
|
+
let currentPayload = args.data ?? args.doc ?? void 0;
|
|
277
|
+
for (const hook of hooks) {
|
|
278
|
+
try {
|
|
279
|
+
const result = await hook({
|
|
280
|
+
...args,
|
|
281
|
+
data: args.data !== void 0 ? currentPayload : void 0,
|
|
282
|
+
doc: args.doc !== void 0 ? currentPayload : void 0
|
|
283
|
+
});
|
|
284
|
+
if (result !== void 0) {
|
|
285
|
+
currentPayload = result;
|
|
286
|
+
}
|
|
287
|
+
} catch (err) {
|
|
288
|
+
if (options.isolated) {
|
|
289
|
+
console.error("[dyrected/core] Side-effect hook failed (error isolated \u2014 DB write was successful):", err);
|
|
290
|
+
} else {
|
|
291
|
+
throw err;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return currentPayload;
|
|
296
|
+
}
|
|
297
|
+
async function executeFieldBeforeChange(fields, data, originalDoc, user) {
|
|
298
|
+
if (!data || typeof data !== "object") return data;
|
|
299
|
+
const result = { ...data };
|
|
300
|
+
for (const field of fields) {
|
|
301
|
+
if (!field.name) continue;
|
|
302
|
+
const value = result[field.name];
|
|
303
|
+
const origValue = originalDoc?.[field.name];
|
|
304
|
+
let updatedValue = value;
|
|
305
|
+
if (field.hooks?.beforeChange) {
|
|
306
|
+
for (const hook of field.hooks.beforeChange) {
|
|
307
|
+
updatedValue = await hook({
|
|
308
|
+
value: updatedValue,
|
|
309
|
+
originalDoc: originalDoc ?? void 0,
|
|
310
|
+
data: result,
|
|
311
|
+
user
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
result[field.name] = updatedValue;
|
|
315
|
+
}
|
|
316
|
+
if (updatedValue !== void 0 && updatedValue !== null) {
|
|
317
|
+
if (field.type === "object" && field.fields) {
|
|
318
|
+
result[field.name] = await executeFieldBeforeChange(
|
|
319
|
+
field.fields,
|
|
320
|
+
updatedValue,
|
|
321
|
+
origValue,
|
|
322
|
+
user
|
|
323
|
+
);
|
|
324
|
+
} else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
|
|
325
|
+
const arrayResult = [];
|
|
326
|
+
for (let i = 0; i < updatedValue.length; i++) {
|
|
327
|
+
const item = updatedValue[i];
|
|
328
|
+
const origItem = Array.isArray(origValue) ? origValue[i] : null;
|
|
329
|
+
arrayResult.push(
|
|
330
|
+
await executeFieldBeforeChange(field.fields, item, origItem, user)
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
result[field.name] = arrayResult;
|
|
334
|
+
} else if (field.type === "blocks" && field.blocks && Array.isArray(updatedValue)) {
|
|
335
|
+
const blocksResult = [];
|
|
336
|
+
for (let i = 0; i < updatedValue.length; i++) {
|
|
337
|
+
const blockData = updatedValue[i];
|
|
338
|
+
const origBlock = Array.isArray(origValue) ? origValue[i] : null;
|
|
339
|
+
const blockConfig = field.blocks.find(
|
|
340
|
+
(b) => b.slug === blockData.blockType
|
|
341
|
+
);
|
|
342
|
+
if (blockConfig) {
|
|
343
|
+
blocksResult.push(
|
|
344
|
+
await executeFieldBeforeChange(
|
|
345
|
+
blockConfig.fields,
|
|
346
|
+
blockData,
|
|
347
|
+
origBlock,
|
|
348
|
+
user
|
|
349
|
+
)
|
|
350
|
+
);
|
|
351
|
+
} else {
|
|
352
|
+
blocksResult.push(blockData);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
result[field.name] = blocksResult;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
async function executeFieldAfterRead(fields, doc, user) {
|
|
362
|
+
if (!doc || typeof doc !== "object") return doc;
|
|
363
|
+
const result = { ...doc };
|
|
364
|
+
for (const field of fields) {
|
|
365
|
+
if (!field.name) continue;
|
|
366
|
+
const value = result[field.name];
|
|
367
|
+
let updatedValue = value;
|
|
368
|
+
if (field.hooks?.afterRead) {
|
|
369
|
+
for (const hook of field.hooks.afterRead) {
|
|
370
|
+
updatedValue = await hook({
|
|
371
|
+
value: updatedValue,
|
|
372
|
+
doc: result,
|
|
373
|
+
user
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
result[field.name] = updatedValue;
|
|
377
|
+
}
|
|
378
|
+
if (updatedValue !== void 0 && updatedValue !== null) {
|
|
379
|
+
if (field.type === "object" && field.fields) {
|
|
380
|
+
result[field.name] = await executeFieldAfterRead(
|
|
381
|
+
field.fields,
|
|
382
|
+
updatedValue,
|
|
383
|
+
user
|
|
384
|
+
);
|
|
385
|
+
} else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
|
|
386
|
+
const arrayResult = [];
|
|
387
|
+
for (const item of updatedValue) {
|
|
388
|
+
arrayResult.push(
|
|
389
|
+
await executeFieldAfterRead(
|
|
390
|
+
field.fields,
|
|
391
|
+
item,
|
|
392
|
+
user
|
|
393
|
+
)
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
result[field.name] = arrayResult;
|
|
397
|
+
} else if (field.type === "blocks" && field.blocks && Array.isArray(updatedValue)) {
|
|
398
|
+
const blocksResult = [];
|
|
399
|
+
for (const blockData of updatedValue) {
|
|
400
|
+
const typedBlock = blockData;
|
|
401
|
+
const blockConfig = field.blocks.find(
|
|
402
|
+
(b) => b.slug === typedBlock.blockType
|
|
403
|
+
);
|
|
404
|
+
if (blockConfig) {
|
|
405
|
+
blocksResult.push(
|
|
406
|
+
await executeFieldAfterRead(
|
|
407
|
+
blockConfig.fields,
|
|
408
|
+
typedBlock,
|
|
409
|
+
user
|
|
410
|
+
)
|
|
411
|
+
);
|
|
412
|
+
} else {
|
|
413
|
+
blocksResult.push(blockData);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
result[field.name] = blocksResult;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
|
|
271
423
|
// src/controllers/collection.controller.ts
|
|
272
424
|
var CollectionController = class {
|
|
273
425
|
collection;
|
|
@@ -282,6 +434,7 @@ var CollectionController = class {
|
|
|
282
434
|
const page = Number(c.req.query("page")) || 1;
|
|
283
435
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 2;
|
|
284
436
|
const sort = c.req.query("sort") || void 0;
|
|
437
|
+
const user = c.get("user");
|
|
285
438
|
let where = void 0;
|
|
286
439
|
const whereRaw = c.req.query("where");
|
|
287
440
|
if (whereRaw) {
|
|
@@ -290,6 +443,14 @@ var CollectionController = class {
|
|
|
290
443
|
} catch {
|
|
291
444
|
}
|
|
292
445
|
}
|
|
446
|
+
const beforeReadResult = await runCollectionHooks(this.collection.hooks?.beforeRead, {
|
|
447
|
+
req: c.req,
|
|
448
|
+
query: where,
|
|
449
|
+
user
|
|
450
|
+
});
|
|
451
|
+
if (beforeReadResult !== void 0) {
|
|
452
|
+
where = beforeReadResult;
|
|
453
|
+
}
|
|
293
454
|
let result = await db.find({
|
|
294
455
|
collection: this.collection.slug,
|
|
295
456
|
limit,
|
|
@@ -311,6 +472,17 @@ var CollectionController = class {
|
|
|
311
472
|
});
|
|
312
473
|
}
|
|
313
474
|
result.docs = result.docs.map((doc) => DefaultsService.apply(this.collection.fields, doc));
|
|
475
|
+
const processedDocs = [];
|
|
476
|
+
for (const doc of result.docs) {
|
|
477
|
+
const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
478
|
+
doc,
|
|
479
|
+
req: c.req,
|
|
480
|
+
user
|
|
481
|
+
});
|
|
482
|
+
const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user);
|
|
483
|
+
processedDocs.push(docWithFieldHooks);
|
|
484
|
+
}
|
|
485
|
+
result.docs = processedDocs;
|
|
314
486
|
if (depth > 0) {
|
|
315
487
|
const populationService = new PopulationService(db, config.collections);
|
|
316
488
|
result = await populationService.populateResult(result, this.collection.fields, depth);
|
|
@@ -323,21 +495,28 @@ var CollectionController = class {
|
|
|
323
495
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
324
496
|
const id = c.req.param("id");
|
|
325
497
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
|
|
498
|
+
const user = c.get("user");
|
|
326
499
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
327
500
|
const doc = await db.findOne({ collection: this.collection.slug, id });
|
|
328
501
|
if (!doc) return c.json({ message: "Not Found" }, 404);
|
|
329
502
|
const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
|
|
330
|
-
|
|
503
|
+
const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
504
|
+
doc: docWithDefaults,
|
|
505
|
+
req: c.req,
|
|
506
|
+
user
|
|
507
|
+
});
|
|
508
|
+
const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user);
|
|
509
|
+
if (depth > 0 && docWithFieldHooks) {
|
|
331
510
|
const populationService = new PopulationService(db, config.collections);
|
|
332
511
|
const populatedDoc = await populationService.populate({
|
|
333
|
-
data:
|
|
512
|
+
data: docWithFieldHooks,
|
|
334
513
|
fields: this.collection.fields,
|
|
335
514
|
currentDepth: 0,
|
|
336
515
|
maxDepth: depth
|
|
337
516
|
});
|
|
338
517
|
return c.json(populatedDoc);
|
|
339
518
|
}
|
|
340
|
-
return c.json(
|
|
519
|
+
return c.json(docWithFieldHooks);
|
|
341
520
|
}
|
|
342
521
|
async create(c) {
|
|
343
522
|
const config = c.get("config");
|
|
@@ -350,7 +529,7 @@ var CollectionController = class {
|
|
|
350
529
|
const body = await c.req.json();
|
|
351
530
|
const user = c.get("user");
|
|
352
531
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
353
|
-
|
|
532
|
+
let data = {
|
|
354
533
|
...body,
|
|
355
534
|
createdAt: now,
|
|
356
535
|
updatedAt: now,
|
|
@@ -360,6 +539,13 @@ var CollectionController = class {
|
|
|
360
539
|
if (this.collection.auth && data.password) {
|
|
361
540
|
data.password = await hashPassword(data.password);
|
|
362
541
|
}
|
|
542
|
+
data = await executeFieldBeforeChange(this.collection.fields, data, null, user);
|
|
543
|
+
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
544
|
+
data,
|
|
545
|
+
req: c.req,
|
|
546
|
+
user,
|
|
547
|
+
operation: "create"
|
|
548
|
+
});
|
|
363
549
|
const doc = await db.create({ collection: this.collection.slug, data });
|
|
364
550
|
if (this.collection.audit && db) {
|
|
365
551
|
AuditService.log(db, {
|
|
@@ -371,7 +557,19 @@ var CollectionController = class {
|
|
|
371
557
|
after: doc
|
|
372
558
|
});
|
|
373
559
|
}
|
|
374
|
-
|
|
560
|
+
await runCollectionHooks(this.collection.hooks?.afterChange, {
|
|
561
|
+
doc,
|
|
562
|
+
user,
|
|
563
|
+
req: c.req,
|
|
564
|
+
operation: "create"
|
|
565
|
+
}, { isolated: true });
|
|
566
|
+
const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
567
|
+
doc,
|
|
568
|
+
req: c.req,
|
|
569
|
+
user
|
|
570
|
+
});
|
|
571
|
+
const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
|
|
572
|
+
return c.json(finalDoc, 201);
|
|
375
573
|
}
|
|
376
574
|
async upload(c) {
|
|
377
575
|
const config = c.get("config");
|
|
@@ -398,18 +596,38 @@ var CollectionController = class {
|
|
|
398
596
|
});
|
|
399
597
|
const user = c.get("user");
|
|
400
598
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
599
|
+
let data = {
|
|
600
|
+
...otherData,
|
|
601
|
+
...fileData,
|
|
602
|
+
createdAt: now,
|
|
603
|
+
updatedAt: now,
|
|
604
|
+
createdBy: user?.sub ?? null,
|
|
605
|
+
updatedBy: user?.sub ?? null
|
|
606
|
+
};
|
|
607
|
+
data = await executeFieldBeforeChange(this.collection.fields, data, null, user);
|
|
608
|
+
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
609
|
+
data,
|
|
610
|
+
req: c.req,
|
|
611
|
+
user,
|
|
612
|
+
operation: "create"
|
|
613
|
+
});
|
|
401
614
|
const doc = await config.db.create({
|
|
402
615
|
collection: this.collection.slug,
|
|
403
|
-
data
|
|
404
|
-
...otherData,
|
|
405
|
-
...fileData,
|
|
406
|
-
createdAt: now,
|
|
407
|
-
updatedAt: now,
|
|
408
|
-
createdBy: user?.sub ?? null,
|
|
409
|
-
updatedBy: user?.sub ?? null
|
|
410
|
-
}
|
|
616
|
+
data
|
|
411
617
|
});
|
|
412
|
-
|
|
618
|
+
await runCollectionHooks(this.collection.hooks?.afterChange, {
|
|
619
|
+
doc,
|
|
620
|
+
user,
|
|
621
|
+
req: c.req,
|
|
622
|
+
operation: "create"
|
|
623
|
+
}, { isolated: true });
|
|
624
|
+
const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
625
|
+
doc,
|
|
626
|
+
req: c.req,
|
|
627
|
+
user
|
|
628
|
+
});
|
|
629
|
+
const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
|
|
630
|
+
return c.json(finalDoc, 201);
|
|
413
631
|
}
|
|
414
632
|
async update(c) {
|
|
415
633
|
const config = c.get("config");
|
|
@@ -419,7 +637,7 @@ var CollectionController = class {
|
|
|
419
637
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
420
638
|
const body = await c.req.json();
|
|
421
639
|
const user = c.get("user");
|
|
422
|
-
|
|
640
|
+
let data = { ...body };
|
|
423
641
|
if (this.collection.auth) {
|
|
424
642
|
delete data.password;
|
|
425
643
|
delete data.oldPassword;
|
|
@@ -429,10 +647,20 @@ var CollectionController = class {
|
|
|
429
647
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
430
648
|
updatedBy: user?.sub ?? null
|
|
431
649
|
});
|
|
650
|
+
const originalDoc = await db.findOne({ collection: this.collection.slug, id });
|
|
651
|
+
if (!originalDoc) return c.json({ message: "Not Found" }, 404);
|
|
432
652
|
let before = null;
|
|
433
653
|
if (this.collection.audit) {
|
|
434
|
-
before =
|
|
435
|
-
}
|
|
654
|
+
before = originalDoc;
|
|
655
|
+
}
|
|
656
|
+
data = await executeFieldBeforeChange(this.collection.fields, data, originalDoc, user);
|
|
657
|
+
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
658
|
+
data,
|
|
659
|
+
doc: originalDoc,
|
|
660
|
+
req: c.req,
|
|
661
|
+
user,
|
|
662
|
+
operation: "update"
|
|
663
|
+
});
|
|
436
664
|
const doc = await db.update({ collection: this.collection.slug, id, data });
|
|
437
665
|
if (this.collection.audit && db) {
|
|
438
666
|
AuditService.log(db, {
|
|
@@ -444,7 +672,20 @@ var CollectionController = class {
|
|
|
444
672
|
after: doc
|
|
445
673
|
});
|
|
446
674
|
}
|
|
447
|
-
|
|
675
|
+
await runCollectionHooks(this.collection.hooks?.afterChange, {
|
|
676
|
+
doc,
|
|
677
|
+
previousDoc: originalDoc,
|
|
678
|
+
user,
|
|
679
|
+
req: c.req,
|
|
680
|
+
operation: "update"
|
|
681
|
+
}, { isolated: true });
|
|
682
|
+
const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
683
|
+
doc,
|
|
684
|
+
req: c.req,
|
|
685
|
+
user
|
|
686
|
+
});
|
|
687
|
+
const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
|
|
688
|
+
return c.json(finalDoc);
|
|
448
689
|
}
|
|
449
690
|
/**
|
|
450
691
|
* POST /api/collections/:slug/:id/change-password
|
|
@@ -524,10 +765,18 @@ var CollectionController = class {
|
|
|
524
765
|
const id = c.req.param("id");
|
|
525
766
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
526
767
|
const user = c.get("user");
|
|
768
|
+
const doc = await db.findOne({ collection: this.collection.slug, id });
|
|
769
|
+
if (!doc) return c.json({ message: "Not Found" }, 404);
|
|
527
770
|
let before = null;
|
|
528
771
|
if (this.collection.audit) {
|
|
529
|
-
before =
|
|
772
|
+
before = doc;
|
|
530
773
|
}
|
|
774
|
+
await runCollectionHooks(this.collection.hooks?.beforeDelete, {
|
|
775
|
+
id,
|
|
776
|
+
doc,
|
|
777
|
+
user,
|
|
778
|
+
req: c.req
|
|
779
|
+
});
|
|
531
780
|
await db.delete({ collection: this.collection.slug, id });
|
|
532
781
|
if (this.collection.audit && db) {
|
|
533
782
|
AuditService.log(db, {
|
|
@@ -539,6 +788,12 @@ var CollectionController = class {
|
|
|
539
788
|
after: null
|
|
540
789
|
});
|
|
541
790
|
}
|
|
791
|
+
await runCollectionHooks(this.collection.hooks?.afterDelete, {
|
|
792
|
+
id,
|
|
793
|
+
doc,
|
|
794
|
+
user,
|
|
795
|
+
req: c.req
|
|
796
|
+
}, { isolated: true });
|
|
542
797
|
return c.json({ message: "Deleted" });
|
|
543
798
|
}
|
|
544
799
|
async deleteMany(c) {
|
|
@@ -563,10 +818,21 @@ var CollectionController = class {
|
|
|
563
818
|
const failed = [];
|
|
564
819
|
for (const id of ids) {
|
|
565
820
|
try {
|
|
821
|
+
const doc = await db.findOne({ collection: this.collection.slug, id });
|
|
822
|
+
if (!doc) {
|
|
823
|
+
failed.push({ id, error: "Not Found" });
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
566
826
|
let before = null;
|
|
567
827
|
if (this.collection.audit) {
|
|
568
|
-
before =
|
|
828
|
+
before = doc;
|
|
569
829
|
}
|
|
830
|
+
await runCollectionHooks(this.collection.hooks?.beforeDelete, {
|
|
831
|
+
id,
|
|
832
|
+
doc,
|
|
833
|
+
user,
|
|
834
|
+
req: c.req
|
|
835
|
+
});
|
|
570
836
|
await db.delete({ collection: this.collection.slug, id });
|
|
571
837
|
deleted.push(id);
|
|
572
838
|
if (this.collection.audit) {
|
|
@@ -579,6 +845,12 @@ var CollectionController = class {
|
|
|
579
845
|
after: null
|
|
580
846
|
});
|
|
581
847
|
}
|
|
848
|
+
await runCollectionHooks(this.collection.hooks?.afterDelete, {
|
|
849
|
+
id,
|
|
850
|
+
doc,
|
|
851
|
+
user,
|
|
852
|
+
req: c.req
|
|
853
|
+
}, { isolated: true });
|
|
582
854
|
} catch (err) {
|
|
583
855
|
failed.push({ id, error: err?.message ?? "Unknown error" });
|
|
584
856
|
}
|
|
@@ -623,6 +895,16 @@ var GlobalController = class {
|
|
|
623
895
|
const db = config.db;
|
|
624
896
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
625
897
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
|
|
898
|
+
const user = c.get("user");
|
|
899
|
+
let query = void 0;
|
|
900
|
+
const beforeReadResult = await runCollectionHooks(this.global.hooks?.beforeRead, {
|
|
901
|
+
req: c.req,
|
|
902
|
+
query,
|
|
903
|
+
user
|
|
904
|
+
});
|
|
905
|
+
if (beforeReadResult !== void 0) {
|
|
906
|
+
query = beforeReadResult;
|
|
907
|
+
}
|
|
626
908
|
let data = await db.getGlobal({ slug: this.global.slug });
|
|
627
909
|
const isEmpty = !data || Object.keys(data).length === 0;
|
|
628
910
|
if (isEmpty && this.global.initialData) {
|
|
@@ -631,24 +913,53 @@ var GlobalController = class {
|
|
|
631
913
|
data = this.global.initialData;
|
|
632
914
|
}
|
|
633
915
|
const dataWithDefaults = DefaultsService.apply(this.global.fields, data);
|
|
634
|
-
|
|
916
|
+
const docWithCollectionHooks = await runCollectionHooks(this.global.hooks?.afterRead, {
|
|
917
|
+
doc: dataWithDefaults,
|
|
918
|
+
req: c.req,
|
|
919
|
+
user
|
|
920
|
+
});
|
|
921
|
+
const docWithFieldHooks = await executeFieldAfterRead(this.global.fields, docWithCollectionHooks, user);
|
|
922
|
+
if (depth > 0 && docWithFieldHooks) {
|
|
635
923
|
const populationService = new PopulationService(db, config.collections);
|
|
636
924
|
const populatedData = await populationService.populate({
|
|
637
|
-
data:
|
|
925
|
+
data: docWithFieldHooks,
|
|
638
926
|
fields: this.global.fields,
|
|
639
927
|
currentDepth: 0,
|
|
640
928
|
maxDepth: depth
|
|
641
929
|
});
|
|
642
930
|
return c.json(populatedData);
|
|
643
931
|
}
|
|
644
|
-
return c.json(
|
|
932
|
+
return c.json(docWithFieldHooks);
|
|
645
933
|
}
|
|
646
934
|
async update(c) {
|
|
647
935
|
const db = c.get("config").db;
|
|
648
936
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
649
937
|
const body = await c.req.json();
|
|
650
|
-
const
|
|
651
|
-
|
|
938
|
+
const user = c.get("user");
|
|
939
|
+
const originalDoc = await db.getGlobal({ slug: this.global.slug }) || {};
|
|
940
|
+
let data = await executeFieldBeforeChange(this.global.fields, body, originalDoc, user);
|
|
941
|
+
data = await runCollectionHooks(this.global.hooks?.beforeChange, {
|
|
942
|
+
data,
|
|
943
|
+
doc: originalDoc,
|
|
944
|
+
req: c.req,
|
|
945
|
+
user,
|
|
946
|
+
operation: "update"
|
|
947
|
+
});
|
|
948
|
+
const updated = await db.updateGlobal({ slug: this.global.slug, data });
|
|
949
|
+
await runCollectionHooks(this.global.hooks?.afterChange, {
|
|
950
|
+
doc: updated,
|
|
951
|
+
previousDoc: originalDoc,
|
|
952
|
+
user,
|
|
953
|
+
req: c.req,
|
|
954
|
+
operation: "update"
|
|
955
|
+
}, { isolated: true });
|
|
956
|
+
const readDoc = await runCollectionHooks(this.global.hooks?.afterRead, {
|
|
957
|
+
doc: updated,
|
|
958
|
+
req: c.req,
|
|
959
|
+
user
|
|
960
|
+
});
|
|
961
|
+
const finalDoc = await executeFieldAfterRead(this.global.fields, readDoc, user);
|
|
962
|
+
return c.json(finalDoc);
|
|
652
963
|
}
|
|
653
964
|
async seed(c) {
|
|
654
965
|
const config = c.get("config");
|
|
@@ -1557,12 +1868,13 @@ function fieldToSchema(field) {
|
|
|
1557
1868
|
schema = { type: "string", format: "date-time" };
|
|
1558
1869
|
break;
|
|
1559
1870
|
case "select":
|
|
1560
|
-
|
|
1871
|
+
case "radio":
|
|
1872
|
+
schema = { type: "string", enum: Array.isArray(field.options) ? field.options.map((o) => typeof o === "string" ? o : o.value) : void 0 };
|
|
1561
1873
|
break;
|
|
1562
1874
|
case "multiSelect":
|
|
1563
1875
|
schema = {
|
|
1564
1876
|
type: "array",
|
|
1565
|
-
items: { type: "string", enum: field.options
|
|
1877
|
+
items: { type: "string", enum: Array.isArray(field.options) ? field.options.map((o) => typeof o === "string" ? o : o.value) : void 0 }
|
|
1566
1878
|
};
|
|
1567
1879
|
break;
|
|
1568
1880
|
case "relationship":
|
|
@@ -1686,6 +1998,49 @@ function accessGate(target, action) {
|
|
|
1686
1998
|
await next();
|
|
1687
1999
|
};
|
|
1688
2000
|
}
|
|
2001
|
+
async function checkAccess(access, accessArgs) {
|
|
2002
|
+
if (access === void 0 || access === null) return true;
|
|
2003
|
+
if (typeof access === "function") {
|
|
2004
|
+
try {
|
|
2005
|
+
const result = await access(accessArgs);
|
|
2006
|
+
return typeof result === "boolean" ? result : !!result;
|
|
2007
|
+
} catch (err) {
|
|
2008
|
+
console.error("[dyrected/core] Functional access check failed:", err);
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
if (typeof access === "string" || typeof access === "boolean") {
|
|
2013
|
+
return evaluateAccess(access, accessArgs);
|
|
2014
|
+
}
|
|
2015
|
+
return true;
|
|
2016
|
+
}
|
|
2017
|
+
function serializeFieldForApi(f) {
|
|
2018
|
+
if (!f) return f;
|
|
2019
|
+
const serialized = { ...f };
|
|
2020
|
+
if (serialized.admin?.hooks) {
|
|
2021
|
+
const hooks = { ...serialized.admin.hooks };
|
|
2022
|
+
if (typeof hooks.onChange === "function") {
|
|
2023
|
+
hooks.onChange = hooks.onChange.toString();
|
|
2024
|
+
}
|
|
2025
|
+
if (typeof hooks.options === "function") {
|
|
2026
|
+
hooks.options = hooks.options.toString();
|
|
2027
|
+
}
|
|
2028
|
+
serialized.admin = { ...serialized.admin, hooks };
|
|
2029
|
+
}
|
|
2030
|
+
if (typeof serialized.options === "function" || serialized.options && typeof serialized.options === "object" && "resolve" in serialized.options) {
|
|
2031
|
+
serialized.options = { _dynamic: true };
|
|
2032
|
+
}
|
|
2033
|
+
if (serialized.fields) {
|
|
2034
|
+
serialized.fields = serialized.fields.map(serializeFieldForApi);
|
|
2035
|
+
}
|
|
2036
|
+
if (serialized.blocks) {
|
|
2037
|
+
serialized.blocks = serialized.blocks.map((b) => ({
|
|
2038
|
+
...b,
|
|
2039
|
+
fields: b.fields?.map(serializeFieldForApi)
|
|
2040
|
+
}));
|
|
2041
|
+
}
|
|
2042
|
+
return serialized;
|
|
2043
|
+
}
|
|
1689
2044
|
function registerRoutes(app, config) {
|
|
1690
2045
|
app.get("/api/schemas", optionalAuth(), async (c) => {
|
|
1691
2046
|
const siteId = c.req.header("X-Site-Id");
|
|
@@ -1701,26 +2056,10 @@ function registerRoutes(app, config) {
|
|
|
1701
2056
|
}
|
|
1702
2057
|
const user = c.get("user");
|
|
1703
2058
|
const accessArgs = { user, req: c.req, doc: null };
|
|
1704
|
-
const resolveAccess = async (access) => {
|
|
1705
|
-
if (access === void 0 || access === null) return true;
|
|
1706
|
-
if (typeof access === "function") {
|
|
1707
|
-
try {
|
|
1708
|
-
const result = await access(accessArgs);
|
|
1709
|
-
return typeof result === "boolean" ? result : !!result;
|
|
1710
|
-
} catch (err) {
|
|
1711
|
-
console.error("[dyrected/core] Functional access check failed:", err);
|
|
1712
|
-
return false;
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
if (typeof access === "string" || typeof access === "boolean") {
|
|
1716
|
-
return evaluateAccess(access, accessArgs);
|
|
1717
|
-
}
|
|
1718
|
-
return true;
|
|
1719
|
-
};
|
|
1720
2059
|
const serializeAccess = async (access) => {
|
|
1721
2060
|
if (typeof access === "string") return access;
|
|
1722
2061
|
if (typeof access === "boolean") return access;
|
|
1723
|
-
return
|
|
2062
|
+
return checkAccess(access, accessArgs);
|
|
1724
2063
|
};
|
|
1725
2064
|
const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
1726
2065
|
slug: col.slug,
|
|
@@ -1731,7 +2070,7 @@ function registerRoutes(app, config) {
|
|
|
1731
2070
|
update: await serializeAccess(col.access?.update),
|
|
1732
2071
|
delete: await serializeAccess(col.access?.delete)
|
|
1733
2072
|
},
|
|
1734
|
-
fields: await Promise.all(col.fields.map(async (f) => ({
|
|
2073
|
+
fields: await Promise.all(col.fields.map(serializeFieldForApi).map(async (f) => ({
|
|
1735
2074
|
name: f.name,
|
|
1736
2075
|
type: f.type,
|
|
1737
2076
|
label: f.label,
|
|
@@ -1759,7 +2098,7 @@ function registerRoutes(app, config) {
|
|
|
1759
2098
|
read: await serializeAccess(glb.access?.read),
|
|
1760
2099
|
update: await serializeAccess(glb.access?.update)
|
|
1761
2100
|
},
|
|
1762
|
-
fields: await Promise.all(glb.fields.map(async (f) => ({
|
|
2101
|
+
fields: await Promise.all(glb.fields.map(serializeFieldForApi).map(async (f) => ({
|
|
1763
2102
|
name: f.name,
|
|
1764
2103
|
type: f.type,
|
|
1765
2104
|
label: f.label,
|
|
@@ -1784,6 +2123,78 @@ function registerRoutes(app, config) {
|
|
|
1784
2123
|
admin: config.admin || {}
|
|
1785
2124
|
});
|
|
1786
2125
|
});
|
|
2126
|
+
app.get("/api/dyrected/options/:collection/:field", optionalAuth(), async (c) => {
|
|
2127
|
+
const { collection: colSlug, field: fieldName } = c.req.param();
|
|
2128
|
+
const siteId = c.req.header("X-Site-Id");
|
|
2129
|
+
let collections = [...config.collections];
|
|
2130
|
+
if (siteId && config.onSchemaFetch) {
|
|
2131
|
+
const dynamic = await config.onSchemaFetch(siteId);
|
|
2132
|
+
if (dynamic.collections) collections = [...collections, ...dynamic.collections];
|
|
2133
|
+
}
|
|
2134
|
+
const user = c.get("user");
|
|
2135
|
+
let collection = collections.find((col) => col.slug === colSlug);
|
|
2136
|
+
let field;
|
|
2137
|
+
if (collection) {
|
|
2138
|
+
const accessExpr = collection.access?.read;
|
|
2139
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
2140
|
+
const accessArgs = { user, req: c.req, doc: null };
|
|
2141
|
+
const allowed = await checkAccess(accessExpr, accessArgs);
|
|
2142
|
+
if (!allowed) {
|
|
2143
|
+
return c.json({ error: true, message: `Access denied: read on ${colSlug}` }, 403);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
field = collection.fields.find((f) => f.name === fieldName);
|
|
2147
|
+
} else {
|
|
2148
|
+
let globals = [...config.globals];
|
|
2149
|
+
if (siteId && config.onSchemaFetch) {
|
|
2150
|
+
const dynamic = await config.onSchemaFetch(siteId);
|
|
2151
|
+
if (dynamic.globals) globals = [...globals, ...dynamic.globals];
|
|
2152
|
+
}
|
|
2153
|
+
const glb = globals.find((g) => g.slug === colSlug);
|
|
2154
|
+
if (!glb) {
|
|
2155
|
+
return c.json({ error: true, message: `${colSlug} not found as collection or global` }, 404);
|
|
2156
|
+
}
|
|
2157
|
+
const accessExpr = glb.access?.read;
|
|
2158
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
2159
|
+
const accessArgs = { user, req: c.req, doc: null };
|
|
2160
|
+
const allowed = await checkAccess(accessExpr, accessArgs);
|
|
2161
|
+
if (!allowed) {
|
|
2162
|
+
return c.json({ error: true, message: `Access denied: read on global ${colSlug}` }, 403);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
field = glb.fields.find((f) => f.name === fieldName);
|
|
2166
|
+
}
|
|
2167
|
+
if (!field) {
|
|
2168
|
+
return c.json({ error: true, message: `Field ${fieldName} not found in ${colSlug}` }, 404);
|
|
2169
|
+
}
|
|
2170
|
+
let resolver;
|
|
2171
|
+
if (typeof field.options === "function") {
|
|
2172
|
+
resolver = field.options;
|
|
2173
|
+
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
2174
|
+
resolver = field.options.resolve;
|
|
2175
|
+
}
|
|
2176
|
+
if (!resolver) {
|
|
2177
|
+
return c.json({ error: true, message: `Field ${fieldName} in ${colSlug} is not dynamic` }, 400);
|
|
2178
|
+
}
|
|
2179
|
+
try {
|
|
2180
|
+
const db = c.get("db") || config.db;
|
|
2181
|
+
const queryParams = c.req.query();
|
|
2182
|
+
const reqContext = {
|
|
2183
|
+
query: queryParams,
|
|
2184
|
+
headers: c.req.header(),
|
|
2185
|
+
raw: c.req.raw
|
|
2186
|
+
};
|
|
2187
|
+
const result = await resolver({
|
|
2188
|
+
db,
|
|
2189
|
+
user,
|
|
2190
|
+
req: reqContext
|
|
2191
|
+
});
|
|
2192
|
+
return c.json(result);
|
|
2193
|
+
} catch (err) {
|
|
2194
|
+
console.error(`[dyrected/core] Failed to resolve dynamic options for field ${fieldName}:`, err);
|
|
2195
|
+
return c.json({ error: true, message: err.message || "Failed to resolve dynamic options" }, 500);
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
1787
2198
|
app.get("/api/openapi.json", (c) => {
|
|
1788
2199
|
return c.json(generateOpenApi(config));
|
|
1789
2200
|
});
|