@dyrected/core 2.5.24 → 2.5.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/__tests__/app.test.d.ts +2 -0
  2. package/dist/__tests__/app.test.d.ts.map +1 -0
  3. package/dist/__tests__/app.test.js +27 -0
  4. package/dist/__tests__/app.test.js.map +1 -0
  5. package/dist/__tests__/config.test.d.ts +2 -0
  6. package/dist/__tests__/config.test.d.ts.map +1 -0
  7. package/dist/__tests__/config.test.js +34 -0
  8. package/dist/__tests__/config.test.js.map +1 -0
  9. package/dist/__tests__/deleteMany.test.d.ts +2 -0
  10. package/dist/__tests__/deleteMany.test.d.ts.map +1 -0
  11. package/dist/__tests__/deleteMany.test.js +75 -0
  12. package/dist/__tests__/deleteMany.test.js.map +1 -0
  13. package/dist/__tests__/depth.test.d.ts +2 -0
  14. package/dist/__tests__/depth.test.d.ts.map +1 -0
  15. package/dist/__tests__/depth.test.js +81 -0
  16. package/dist/__tests__/depth.test.js.map +1 -0
  17. package/dist/__tests__/dynamic-options.test.d.ts +2 -0
  18. package/dist/__tests__/dynamic-options.test.d.ts.map +1 -0
  19. package/dist/__tests__/dynamic-options.test.js +132 -0
  20. package/dist/__tests__/dynamic-options.test.js.map +1 -0
  21. package/dist/__tests__/field-inference.test-types.d.ts +24 -0
  22. package/dist/__tests__/field-inference.test-types.d.ts.map +1 -0
  23. package/dist/__tests__/field-inference.test-types.js +87 -0
  24. package/dist/__tests__/field-inference.test-types.js.map +1 -0
  25. package/dist/__tests__/hooks.test.d.ts +2 -0
  26. package/dist/__tests__/hooks.test.d.ts.map +1 -0
  27. package/dist/__tests__/hooks.test.js +320 -0
  28. package/dist/__tests__/hooks.test.js.map +1 -0
  29. package/dist/__tests__/mocks.d.ts +68 -0
  30. package/dist/__tests__/mocks.d.ts.map +1 -0
  31. package/dist/__tests__/mocks.js +151 -0
  32. package/dist/__tests__/mocks.js.map +1 -0
  33. package/dist/__tests__/router.test.d.ts +2 -0
  34. package/dist/__tests__/router.test.d.ts.map +1 -0
  35. package/dist/__tests__/router.test.js +48 -0
  36. package/dist/__tests__/router.test.js.map +1 -0
  37. package/dist/__tests__/where.test.d.ts +2 -0
  38. package/dist/__tests__/where.test.d.ts.map +1 -0
  39. package/dist/__tests__/where.test.js +97 -0
  40. package/dist/__tests__/where.test.js.map +1 -0
  41. package/dist/app-BOrsS7Tz.d.cts +1771 -0
  42. package/dist/app-BOrsS7Tz.d.ts +1771 -0
  43. package/dist/app-BibuoHQG.d.cts +1764 -0
  44. package/dist/app-BibuoHQG.d.ts +1764 -0
  45. package/dist/app-aW2FMuYM.d.cts +1759 -0
  46. package/dist/app-aW2FMuYM.d.ts +1759 -0
  47. package/dist/app.d.ts +21 -0
  48. package/dist/app.d.ts.map +1 -0
  49. package/dist/app.js +56 -0
  50. package/dist/app.js.map +1 -0
  51. package/dist/auth/jexl.d.ts +10 -0
  52. package/dist/auth/jexl.d.ts.map +1 -0
  53. package/dist/auth/jexl.js +22 -0
  54. package/dist/auth/jexl.js.map +1 -0
  55. package/dist/auth/password.d.ts +10 -0
  56. package/dist/auth/password.d.ts.map +1 -0
  57. package/dist/auth/password.js +28 -0
  58. package/dist/auth/password.js.map +1 -0
  59. package/dist/auth/token.d.ts +20 -0
  60. package/dist/auth/token.d.ts.map +1 -0
  61. package/dist/auth/token.js +40 -0
  62. package/dist/auth/token.js.map +1 -0
  63. package/dist/chunk-4EDMZAM5.js +2692 -0
  64. package/dist/chunk-FDQYPPG3.js +2698 -0
  65. package/dist/chunk-NKDX67AW.js +2698 -0
  66. package/dist/chunk-SUGK7UYL.js +311 -0
  67. package/dist/chunk-ZFAOBRHT.js +2709 -0
  68. package/dist/controllers/auth.controller.d.ts +125 -0
  69. package/dist/controllers/auth.controller.d.ts.map +1 -0
  70. package/dist/controllers/auth.controller.js +323 -0
  71. package/dist/controllers/auth.controller.js.map +1 -0
  72. package/dist/controllers/collection.controller.d.ts +88 -0
  73. package/dist/controllers/collection.controller.d.ts.map +1 -0
  74. package/dist/controllers/collection.controller.js +554 -0
  75. package/dist/controllers/collection.controller.js.map +1 -0
  76. package/dist/controllers/global.controller.d.ts +17 -0
  77. package/dist/controllers/global.controller.d.ts.map +1 -0
  78. package/dist/controllers/global.controller.js +116 -0
  79. package/dist/controllers/global.controller.js.map +1 -0
  80. package/dist/controllers/media.controller.d.ts +36 -0
  81. package/dist/controllers/media.controller.d.ts.map +1 -0
  82. package/dist/controllers/media.controller.js +155 -0
  83. package/dist/controllers/media.controller.js.map +1 -0
  84. package/dist/controllers/preview.controller.d.ts +37 -0
  85. package/dist/controllers/preview.controller.d.ts.map +1 -0
  86. package/dist/controllers/preview.controller.js +48 -0
  87. package/dist/controllers/preview.controller.js.map +1 -0
  88. package/dist/index-Bp7PDOYG.d.cts +1750 -0
  89. package/dist/index-Bp7PDOYG.d.ts +1750 -0
  90. package/dist/index-DfAmTZXk.d.cts +1749 -0
  91. package/dist/index-DfAmTZXk.d.ts +1749 -0
  92. package/dist/index.cjs +19 -2324
  93. package/dist/index.d.cts +5 -6
  94. package/dist/index.d.ts +5 -6
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +3 -5
  97. package/dist/index.js.map +1 -0
  98. package/dist/middleware/auth.d.ts +18 -0
  99. package/dist/middleware/auth.d.ts.map +1 -0
  100. package/dist/middleware/auth.js +45 -0
  101. package/dist/middleware/auth.js.map +1 -0
  102. package/dist/router.d.ts +8 -0
  103. package/dist/router.d.ts.map +1 -0
  104. package/dist/router.js +463 -0
  105. package/dist/router.js.map +1 -0
  106. package/dist/server.cjs +237 -45
  107. package/dist/server.d.cts +22 -4
  108. package/dist/server.d.ts +22 -4
  109. package/dist/server.d.ts.map +1 -0
  110. package/dist/server.js +2429 -8
  111. package/dist/server.js.map +1 -0
  112. package/dist/services/audit.service.d.ts +23 -0
  113. package/dist/services/audit.service.d.ts.map +1 -0
  114. package/dist/services/audit.service.js +28 -0
  115. package/dist/services/audit.service.js.map +1 -0
  116. package/dist/services/defaults.service.d.ts +8 -0
  117. package/dist/services/defaults.service.d.ts.map +1 -0
  118. package/dist/services/defaults.service.js +55 -0
  119. package/dist/services/defaults.service.js.map +1 -0
  120. package/dist/services/email.service.d.ts +33 -0
  121. package/dist/services/email.service.d.ts.map +1 -0
  122. package/dist/services/email.service.js +219 -0
  123. package/dist/services/email.service.js.map +1 -0
  124. package/dist/services/media.service.d.ts +20 -0
  125. package/dist/services/media.service.d.ts.map +1 -0
  126. package/dist/services/media.service.js +49 -0
  127. package/dist/services/media.service.js.map +1 -0
  128. package/dist/services/population.service.d.ts +20 -0
  129. package/dist/services/population.service.d.ts.map +1 -0
  130. package/dist/services/population.service.js +168 -0
  131. package/dist/services/population.service.js.map +1 -0
  132. package/dist/types/index.d.ts +1749 -0
  133. package/dist/types/index.d.ts.map +1 -0
  134. package/dist/types/index.js +3 -0
  135. package/dist/types/index.js.map +1 -0
  136. package/dist/utils/config.d.ts +8 -0
  137. package/dist/utils/config.d.ts.map +1 -0
  138. package/dist/utils/config.js +153 -0
  139. package/dist/utils/config.js.map +1 -0
  140. package/dist/utils/hooks.d.ts +41 -0
  141. package/dist/utils/hooks.d.ts.map +1 -0
  142. package/dist/utils/hooks.js +169 -0
  143. package/dist/utils/hooks.js.map +1 -0
  144. package/dist/utils/openapi.d.ts +6 -0
  145. package/dist/utils/openapi.d.ts.map +1 -0
  146. package/dist/utils/openapi.js +331 -0
  147. package/dist/utils/openapi.js.map +1 -0
  148. package/dist/utils/parse-where.d.ts +63 -0
  149. package/dist/utils/parse-where.d.ts.map +1 -0
  150. package/dist/utils/parse-where.js +196 -0
  151. package/dist/utils/parse-where.js.map +1 -0
  152. package/dist/utils/readonly-db.d.ts +9 -0
  153. package/dist/utils/readonly-db.d.ts.map +1 -0
  154. package/dist/utils/readonly-db.js +21 -0
  155. package/dist/utils/readonly-db.js.map +1 -0
  156. package/dist/utils/setup-prompt.d.ts +11 -0
  157. package/dist/utils/setup-prompt.d.ts.map +1 -0
  158. package/dist/utils/setup-prompt.js +863 -0
  159. package/dist/utils/setup-prompt.js.map +1 -0
  160. package/dist/utils/swagger.d.ts +5 -0
  161. package/dist/utils/swagger.d.ts.map +1 -0
  162. package/dist/utils/swagger.js +51 -0
  163. package/dist/utils/swagger.js.map +1 -0
  164. package/dist/utils/where-sanitizer.d.ts +10 -0
  165. package/dist/utils/where-sanitizer.d.ts.map +1 -0
  166. package/dist/utils/where-sanitizer.js +63 -0
  167. package/dist/utils/where-sanitizer.js.map +1 -0
  168. package/dist/where-sanitizer-DQIWTQZW.js +50 -0
  169. package/package.json +1 -1
package/dist/server.cjs CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,6 +30,64 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/utils/where-sanitizer.ts
34
+ var where_sanitizer_exports = {};
35
+ __export(where_sanitizer_exports, {
36
+ sanitizeWhereClause: () => sanitizeWhereClause
37
+ });
38
+ function sanitizeWhereClause(where, fields) {
39
+ const fieldMap = /* @__PURE__ */ new Map();
40
+ function addFieldsToMap(fList, prefix = "") {
41
+ for (const f of fList) {
42
+ if ("name" in f && f.name) {
43
+ fieldMap.set(prefix + f.name, f);
44
+ }
45
+ if (f.type === "object" || f.type === "row" || f.type === "array") {
46
+ if ("fields" in f && Array.isArray(f.fields)) {
47
+ addFieldsToMap(f.fields, f.type === "object" || f.type === "array" ? `${prefix}${f.name}.` : prefix);
48
+ }
49
+ }
50
+ }
51
+ }
52
+ addFieldsToMap(fields);
53
+ function walk(node) {
54
+ const result = {};
55
+ for (const [key, value] of Object.entries(node)) {
56
+ if (key === "AND" || key === "OR") {
57
+ if (Array.isArray(value)) {
58
+ const processed = value.map((v) => walk(v)).filter((v) => Object.keys(v).length > 0);
59
+ if (processed.length > 0) {
60
+ result[key] = processed;
61
+ }
62
+ }
63
+ continue;
64
+ }
65
+ const fieldDef = fieldMap.get(key);
66
+ if (!fieldDef) continue;
67
+ if (fieldDef.admin?.filterable === false) continue;
68
+ if (NEVER_FILTERABLE_TYPES.includes(fieldDef.type)) continue;
69
+ result[key] = value;
70
+ }
71
+ return result;
72
+ }
73
+ return walk(where);
74
+ }
75
+ var NEVER_FILTERABLE_TYPES;
76
+ var init_where_sanitizer = __esm({
77
+ "src/utils/where-sanitizer.ts"() {
78
+ "use strict";
79
+ NEVER_FILTERABLE_TYPES = [
80
+ "password",
81
+ "richText",
82
+ "json",
83
+ "file",
84
+ "image",
85
+ "join",
86
+ "collapsible"
87
+ ];
88
+ }
89
+ });
90
+
30
91
  // src/server.ts
31
92
  var server_exports = {};
32
93
  __export(server_exports, {
@@ -109,7 +170,33 @@ var PopulationService = class {
109
170
  }
110
171
  const populatedDoc = { ...data };
111
172
  for (const field of fields) {
112
- if (field.type === "join") continue;
173
+ if (field.type === "join" && field.collection && field.on && currentDepth === 0) {
174
+ const targetCollection = this.collections.find((c) => c.slug === field.collection);
175
+ if (targetCollection) {
176
+ const docId = populatedDoc.id;
177
+ if (docId) {
178
+ const where = { [field.on]: { equals: docId } };
179
+ const joinLimit = field.limit ?? 10;
180
+ const result = await this.db.find({
181
+ collection: field.collection,
182
+ where,
183
+ limit: joinLimit
184
+ });
185
+ const populatedDocs = await this.populate({
186
+ data: result.docs.map((doc) => DefaultsService.apply(targetCollection.fields, doc)),
187
+ fields: targetCollection.fields,
188
+ currentDepth: 1,
189
+ maxDepth
190
+ });
191
+ populatedDoc[field.name] = {
192
+ docs: populatedDocs,
193
+ hasNextPage: result.page * result.limit < result.total,
194
+ totalDocs: result.total
195
+ };
196
+ }
197
+ }
198
+ continue;
199
+ }
113
200
  if (field.type === "row" && field.fields) {
114
201
  const rowPopulated = await this.populate({ data, fields: field.fields, currentDepth, maxDepth });
115
202
  Object.assign(populatedDoc, rowPopulated);
@@ -294,7 +381,7 @@ async function runCollectionHooks(hooks, args, options = {}) {
294
381
  }
295
382
  return currentPayload;
296
383
  }
297
- async function executeFieldBeforeChange(fields, data, originalDoc, user) {
384
+ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
298
385
  if (!data || typeof data !== "object") return data;
299
386
  const result = { ...data };
300
387
  for (const field of fields) {
@@ -308,7 +395,8 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user) {
308
395
  value: updatedValue,
309
396
  originalDoc: originalDoc ?? void 0,
310
397
  data: result,
311
- user
398
+ user,
399
+ db
312
400
  });
313
401
  }
314
402
  result[field.name] = updatedValue;
@@ -319,7 +407,8 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user) {
319
407
  field.fields,
320
408
  updatedValue,
321
409
  origValue,
322
- user
410
+ user,
411
+ db
323
412
  );
324
413
  } else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
325
414
  const arrayResult = [];
@@ -327,7 +416,7 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user) {
327
416
  const item = updatedValue[i];
328
417
  const origItem = Array.isArray(origValue) ? origValue[i] : null;
329
418
  arrayResult.push(
330
- await executeFieldBeforeChange(field.fields, item, origItem, user)
419
+ await executeFieldBeforeChange(field.fields, item, origItem, user, db)
331
420
  );
332
421
  }
333
422
  result[field.name] = arrayResult;
@@ -345,7 +434,8 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user) {
345
434
  blockConfig.fields,
346
435
  blockData,
347
436
  origBlock,
348
- user
437
+ user,
438
+ db
349
439
  )
350
440
  );
351
441
  } else {
@@ -358,7 +448,7 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user) {
358
448
  }
359
449
  return result;
360
450
  }
361
- async function executeFieldAfterRead(fields, doc, user) {
451
+ async function executeFieldAfterRead(fields, doc, user, db) {
362
452
  if (!doc || typeof doc !== "object") return doc;
363
453
  const result = { ...doc };
364
454
  for (const field of fields) {
@@ -370,7 +460,8 @@ async function executeFieldAfterRead(fields, doc, user) {
370
460
  updatedValue = await hook({
371
461
  value: updatedValue,
372
462
  doc: result,
373
- user
463
+ user,
464
+ db
374
465
  });
375
466
  }
376
467
  result[field.name] = updatedValue;
@@ -380,7 +471,8 @@ async function executeFieldAfterRead(fields, doc, user) {
380
471
  result[field.name] = await executeFieldAfterRead(
381
472
  field.fields,
382
473
  updatedValue,
383
- user
474
+ user,
475
+ db
384
476
  );
385
477
  } else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
386
478
  const arrayResult = [];
@@ -389,7 +481,8 @@ async function executeFieldAfterRead(fields, doc, user) {
389
481
  await executeFieldAfterRead(
390
482
  field.fields,
391
483
  item,
392
- user
484
+ user,
485
+ db
393
486
  )
394
487
  );
395
488
  }
@@ -406,7 +499,8 @@ async function executeFieldAfterRead(fields, doc, user) {
406
499
  await executeFieldAfterRead(
407
500
  blockConfig.fields,
408
501
  typedBlock,
409
- user
502
+ user,
503
+ db
410
504
  )
411
505
  );
412
506
  } else {
@@ -420,6 +514,23 @@ async function executeFieldAfterRead(fields, doc, user) {
420
514
  return result;
421
515
  }
422
516
 
517
+ // src/utils/readonly-db.ts
518
+ var WRITE_METHODS = ["create", "update", "delete", "updateGlobal", "execute"];
519
+ function createReadonlyDb(db) {
520
+ return new Proxy(db, {
521
+ get(target, prop) {
522
+ if (WRITE_METHODS.includes(prop)) {
523
+ return () => {
524
+ throw new Error(
525
+ `[dyrected] Write operation "${String(prop)}" is not allowed in this hook phase. Use afterChange/afterDelete hooks for write operations.`
526
+ );
527
+ };
528
+ }
529
+ return Reflect.get(target, prop);
530
+ }
531
+ });
532
+ }
533
+
423
534
  // src/controllers/collection.controller.ts
424
535
  var CollectionController = class {
425
536
  collection;
@@ -430,6 +541,7 @@ var CollectionController = class {
430
541
  const config = c.get("config");
431
542
  const db = config.db;
432
543
  if (!db) return c.json({ message: "Database not configured" }, 500);
544
+ const readonlyDb = createReadonlyDb(db);
433
545
  const limit = Number(c.req.query("limit")) || 10;
434
546
  const page = Number(c.req.query("page")) || 1;
435
547
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 2;
@@ -443,10 +555,22 @@ var CollectionController = class {
443
555
  } catch {
444
556
  }
445
557
  }
558
+ if (where) {
559
+ if (this.collection.admin?.filterable === false) {
560
+ where = void 0;
561
+ } else {
562
+ const { sanitizeWhereClause: sanitizeWhereClause2 } = await Promise.resolve().then(() => (init_where_sanitizer(), where_sanitizer_exports));
563
+ where = sanitizeWhereClause2(where, this.collection.fields);
564
+ if (Object.keys(where).length === 0) {
565
+ where = void 0;
566
+ }
567
+ }
568
+ }
446
569
  const beforeReadResult = await runCollectionHooks(this.collection.hooks?.beforeRead, {
447
570
  req: c.req,
448
571
  query: where,
449
- user
572
+ user,
573
+ db: readonlyDb
450
574
  });
451
575
  if (beforeReadResult !== void 0) {
452
576
  where = beforeReadResult;
@@ -477,9 +601,10 @@ var CollectionController = class {
477
601
  const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
478
602
  doc,
479
603
  req: c.req,
480
- user
604
+ user,
605
+ db: readonlyDb
481
606
  });
482
- const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user);
607
+ const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user, readonlyDb);
483
608
  processedDocs.push(docWithFieldHooks);
484
609
  }
485
610
  result.docs = processedDocs;
@@ -493,6 +618,7 @@ var CollectionController = class {
493
618
  const config = c.get("config");
494
619
  const db = config.db;
495
620
  if (!db) return c.json({ message: "Database not configured" }, 500);
621
+ const readonlyDb = createReadonlyDb(db);
496
622
  const id = c.req.param("id");
497
623
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
498
624
  const user = c.get("user");
@@ -503,9 +629,10 @@ var CollectionController = class {
503
629
  const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
504
630
  doc: docWithDefaults,
505
631
  req: c.req,
506
- user
632
+ user,
633
+ db: readonlyDb
507
634
  });
508
- const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user);
635
+ const docWithFieldHooks = await executeFieldAfterRead(this.collection.fields, docWithCollectionHooks, user, readonlyDb);
509
636
  if (depth > 0 && docWithFieldHooks) {
510
637
  const populationService = new PopulationService(db, config.collections);
511
638
  const populatedDoc = await populationService.populate({
@@ -522,6 +649,7 @@ var CollectionController = class {
522
649
  const config = c.get("config");
523
650
  const db = config.db;
524
651
  if (!db) return c.json({ message: "Database not configured" }, 500);
652
+ const readonlyDb = createReadonlyDb(db);
525
653
  const contentType = c.req.header("Content-Type") || "";
526
654
  if (contentType.toLowerCase().includes("multipart/form-data")) {
527
655
  return this.upload(c);
@@ -539,12 +667,13 @@ var CollectionController = class {
539
667
  if (this.collection.auth && data.password) {
540
668
  data.password = await hashPassword(data.password);
541
669
  }
542
- data = await executeFieldBeforeChange(this.collection.fields, data, null, user);
670
+ data = await executeFieldBeforeChange(this.collection.fields, data, null, user, readonlyDb);
543
671
  data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
544
672
  data,
545
673
  req: c.req,
546
674
  user,
547
- operation: "create"
675
+ operation: "create",
676
+ db: readonlyDb
548
677
  });
549
678
  const doc = await db.create({ collection: this.collection.slug, data });
550
679
  if (this.collection.audit && db) {
@@ -561,20 +690,25 @@ var CollectionController = class {
561
690
  doc,
562
691
  user,
563
692
  req: c.req,
564
- operation: "create"
693
+ operation: "create",
694
+ db
565
695
  }, { isolated: true });
566
696
  const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
567
697
  doc,
568
698
  req: c.req,
569
- user
699
+ user,
700
+ db: readonlyDb
570
701
  });
571
- const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
702
+ const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user, readonlyDb);
572
703
  return c.json(finalDoc, 201);
573
704
  }
574
705
  async upload(c) {
575
706
  const config = c.get("config");
576
707
  const storage = config.storage;
577
708
  if (!storage) return c.json({ message: "Storage not configured" }, 500);
709
+ const db = config.db;
710
+ if (!db) return c.json({ message: "Database not configured" }, 500);
711
+ const readonlyDb = createReadonlyDb(db);
578
712
  const formData = await c.req.formData();
579
713
  const file = formData.get("file");
580
714
  if (!file) return c.json({ message: "No file uploaded" }, 400);
@@ -604,14 +738,15 @@ var CollectionController = class {
604
738
  createdBy: user?.sub ?? null,
605
739
  updatedBy: user?.sub ?? null
606
740
  };
607
- data = await executeFieldBeforeChange(this.collection.fields, data, null, user);
741
+ data = await executeFieldBeforeChange(this.collection.fields, data, null, user, readonlyDb);
608
742
  data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
609
743
  data,
610
744
  req: c.req,
611
745
  user,
612
- operation: "create"
746
+ operation: "create",
747
+ db: readonlyDb
613
748
  });
614
- const doc = await config.db.create({
749
+ const doc = await db.create({
615
750
  collection: this.collection.slug,
616
751
  data
617
752
  });
@@ -619,20 +754,23 @@ var CollectionController = class {
619
754
  doc,
620
755
  user,
621
756
  req: c.req,
622
- operation: "create"
757
+ operation: "create",
758
+ db
623
759
  }, { isolated: true });
624
760
  const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
625
761
  doc,
626
762
  req: c.req,
627
- user
763
+ user,
764
+ db: readonlyDb
628
765
  });
629
- const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
766
+ const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user, readonlyDb);
630
767
  return c.json(finalDoc, 201);
631
768
  }
632
769
  async update(c) {
633
770
  const config = c.get("config");
634
771
  const db = config.db;
635
772
  if (!db) return c.json({ message: "Database not configured" }, 500);
773
+ const readonlyDb = createReadonlyDb(db);
636
774
  const id = c.req.param("id");
637
775
  if (!id) return c.json({ message: "Missing ID" }, 400);
638
776
  const body = await c.req.json();
@@ -653,13 +791,14 @@ var CollectionController = class {
653
791
  if (this.collection.audit) {
654
792
  before = originalDoc;
655
793
  }
656
- data = await executeFieldBeforeChange(this.collection.fields, data, originalDoc, user);
794
+ data = await executeFieldBeforeChange(this.collection.fields, data, originalDoc, user, readonlyDb);
657
795
  data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
658
796
  data,
659
797
  doc: originalDoc,
660
798
  req: c.req,
661
799
  user,
662
- operation: "update"
800
+ operation: "update",
801
+ db: readonlyDb
663
802
  });
664
803
  const doc = await db.update({ collection: this.collection.slug, id, data });
665
804
  if (this.collection.audit && db) {
@@ -677,14 +816,16 @@ var CollectionController = class {
677
816
  previousDoc: originalDoc,
678
817
  user,
679
818
  req: c.req,
680
- operation: "update"
819
+ operation: "update",
820
+ db
681
821
  }, { isolated: true });
682
822
  const readDoc = await runCollectionHooks(this.collection.hooks?.afterRead, {
683
823
  doc,
684
824
  req: c.req,
685
- user
825
+ user,
826
+ db: readonlyDb
686
827
  });
687
- const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user);
828
+ const finalDoc = await executeFieldAfterRead(this.collection.fields, readDoc, user, readonlyDb);
688
829
  return c.json(finalDoc);
689
830
  }
690
831
  /**
@@ -762,6 +903,7 @@ var CollectionController = class {
762
903
  const config = c.get("config");
763
904
  const db = config.db;
764
905
  if (!db) return c.json({ message: "Database not configured" }, 500);
906
+ const readonlyDb = createReadonlyDb(db);
765
907
  const id = c.req.param("id");
766
908
  if (!id) return c.json({ message: "Missing ID" }, 400);
767
909
  const user = c.get("user");
@@ -775,7 +917,8 @@ var CollectionController = class {
775
917
  id,
776
918
  doc,
777
919
  user,
778
- req: c.req
920
+ req: c.req,
921
+ db: readonlyDb
779
922
  });
780
923
  await db.delete({ collection: this.collection.slug, id });
781
924
  if (this.collection.audit && db) {
@@ -792,7 +935,8 @@ var CollectionController = class {
792
935
  id,
793
936
  doc,
794
937
  user,
795
- req: c.req
938
+ req: c.req,
939
+ db
796
940
  }, { isolated: true });
797
941
  return c.json({ message: "Deleted" });
798
942
  }
@@ -800,6 +944,7 @@ var CollectionController = class {
800
944
  const config = c.get("config");
801
945
  const db = config.db;
802
946
  if (!db) return c.json({ message: "Database not configured" }, 500);
947
+ const readonlyDb = createReadonlyDb(db);
803
948
  const user = c.get("user");
804
949
  let ids = [];
805
950
  try {
@@ -831,7 +976,8 @@ var CollectionController = class {
831
976
  id,
832
977
  doc,
833
978
  user,
834
- req: c.req
979
+ req: c.req,
980
+ db: readonlyDb
835
981
  });
836
982
  await db.delete({ collection: this.collection.slug, id });
837
983
  deleted.push(id);
@@ -849,7 +995,8 @@ var CollectionController = class {
849
995
  id,
850
996
  doc,
851
997
  user,
852
- req: c.req
998
+ req: c.req,
999
+ db
853
1000
  }, { isolated: true });
854
1001
  } catch (err) {
855
1002
  failed.push({ id, error: err?.message ?? "Unknown error" });
@@ -894,13 +1041,15 @@ var GlobalController = class {
894
1041
  const config = c.get("config");
895
1042
  const db = config.db;
896
1043
  if (!db) return c.json({ message: "Database not configured" }, 500);
1044
+ const readonlyDb = createReadonlyDb(db);
897
1045
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
898
1046
  const user = c.get("user");
899
1047
  let query = void 0;
900
1048
  const beforeReadResult = await runCollectionHooks(this.global.hooks?.beforeRead, {
901
1049
  req: c.req,
902
1050
  query,
903
- user
1051
+ user,
1052
+ db: readonlyDb
904
1053
  });
905
1054
  if (beforeReadResult !== void 0) {
906
1055
  query = beforeReadResult;
@@ -916,9 +1065,10 @@ var GlobalController = class {
916
1065
  const docWithCollectionHooks = await runCollectionHooks(this.global.hooks?.afterRead, {
917
1066
  doc: dataWithDefaults,
918
1067
  req: c.req,
919
- user
1068
+ user,
1069
+ db: readonlyDb
920
1070
  });
921
- const docWithFieldHooks = await executeFieldAfterRead(this.global.fields, docWithCollectionHooks, user);
1071
+ const docWithFieldHooks = await executeFieldAfterRead(this.global.fields, docWithCollectionHooks, user, readonlyDb);
922
1072
  if (depth > 0 && docWithFieldHooks) {
923
1073
  const populationService = new PopulationService(db, config.collections);
924
1074
  const populatedData = await populationService.populate({
@@ -932,18 +1082,21 @@ var GlobalController = class {
932
1082
  return c.json(docWithFieldHooks);
933
1083
  }
934
1084
  async update(c) {
935
- const db = c.get("config").db;
1085
+ const config = c.get("config");
1086
+ const db = config.db;
936
1087
  if (!db) return c.json({ message: "Database not configured" }, 500);
1088
+ const readonlyDb = createReadonlyDb(db);
937
1089
  const body = await c.req.json();
938
1090
  const user = c.get("user");
939
1091
  const originalDoc = await db.getGlobal({ slug: this.global.slug }) || {};
940
- let data = await executeFieldBeforeChange(this.global.fields, body, originalDoc, user);
1092
+ let data = await executeFieldBeforeChange(this.global.fields, body, originalDoc, user, readonlyDb);
941
1093
  data = await runCollectionHooks(this.global.hooks?.beforeChange, {
942
1094
  data,
943
1095
  doc: originalDoc,
944
1096
  req: c.req,
945
1097
  user,
946
- operation: "update"
1098
+ operation: "update",
1099
+ db: readonlyDb
947
1100
  });
948
1101
  const updated = await db.updateGlobal({ slug: this.global.slug, data });
949
1102
  await runCollectionHooks(this.global.hooks?.afterChange, {
@@ -951,14 +1104,16 @@ var GlobalController = class {
951
1104
  previousDoc: originalDoc,
952
1105
  user,
953
1106
  req: c.req,
954
- operation: "update"
1107
+ operation: "update",
1108
+ db
955
1109
  }, { isolated: true });
956
1110
  const readDoc = await runCollectionHooks(this.global.hooks?.afterRead, {
957
1111
  doc: updated,
958
1112
  req: c.req,
959
- user
1113
+ user,
1114
+ db: readonlyDb
960
1115
  });
961
- const finalDoc = await executeFieldAfterRead(this.global.fields, readDoc, user);
1116
+ const finalDoc = await executeFieldAfterRead(this.global.fields, readDoc, user, readonlyDb);
962
1117
  return c.json(finalDoc);
963
1118
  }
964
1119
  async seed(c) {
@@ -2207,6 +2362,9 @@ function registerRoutes(app, config) {
2207
2362
  hasMany: f.hasMany,
2208
2363
  fields: f.fields,
2209
2364
  blocks: f.blocks,
2365
+ collection: f.collection,
2366
+ on: f.on,
2367
+ limit: f.limit,
2210
2368
  admin: f.admin,
2211
2369
  access: {
2212
2370
  read: await serializeAccess(f.access?.read),
@@ -2235,6 +2393,9 @@ function registerRoutes(app, config) {
2235
2393
  hasMany: f.hasMany,
2236
2394
  fields: f.fields,
2237
2395
  blocks: f.blocks,
2396
+ collection: f.collection,
2397
+ on: f.on,
2398
+ limit: f.limit,
2238
2399
  admin: f.admin,
2239
2400
  access: {
2240
2401
  read: await serializeAccess(f.access?.read),
@@ -2327,6 +2488,37 @@ function registerRoutes(app, config) {
2327
2488
  app.get("/api/docs", (c) => {
2328
2489
  return c.html(getSwaggerHtml());
2329
2490
  });
2491
+ app.get("/api/preferences/:key", requireAuth(), async (c) => {
2492
+ const db = config.db;
2493
+ const user = c.get("user");
2494
+ const key = c.req.param("key");
2495
+ if (!db) return c.json({ message: "Database not configured" }, 500);
2496
+ if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
2497
+ if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
2498
+ const doc = await db.findOne({ collection: user.collection, id: user.sub });
2499
+ if (!doc) return c.json({ error: true, message: "User not found." }, 404);
2500
+ const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? doc.__preferences : {};
2501
+ return c.json({ key, value: preferences[key] ?? null });
2502
+ });
2503
+ app.put("/api/preferences/:key", requireAuth(), async (c) => {
2504
+ const db = config.db;
2505
+ const user = c.get("user");
2506
+ const key = c.req.param("key");
2507
+ if (!db) return c.json({ message: "Database not configured" }, 500);
2508
+ if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
2509
+ if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
2510
+ const body = await c.req.json().catch(() => ({}));
2511
+ const doc = await db.findOne({ collection: user.collection, id: user.sub });
2512
+ if (!doc) return c.json({ error: true, message: "User not found." }, 404);
2513
+ const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? doc.__preferences : {};
2514
+ const nextPreferences = { ...preferences, [key]: body.value };
2515
+ await db.update({
2516
+ collection: user.collection,
2517
+ id: user.sub,
2518
+ data: { __preferences: nextPreferences }
2519
+ });
2520
+ return c.json({ key, value: body.value });
2521
+ });
2330
2522
  app.get("/api/media/:filename{.+$}", async (c) => {
2331
2523
  const mediaController = new MediaController("media");
2332
2524
  return mediaController.serve(c);
package/dist/server.d.cts CHANGED
@@ -1,10 +1,28 @@
1
- import { p as DyrectedContext, D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-C-HgyliB.cjs';
2
- export { M as createDyrectedApp } from './app-C-HgyliB.cjs';
1
+ import * as hono_types from 'hono/types';
3
2
  import * as hono from 'hono';
4
3
  import { Hono, Context } from 'hono';
4
+ import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './index-Bp7PDOYG.cjs';
5
5
  import * as hono_utils_http_status from 'hono/utils/http-status';
6
6
  import * as hono_utils_types from 'hono/utils/types';
7
- import 'hono/types';
7
+
8
+ interface DyrectedContext {
9
+ Variables: {
10
+ config: DyrectedConfig;
11
+ siteId?: string;
12
+ workspaceId?: string;
13
+ /** Decoded JWT payload set by requireAuth() or optionalAuth() middleware. */
14
+ user?: {
15
+ sub: string;
16
+ email: string;
17
+ collection: string;
18
+ [key: string]: any;
19
+ };
20
+ };
21
+ }
22
+ /**
23
+ * Create the main Dyrected Hono application.
24
+ */
25
+ declare function createDyrectedApp(rawConfig: DyrectedConfig): Promise<Hono<DyrectedContext, hono_types.BlankSchema, "/">>;
8
26
 
9
27
  /**
10
28
  * Register dynamic routes based on the provided configuration.
@@ -301,4 +319,4 @@ declare class PreviewController {
301
319
  }, 401, "json">)>;
302
320
  }
303
321
 
304
- export { AuthController, CollectionController, DyrectedContext, GlobalController, MediaController, PreviewController, registerRoutes };
322
+ export { AuthController, CollectionController, type DyrectedContext, GlobalController, MediaController, PreviewController, createDyrectedApp, registerRoutes };
package/dist/server.d.ts CHANGED
@@ -1,10 +1,28 @@
1
- import { p as DyrectedContext, D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-C-HgyliB.js';
2
- export { M as createDyrectedApp } from './app-C-HgyliB.js';
1
+ import * as hono_types from 'hono/types';
3
2
  import * as hono from 'hono';
4
3
  import { Hono, Context } from 'hono';
4
+ import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './index-Bp7PDOYG.js';
5
5
  import * as hono_utils_http_status from 'hono/utils/http-status';
6
6
  import * as hono_utils_types from 'hono/utils/types';
7
- import 'hono/types';
7
+
8
+ interface DyrectedContext {
9
+ Variables: {
10
+ config: DyrectedConfig;
11
+ siteId?: string;
12
+ workspaceId?: string;
13
+ /** Decoded JWT payload set by requireAuth() or optionalAuth() middleware. */
14
+ user?: {
15
+ sub: string;
16
+ email: string;
17
+ collection: string;
18
+ [key: string]: any;
19
+ };
20
+ };
21
+ }
22
+ /**
23
+ * Create the main Dyrected Hono application.
24
+ */
25
+ declare function createDyrectedApp(rawConfig: DyrectedConfig): Promise<Hono<DyrectedContext, hono_types.BlankSchema, "/">>;
8
26
 
9
27
  /**
10
28
  * Register dynamic routes based on the provided configuration.
@@ -301,4 +319,4 @@ declare class PreviewController {
301
319
  }, 401, "json">)>;
302
320
  }
303
321
 
304
- export { AuthController, CollectionController, DyrectedContext, GlobalController, MediaController, PreviewController, registerRoutes };
322
+ export { AuthController, CollectionController, type DyrectedContext, GlobalController, MediaController, PreviewController, createDyrectedApp, registerRoutes };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,kCAAkC,CAAC;AACjD,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,qCAAqC,CAAC"}