@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.
- package/dist/app-B2tg7Djj.d.cts +1575 -0
- package/dist/app-B2tg7Djj.d.ts +1575 -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-PKNFV7KE.js +2469 -0
- package/dist/chunk-UBTRANFX.js +2476 -0
- package/dist/chunk-W6KURRMW.js +2471 -0
- package/dist/index.cjs +457 -48
- package/dist/index.d.cts +117 -8
- package/dist/index.d.ts +117 -8
- package/dist/index.js +9 -3
- package/dist/server.cjs +449 -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,150 @@ 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) {
|
|
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
|
+
const result = await hook({
|
|
279
|
+
...args,
|
|
280
|
+
data: args.data !== void 0 ? currentPayload : void 0,
|
|
281
|
+
doc: args.doc !== void 0 ? currentPayload : void 0
|
|
282
|
+
});
|
|
283
|
+
if (result !== void 0) {
|
|
284
|
+
currentPayload = result;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return currentPayload;
|
|
288
|
+
}
|
|
289
|
+
async function executeFieldBeforeChange(fields, data, originalDoc, user) {
|
|
290
|
+
if (!data || typeof data !== "object") return data;
|
|
291
|
+
const result = { ...data };
|
|
292
|
+
for (const field of fields) {
|
|
293
|
+
if (!field.name) continue;
|
|
294
|
+
const value = result[field.name];
|
|
295
|
+
const origValue = originalDoc?.[field.name];
|
|
296
|
+
let updatedValue = value;
|
|
297
|
+
if (field.hooks?.beforeChange) {
|
|
298
|
+
for (const hook of field.hooks.beforeChange) {
|
|
299
|
+
updatedValue = await hook({
|
|
300
|
+
value: updatedValue,
|
|
301
|
+
originalDoc: originalDoc ?? void 0,
|
|
302
|
+
data: result,
|
|
303
|
+
user
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
result[field.name] = updatedValue;
|
|
307
|
+
}
|
|
308
|
+
if (updatedValue !== void 0 && updatedValue !== null) {
|
|
309
|
+
if (field.type === "object" && field.fields) {
|
|
310
|
+
result[field.name] = await executeFieldBeforeChange(
|
|
311
|
+
field.fields,
|
|
312
|
+
updatedValue,
|
|
313
|
+
origValue,
|
|
314
|
+
user
|
|
315
|
+
);
|
|
316
|
+
} else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
|
|
317
|
+
const arrayResult = [];
|
|
318
|
+
for (let i = 0; i < updatedValue.length; i++) {
|
|
319
|
+
const item = updatedValue[i];
|
|
320
|
+
const origItem = Array.isArray(origValue) ? origValue[i] : null;
|
|
321
|
+
arrayResult.push(
|
|
322
|
+
await executeFieldBeforeChange(field.fields, item, origItem, user)
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
result[field.name] = arrayResult;
|
|
326
|
+
} else if (field.type === "blocks" && field.blocks && Array.isArray(updatedValue)) {
|
|
327
|
+
const blocksResult = [];
|
|
328
|
+
for (let i = 0; i < updatedValue.length; i++) {
|
|
329
|
+
const blockData = updatedValue[i];
|
|
330
|
+
const origBlock = Array.isArray(origValue) ? origValue[i] : null;
|
|
331
|
+
const blockConfig = field.blocks.find(
|
|
332
|
+
(b) => b.slug === blockData.blockType
|
|
333
|
+
);
|
|
334
|
+
if (blockConfig) {
|
|
335
|
+
blocksResult.push(
|
|
336
|
+
await executeFieldBeforeChange(
|
|
337
|
+
blockConfig.fields,
|
|
338
|
+
blockData,
|
|
339
|
+
origBlock,
|
|
340
|
+
user
|
|
341
|
+
)
|
|
342
|
+
);
|
|
343
|
+
} else {
|
|
344
|
+
blocksResult.push(blockData);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
result[field.name] = blocksResult;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
async function executeFieldAfterRead(fields, doc, user) {
|
|
354
|
+
if (!doc || typeof doc !== "object") return doc;
|
|
355
|
+
const result = { ...doc };
|
|
356
|
+
for (const field of fields) {
|
|
357
|
+
if (!field.name) continue;
|
|
358
|
+
const value = result[field.name];
|
|
359
|
+
let updatedValue = value;
|
|
360
|
+
if (field.hooks?.afterRead) {
|
|
361
|
+
for (const hook of field.hooks.afterRead) {
|
|
362
|
+
updatedValue = await hook({
|
|
363
|
+
value: updatedValue,
|
|
364
|
+
doc: result,
|
|
365
|
+
user
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
result[field.name] = updatedValue;
|
|
369
|
+
}
|
|
370
|
+
if (updatedValue !== void 0 && updatedValue !== null) {
|
|
371
|
+
if (field.type === "object" && field.fields) {
|
|
372
|
+
result[field.name] = await executeFieldAfterRead(
|
|
373
|
+
field.fields,
|
|
374
|
+
updatedValue,
|
|
375
|
+
user
|
|
376
|
+
);
|
|
377
|
+
} else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
|
|
378
|
+
const arrayResult = [];
|
|
379
|
+
for (const item of updatedValue) {
|
|
380
|
+
arrayResult.push(
|
|
381
|
+
await executeFieldAfterRead(
|
|
382
|
+
field.fields,
|
|
383
|
+
item,
|
|
384
|
+
user
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
result[field.name] = arrayResult;
|
|
389
|
+
} else if (field.type === "blocks" && field.blocks && Array.isArray(updatedValue)) {
|
|
390
|
+
const blocksResult = [];
|
|
391
|
+
for (const blockData of updatedValue) {
|
|
392
|
+
const typedBlock = blockData;
|
|
393
|
+
const blockConfig = field.blocks.find(
|
|
394
|
+
(b) => b.slug === typedBlock.blockType
|
|
395
|
+
);
|
|
396
|
+
if (blockConfig) {
|
|
397
|
+
blocksResult.push(
|
|
398
|
+
await executeFieldAfterRead(
|
|
399
|
+
blockConfig.fields,
|
|
400
|
+
typedBlock,
|
|
401
|
+
user
|
|
402
|
+
)
|
|
403
|
+
);
|
|
404
|
+
} else {
|
|
405
|
+
blocksResult.push(blockData);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
result[field.name] = blocksResult;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
414
|
+
|
|
271
415
|
// src/controllers/collection.controller.ts
|
|
272
416
|
var CollectionController = class {
|
|
273
417
|
collection;
|
|
@@ -282,6 +426,7 @@ var CollectionController = class {
|
|
|
282
426
|
const page = Number(c.req.query("page")) || 1;
|
|
283
427
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 2;
|
|
284
428
|
const sort = c.req.query("sort") || void 0;
|
|
429
|
+
const user = c.get("user");
|
|
285
430
|
let where = void 0;
|
|
286
431
|
const whereRaw = c.req.query("where");
|
|
287
432
|
if (whereRaw) {
|
|
@@ -290,6 +435,14 @@ var CollectionController = class {
|
|
|
290
435
|
} catch {
|
|
291
436
|
}
|
|
292
437
|
}
|
|
438
|
+
const beforeReadResult = await runCollectionHooks(this.collection.hooks?.beforeRead, {
|
|
439
|
+
req: c.req,
|
|
440
|
+
query: where,
|
|
441
|
+
user
|
|
442
|
+
});
|
|
443
|
+
if (beforeReadResult !== void 0) {
|
|
444
|
+
where = beforeReadResult;
|
|
445
|
+
}
|
|
293
446
|
let result = await db.find({
|
|
294
447
|
collection: this.collection.slug,
|
|
295
448
|
limit,
|
|
@@ -311,6 +464,17 @@ var CollectionController = class {
|
|
|
311
464
|
});
|
|
312
465
|
}
|
|
313
466
|
result.docs = result.docs.map((doc) => DefaultsService.apply(this.collection.fields, doc));
|
|
467
|
+
const processedDocs = [];
|
|
468
|
+
for (const doc of result.docs) {
|
|
469
|
+
const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
470
|
+
doc,
|
|
471
|
+
req: c.req,
|
|
472
|
+
user
|
|
473
|
+
});
|
|
474
|
+
const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user);
|
|
475
|
+
processedDocs.push(docWithFieldHooks);
|
|
476
|
+
}
|
|
477
|
+
result.docs = processedDocs;
|
|
314
478
|
if (depth > 0) {
|
|
315
479
|
const populationService = new PopulationService(db, config.collections);
|
|
316
480
|
result = await populationService.populateResult(result, this.collection.fields, depth);
|
|
@@ -323,21 +487,28 @@ var CollectionController = class {
|
|
|
323
487
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
324
488
|
const id = c.req.param("id");
|
|
325
489
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
|
|
490
|
+
const user = c.get("user");
|
|
326
491
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
327
492
|
const doc = await db.findOne({ collection: this.collection.slug, id });
|
|
328
493
|
if (!doc) return c.json({ message: "Not Found" }, 404);
|
|
329
494
|
const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
|
|
330
|
-
|
|
495
|
+
const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
496
|
+
doc: docWithDefaults,
|
|
497
|
+
req: c.req,
|
|
498
|
+
user
|
|
499
|
+
});
|
|
500
|
+
const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user);
|
|
501
|
+
if (depth > 0 && docWithFieldHooks) {
|
|
331
502
|
const populationService = new PopulationService(db, config.collections);
|
|
332
503
|
const populatedDoc = await populationService.populate({
|
|
333
|
-
data:
|
|
504
|
+
data: docWithFieldHooks,
|
|
334
505
|
fields: this.collection.fields,
|
|
335
506
|
currentDepth: 0,
|
|
336
507
|
maxDepth: depth
|
|
337
508
|
});
|
|
338
509
|
return c.json(populatedDoc);
|
|
339
510
|
}
|
|
340
|
-
return c.json(
|
|
511
|
+
return c.json(docWithFieldHooks);
|
|
341
512
|
}
|
|
342
513
|
async create(c) {
|
|
343
514
|
const config = c.get("config");
|
|
@@ -350,7 +521,7 @@ var CollectionController = class {
|
|
|
350
521
|
const body = await c.req.json();
|
|
351
522
|
const user = c.get("user");
|
|
352
523
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
353
|
-
|
|
524
|
+
let data = {
|
|
354
525
|
...body,
|
|
355
526
|
createdAt: now,
|
|
356
527
|
updatedAt: now,
|
|
@@ -360,6 +531,13 @@ var CollectionController = class {
|
|
|
360
531
|
if (this.collection.auth && data.password) {
|
|
361
532
|
data.password = await hashPassword(data.password);
|
|
362
533
|
}
|
|
534
|
+
data = await executeFieldBeforeChange(this.collection.fields, data, null, user);
|
|
535
|
+
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
536
|
+
data,
|
|
537
|
+
req: c.req,
|
|
538
|
+
user,
|
|
539
|
+
operation: "create"
|
|
540
|
+
});
|
|
363
541
|
const doc = await db.create({ collection: this.collection.slug, data });
|
|
364
542
|
if (this.collection.audit && db) {
|
|
365
543
|
AuditService.log(db, {
|
|
@@ -371,7 +549,19 @@ var CollectionController = class {
|
|
|
371
549
|
after: doc
|
|
372
550
|
});
|
|
373
551
|
}
|
|
374
|
-
|
|
552
|
+
await runCollectionHooks(this.collection.hooks?.afterChange, {
|
|
553
|
+
doc,
|
|
554
|
+
user,
|
|
555
|
+
req: c.req,
|
|
556
|
+
operation: "create"
|
|
557
|
+
});
|
|
558
|
+
const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
559
|
+
doc,
|
|
560
|
+
req: c.req,
|
|
561
|
+
user
|
|
562
|
+
});
|
|
563
|
+
const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
|
|
564
|
+
return c.json(finalDoc, 201);
|
|
375
565
|
}
|
|
376
566
|
async upload(c) {
|
|
377
567
|
const config = c.get("config");
|
|
@@ -398,18 +588,38 @@ var CollectionController = class {
|
|
|
398
588
|
});
|
|
399
589
|
const user = c.get("user");
|
|
400
590
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
591
|
+
let data = {
|
|
592
|
+
...otherData,
|
|
593
|
+
...fileData,
|
|
594
|
+
createdAt: now,
|
|
595
|
+
updatedAt: now,
|
|
596
|
+
createdBy: user?.sub ?? null,
|
|
597
|
+
updatedBy: user?.sub ?? null
|
|
598
|
+
};
|
|
599
|
+
data = await executeFieldBeforeChange(this.collection.fields, data, null, user);
|
|
600
|
+
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
601
|
+
data,
|
|
602
|
+
req: c.req,
|
|
603
|
+
user,
|
|
604
|
+
operation: "create"
|
|
605
|
+
});
|
|
401
606
|
const doc = await config.db.create({
|
|
402
607
|
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
|
-
}
|
|
608
|
+
data
|
|
411
609
|
});
|
|
412
|
-
|
|
610
|
+
await runCollectionHooks(this.collection.hooks?.afterChange, {
|
|
611
|
+
doc,
|
|
612
|
+
user,
|
|
613
|
+
req: c.req,
|
|
614
|
+
operation: "create"
|
|
615
|
+
});
|
|
616
|
+
const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
617
|
+
doc,
|
|
618
|
+
req: c.req,
|
|
619
|
+
user
|
|
620
|
+
});
|
|
621
|
+
const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
|
|
622
|
+
return c.json(finalDoc, 201);
|
|
413
623
|
}
|
|
414
624
|
async update(c) {
|
|
415
625
|
const config = c.get("config");
|
|
@@ -419,7 +629,7 @@ var CollectionController = class {
|
|
|
419
629
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
420
630
|
const body = await c.req.json();
|
|
421
631
|
const user = c.get("user");
|
|
422
|
-
|
|
632
|
+
let data = { ...body };
|
|
423
633
|
if (this.collection.auth) {
|
|
424
634
|
delete data.password;
|
|
425
635
|
delete data.oldPassword;
|
|
@@ -429,10 +639,20 @@ var CollectionController = class {
|
|
|
429
639
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
430
640
|
updatedBy: user?.sub ?? null
|
|
431
641
|
});
|
|
642
|
+
const originalDoc = await db.findOne({ collection: this.collection.slug, id });
|
|
643
|
+
if (!originalDoc) return c.json({ message: "Not Found" }, 404);
|
|
432
644
|
let before = null;
|
|
433
645
|
if (this.collection.audit) {
|
|
434
|
-
before =
|
|
435
|
-
}
|
|
646
|
+
before = originalDoc;
|
|
647
|
+
}
|
|
648
|
+
data = await executeFieldBeforeChange(this.collection.fields, data, originalDoc, user);
|
|
649
|
+
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
650
|
+
data,
|
|
651
|
+
doc: originalDoc,
|
|
652
|
+
req: c.req,
|
|
653
|
+
user,
|
|
654
|
+
operation: "update"
|
|
655
|
+
});
|
|
436
656
|
const doc = await db.update({ collection: this.collection.slug, id, data });
|
|
437
657
|
if (this.collection.audit && db) {
|
|
438
658
|
AuditService.log(db, {
|
|
@@ -444,7 +664,20 @@ var CollectionController = class {
|
|
|
444
664
|
after: doc
|
|
445
665
|
});
|
|
446
666
|
}
|
|
447
|
-
|
|
667
|
+
await runCollectionHooks(this.collection.hooks?.afterChange, {
|
|
668
|
+
doc,
|
|
669
|
+
previousDoc: originalDoc,
|
|
670
|
+
user,
|
|
671
|
+
req: c.req,
|
|
672
|
+
operation: "update"
|
|
673
|
+
});
|
|
674
|
+
const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
|
|
675
|
+
doc,
|
|
676
|
+
req: c.req,
|
|
677
|
+
user
|
|
678
|
+
});
|
|
679
|
+
const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
|
|
680
|
+
return c.json(finalDoc);
|
|
448
681
|
}
|
|
449
682
|
/**
|
|
450
683
|
* POST /api/collections/:slug/:id/change-password
|
|
@@ -524,10 +757,18 @@ var CollectionController = class {
|
|
|
524
757
|
const id = c.req.param("id");
|
|
525
758
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
526
759
|
const user = c.get("user");
|
|
760
|
+
const doc = await db.findOne({ collection: this.collection.slug, id });
|
|
761
|
+
if (!doc) return c.json({ message: "Not Found" }, 404);
|
|
527
762
|
let before = null;
|
|
528
763
|
if (this.collection.audit) {
|
|
529
|
-
before =
|
|
764
|
+
before = doc;
|
|
530
765
|
}
|
|
766
|
+
await runCollectionHooks(this.collection.hooks?.beforeDelete, {
|
|
767
|
+
id,
|
|
768
|
+
doc,
|
|
769
|
+
user,
|
|
770
|
+
req: c.req
|
|
771
|
+
});
|
|
531
772
|
await db.delete({ collection: this.collection.slug, id });
|
|
532
773
|
if (this.collection.audit && db) {
|
|
533
774
|
AuditService.log(db, {
|
|
@@ -539,6 +780,12 @@ var CollectionController = class {
|
|
|
539
780
|
after: null
|
|
540
781
|
});
|
|
541
782
|
}
|
|
783
|
+
await runCollectionHooks(this.collection.hooks?.afterDelete, {
|
|
784
|
+
id,
|
|
785
|
+
doc,
|
|
786
|
+
user,
|
|
787
|
+
req: c.req
|
|
788
|
+
});
|
|
542
789
|
return c.json({ message: "Deleted" });
|
|
543
790
|
}
|
|
544
791
|
async deleteMany(c) {
|
|
@@ -563,10 +810,21 @@ var CollectionController = class {
|
|
|
563
810
|
const failed = [];
|
|
564
811
|
for (const id of ids) {
|
|
565
812
|
try {
|
|
813
|
+
const doc = await db.findOne({ collection: this.collection.slug, id });
|
|
814
|
+
if (!doc) {
|
|
815
|
+
failed.push({ id, error: "Not Found" });
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
566
818
|
let before = null;
|
|
567
819
|
if (this.collection.audit) {
|
|
568
|
-
before =
|
|
820
|
+
before = doc;
|
|
569
821
|
}
|
|
822
|
+
await runCollectionHooks(this.collection.hooks?.beforeDelete, {
|
|
823
|
+
id,
|
|
824
|
+
doc,
|
|
825
|
+
user,
|
|
826
|
+
req: c.req
|
|
827
|
+
});
|
|
570
828
|
await db.delete({ collection: this.collection.slug, id });
|
|
571
829
|
deleted.push(id);
|
|
572
830
|
if (this.collection.audit) {
|
|
@@ -579,6 +837,12 @@ var CollectionController = class {
|
|
|
579
837
|
after: null
|
|
580
838
|
});
|
|
581
839
|
}
|
|
840
|
+
await runCollectionHooks(this.collection.hooks?.afterDelete, {
|
|
841
|
+
id,
|
|
842
|
+
doc,
|
|
843
|
+
user,
|
|
844
|
+
req: c.req
|
|
845
|
+
});
|
|
582
846
|
} catch (err) {
|
|
583
847
|
failed.push({ id, error: err?.message ?? "Unknown error" });
|
|
584
848
|
}
|
|
@@ -623,6 +887,16 @@ var GlobalController = class {
|
|
|
623
887
|
const db = config.db;
|
|
624
888
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
625
889
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
|
|
890
|
+
const user = c.get("user");
|
|
891
|
+
let query = void 0;
|
|
892
|
+
const beforeReadResult = await runCollectionHooks(this.global.hooks?.beforeRead, {
|
|
893
|
+
req: c.req,
|
|
894
|
+
query,
|
|
895
|
+
user
|
|
896
|
+
});
|
|
897
|
+
if (beforeReadResult !== void 0) {
|
|
898
|
+
query = beforeReadResult;
|
|
899
|
+
}
|
|
626
900
|
let data = await db.getGlobal({ slug: this.global.slug });
|
|
627
901
|
const isEmpty = !data || Object.keys(data).length === 0;
|
|
628
902
|
if (isEmpty && this.global.initialData) {
|
|
@@ -631,24 +905,53 @@ var GlobalController = class {
|
|
|
631
905
|
data = this.global.initialData;
|
|
632
906
|
}
|
|
633
907
|
const dataWithDefaults = DefaultsService.apply(this.global.fields, data);
|
|
634
|
-
|
|
908
|
+
const docWithCollectionHooks = await runCollectionHooks(this.global.hooks?.afterRead, {
|
|
909
|
+
doc: dataWithDefaults,
|
|
910
|
+
req: c.req,
|
|
911
|
+
user
|
|
912
|
+
});
|
|
913
|
+
const docWithFieldHooks = await executeFieldAfterRead(this.global.fields, docWithCollectionHooks, user);
|
|
914
|
+
if (depth > 0 && docWithFieldHooks) {
|
|
635
915
|
const populationService = new PopulationService(db, config.collections);
|
|
636
916
|
const populatedData = await populationService.populate({
|
|
637
|
-
data:
|
|
917
|
+
data: docWithFieldHooks,
|
|
638
918
|
fields: this.global.fields,
|
|
639
919
|
currentDepth: 0,
|
|
640
920
|
maxDepth: depth
|
|
641
921
|
});
|
|
642
922
|
return c.json(populatedData);
|
|
643
923
|
}
|
|
644
|
-
return c.json(
|
|
924
|
+
return c.json(docWithFieldHooks);
|
|
645
925
|
}
|
|
646
926
|
async update(c) {
|
|
647
927
|
const db = c.get("config").db;
|
|
648
928
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
649
929
|
const body = await c.req.json();
|
|
650
|
-
const
|
|
651
|
-
|
|
930
|
+
const user = c.get("user");
|
|
931
|
+
const originalDoc = await db.getGlobal({ slug: this.global.slug }) || {};
|
|
932
|
+
let data = await executeFieldBeforeChange(this.global.fields, body, originalDoc, user);
|
|
933
|
+
data = await runCollectionHooks(this.global.hooks?.beforeChange, {
|
|
934
|
+
data,
|
|
935
|
+
doc: originalDoc,
|
|
936
|
+
req: c.req,
|
|
937
|
+
user,
|
|
938
|
+
operation: "update"
|
|
939
|
+
});
|
|
940
|
+
const updated = await db.updateGlobal({ slug: this.global.slug, data });
|
|
941
|
+
await runCollectionHooks(this.global.hooks?.afterChange, {
|
|
942
|
+
doc: updated,
|
|
943
|
+
previousDoc: originalDoc,
|
|
944
|
+
user,
|
|
945
|
+
req: c.req,
|
|
946
|
+
operation: "update"
|
|
947
|
+
});
|
|
948
|
+
const readDoc = await runCollectionHooks(this.global.hooks?.afterRead, {
|
|
949
|
+
doc: updated,
|
|
950
|
+
req: c.req,
|
|
951
|
+
user
|
|
952
|
+
});
|
|
953
|
+
const finalDoc = await executeFieldAfterRead(this.global.fields, readDoc, user);
|
|
954
|
+
return c.json(finalDoc);
|
|
652
955
|
}
|
|
653
956
|
async seed(c) {
|
|
654
957
|
const config = c.get("config");
|
|
@@ -1557,12 +1860,13 @@ function fieldToSchema(field) {
|
|
|
1557
1860
|
schema = { type: "string", format: "date-time" };
|
|
1558
1861
|
break;
|
|
1559
1862
|
case "select":
|
|
1560
|
-
|
|
1863
|
+
case "radio":
|
|
1864
|
+
schema = { type: "string", enum: Array.isArray(field.options) ? field.options.map((o) => typeof o === "string" ? o : o.value) : void 0 };
|
|
1561
1865
|
break;
|
|
1562
1866
|
case "multiSelect":
|
|
1563
1867
|
schema = {
|
|
1564
1868
|
type: "array",
|
|
1565
|
-
items: { type: "string", enum: field.options
|
|
1869
|
+
items: { type: "string", enum: Array.isArray(field.options) ? field.options.map((o) => typeof o === "string" ? o : o.value) : void 0 }
|
|
1566
1870
|
};
|
|
1567
1871
|
break;
|
|
1568
1872
|
case "relationship":
|
|
@@ -1686,6 +1990,49 @@ function accessGate(target, action) {
|
|
|
1686
1990
|
await next();
|
|
1687
1991
|
};
|
|
1688
1992
|
}
|
|
1993
|
+
async function checkAccess(access, accessArgs) {
|
|
1994
|
+
if (access === void 0 || access === null) return true;
|
|
1995
|
+
if (typeof access === "function") {
|
|
1996
|
+
try {
|
|
1997
|
+
const result = await access(accessArgs);
|
|
1998
|
+
return typeof result === "boolean" ? result : !!result;
|
|
1999
|
+
} catch (err) {
|
|
2000
|
+
console.error("[dyrected/core] Functional access check failed:", err);
|
|
2001
|
+
return false;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
if (typeof access === "string" || typeof access === "boolean") {
|
|
2005
|
+
return evaluateAccess(access, accessArgs);
|
|
2006
|
+
}
|
|
2007
|
+
return true;
|
|
2008
|
+
}
|
|
2009
|
+
function serializeFieldForApi(f) {
|
|
2010
|
+
if (!f) return f;
|
|
2011
|
+
const serialized = { ...f };
|
|
2012
|
+
if (serialized.admin?.hooks) {
|
|
2013
|
+
const hooks = { ...serialized.admin.hooks };
|
|
2014
|
+
if (typeof hooks.onChange === "function") {
|
|
2015
|
+
hooks.onChange = hooks.onChange.toString();
|
|
2016
|
+
}
|
|
2017
|
+
if (typeof hooks.options === "function") {
|
|
2018
|
+
hooks.options = hooks.options.toString();
|
|
2019
|
+
}
|
|
2020
|
+
serialized.admin = { ...serialized.admin, hooks };
|
|
2021
|
+
}
|
|
2022
|
+
if (typeof serialized.options === "function" || serialized.options && typeof serialized.options === "object" && "resolve" in serialized.options) {
|
|
2023
|
+
serialized.options = { _dynamic: true };
|
|
2024
|
+
}
|
|
2025
|
+
if (serialized.fields) {
|
|
2026
|
+
serialized.fields = serialized.fields.map(serializeFieldForApi);
|
|
2027
|
+
}
|
|
2028
|
+
if (serialized.blocks) {
|
|
2029
|
+
serialized.blocks = serialized.blocks.map((b) => ({
|
|
2030
|
+
...b,
|
|
2031
|
+
fields: b.fields?.map(serializeFieldForApi)
|
|
2032
|
+
}));
|
|
2033
|
+
}
|
|
2034
|
+
return serialized;
|
|
2035
|
+
}
|
|
1689
2036
|
function registerRoutes(app, config) {
|
|
1690
2037
|
app.get("/api/schemas", optionalAuth(), async (c) => {
|
|
1691
2038
|
const siteId = c.req.header("X-Site-Id");
|
|
@@ -1701,26 +2048,10 @@ function registerRoutes(app, config) {
|
|
|
1701
2048
|
}
|
|
1702
2049
|
const user = c.get("user");
|
|
1703
2050
|
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
2051
|
const serializeAccess = async (access) => {
|
|
1721
2052
|
if (typeof access === "string") return access;
|
|
1722
2053
|
if (typeof access === "boolean") return access;
|
|
1723
|
-
return
|
|
2054
|
+
return checkAccess(access, accessArgs);
|
|
1724
2055
|
};
|
|
1725
2056
|
const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
1726
2057
|
slug: col.slug,
|
|
@@ -1731,7 +2062,7 @@ function registerRoutes(app, config) {
|
|
|
1731
2062
|
update: await serializeAccess(col.access?.update),
|
|
1732
2063
|
delete: await serializeAccess(col.access?.delete)
|
|
1733
2064
|
},
|
|
1734
|
-
fields: await Promise.all(col.fields.map(async (f) => ({
|
|
2065
|
+
fields: await Promise.all(col.fields.map(serializeFieldForApi).map(async (f) => ({
|
|
1735
2066
|
name: f.name,
|
|
1736
2067
|
type: f.type,
|
|
1737
2068
|
label: f.label,
|
|
@@ -1759,7 +2090,7 @@ function registerRoutes(app, config) {
|
|
|
1759
2090
|
read: await serializeAccess(glb.access?.read),
|
|
1760
2091
|
update: await serializeAccess(glb.access?.update)
|
|
1761
2092
|
},
|
|
1762
|
-
fields: await Promise.all(glb.fields.map(async (f) => ({
|
|
2093
|
+
fields: await Promise.all(glb.fields.map(serializeFieldForApi).map(async (f) => ({
|
|
1763
2094
|
name: f.name,
|
|
1764
2095
|
type: f.type,
|
|
1765
2096
|
label: f.label,
|
|
@@ -1784,6 +2115,78 @@ function registerRoutes(app, config) {
|
|
|
1784
2115
|
admin: config.admin || {}
|
|
1785
2116
|
});
|
|
1786
2117
|
});
|
|
2118
|
+
app.get("/api/dyrected/options/:collection/:field", optionalAuth(), async (c) => {
|
|
2119
|
+
const { collection: colSlug, field: fieldName } = c.req.param();
|
|
2120
|
+
const siteId = c.req.header("X-Site-Id");
|
|
2121
|
+
let collections = [...config.collections];
|
|
2122
|
+
if (siteId && config.onSchemaFetch) {
|
|
2123
|
+
const dynamic = await config.onSchemaFetch(siteId);
|
|
2124
|
+
if (dynamic.collections) collections = [...collections, ...dynamic.collections];
|
|
2125
|
+
}
|
|
2126
|
+
const user = c.get("user");
|
|
2127
|
+
let collection = collections.find((col) => col.slug === colSlug);
|
|
2128
|
+
let field;
|
|
2129
|
+
if (collection) {
|
|
2130
|
+
const accessExpr = collection.access?.read;
|
|
2131
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
2132
|
+
const accessArgs = { user, req: c.req, doc: null };
|
|
2133
|
+
const allowed = await checkAccess(accessExpr, accessArgs);
|
|
2134
|
+
if (!allowed) {
|
|
2135
|
+
return c.json({ error: true, message: `Access denied: read on ${colSlug}` }, 403);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
field = collection.fields.find((f) => f.name === fieldName);
|
|
2139
|
+
} else {
|
|
2140
|
+
let globals = [...config.globals];
|
|
2141
|
+
if (siteId && config.onSchemaFetch) {
|
|
2142
|
+
const dynamic = await config.onSchemaFetch(siteId);
|
|
2143
|
+
if (dynamic.globals) globals = [...globals, ...dynamic.globals];
|
|
2144
|
+
}
|
|
2145
|
+
const glb = globals.find((g) => g.slug === colSlug);
|
|
2146
|
+
if (!glb) {
|
|
2147
|
+
return c.json({ error: true, message: `${colSlug} not found as collection or global` }, 404);
|
|
2148
|
+
}
|
|
2149
|
+
const accessExpr = glb.access?.read;
|
|
2150
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
2151
|
+
const accessArgs = { user, req: c.req, doc: null };
|
|
2152
|
+
const allowed = await checkAccess(accessExpr, accessArgs);
|
|
2153
|
+
if (!allowed) {
|
|
2154
|
+
return c.json({ error: true, message: `Access denied: read on global ${colSlug}` }, 403);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
field = glb.fields.find((f) => f.name === fieldName);
|
|
2158
|
+
}
|
|
2159
|
+
if (!field) {
|
|
2160
|
+
return c.json({ error: true, message: `Field ${fieldName} not found in ${colSlug}` }, 404);
|
|
2161
|
+
}
|
|
2162
|
+
let resolver;
|
|
2163
|
+
if (typeof field.options === "function") {
|
|
2164
|
+
resolver = field.options;
|
|
2165
|
+
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
2166
|
+
resolver = field.options.resolve;
|
|
2167
|
+
}
|
|
2168
|
+
if (!resolver) {
|
|
2169
|
+
return c.json({ error: true, message: `Field ${fieldName} in ${colSlug} is not dynamic` }, 400);
|
|
2170
|
+
}
|
|
2171
|
+
try {
|
|
2172
|
+
const db = c.get("db") || config.db;
|
|
2173
|
+
const queryParams = c.req.query();
|
|
2174
|
+
const reqContext = {
|
|
2175
|
+
query: queryParams,
|
|
2176
|
+
headers: c.req.header(),
|
|
2177
|
+
raw: c.req.raw
|
|
2178
|
+
};
|
|
2179
|
+
const result = await resolver({
|
|
2180
|
+
db,
|
|
2181
|
+
user,
|
|
2182
|
+
req: reqContext
|
|
2183
|
+
});
|
|
2184
|
+
return c.json(result);
|
|
2185
|
+
} catch (err) {
|
|
2186
|
+
console.error(`[dyrected/core] Failed to resolve dynamic options for field ${fieldName}:`, err);
|
|
2187
|
+
return c.json({ error: true, message: err.message || "Failed to resolve dynamic options" }, 500);
|
|
2188
|
+
}
|
|
2189
|
+
});
|
|
1787
2190
|
app.get("/api/openapi.json", (c) => {
|
|
1788
2191
|
return c.json(generateOpenApi(config));
|
|
1789
2192
|
});
|