@dyrected/core 2.5.42 → 2.5.44

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/server.cjs CHANGED
@@ -30,7 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
- // src/utils/where-sanitizer.ts
33
+ // src/utils/where-sanitizer.js
34
34
  var where_sanitizer_exports = {};
35
35
  __export(where_sanitizer_exports, {
36
36
  sanitizeWhereClause: () => sanitizeWhereClause
@@ -67,9 +67,12 @@ function sanitizeWhereClause(where, fields) {
67
67
  continue;
68
68
  }
69
69
  const fieldDef = fieldMap.get(key);
70
- if (!fieldDef) continue;
71
- if (fieldDef.admin?.filterable === false) continue;
72
- if (NEVER_FILTERABLE_TYPES.includes(fieldDef.type)) continue;
70
+ if (!fieldDef)
71
+ continue;
72
+ if (fieldDef.admin?.filterable === false)
73
+ continue;
74
+ if (NEVER_FILTERABLE_TYPES.includes(fieldDef.type))
75
+ continue;
73
76
  result[key] = value;
74
77
  }
75
78
  return result;
@@ -78,7 +81,7 @@ function sanitizeWhereClause(where, fields) {
78
81
  }
79
82
  var NEVER_FILTERABLE_TYPES;
80
83
  var init_where_sanitizer = __esm({
81
- "src/utils/where-sanitizer.ts"() {
84
+ "src/utils/where-sanitizer.js"() {
82
85
  "use strict";
83
86
  NEVER_FILTERABLE_TYPES = [
84
87
  "password",
@@ -105,12 +108,12 @@ __export(server_exports, {
105
108
  });
106
109
  module.exports = __toCommonJS(server_exports);
107
110
 
108
- // src/app.ts
111
+ // src/app.js
109
112
  var import_hono = require("hono");
110
113
  var import_cors = require("hono/cors");
111
114
  var import_request_id = require("hono/request-id");
112
115
 
113
- // src/services/defaults.service.ts
116
+ // src/services/defaults.service.js
114
117
  var DefaultsService = class {
115
118
  /**
116
119
  * Recursively apply default values to a data object based on field definitions.
@@ -118,12 +121,14 @@ var DefaultsService = class {
118
121
  static apply(fields, data = {}) {
119
122
  const result = { ...data || {} };
120
123
  fields.forEach((field) => {
121
- if (field.type === "join") return;
124
+ if (field.type === "join")
125
+ return;
122
126
  if (field.type === "row" && field.fields) {
123
127
  Object.assign(result, this.apply(field.fields, data));
124
128
  return;
125
129
  }
126
- if (!field.name) return;
130
+ if (!field.name)
131
+ return;
127
132
  let value = result[field.name];
128
133
  if ((value === void 0 || value === null) && field.renameTo) {
129
134
  const legacyValue = result[field.renameTo];
@@ -136,9 +141,12 @@ var DefaultsService = class {
136
141
  if (field.defaultValue !== void 0) {
137
142
  result[field.name] = field.defaultValue;
138
143
  } else {
139
- if (field.type === "boolean") result[field.name] = false;
140
- else if (field.type === "array") result[field.name] = [];
141
- else if (field.type === "multiSelect") result[field.name] = [];
144
+ if (field.type === "boolean")
145
+ result[field.name] = false;
146
+ else if (field.type === "array")
147
+ result[field.name] = [];
148
+ else if (field.type === "multiSelect")
149
+ result[field.name] = [];
142
150
  else if (field.type === "object") {
143
151
  result[field.name] = this.apply(field.fields || [], {});
144
152
  }
@@ -153,7 +161,7 @@ var DefaultsService = class {
153
161
  }
154
162
  };
155
163
 
156
- // src/services/population.service.ts
164
+ // src/services/population.service.js
157
165
  var PopulationService = class {
158
166
  db;
159
167
  collections;
@@ -206,29 +214,31 @@ var PopulationService = class {
206
214
  Object.assign(populatedDoc, rowPopulated);
207
215
  continue;
208
216
  }
209
- if (!field.name) continue;
217
+ if (!field.name)
218
+ continue;
210
219
  const value = populatedDoc[field.name];
211
220
  if (field.type === "relationship" && field.relationTo && value) {
212
221
  const relatedCollection = this.collections.find((c) => c.slug === field.relationTo);
213
- if (!relatedCollection) continue;
222
+ if (!relatedCollection)
223
+ continue;
214
224
  if (Array.isArray(value)) {
215
- populatedDoc[field.name] = await Promise.all(
216
- value.map(async (id) => {
217
- if (!id) return id;
218
- let doc = id;
219
- if (typeof id === "string") {
220
- doc = await this.db.findOne({ collection: field.relationTo, id });
221
- }
222
- if (!doc || typeof doc !== "object") return id;
223
- const docWithDefaults = DefaultsService.apply(relatedCollection.fields, doc);
224
- return this.populate({
225
- data: docWithDefaults,
226
- fields: relatedCollection.fields,
227
- currentDepth: currentDepth + 1,
228
- maxDepth
229
- });
230
- })
231
- );
225
+ populatedDoc[field.name] = await Promise.all(value.map(async (id) => {
226
+ if (!id)
227
+ return id;
228
+ let doc = id;
229
+ if (typeof id === "string") {
230
+ doc = await this.db.findOne({ collection: field.relationTo, id });
231
+ }
232
+ if (!doc || typeof doc !== "object")
233
+ return id;
234
+ const docWithDefaults = DefaultsService.apply(relatedCollection.fields, doc);
235
+ return this.populate({
236
+ data: docWithDefaults,
237
+ fields: relatedCollection.fields,
238
+ currentDepth: currentDepth + 1,
239
+ maxDepth
240
+ });
241
+ }));
232
242
  } else if (value) {
233
243
  let doc = value;
234
244
  if (typeof value === "string") {
@@ -277,18 +287,17 @@ var PopulationService = class {
277
287
  });
278
288
  }
279
289
  if (field.type === "blocks" && field.blocks && Array.isArray(value)) {
280
- populatedDoc[field.name] = await Promise.all(
281
- value.map(async (blockData) => {
282
- const blockConfig = field.blocks.find((b) => b.slug === blockData.blockType);
283
- if (!blockConfig) return blockData;
284
- return this.populate({
285
- data: blockData,
286
- fields: blockConfig.fields,
287
- currentDepth,
288
- maxDepth
289
- });
290
- })
291
- );
290
+ populatedDoc[field.name] = await Promise.all(value.map(async (blockData) => {
291
+ const blockConfig = field.blocks.find((b) => b.slug === blockData.blockType);
292
+ if (!blockConfig)
293
+ return blockData;
294
+ return this.populate({
295
+ data: blockData,
296
+ fields: blockConfig.fields,
297
+ currentDepth,
298
+ maxDepth
299
+ });
300
+ }));
292
301
  }
293
302
  }
294
303
  return populatedDoc;
@@ -297,7 +306,8 @@ var PopulationService = class {
297
306
  * Helper to populate a PaginatedResult
298
307
  */
299
308
  async populateResult(result, fields, maxDepth) {
300
- if (maxDepth <= 0) return result;
309
+ if (maxDepth <= 0)
310
+ return result;
301
311
  const populatedDocs = await this.populate({
302
312
  data: result.docs,
303
313
  fields,
@@ -311,7 +321,7 @@ var PopulationService = class {
311
321
  }
312
322
  };
313
323
 
314
- // src/services/audit.service.ts
324
+ // src/services/audit.service.js
315
325
  var AuditService = class {
316
326
  /**
317
327
  * Writes a single entry to the __audit collection.
@@ -339,7 +349,7 @@ var AuditService = class {
339
349
  }
340
350
  };
341
351
 
342
- // src/auth/password.ts
352
+ // src/auth/password.js
343
353
  var import_node_util = require("util");
344
354
  var import_node_crypto = require("crypto");
345
355
  var scryptAsync = (0, import_node_util.promisify)(import_node_crypto.scrypt);
@@ -352,14 +362,16 @@ async function hashPassword(plain) {
352
362
  }
353
363
  async function verifyPassword(plain, stored) {
354
364
  const [salt, storedHash] = stored.split(":");
355
- if (!salt || !storedHash) return false;
365
+ if (!salt || !storedHash)
366
+ return false;
356
367
  const derivedKey = await scryptAsync(plain, salt, KEY_LEN);
357
368
  const storedBuffer = Buffer.from(storedHash, "hex");
358
- if (derivedKey.length !== storedBuffer.length) return false;
369
+ if (derivedKey.length !== storedBuffer.length)
370
+ return false;
359
371
  return (0, import_node_crypto.timingSafeEqual)(derivedKey, storedBuffer);
360
372
  }
361
373
 
362
- // src/utils/hooks.ts
374
+ // src/utils/hooks.js
363
375
  async function runCollectionHooks(hooks, args, options = {}) {
364
376
  if (!hooks || hooks.length === 0) {
365
377
  return args.data ?? args.doc ?? void 0;
@@ -386,10 +398,12 @@ async function runCollectionHooks(hooks, args, options = {}) {
386
398
  return currentPayload;
387
399
  }
388
400
  async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
389
- if (!data || typeof data !== "object") return data;
401
+ if (!data || typeof data !== "object")
402
+ return data;
390
403
  const result = { ...data };
391
404
  for (const field of fields) {
392
- if (!field.name) continue;
405
+ if (!field.name)
406
+ continue;
393
407
  const value = result[field.name];
394
408
  const origValue = originalDoc?.[field.name];
395
409
  let updatedValue = value;
@@ -409,21 +423,13 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
409
423
  }
410
424
  if (updatedValue !== void 0 && updatedValue !== null) {
411
425
  if (field.type === "object" && field.fields) {
412
- result[field.name] = await executeFieldBeforeChange(
413
- field.fields,
414
- updatedValue,
415
- origValue,
416
- user,
417
- db
418
- );
426
+ result[field.name] = await executeFieldBeforeChange(field.fields, updatedValue, origValue, user, db);
419
427
  } else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
420
428
  const arrayResult = [];
421
429
  for (let i = 0; i < updatedValue.length; i++) {
422
430
  const item = updatedValue[i];
423
431
  const origItem = Array.isArray(origValue) ? origValue[i] : null;
424
- arrayResult.push(
425
- await executeFieldBeforeChange(field.fields, item, origItem, user, db)
426
- );
432
+ arrayResult.push(await executeFieldBeforeChange(field.fields, item, origItem, user, db));
427
433
  }
428
434
  result[field.name] = arrayResult;
429
435
  } else if (field.type === "blocks" && field.blocks && Array.isArray(updatedValue)) {
@@ -431,19 +437,9 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
431
437
  for (let i = 0; i < updatedValue.length; i++) {
432
438
  const blockData = updatedValue[i];
433
439
  const origBlock = Array.isArray(origValue) ? origValue[i] : null;
434
- const blockConfig = field.blocks.find(
435
- (b) => b.slug === blockData.blockType
436
- );
440
+ const blockConfig = field.blocks.find((b) => b.slug === blockData.blockType);
437
441
  if (blockConfig) {
438
- blocksResult.push(
439
- await executeFieldBeforeChange(
440
- blockConfig.fields,
441
- blockData,
442
- origBlock,
443
- user,
444
- db
445
- )
446
- );
442
+ blocksResult.push(await executeFieldBeforeChange(blockConfig.fields, blockData, origBlock, user, db));
447
443
  } else {
448
444
  blocksResult.push(blockData);
449
445
  }
@@ -455,10 +451,12 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
455
451
  return result;
456
452
  }
457
453
  async function executeFieldAfterRead(fields, doc, user, db) {
458
- if (!doc || typeof doc !== "object") return doc;
454
+ if (!doc || typeof doc !== "object")
455
+ return doc;
459
456
  const result = { ...doc };
460
457
  for (const field of fields) {
461
- if (!field.name) continue;
458
+ if (!field.name)
459
+ continue;
462
460
  const value = result[field.name];
463
461
  let updatedValue = value;
464
462
  if (field.hooks?.afterRead) {
@@ -476,41 +474,20 @@ async function executeFieldAfterRead(fields, doc, user, db) {
476
474
  }
477
475
  if (updatedValue !== void 0 && updatedValue !== null) {
478
476
  if (field.type === "object" && field.fields) {
479
- result[field.name] = await executeFieldAfterRead(
480
- field.fields,
481
- updatedValue,
482
- user,
483
- db
484
- );
477
+ result[field.name] = await executeFieldAfterRead(field.fields, updatedValue, user, db);
485
478
  } else if (field.type === "array" && field.fields && Array.isArray(updatedValue)) {
486
479
  const arrayResult = [];
487
480
  for (const item of updatedValue) {
488
- arrayResult.push(
489
- await executeFieldAfterRead(
490
- field.fields,
491
- item,
492
- user,
493
- db
494
- )
495
- );
481
+ arrayResult.push(await executeFieldAfterRead(field.fields, item, user, db));
496
482
  }
497
483
  result[field.name] = arrayResult;
498
484
  } else if (field.type === "blocks" && field.blocks && Array.isArray(updatedValue)) {
499
485
  const blocksResult = [];
500
486
  for (const blockData of updatedValue) {
501
487
  const typedBlock = blockData;
502
- const blockConfig = field.blocks.find(
503
- (b) => b.slug === typedBlock.blockType
504
- );
488
+ const blockConfig = field.blocks.find((b) => b.slug === typedBlock.blockType);
505
489
  if (blockConfig) {
506
- blocksResult.push(
507
- await executeFieldAfterRead(
508
- blockConfig.fields,
509
- typedBlock,
510
- user,
511
- db
512
- )
513
- );
490
+ blocksResult.push(await executeFieldAfterRead(blockConfig.fields, typedBlock, user, db));
514
491
  } else {
515
492
  blocksResult.push(blockData);
516
493
  }
@@ -522,16 +499,14 @@ async function executeFieldAfterRead(fields, doc, user, db) {
522
499
  return result;
523
500
  }
524
501
 
525
- // src/utils/readonly-db.ts
502
+ // src/utils/readonly-db.js
526
503
  var WRITE_METHODS = ["create", "update", "delete", "updateGlobal", "execute"];
527
504
  function createReadonlyDb(db) {
528
505
  return new Proxy(db, {
529
506
  get(target, prop) {
530
507
  if (WRITE_METHODS.includes(prop)) {
531
508
  return () => {
532
- throw new Error(
533
- `[dyrected] Write operation "${String(prop)}" is not allowed in this hook phase. Use afterChange/afterDelete hooks for write operations.`
534
- );
509
+ throw new Error(`[dyrected] Write operation "${String(prop)}" is not allowed in this hook phase. Use afterChange/afterDelete hooks for write operations.`);
535
510
  };
536
511
  }
537
512
  return Reflect.get(target, prop);
@@ -539,11 +514,13 @@ function createReadonlyDb(db) {
539
514
  });
540
515
  }
541
516
 
542
- // src/auth/jexl.ts
517
+ // src/auth/jexl.js
543
518
  var import_jexl = __toESM(require("jexl"), 1);
544
519
  async function evaluateAccess(expression, context) {
545
- if (expression === void 0 || expression === null) return false;
546
- if (typeof expression === "boolean") return expression;
520
+ if (expression === void 0 || expression === null)
521
+ return false;
522
+ if (typeof expression === "boolean")
523
+ return expression;
547
524
  try {
548
525
  const result = await import_jexl.default.eval(expression, context);
549
526
  return !!result;
@@ -553,7 +530,7 @@ async function evaluateAccess(expression, context) {
553
530
  }
554
531
  }
555
532
 
556
- // src/workflows.ts
533
+ // src/workflows.js
557
534
  var WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
558
535
  var LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
559
536
  function publicMetadata(meta) {
@@ -564,17 +541,18 @@ function workflowCapabilities(workflow, user) {
564
541
  const capabilities = new Set(Array.isArray(user?.capabilities) ? user.capabilities : []);
565
542
  const roles = Array.isArray(user?.roles) ? user.roles : [];
566
543
  for (const mapping of workflow.roles ?? []) {
567
- if (roles.includes(mapping.role)) mapping.capabilities.forEach((capability) => capabilities.add(capability));
544
+ if (roles.includes(mapping.role))
545
+ mapping.capabilities.forEach((capability) => capabilities.add(capability));
568
546
  }
569
547
  return capabilities;
570
548
  }
571
549
  function canViewWorkflowDraft(workflow, user) {
572
- if (!user) return false;
550
+ if (!user)
551
+ return false;
573
552
  const capabilities = workflowCapabilities(workflow, user);
574
- if (capabilities.has("entry.edit")) return true;
575
- return workflow.transitions.some(
576
- (transition) => (transition.requiredCapabilities ?? []).some((capability) => capabilities.has(capability))
577
- );
553
+ if (capabilities.has("entry.edit"))
554
+ return true;
555
+ return workflow.transitions.some((transition) => (transition.requiredCapabilities ?? []).some((capability) => capabilities.has(capability)));
578
556
  }
579
557
  function availableWorkflowTransitions(workflow, state, user) {
580
558
  const capabilities = workflowCapabilities(workflow, user);
@@ -591,10 +569,12 @@ function initializeWorkflowDocument(data, workflow) {
591
569
  }
592
570
  function materializeWorkflowDocument(doc, workflow, user) {
593
571
  const meta = doc.__workflow;
594
- if (!meta) return doc;
572
+ if (!meta)
573
+ return doc;
595
574
  const { __published, __workflow, ...working } = doc;
596
575
  if (!canViewWorkflowDraft(workflow, user)) {
597
- if (!__published || typeof __published !== "object") return null;
576
+ if (!__published || typeof __published !== "object")
577
+ return null;
598
578
  return { id: doc.id, ...__published, _workflow: publicMetadata(meta) };
599
579
  }
600
580
  return {
@@ -626,13 +606,15 @@ async function persistEvent(db, event) {
626
606
  }
627
607
  async function dispatchLifecycleEvent(config, event) {
628
608
  const db = config.db;
629
- if (!db || !config.events?.handlers.length) return;
609
+ if (!db || !config.events?.handlers.length)
610
+ return;
630
611
  const maxAttempts = config.events.maxAttempts ?? 8;
631
612
  const retryDelayMs = config.events.retryDelayMs ?? 1e3;
632
613
  const attempts = event.attempts + 1;
633
614
  try {
634
615
  await db.update({ collection: LIFECYCLE_EVENTS_COLLECTION, id: event.id, data: { status: "processing", attempts } });
635
- for (const handler of config.events.handlers) await handler({ ...event, status: "processing", attempts });
616
+ for (const handler of config.events.handlers)
617
+ await handler({ ...event, status: "processing", attempts });
636
618
  await db.update({
637
619
  collection: LIFECYCLE_EVENTS_COLLECTION,
638
620
  id: event.id,
@@ -656,7 +638,8 @@ async function saveWorkflowDraft(args) {
656
638
  const { config, collection, id, originalDoc, data, user } = args;
657
639
  const db = config.db;
658
640
  const workflow = collection.workflow;
659
- if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
641
+ if (!db.transaction)
642
+ throw new Error(`The configured database adapter does not support workflow transactions.`);
660
643
  const previous = originalDoc.__workflow;
661
644
  const event = createLifecycleEvent({
662
645
  name: "revision.created",
@@ -681,7 +664,8 @@ async function saveWorkflowDraft(args) {
681
664
  async function createWorkflowDocument(args) {
682
665
  const { config, collection, data, user } = args;
683
666
  const db = config.db;
684
- if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
667
+ if (!db.transaction)
668
+ throw new Error(`The configured database adapter does not support workflow transactions.`);
685
669
  let event;
686
670
  const doc = await db.transaction(async (tx) => {
687
671
  const created = await tx.create({ collection: collection.slug, data });
@@ -702,11 +686,14 @@ async function transitionWorkflow(args) {
702
686
  const { config, collection, id, transitionName, expectedRevision, comment, user, req } = args;
703
687
  const db = config.db;
704
688
  const workflow = collection.workflow;
705
- if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
689
+ if (!db.transaction)
690
+ throw new Error(`The configured database adapter does not support workflow transactions.`);
706
691
  const original = await db.findOne({ collection: collection.slug, id });
707
- if (!original) throw Object.assign(new Error("Not Found"), { statusCode: 404 });
692
+ if (!original)
693
+ throw Object.assign(new Error("Not Found"), { statusCode: 404 });
708
694
  const meta = original.__workflow;
709
- if (!meta) throw Object.assign(new Error("Entry has no workflow metadata"), { statusCode: 409 });
695
+ if (!meta)
696
+ throw Object.assign(new Error("Entry has no workflow metadata"), { statusCode: 409 });
710
697
  if (expectedRevision !== void 0 && expectedRevision !== meta.revision) {
711
698
  throw Object.assign(new Error("This entry changed since it was loaded. Refresh before transitioning it."), { statusCode: 409 });
712
699
  }
@@ -722,7 +709,8 @@ async function transitionWorkflow(args) {
722
709
  throw Object.assign(new Error(`A comment is required for "${transition.label}".`), { statusCode: 400 });
723
710
  }
724
711
  const hookContext = { transition, from: meta.state, to: transition.to, doc: original, user, comment, req, db };
725
- for (const hook of workflow.hooks?.beforeTransition ?? []) await hook(hookContext);
712
+ for (const hook of workflow.hooks?.beforeTransition ?? [])
713
+ await hook(hookContext);
726
714
  const events = [createLifecycleEvent({
727
715
  name: "workflow.transitioned",
728
716
  collection: collection.slug,
@@ -738,7 +726,8 @@ async function transitionWorkflow(args) {
738
726
  }
739
727
  const updated = await db.transaction(async (tx) => {
740
728
  const locked = await tx.findOne({ collection: collection.slug, id });
741
- if (!locked) throw Object.assign(new Error("Not Found"), { statusCode: 404 });
729
+ if (!locked)
730
+ throw Object.assign(new Error("Not Found"), { statusCode: 404 });
742
731
  const lockedMeta = locked.__workflow;
743
732
  if (lockedMeta.revision !== meta.revision || lockedMeta.state !== meta.state || expectedRevision !== void 0 && lockedMeta.revision !== expectedRevision) {
744
733
  throw Object.assign(new Error("This entry changed since it was loaded. Refresh before transitioning it."), { statusCode: 409 });
@@ -752,20 +741,24 @@ async function transitionWorkflow(args) {
752
741
  ...transition.unpublish ? { publishedRevision: void 0, publishedAt: void 0, publishedBy: void 0 } : {}
753
742
  };
754
743
  const data = { __workflow: nextMeta };
755
- if (targetState.published) data.__published = working;
756
- if (transition.unpublish) data.__published = null;
744
+ if (targetState.published)
745
+ data.__published = working;
746
+ if (transition.unpublish)
747
+ data.__published = null;
757
748
  const next = await tx.update({ collection: collection.slug, id, data });
758
749
  await tx.create({
759
750
  collection: WORKFLOW_HISTORY_COLLECTION,
760
751
  data: { collection: collection.slug, documentId: id, transition: transition.name, from: lockedMeta.state, to: transition.to, revision: lockedMeta.revision, comment: comment ?? null, actorId: user?.sub ?? null, createdAt: now }
761
752
  });
762
- for (const event of events) await persistEvent(tx, event);
753
+ for (const event of events)
754
+ await persistEvent(tx, event);
763
755
  if (collection.audit) {
764
756
  await tx.create({ collection: "__audit", data: { collection: collection.slug, documentId: id, operation: "workflow.transition", user: user?.sub ?? null, timestamp: now, changes: JSON.stringify({ transition: transition.name, from: lockedMeta.state, to: transition.to, revision: lockedMeta.revision }) } });
765
757
  }
766
758
  return next;
767
759
  });
768
- for (const event of events) void dispatchLifecycleEvent(config, event);
760
+ for (const event of events)
761
+ void dispatchLifecycleEvent(config, event);
769
762
  for (const hook of workflow.hooks?.afterTransition ?? []) {
770
763
  try {
771
764
  await hook({ ...hookContext, doc: updated, event: events[0] });
@@ -776,16 +769,61 @@ async function transitionWorkflow(args) {
776
769
  return updated;
777
770
  }
778
771
 
779
- // src/controllers/collection.controller.ts
772
+ // src/controllers/collection.controller.js
780
773
  var CollectionController = class {
781
774
  collection;
782
775
  constructor(collection) {
783
776
  this.collection = collection;
784
777
  }
778
+ getDelegatedProvider(c) {
779
+ const config = c.get("config");
780
+ const authCollectionSlug = config.adminAuth?.collectionSlug || "__admins";
781
+ if (this.collection.slug !== authCollectionSlug) {
782
+ return null;
783
+ }
784
+ return config.adminAuth?.providers?.find((p) => p.members) || null;
785
+ }
785
786
  async find(c) {
786
787
  const config = c.get("config");
787
788
  const db = config.db;
788
- if (!db) return c.json({ message: "Database not configured" }, 500);
789
+ if (!db)
790
+ return c.json({ message: "Database not configured" }, 500);
791
+ const provider = this.getDelegatedProvider(c);
792
+ if (provider && provider.members?.list) {
793
+ const limit2 = Number(c.req.query("limit")) || 10;
794
+ const page2 = Number(c.req.query("page")) || 1;
795
+ const sort2 = c.req.query("sort") || void 0;
796
+ let where2 = void 0;
797
+ const whereRaw2 = c.req.query("where");
798
+ if (whereRaw2) {
799
+ try {
800
+ where2 = JSON.parse(decodeURIComponent(whereRaw2));
801
+ } catch {
802
+ }
803
+ }
804
+ const paginatedResult = await provider.members.list({ limit: limit2, page: page2, sort: sort2, where: where2, req: c.req });
805
+ const mappedDocs = [];
806
+ for (const m of paginatedResult.docs) {
807
+ let localId = m.id;
808
+ const localDoc = await db.find({
809
+ collection: this.collection.slug,
810
+ where: m.id ? { externalSubject: { equals: m.id } } : { email: { equals: m.email } },
811
+ limit: 1
812
+ });
813
+ if (localDoc.docs[0]) {
814
+ localId = localDoc.docs[0].id;
815
+ }
816
+ mappedDocs.push({
817
+ ...m,
818
+ id: localId,
819
+ externalSubject: m.id
820
+ });
821
+ }
822
+ return c.json({
823
+ ...paginatedResult,
824
+ docs: mappedDocs
825
+ });
826
+ }
789
827
  const readonlyDb = createReadonlyDb(db);
790
828
  const limit = Number(c.req.query("limit")) || 10;
791
829
  const page = Number(c.req.query("page")) || 1;
@@ -843,11 +881,13 @@ var CollectionController = class {
843
881
  where
844
882
  });
845
883
  }
846
- result.docs = result.docs.map((doc) => this.collection.workflow ? materializeWorkflowDocument(doc, this.collection.workflow, user) : doc).filter((doc) => doc !== null).map((doc) => DefaultsService.apply(this.collection.fields, doc));
884
+ result.docs = result.docs.map((doc) => this.collection.workflow ? materializeWorkflowDocument(doc, this.collection.workflow, user) : doc).filter((doc) => doc !== null);
847
885
  const processedDocs = [];
886
+ const readonlyDbForHooks = createReadonlyDb(db);
848
887
  for (const doc of result.docs) {
888
+ const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
849
889
  const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
850
- doc,
890
+ doc: docWithDefaults,
851
891
  req: c.req,
852
892
  user,
853
893
  db: readonlyDb
@@ -865,17 +905,43 @@ var CollectionController = class {
865
905
  async findOne(c) {
866
906
  const config = c.get("config");
867
907
  const db = config.db;
868
- if (!db) return c.json({ message: "Database not configured" }, 500);
908
+ if (!db)
909
+ return c.json({ message: "Database not configured" }, 500);
910
+ const provider = this.getDelegatedProvider(c);
911
+ if (provider && (provider.members?.get || provider.members?.list)) {
912
+ const id2 = c.req.param("id");
913
+ if (!id2)
914
+ return c.json({ message: "Missing ID" }, 400);
915
+ const localDoc = await db.findOne({ collection: this.collection.slug, id: id2 });
916
+ const externalSubject = localDoc?.externalSubject || id2;
917
+ let member = null;
918
+ if (provider.members.get) {
919
+ member = await provider.members.get({ externalSubject, req: c.req });
920
+ } else if (provider.members.list) {
921
+ const listResult = await provider.members.list({ req: c.req });
922
+ member = listResult.docs.find((m) => m.id === externalSubject) || null;
923
+ }
924
+ if (!member)
925
+ return c.json({ message: "Not Found" }, 404);
926
+ return c.json({
927
+ ...member,
928
+ id: localDoc ? localDoc.id : member.id,
929
+ externalSubject: member.id
930
+ });
931
+ }
869
932
  const readonlyDb = createReadonlyDb(db);
870
933
  const id = c.req.param("id");
871
934
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
872
935
  const user = c.get("user");
873
- if (!id) return c.json({ message: "Missing ID" }, 400);
936
+ if (!id)
937
+ return c.json({ message: "Missing ID" }, 400);
874
938
  let doc = await db.findOne({ collection: this.collection.slug, id });
875
- if (!doc) return c.json({ message: "Not Found" }, 404);
939
+ if (!doc)
940
+ return c.json({ message: "Not Found" }, 404);
876
941
  if (this.collection.workflow) {
877
942
  doc = materializeWorkflowDocument(doc, this.collection.workflow, user);
878
- if (!doc) return c.json({ message: "Not Found" }, 404);
943
+ if (!doc)
944
+ return c.json({ message: "Not Found" }, 404);
879
945
  }
880
946
  const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
881
947
  const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
@@ -900,7 +966,22 @@ var CollectionController = class {
900
966
  async create(c) {
901
967
  const config = c.get("config");
902
968
  const db = config.db;
903
- if (!db) return c.json({ message: "Database not configured" }, 500);
969
+ if (!db)
970
+ return c.json({ message: "Database not configured" }, 500);
971
+ const provider = this.getDelegatedProvider(c);
972
+ if (provider && provider.members?.create) {
973
+ const contentType2 = c.req.header("Content-Type") || "";
974
+ if (contentType2.toLowerCase().includes("multipart/form-data")) {
975
+ return this.upload(c);
976
+ }
977
+ const body2 = await c.req.json();
978
+ const member = await provider.members.create({ data: body2, req: c.req });
979
+ return c.json({
980
+ ...member,
981
+ id: member.id,
982
+ externalSubject: member.id
983
+ }, 201);
984
+ }
904
985
  const readonlyDb = createReadonlyDb(db);
905
986
  const contentType = c.req.header("Content-Type") || "";
906
987
  if (contentType.toLowerCase().includes("multipart/form-data")) {
@@ -961,13 +1042,16 @@ var CollectionController = class {
961
1042
  async upload(c) {
962
1043
  const config = c.get("config");
963
1044
  const storage = config.storage;
964
- if (!storage) return c.json({ message: "Storage not configured" }, 500);
1045
+ if (!storage)
1046
+ return c.json({ message: "Storage not configured" }, 500);
965
1047
  const db = config.db;
966
- if (!db) return c.json({ message: "Database not configured" }, 500);
1048
+ if (!db)
1049
+ return c.json({ message: "Database not configured" }, 500);
967
1050
  const readonlyDb = createReadonlyDb(db);
968
1051
  const formData = await c.req.formData();
969
1052
  const file = formData.get("file");
970
- if (!file) return c.json({ message: "No file uploaded" }, 400);
1053
+ if (!file)
1054
+ return c.json({ message: "No file uploaded" }, 400);
971
1055
  const buffer = new Uint8Array(await file.arrayBuffer());
972
1056
  const siteId = c.get("siteId");
973
1057
  const workspaceId = c.get("workspaceId");
@@ -1026,10 +1110,27 @@ var CollectionController = class {
1026
1110
  async update(c) {
1027
1111
  const config = c.get("config");
1028
1112
  const db = config.db;
1029
- if (!db) return c.json({ message: "Database not configured" }, 500);
1113
+ if (!db)
1114
+ return c.json({ message: "Database not configured" }, 500);
1115
+ const provider = this.getDelegatedProvider(c);
1116
+ if (provider && provider.members?.update) {
1117
+ const id2 = c.req.param("id");
1118
+ if (!id2)
1119
+ return c.json({ message: "Missing ID" }, 400);
1120
+ const body2 = await c.req.json();
1121
+ const localDoc = await db.findOne({ collection: this.collection.slug, id: id2 });
1122
+ const externalSubject = localDoc?.externalSubject || id2;
1123
+ const member = await provider.members.update({ externalSubject, data: body2, req: c.req });
1124
+ return c.json({
1125
+ ...member,
1126
+ id: localDoc ? localDoc.id : member.id,
1127
+ externalSubject: member.id
1128
+ });
1129
+ }
1030
1130
  const readonlyDb = createReadonlyDb(db);
1031
1131
  const id = c.req.param("id");
1032
- if (!id) return c.json({ message: "Missing ID" }, 400);
1132
+ if (!id)
1133
+ return c.json({ message: "Missing ID" }, 400);
1033
1134
  const body = await c.req.json();
1034
1135
  const user = c.get("user");
1035
1136
  let data = { ...body };
@@ -1043,7 +1144,8 @@ var CollectionController = class {
1043
1144
  updatedBy: user?.sub ?? null
1044
1145
  });
1045
1146
  const originalDoc = await db.findOne({ collection: this.collection.slug, id });
1046
- if (!originalDoc) return c.json({ message: "Not Found" }, 404);
1147
+ if (!originalDoc)
1148
+ return c.json({ message: "Not Found" }, 404);
1047
1149
  let before = null;
1048
1150
  if (this.collection.audit) {
1049
1151
  before = originalDoc;
@@ -1087,8 +1189,10 @@ var CollectionController = class {
1087
1189
  }
1088
1190
  async transition(c) {
1089
1191
  const config = c.get("config");
1090
- if (!config.db) return c.json({ message: "Database not configured" }, 500);
1091
- if (!this.collection.workflow) return c.json({ message: "Workflows are not enabled for this collection" }, 404);
1192
+ if (!config.db)
1193
+ return c.json({ message: "Database not configured" }, 500);
1194
+ if (!this.collection.workflow)
1195
+ return c.json({ message: "Workflows are not enabled for this collection" }, 404);
1092
1196
  const id = c.req.param("id");
1093
1197
  const transitionName = c.req.param("transition");
1094
1198
  const body = await c.req.json().catch(() => ({}));
@@ -1111,12 +1215,16 @@ var CollectionController = class {
1111
1215
  }
1112
1216
  async workflowHistory(c) {
1113
1217
  const config = c.get("config");
1114
- if (!config.db) return c.json({ message: "Database not configured" }, 500);
1115
- if (!this.collection.workflow) return c.json({ message: "Workflows are not enabled for this collection" }, 404);
1218
+ if (!config.db)
1219
+ return c.json({ message: "Database not configured" }, 500);
1220
+ if (!this.collection.workflow)
1221
+ return c.json({ message: "Workflows are not enabled for this collection" }, 404);
1116
1222
  const documentId = c.req.param("id");
1117
- if (!documentId) return c.json({ message: "Missing ID" }, 400);
1223
+ if (!documentId)
1224
+ return c.json({ message: "Missing ID" }, 400);
1118
1225
  const document = await config.db.findOne({ collection: this.collection.slug, id: documentId });
1119
- if (!document) return c.json({ message: "Not Found" }, 404);
1226
+ if (!document)
1227
+ return c.json({ message: "Not Found" }, 404);
1120
1228
  const readAccess = this.collection.access?.read;
1121
1229
  if (readAccess !== void 0 && readAccess !== null) {
1122
1230
  const args = { user: c.get("user"), req: c.req, doc: document };
@@ -1130,7 +1238,8 @@ var CollectionController = class {
1130
1238
  });
1131
1239
  allowed = match.total > 0;
1132
1240
  }
1133
- if (!allowed) return c.json({ error: true, message: `Access denied: read on ${this.collection.slug}` }, 403);
1241
+ if (!allowed)
1242
+ return c.json({ error: true, message: `Access denied: read on ${this.collection.slug}` }, 403);
1134
1243
  }
1135
1244
  const result = await config.db.find({
1136
1245
  collection: WORKFLOW_HISTORY_COLLECTION,
@@ -1154,14 +1263,17 @@ var CollectionController = class {
1154
1263
  async changePassword(c) {
1155
1264
  const config = c.get("config");
1156
1265
  const db = config.db;
1157
- if (!db) return c.json({ message: "Database not configured" }, 500);
1266
+ if (!db)
1267
+ return c.json({ message: "Database not configured" }, 500);
1158
1268
  if (!this.collection.auth) {
1159
1269
  return c.json({ message: "This collection does not support authentication" }, 400);
1160
1270
  }
1161
1271
  const id = c.req.param("id");
1162
- if (!id) return c.json({ message: "Missing ID" }, 400);
1272
+ if (!id)
1273
+ return c.json({ message: "Missing ID" }, 400);
1163
1274
  const user = c.get("user");
1164
- if (!user) return c.json({ message: "Authentication required" }, 401);
1275
+ if (!user)
1276
+ return c.json({ message: "Authentication required" }, 401);
1165
1277
  const body = await c.req.json().catch(() => null);
1166
1278
  const { oldPassword, newPassword, confirmPassword } = body ?? {};
1167
1279
  if (!newPassword) {
@@ -1183,7 +1295,8 @@ var CollectionController = class {
1183
1295
  return c.json({ message: "Current password is required" }, 400);
1184
1296
  }
1185
1297
  const existing = await db.findOne({ collection: this.collection.slug, id });
1186
- if (!existing) return c.json({ message: "User not found" }, 404);
1298
+ if (!existing)
1299
+ return c.json({ message: "User not found" }, 404);
1187
1300
  const valid = await verifyPassword(oldPassword, existing.password);
1188
1301
  if (!valid) {
1189
1302
  return c.json({ message: "Invalid current password" }, 400);
@@ -1214,13 +1327,26 @@ var CollectionController = class {
1214
1327
  async delete(c) {
1215
1328
  const config = c.get("config");
1216
1329
  const db = config.db;
1217
- if (!db) return c.json({ message: "Database not configured" }, 500);
1330
+ if (!db)
1331
+ return c.json({ message: "Database not configured" }, 500);
1332
+ const provider = this.getDelegatedProvider(c);
1333
+ if (provider && provider.members?.delete) {
1334
+ const id2 = c.req.param("id");
1335
+ if (!id2)
1336
+ return c.json({ message: "Missing ID" }, 400);
1337
+ const localDoc = await db.findOne({ collection: this.collection.slug, id: id2 });
1338
+ const externalSubject = localDoc?.externalSubject || id2;
1339
+ await provider.members.delete({ externalSubject, req: c.req });
1340
+ return c.json({ message: "Deleted" });
1341
+ }
1218
1342
  const readonlyDb = createReadonlyDb(db);
1219
1343
  const id = c.req.param("id");
1220
- if (!id) return c.json({ message: "Missing ID" }, 400);
1344
+ if (!id)
1345
+ return c.json({ message: "Missing ID" }, 400);
1221
1346
  const user = c.get("user");
1222
1347
  const doc = await db.findOne({ collection: this.collection.slug, id });
1223
- if (!doc) return c.json({ message: "Not Found" }, 404);
1348
+ if (!doc)
1349
+ return c.json({ message: "Not Found" }, 404);
1224
1350
  let before = null;
1225
1351
  if (this.collection.audit) {
1226
1352
  before = doc;
@@ -1255,7 +1381,8 @@ var CollectionController = class {
1255
1381
  async deleteMany(c) {
1256
1382
  const config = c.get("config");
1257
1383
  const db = config.db;
1258
- if (!db) return c.json({ message: "Database not configured" }, 500);
1384
+ if (!db)
1385
+ return c.json({ message: "Database not configured" }, 500);
1259
1386
  const readonlyDb = createReadonlyDb(db);
1260
1387
  const user = c.get("user");
1261
1388
  let ids = [];
@@ -1270,7 +1397,8 @@ var CollectionController = class {
1270
1397
  const raw = c.req.queries("ids") ?? c.req.queries("ids[]") ?? [];
1271
1398
  ids = raw.filter(Boolean);
1272
1399
  }
1273
- if (!ids.length) return c.json({ message: "No IDs provided" }, 400);
1400
+ if (!ids.length)
1401
+ return c.json({ message: "No IDs provided" }, 400);
1274
1402
  const deleted = [];
1275
1403
  const failed = [];
1276
1404
  for (const id of ids) {
@@ -1323,7 +1451,8 @@ var CollectionController = class {
1323
1451
  async seed(c) {
1324
1452
  const config = c.get("config");
1325
1453
  const db = config.db;
1326
- if (!db) return c.json({ message: "Database not configured" }, 500);
1454
+ if (!db)
1455
+ return c.json({ message: "Database not configured" }, 500);
1327
1456
  const body = await c.req.json();
1328
1457
  const initialData = body.data;
1329
1458
  if (!initialData || !Array.isArray(initialData)) {
@@ -1343,7 +1472,7 @@ var CollectionController = class {
1343
1472
  }
1344
1473
  };
1345
1474
 
1346
- // src/controllers/global.controller.ts
1475
+ // src/controllers/global.controller.js
1347
1476
  var GlobalController = class {
1348
1477
  global;
1349
1478
  constructor(global) {
@@ -1352,7 +1481,8 @@ var GlobalController = class {
1352
1481
  async get(c) {
1353
1482
  const config = c.get("config");
1354
1483
  const db = config.db;
1355
- if (!db) return c.json({ message: "Database not configured" }, 500);
1484
+ if (!db)
1485
+ return c.json({ message: "Database not configured" }, 500);
1356
1486
  const readonlyDb = createReadonlyDb(db);
1357
1487
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
1358
1488
  const user = c.get("user");
@@ -1396,7 +1526,8 @@ var GlobalController = class {
1396
1526
  async update(c) {
1397
1527
  const config = c.get("config");
1398
1528
  const db = config.db;
1399
- if (!db) return c.json({ message: "Database not configured" }, 500);
1529
+ if (!db)
1530
+ return c.json({ message: "Database not configured" }, 500);
1400
1531
  const readonlyDb = createReadonlyDb(db);
1401
1532
  const body = await c.req.json();
1402
1533
  const user = c.get("user");
@@ -1431,7 +1562,8 @@ var GlobalController = class {
1431
1562
  async seed(c) {
1432
1563
  const config = c.get("config");
1433
1564
  const db = config.db;
1434
- if (!db) return c.json({ message: "Database not configured" }, 500);
1565
+ if (!db)
1566
+ return c.json({ message: "Database not configured" }, 500);
1435
1567
  const body = await c.req.json();
1436
1568
  const initialData = body.data;
1437
1569
  if (!initialData) {
@@ -1447,20 +1579,23 @@ var GlobalController = class {
1447
1579
  }
1448
1580
  };
1449
1581
  function isFunctionallyEmpty(obj) {
1450
- if (obj === null || obj === void 0 || obj === "") return true;
1582
+ if (obj === null || obj === void 0 || obj === "")
1583
+ return true;
1451
1584
  if (Array.isArray(obj)) {
1452
- if (obj.length === 0) return true;
1585
+ if (obj.length === 0)
1586
+ return true;
1453
1587
  return obj.every(isFunctionallyEmpty);
1454
1588
  }
1455
1589
  if (typeof obj === "object") {
1456
1590
  const keys = Object.keys(obj);
1457
- if (keys.length === 0) return true;
1591
+ if (keys.length === 0)
1592
+ return true;
1458
1593
  return keys.every((key) => isFunctionallyEmpty(obj[key]));
1459
1594
  }
1460
1595
  return false;
1461
1596
  }
1462
1597
 
1463
- // src/controllers/media.controller.ts
1598
+ // src/controllers/media.controller.js
1464
1599
  var MediaController = class {
1465
1600
  collection;
1466
1601
  constructor(collection = "media") {
@@ -1540,7 +1675,8 @@ var MediaController = class {
1540
1675
  }
1541
1676
  }
1542
1677
  const db = config.db;
1543
- if (!db) return c.json({ message: "Database not configured" }, 500);
1678
+ if (!db)
1679
+ return c.json({ message: "Database not configured" }, 500);
1544
1680
  const doc = await db.create({
1545
1681
  collection: this.collection,
1546
1682
  data: finalFileData
@@ -1549,7 +1685,8 @@ var MediaController = class {
1549
1685
  }
1550
1686
  async find(c) {
1551
1687
  const db = c.get("config").db;
1552
- if (!db) return c.json({ message: "Database not configured" }, 500);
1688
+ if (!db)
1689
+ return c.json({ message: "Database not configured" }, 500);
1553
1690
  const limit = Number(c.req.query("limit")) || 10;
1554
1691
  const page = Number(c.req.query("page")) || 1;
1555
1692
  const result = await db.find({
@@ -1563,11 +1700,14 @@ var MediaController = class {
1563
1700
  const config = c.get("config");
1564
1701
  const storage = config.storage;
1565
1702
  const db = config.db;
1566
- if (!db) return c.json({ message: "Database not configured" }, 500);
1703
+ if (!db)
1704
+ return c.json({ message: "Database not configured" }, 500);
1567
1705
  const id = c.req.param("id");
1568
- if (!id) return c.json({ message: "Missing ID" }, 400);
1706
+ if (!id)
1707
+ return c.json({ message: "Missing ID" }, 400);
1569
1708
  const doc = await db.findOne({ collection: this.collection, id });
1570
- if (!doc) return c.json({ message: "Not Found" }, 404);
1709
+ if (!doc)
1710
+ return c.json({ message: "Not Found" }, 404);
1571
1711
  if (storage) {
1572
1712
  await storage.delete({ filename: doc.filename });
1573
1713
  if (doc.sizes) {
@@ -1588,26 +1728,26 @@ var MediaController = class {
1588
1728
  return c.json({ message: "Storage not configured for serving" }, 404);
1589
1729
  }
1590
1730
  const filename = c.req.param("filename");
1591
- if (!filename) return c.json({ message: "Missing filename" }, 400);
1731
+ if (!filename)
1732
+ return c.json({ message: "Missing filename" }, 400);
1592
1733
  let res = await storage.resolve({ filename });
1593
1734
  if (!res && !filename.includes("/")) {
1594
1735
  res = await storage.resolve({ filename: `default/${filename}` });
1595
1736
  }
1596
- if (!res) return c.json({ message: "Not Found" }, 404);
1737
+ if (!res)
1738
+ return c.json({ message: "Not Found" }, 404);
1597
1739
  c.header("Content-Type", res.mimeType);
1598
1740
  return c.body(res.buffer);
1599
1741
  }
1600
1742
  };
1601
1743
 
1602
- // src/auth/token.ts
1744
+ // src/auth/token.js
1603
1745
  var import_jose = require("jose");
1604
1746
  var import_node_util2 = require("util");
1605
1747
  function getSecret() {
1606
1748
  const secret = process.env.DYRECTED_JWT_SECRET || process.env.JWT_SECRET;
1607
1749
  if (!secret) {
1608
- throw new Error(
1609
- "[dyrected/core] DYRECTED_JWT_SECRET is not set. Add it to your environment variables to enable auth collections."
1610
- );
1750
+ throw new Error("[dyrected/core] DYRECTED_JWT_SECRET is not set. Add it to your environment variables to enable auth collections.");
1611
1751
  }
1612
1752
  return new import_node_util2.TextEncoder().encode(secret);
1613
1753
  }
@@ -1620,7 +1760,7 @@ async function verifyCollectionToken(token) {
1620
1760
  return payload;
1621
1761
  }
1622
1762
 
1623
- // src/services/email-template.ts
1763
+ // src/services/email-template.js
1624
1764
  var emailTokens = {
1625
1765
  colors: {
1626
1766
  canvas: "#f6f7f2",
@@ -1684,11 +1824,9 @@ function detailBox(content, monospace = false) {
1684
1824
  }
1685
1825
  function ctaButton(label, href) {
1686
1826
  const safeHref = safeHttpUrl(href);
1687
- if (!safeHref) return "";
1688
- return table(row(
1689
- `<a href="${safeHref}" style="display:inline-block;padding:13px 22px;border-radius:${emailTokens.radius.control};background:${emailTokens.colors.accent};font-family:${emailTokens.font};font-size:14px;line-height:1.2;font-weight:700;color:${emailTokens.colors.text};text-decoration:none">${escapeHtml(label)}</a>`,
1690
- "padding:8px 0 24px"
1691
- ), "width:auto");
1827
+ if (!safeHref)
1828
+ return "";
1829
+ return table(row(`<a href="${safeHref}" style="display:inline-block;padding:13px 22px;border-radius:${emailTokens.radius.control};background:${emailTokens.colors.accent};font-family:${emailTokens.font};font-size:14px;line-height:1.2;font-weight:700;color:${emailTokens.colors.text};text-decoration:none">${escapeHtml(label)}</a>`, "padding:8px 0 24px"), "width:auto");
1692
1830
  }
1693
1831
  function alertBox(content) {
1694
1832
  return table(row(escapeHtml(content), `padding:13px 16px;font-family:${emailTokens.font};font-size:13px;line-height:1.5;color:${emailTokens.colors.dangerText}`), `width:100%;background:${emailTokens.colors.dangerSurface};border:1px solid ${emailTokens.colors.dangerBorder};border-radius:${emailTokens.radius.control}`);
@@ -1699,23 +1837,19 @@ function layout({ preheader, title, content, footer }) {
1699
1837
  <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"></head>
1700
1838
  <body style="margin:0;padding:0;background:${emailTokens.colors.canvas}">
1701
1839
  <div style="display:none;max-height:0;overflow:hidden;opacity:0;color:transparent">${escapeHtml(preheader)}</div>
1702
- ${table(row(
1703
- table(
1704
- row("&nbsp;", `height:5px;background:${emailTokens.colors.accent};font-size:0;line-height:0`) + row(`${sectionLabel("Dyrected")}${heading(title)}`, "padding:30px 32px 24px") + row(content, "padding:0 32px 32px") + row(`${divider()}${paragraph(footer, "20px 0 6px")}${paragraph("Privacy: this message contains account-related information; please avoid forwarding it.", "0")}`, "padding:0 32px 28px"),
1705
- `width:100%;max-width:${emailTokens.width};background:${emailTokens.colors.surface};border:1px solid ${emailTokens.colors.border};border-radius:${emailTokens.radius.card};overflow:hidden`
1706
- ),
1707
- "padding:32px 12px"
1708
- ), `width:100%;background:${emailTokens.colors.canvas}`)}
1840
+ ${table(row(table(row("&nbsp;", `height:5px;background:${emailTokens.colors.accent};font-size:0;line-height:0`) + row(`${sectionLabel("Dyrected")}${heading(title)}`, "padding:30px 32px 24px") + row(content, "padding:0 32px 32px") + row(`${divider()}${paragraph(footer, "20px 0 6px")}${paragraph("Privacy: this message contains account-related information; please avoid forwarding it.", "0")}`, "padding:0 32px 28px"), `width:100%;max-width:${emailTokens.width};background:${emailTokens.colors.surface};border:1px solid ${emailTokens.colors.border};border-radius:${emailTokens.radius.card};overflow:hidden`), "padding:32px 12px"), `width:100%;background:${emailTokens.colors.canvas}`)}
1709
1841
  </body>
1710
1842
  </html>`;
1711
1843
  }
1712
1844
 
1713
- // src/services/email.service.ts
1845
+ // src/services/email.service.js
1714
1846
  var _devSend = null;
1715
1847
  var _devSendPromise = null;
1716
1848
  async function getDevSend() {
1717
- if (_devSend) return _devSend;
1718
- if (_devSendPromise) return _devSendPromise;
1849
+ if (_devSend)
1850
+ return _devSend;
1851
+ if (_devSendPromise)
1852
+ return _devSendPromise;
1719
1853
  _devSendPromise = (async () => {
1720
1854
  try {
1721
1855
  const nodemailer = await import("nodemailer");
@@ -1799,7 +1933,7 @@ function buildPasswordChangedEmail(config, args) {
1799
1933
  };
1800
1934
  }
1801
1935
 
1802
- // src/controllers/auth.controller.ts
1936
+ // src/controllers/auth.controller.js
1803
1937
  var AuthController = class {
1804
1938
  collection;
1805
1939
  constructor(collection) {
@@ -1811,7 +1945,8 @@ var AuthController = class {
1811
1945
  // ---------------------------------------------------------------------------
1812
1946
  async init(c) {
1813
1947
  const db = c.get("config").db;
1814
- if (!db) return c.json({ message: "Database not configured" }, 500);
1948
+ if (!db)
1949
+ return c.json({ message: "Database not configured" }, 500);
1815
1950
  const result = await db.find({
1816
1951
  collection: this.collection.slug,
1817
1952
  limit: 1
@@ -1827,7 +1962,8 @@ var AuthController = class {
1827
1962
  async registerFirstUser(c) {
1828
1963
  const config = c.get("config");
1829
1964
  const db = config.db;
1830
- if (!db) return c.json({ message: "Database not configured" }, 500);
1965
+ if (!db)
1966
+ return c.json({ message: "Database not configured" }, 500);
1831
1967
  const check = await db.find({
1832
1968
  collection: this.collection.slug,
1833
1969
  limit: 1
@@ -1855,9 +1991,7 @@ var AuthController = class {
1855
1991
  collection: this.collection.slug
1856
1992
  });
1857
1993
  const { subject, html } = buildWelcomeEmail(config, { email: body.email });
1858
- sendEmail(config, { to: body.email, subject, html }).catch(
1859
- (err) => console.error("[dyrected/core] Failed to send welcome email:", err)
1860
- );
1994
+ sendEmail(config, { to: body.email, subject, html }).catch((err) => console.error("[dyrected/core] Failed to send welcome email:", err));
1861
1995
  const { password: _, ...safeUser } = user;
1862
1996
  return c.json({ token, user: safeUser });
1863
1997
  }
@@ -1866,7 +2000,8 @@ var AuthController = class {
1866
2000
  // ---------------------------------------------------------------------------
1867
2001
  async login(c) {
1868
2002
  const db = c.get("config").db;
1869
- if (!db) return c.json({ message: "Database not configured" }, 500);
2003
+ if (!db)
2004
+ return c.json({ message: "Database not configured" }, 500);
1870
2005
  const body = await c.req.json().catch(() => null);
1871
2006
  if (!body?.email || !body?.password) {
1872
2007
  return c.json({ error: true, message: "email and password are required." }, 400);
@@ -1905,7 +2040,8 @@ var AuthController = class {
1905
2040
  // ---------------------------------------------------------------------------
1906
2041
  async me(c) {
1907
2042
  const db = c.get("config").db;
1908
- if (!db) return c.json({ message: "Database not configured" }, 500);
2043
+ if (!db)
2044
+ return c.json({ message: "Database not configured" }, 500);
1909
2045
  const requestUser = c.get("user");
1910
2046
  if (!requestUser) {
1911
2047
  return c.json({ error: true, message: "Authentication required." }, 401);
@@ -1940,7 +2076,8 @@ var AuthController = class {
1940
2076
  async forgotPassword(c) {
1941
2077
  const config = c.get("config");
1942
2078
  const db = config.db;
1943
- if (!db) return c.json({ message: "Database not configured" }, 500);
2079
+ if (!db)
2080
+ return c.json({ message: "Database not configured" }, 500);
1944
2081
  const body = await c.req.json().catch(() => null);
1945
2082
  if (!body?.email) {
1946
2083
  return c.json({ error: true, message: "email is required." }, 400);
@@ -1952,10 +2089,7 @@ var AuthController = class {
1952
2089
  });
1953
2090
  const user = result.docs[0];
1954
2091
  if (user) {
1955
- const resetToken = await signCollectionToken(
1956
- { sub: user.id, email: user.email, collection: this.collection.slug, purpose: "reset" },
1957
- "1h"
1958
- );
2092
+ const resetToken = await signCollectionToken({ sub: user.id, email: user.email, collection: this.collection.slug, purpose: "reset" }, "1h");
1959
2093
  const resetUrl = body?.resetUrl;
1960
2094
  const url = resetUrl ? `${resetUrl}${resetUrl.includes("?") ? "&" : "?"}token=${encodeURIComponent(resetToken)}` : void 0;
1961
2095
  try {
@@ -1978,7 +2112,8 @@ var AuthController = class {
1978
2112
  async resetPassword(c) {
1979
2113
  const config = c.get("config");
1980
2114
  const db = config.db;
1981
- if (!db) return c.json({ message: "Database not configured" }, 500);
2115
+ if (!db)
2116
+ return c.json({ message: "Database not configured" }, 500);
1982
2117
  const body = await c.req.json().catch(() => null);
1983
2118
  if (!body?.token || !body?.password) {
1984
2119
  return c.json({ error: true, message: "token and password are required." }, 400);
@@ -1999,9 +2134,7 @@ var AuthController = class {
1999
2134
  data: { password: hashedPassword }
2000
2135
  });
2001
2136
  const { subject, html } = buildPasswordChangedEmail(config, { email: payload.email });
2002
- sendEmail(config, { to: payload.email, subject, html }).catch(
2003
- (err) => console.error("[dyrected/core] Failed to send password-changed email:", err)
2004
- );
2137
+ sendEmail(config, { to: payload.email, subject, html }).catch((err) => console.error("[dyrected/core] Failed to send password-changed email:", err));
2005
2138
  return c.json({ success: true, message: "Password has been reset. You can now log in." });
2006
2139
  }
2007
2140
  // ---------------------------------------------------------------------------
@@ -2011,7 +2144,8 @@ var AuthController = class {
2011
2144
  async invite(c) {
2012
2145
  const config = c.get("config");
2013
2146
  const db = config.db;
2014
- if (!db) return c.json({ message: "Database not configured" }, 500);
2147
+ if (!db)
2148
+ return c.json({ message: "Database not configured" }, 500);
2015
2149
  const requestUser = c.get("user");
2016
2150
  if (!requestUser) {
2017
2151
  return c.json({ error: true, message: "Authentication required." }, 401);
@@ -2028,10 +2162,7 @@ var AuthController = class {
2028
2162
  if (existing.total > 0) {
2029
2163
  return c.json({ error: true, message: "An account with that email already exists." }, 409);
2030
2164
  }
2031
- const inviteToken = await signCollectionToken(
2032
- { sub: body.email, email: body.email, collection: this.collection.slug, purpose: "invite" },
2033
- "7d"
2034
- );
2165
+ const inviteToken = await signCollectionToken({ sub: body.email, email: body.email, collection: this.collection.slug, purpose: "invite" }, "7d");
2035
2166
  try {
2036
2167
  const { subject, html } = buildInviteEmail(config, {
2037
2168
  token: inviteToken,
@@ -2051,7 +2182,8 @@ var AuthController = class {
2051
2182
  async acceptInvite(c) {
2052
2183
  const config = c.get("config");
2053
2184
  const db = config.db;
2054
- if (!db) return c.json({ message: "Database not configured" }, 500);
2185
+ if (!db)
2186
+ return c.json({ message: "Database not configured" }, 500);
2055
2187
  const body = await c.req.json().catch(() => null);
2056
2188
  if (!body?.token || !body?.password) {
2057
2189
  return c.json({ error: true, message: "token and password are required." }, 400);
@@ -2086,20 +2218,18 @@ var AuthController = class {
2086
2218
  collection: this.collection.slug
2087
2219
  });
2088
2220
  const { subject, html } = buildWelcomeEmail(config, { email: inviteeEmail });
2089
- sendEmail(config, { to: inviteeEmail, subject, html }).catch(
2090
- (err) => console.error("[dyrected/core] Failed to send welcome email:", err)
2091
- );
2221
+ sendEmail(config, { to: inviteeEmail, subject, html }).catch((err) => console.error("[dyrected/core] Failed to send welcome email:", err));
2092
2222
  const { password: _, ...safeUser } = user;
2093
2223
  return c.json({ token: sessionToken, user: safeUser }, 201);
2094
2224
  }
2095
2225
  };
2096
2226
 
2097
- // src/controllers/admin-auth.controller.ts
2227
+ // src/controllers/admin-auth.controller.js
2098
2228
  var import_node_crypto2 = require("crypto");
2099
2229
  var import_node_util3 = require("util");
2100
2230
  var import_jose2 = require("jose");
2101
2231
 
2102
- // src/utils/admin-auth.ts
2232
+ // src/utils/admin-auth.js
2103
2233
  function getAdminAuthCollection(config) {
2104
2234
  const requestedSlug = config.adminAuth?.collectionSlug;
2105
2235
  if (requestedSlug) {
@@ -2123,16 +2253,17 @@ function getPublicAdminAuthConfig(adminAuth) {
2123
2253
  }
2124
2254
  function humanizeProviderName(id, type) {
2125
2255
  const cleaned = id.replace(/[-_]+/g, " ").trim();
2126
- if (!cleaned) return type.toUpperCase();
2256
+ if (!cleaned)
2257
+ return type.toUpperCase();
2127
2258
  return cleaned.replace(/\b\w/g, (char) => char.toUpperCase());
2128
2259
  }
2129
2260
 
2130
- // src/controllers/admin-auth.controller.ts
2261
+ // src/controllers/admin-auth.controller.js
2131
2262
  var AdminAuthController = class {
2263
+ config;
2132
2264
  constructor(config) {
2133
2265
  this.config = config;
2134
2266
  }
2135
- config;
2136
2267
  providers(c) {
2137
2268
  return c.json(getPublicAdminAuthConfig(this.config.adminAuth));
2138
2269
  }
@@ -2145,20 +2276,22 @@ var AdminAuthController = class {
2145
2276
  const siteId = this.getSiteId(c);
2146
2277
  const returnTo = this.normalizeReturnTo(c.req.query("returnTo"), c);
2147
2278
  if (provider.type === "oidc") {
2148
- const redirectUrl = await this.buildOidcStartUrl(
2149
- provider,
2150
- returnTo,
2151
- siteId,
2152
- new URL(c.req.url).origin
2153
- );
2279
+ const redirectUrl = await this.buildOidcStartUrl(provider, returnTo, siteId, new URL(c.req.url).origin);
2154
2280
  return c.redirect(redirectUrl, 302);
2155
2281
  }
2156
2282
  if (!provider.startUrl) {
2157
2283
  return c.json({ error: true, message: "This provider does not expose a start URL." }, 400);
2158
2284
  }
2159
- const url = new URL(provider.startUrl);
2285
+ const url = this.buildCustomProviderStartUrl(provider.startUrl, new URL(c.req.url).origin);
2286
+ if (!url) {
2287
+ return c.json({
2288
+ error: true,
2289
+ message: `Admin auth provider "${provider.id}" has an invalid start URL.`
2290
+ }, 500);
2291
+ }
2160
2292
  url.searchParams.set("returnTo", returnTo);
2161
- if (siteId) url.searchParams.set("siteId", siteId);
2293
+ if (siteId)
2294
+ url.searchParams.set("siteId", siteId);
2162
2295
  url.searchParams.set("provider", provider.id);
2163
2296
  return c.redirect(url.toString(), 302);
2164
2297
  }
@@ -2205,7 +2338,8 @@ var AdminAuthController = class {
2205
2338
  return c.json({ success: true, message: "Logged out. Discard your token." });
2206
2339
  }
2207
2340
  getProvider(config, id) {
2208
- if (config.adminAuth?.mode !== "external") return void 0;
2341
+ if (config.adminAuth?.mode !== "external")
2342
+ return void 0;
2209
2343
  return config.adminAuth.providers.find((provider) => provider.id === id);
2210
2344
  }
2211
2345
  async completeProviderAuth(requestConfig, provider, c) {
@@ -2217,7 +2351,8 @@ var AdminAuthController = class {
2217
2351
  throw new Error("Admin auth collection is not configured.");
2218
2352
  }
2219
2353
  const db = c.get("config").db;
2220
- if (!db) throw new Error("Database not configured.");
2354
+ if (!db)
2355
+ throw new Error("Database not configured.");
2221
2356
  let user = await this.findUserByExternalIdentity(db, adminCollection.slug, provider.id, identity.sub) ?? (identity.email ? (await db.find({
2222
2357
  collection: adminCollection.slug,
2223
2358
  where: { email: identity.email },
@@ -2277,7 +2412,8 @@ var AdminAuthController = class {
2277
2412
  },
2278
2413
  user
2279
2414
  });
2280
- if (resolved) return resolved;
2415
+ if (resolved)
2416
+ return resolved;
2281
2417
  return { allowed: true, roles: identity.roles, data: {} };
2282
2418
  }
2283
2419
  async buildUserData(identity, providerId, roles, extraData) {
@@ -2308,7 +2444,8 @@ var AdminAuthController = class {
2308
2444
  }
2309
2445
  async getRequestConfig(c) {
2310
2446
  const siteId = this.getSiteId(c);
2311
- if (!siteId || !this.config.onSchemaFetch) return this.config;
2447
+ if (!siteId || !this.config.onSchemaFetch)
2448
+ return this.config;
2312
2449
  const dynamic = await this.config.onSchemaFetch(siteId);
2313
2450
  return {
2314
2451
  ...this.config,
@@ -2339,7 +2476,8 @@ var AdminAuthController = class {
2339
2476
  async resolveOidcIdentity(provider, c) {
2340
2477
  const code = c.req.query("code");
2341
2478
  const state = c.req.query("state");
2342
- if (!code || !state) throw new Error("Missing OIDC callback parameters.");
2479
+ if (!code || !state)
2480
+ throw new Error("Missing OIDC callback parameters.");
2343
2481
  const parsedState = await this.verifyState(state, provider.id);
2344
2482
  const discovery = await this.getOidcDiscovery(provider);
2345
2483
  const redirectUri = provider.redirectUri || this.defaultCallbackPath(provider.id, new URL(c.req.url).origin);
@@ -2374,7 +2512,8 @@ var AdminAuthController = class {
2374
2512
  const userInfo = await fetch(provider.userInfoEndpoint || discovery.userinfo_endpoint, {
2375
2513
  headers: { Authorization: `Bearer ${tokenBody.access_token}` }
2376
2514
  });
2377
- if (!userInfo.ok) throw new Error("OIDC userinfo request failed.");
2515
+ if (!userInfo.ok)
2516
+ throw new Error("OIDC userinfo request failed.");
2378
2517
  claims = await userInfo.json();
2379
2518
  } else {
2380
2519
  throw new Error("OIDC provider did not return usable identity claims.");
@@ -2414,15 +2553,13 @@ var AdminAuthController = class {
2414
2553
  if (provider.audience) {
2415
2554
  const aud = claims.aud;
2416
2555
  const matches = Array.isArray(aud) ? aud.includes(provider.audience) : aud === provider.audience;
2417
- if (!matches) throw new Error("External auth audience mismatch.");
2556
+ if (!matches)
2557
+ throw new Error("External auth audience mismatch.");
2418
2558
  }
2419
2559
  }
2420
2560
  return {
2421
2561
  identity: this.mapClaims(claims, provider.claimMapping),
2422
- returnTo: this.normalizeReturnTo(
2423
- body?.returnTo || c.req.query("returnTo"),
2424
- c
2425
- )
2562
+ returnTo: this.normalizeReturnTo(body?.returnTo || c.req.query("returnTo"), c)
2426
2563
  };
2427
2564
  }
2428
2565
  mapClaims(claims, mapping) {
@@ -2452,24 +2589,38 @@ var AdminAuthController = class {
2452
2589
  return typeof value === "string" ? value : void 0;
2453
2590
  }
2454
2591
  readStringArrayClaim(value) {
2455
- if (!value) return void 0;
2592
+ if (!value)
2593
+ return void 0;
2456
2594
  if (Array.isArray(value)) {
2457
2595
  return value.filter((entry) => typeof entry === "string");
2458
2596
  }
2459
- if (typeof value === "string") return [value];
2597
+ if (typeof value === "string")
2598
+ return [value];
2460
2599
  return void 0;
2461
2600
  }
2462
2601
  async getOidcDiscovery(provider) {
2463
- const discoveryUrl = new URL(
2464
- provider.issuer.replace(/\/$/, "") + "/.well-known/openid-configuration"
2465
- );
2602
+ const discoveryUrl = new URL(provider.issuer.replace(/\/$/, "") + "/.well-known/openid-configuration");
2466
2603
  const response = await fetch(discoveryUrl.toString());
2467
- if (!response.ok) throw new Error("Failed to load OIDC discovery document.");
2604
+ if (!response.ok)
2605
+ throw new Error("Failed to load OIDC discovery document.");
2468
2606
  return response.json();
2469
2607
  }
2470
2608
  defaultCallbackPath(providerId, origin = "") {
2471
2609
  return `${origin}/api/admin/auth/${providerId}/callback`;
2472
2610
  }
2611
+ buildCustomProviderStartUrl(startUrl, origin) {
2612
+ const value = startUrl.trim();
2613
+ if (!value || /^(undefined|null)(\/|$)/i.test(value))
2614
+ return null;
2615
+ try {
2616
+ const url = new URL(value, origin);
2617
+ if (url.protocol !== "http:" && url.protocol !== "https:")
2618
+ return null;
2619
+ return url;
2620
+ } catch {
2621
+ return null;
2622
+ }
2623
+ }
2473
2624
  async signState(payload) {
2474
2625
  return new import_jose2.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime("15m").sign(this.getStateSecret());
2475
2626
  }
@@ -2489,13 +2640,14 @@ var AdminAuthController = class {
2489
2640
  return new import_node_util3.TextEncoder().encode(secret);
2490
2641
  }
2491
2642
  normalizeReturnTo(returnTo, c) {
2492
- if (returnTo) return returnTo;
2643
+ if (returnTo)
2644
+ return returnTo;
2493
2645
  const url = new URL(c.req.url);
2494
2646
  return `${url.origin}/admin`;
2495
2647
  }
2496
2648
  };
2497
2649
 
2498
- // src/controllers/preview.controller.ts
2650
+ // src/controllers/preview.controller.js
2499
2651
  var import_jose3 = require("jose");
2500
2652
  var import_node_util4 = require("util");
2501
2653
  var PreviewController = class {
@@ -2538,7 +2690,7 @@ var PreviewController = class {
2538
2690
  }
2539
2691
  };
2540
2692
 
2541
- // src/middleware/auth.ts
2693
+ // src/middleware/auth.js
2542
2694
  function requireAuth() {
2543
2695
  return async (c, next) => {
2544
2696
  const authHeader = c.req.header("Authorization");
@@ -2570,7 +2722,7 @@ function optionalAuth() {
2570
2722
  };
2571
2723
  }
2572
2724
 
2573
- // src/utils/openapi.ts
2725
+ // src/utils/openapi.js
2574
2726
  function generateOpenApi(config) {
2575
2727
  const spec = {
2576
2728
  openapi: "3.0.0",
@@ -3279,7 +3431,8 @@ function fieldsToProperties(fields) {
3279
3431
  required.push(...nested.required);
3280
3432
  continue;
3281
3433
  }
3282
- if (!field.name) continue;
3434
+ if (!field.name)
3435
+ continue;
3283
3436
  props[field.name] = fieldToSchema(field);
3284
3437
  if (field.required) {
3285
3438
  required.push(field.name);
@@ -3391,11 +3544,12 @@ function fieldToSchema(field) {
3391
3544
  default:
3392
3545
  schema = { type: "string" };
3393
3546
  }
3394
- if (field.label) schema.description = field.label;
3547
+ if (field.label)
3548
+ schema.description = field.label;
3395
3549
  return schema;
3396
3550
  }
3397
3551
 
3398
- // src/utils/swagger.ts
3552
+ // src/utils/swagger.js
3399
3553
  function getSwaggerHtml(specUrl = "/api/openapi.json") {
3400
3554
  return `
3401
3555
  <!DOCTYPE html>
@@ -3444,7 +3598,7 @@ function getSwaggerHtml(specUrl = "/api/openapi.json") {
3444
3598
  `;
3445
3599
  }
3446
3600
 
3447
- // src/router.ts
3601
+ // src/router.js
3448
3602
  function accessGate(target, action) {
3449
3603
  return async (c, next) => {
3450
3604
  const user = c.get("user");
@@ -3461,7 +3615,8 @@ function accessGate(target, action) {
3461
3615
  };
3462
3616
  }
3463
3617
  async function checkAccess(access, accessArgs) {
3464
- if (access === void 0 || access === null) return true;
3618
+ if (access === void 0 || access === null)
3619
+ return true;
3465
3620
  if (typeof access === "function") {
3466
3621
  try {
3467
3622
  const result = await access(accessArgs);
@@ -3477,7 +3632,8 @@ async function checkAccess(access, accessArgs) {
3477
3632
  return true;
3478
3633
  }
3479
3634
  function serializeFieldForApi(f) {
3480
- if (!f) return f;
3635
+ if (!f)
3636
+ return f;
3481
3637
  const serialized = { ...f };
3482
3638
  if (serialized.admin?.hooks) {
3483
3639
  const hooks = { ...serialized.admin.hooks };
@@ -3510,8 +3666,10 @@ function registerRoutes(app, config) {
3510
3666
  let globals = [...config.globals];
3511
3667
  if (siteId && config.onSchemaFetch) {
3512
3668
  const dynamic = await config.onSchemaFetch(siteId);
3513
- if (dynamic.collections) collections = [...collections, ...dynamic.collections];
3514
- if (dynamic.globals) globals = [...globals, ...dynamic.globals];
3669
+ if (dynamic.collections)
3670
+ collections = [...collections, ...dynamic.collections];
3671
+ if (dynamic.globals)
3672
+ globals = [...globals, ...dynamic.globals];
3515
3673
  if (dynamic.admin) {
3516
3674
  config.admin = { ...config.admin, ...dynamic.admin };
3517
3675
  }
@@ -3522,8 +3680,10 @@ function registerRoutes(app, config) {
3522
3680
  const user = c.get("user");
3523
3681
  const accessArgs = { user, req: c.req, doc: null };
3524
3682
  const serializeAccess = async (access) => {
3525
- if (typeof access === "string") return access;
3526
- if (typeof access === "boolean") return access;
3683
+ if (typeof access === "string")
3684
+ return access;
3685
+ if (typeof access === "boolean")
3686
+ return access;
3527
3687
  return checkAccess(access, accessArgs);
3528
3688
  };
3529
3689
  const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
@@ -3616,7 +3776,8 @@ function registerRoutes(app, config) {
3616
3776
  let collections = [...config.collections];
3617
3777
  if (siteId && config.onSchemaFetch) {
3618
3778
  const dynamic = await config.onSchemaFetch(siteId);
3619
- if (dynamic.collections) collections = [...collections, ...dynamic.collections];
3779
+ if (dynamic.collections)
3780
+ collections = [...collections, ...dynamic.collections];
3620
3781
  }
3621
3782
  const user = c.get("user");
3622
3783
  let collection = collections.find((col) => col.slug === colSlug);
@@ -3635,7 +3796,8 @@ function registerRoutes(app, config) {
3635
3796
  let globals = [...config.globals];
3636
3797
  if (siteId && config.onSchemaFetch) {
3637
3798
  const dynamic = await config.onSchemaFetch(siteId);
3638
- if (dynamic.globals) globals = [...globals, ...dynamic.globals];
3799
+ if (dynamic.globals)
3800
+ globals = [...globals, ...dynamic.globals];
3639
3801
  }
3640
3802
  const glb = globals.find((g) => g.slug === colSlug);
3641
3803
  if (!glb) {
@@ -3693,9 +3855,12 @@ function registerRoutes(app, config) {
3693
3855
  const user = c.get("user");
3694
3856
  const key = c.req.param("key");
3695
3857
  const scope = c.req.query("scope");
3696
- if (!db) return c.json({ message: "Database not configured" }, 500);
3697
- if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
3698
- if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
3858
+ if (!db)
3859
+ return c.json({ message: "Database not configured" }, 500);
3860
+ if (!user?.collection || !user.sub)
3861
+ return c.json({ error: true, message: "Authentication required." }, 401);
3862
+ if (!key)
3863
+ return c.json({ error: true, message: "Preference key is required." }, 400);
3699
3864
  const getGlobalPreference = async () => {
3700
3865
  const globalDoc = await db.findOne({ collection: "__global_preferences", id: key });
3701
3866
  return globalDoc ? globalDoc.value : null;
@@ -3705,7 +3870,8 @@ function registerRoutes(app, config) {
3705
3870
  return c.json({ key, value: globalValue2 });
3706
3871
  }
3707
3872
  const doc = await db.findOne({ collection: user.collection, id: user.sub });
3708
- if (!doc) return c.json({ error: true, message: "User not found." }, 404);
3873
+ if (!doc)
3874
+ return c.json({ error: true, message: "User not found." }, 404);
3709
3875
  const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? doc.__preferences : {};
3710
3876
  if (key in preferences) {
3711
3877
  return c.json({ key, value: preferences[key] ?? null });
@@ -3718,9 +3884,12 @@ function registerRoutes(app, config) {
3718
3884
  const user = c.get("user");
3719
3885
  const key = c.req.param("key");
3720
3886
  const scope = c.req.query("scope");
3721
- if (!db) return c.json({ message: "Database not configured" }, 500);
3722
- if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
3723
- if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
3887
+ if (!db)
3888
+ return c.json({ message: "Database not configured" }, 500);
3889
+ if (!user?.collection || !user.sub)
3890
+ return c.json({ error: true, message: "Authentication required." }, 401);
3891
+ if (!key)
3892
+ return c.json({ error: true, message: "Preference key is required." }, 400);
3724
3893
  const body = await c.req.json().catch(() => ({}));
3725
3894
  if (scope === "global") {
3726
3895
  const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
@@ -3743,7 +3912,8 @@ function registerRoutes(app, config) {
3743
3912
  return c.json({ key, value: body.value });
3744
3913
  }
3745
3914
  const doc = await db.findOne({ collection: user.collection, id: user.sub });
3746
- if (!doc) return c.json({ error: true, message: "User not found." }, 404);
3915
+ if (!doc)
3916
+ return c.json({ error: true, message: "User not found." }, 404);
3747
3917
  const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? doc.__preferences : {};
3748
3918
  const nextPreferences = { ...preferences, [key]: body.value };
3749
3919
  await db.update({
@@ -3758,9 +3928,12 @@ function registerRoutes(app, config) {
3758
3928
  const user = c.get("user");
3759
3929
  const key = c.req.param("key");
3760
3930
  const scope = c.req.query("scope");
3761
- if (!db) return c.json({ message: "Database not configured" }, 500);
3762
- if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
3763
- if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
3931
+ if (!db)
3932
+ return c.json({ message: "Database not configured" }, 500);
3933
+ if (!user?.collection || !user.sub)
3934
+ return c.json({ error: true, message: "Authentication required." }, 401);
3935
+ if (!key)
3936
+ return c.json({ error: true, message: "Preference key is required." }, 400);
3764
3937
  if (scope === "global") {
3765
3938
  const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
3766
3939
  if (!isAdminUser) {
@@ -3770,7 +3943,8 @@ function registerRoutes(app, config) {
3770
3943
  return c.json({ success: true });
3771
3944
  }
3772
3945
  const doc = await db.findOne({ collection: user.collection, id: user.sub });
3773
- if (!doc) return c.json({ error: true, message: "User not found." }, 404);
3946
+ if (!doc)
3947
+ return c.json({ error: true, message: "User not found." }, 404);
3774
3948
  const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? { ...doc.__preferences } : {};
3775
3949
  delete preferences[key];
3776
3950
  await db.update({
@@ -3806,7 +3980,8 @@ function registerRoutes(app, config) {
3806
3980
  app.post("/api/admin/auth/:provider/exchange", (c) => adminAuthController.exchange(c));
3807
3981
  app.post("/api/admin/logout", (c) => adminAuthController.logout(c));
3808
3982
  for (const collection of config.collections) {
3809
- if (!collection.auth) continue;
3983
+ if (!collection.auth)
3984
+ continue;
3810
3985
  const path = `/api/collections/${collection.slug}`;
3811
3986
  const authController = new AuthController(collection);
3812
3987
  app.post(`${path}/login`, (c) => authController.login(c));
@@ -3908,25 +4083,39 @@ function registerRoutes(app, config) {
3908
4083
  if (collection.auth && id) {
3909
4084
  const authController = new AuthController(collection);
3910
4085
  const method2 = c.req.method;
3911
- if (method2 === "POST" && id === "login") return authController.login(c);
3912
- if (method2 === "POST" && id === "logout") return authController.logout(c);
3913
- if (method2 === "GET" && id === "me") return authController.me(c);
3914
- if (method2 === "POST" && id === "refresh-token") return authController.refreshToken(c);
3915
- if (method2 === "POST" && id === "forgot-password") return authController.forgotPassword(c);
3916
- if (method2 === "POST" && id === "reset-password") return authController.resetPassword(c);
4086
+ if (method2 === "POST" && id === "login")
4087
+ return authController.login(c);
4088
+ if (method2 === "POST" && id === "logout")
4089
+ return authController.logout(c);
4090
+ if (method2 === "GET" && id === "me")
4091
+ return authController.me(c);
4092
+ if (method2 === "POST" && id === "refresh-token")
4093
+ return authController.refreshToken(c);
4094
+ if (method2 === "POST" && id === "forgot-password")
4095
+ return authController.forgotPassword(c);
4096
+ if (method2 === "POST" && id === "reset-password")
4097
+ return authController.resetPassword(c);
3917
4098
  }
3918
4099
  const controller = new CollectionController(collection);
3919
4100
  const method = c.req.method;
3920
4101
  if (id) {
3921
- if (method === "GET") return controller.findOne(c);
3922
- if (method === "PATCH") return controller.update(c);
3923
- if (method === "DELETE" && id === "delete-many") return controller.deleteMany(c);
3924
- if (method === "DELETE") return controller.delete(c);
3925
- if (method === "POST" && id === "media") return controller.create(c);
3926
- if (method === "POST" && id === "seed") return controller.seed(c);
4102
+ if (method === "GET")
4103
+ return controller.findOne(c);
4104
+ if (method === "PATCH")
4105
+ return controller.update(c);
4106
+ if (method === "DELETE" && id === "delete-many")
4107
+ return controller.deleteMany(c);
4108
+ if (method === "DELETE")
4109
+ return controller.delete(c);
4110
+ if (method === "POST" && id === "media")
4111
+ return controller.create(c);
4112
+ if (method === "POST" && id === "seed")
4113
+ return controller.seed(c);
3927
4114
  } else {
3928
- if (method === "GET") return controller.find(c);
3929
- if (method === "POST") return controller.create(c);
4115
+ if (method === "GET")
4116
+ return controller.find(c);
4117
+ if (method === "POST")
4118
+ return controller.create(c);
3930
4119
  }
3931
4120
  }
3932
4121
  }
@@ -3947,10 +4136,13 @@ function registerRoutes(app, config) {
3947
4136
  const controller = new GlobalController(global);
3948
4137
  const method = c.req.method;
3949
4138
  if (id) {
3950
- if (method === "POST" && id === "seed") return controller.seed(c);
4139
+ if (method === "POST" && id === "seed")
4140
+ return controller.seed(c);
3951
4141
  } else {
3952
- if (method === "GET") return controller.get(c);
3953
- if (method === "PATCH") return controller.update(c);
4142
+ if (method === "GET")
4143
+ return controller.get(c);
4144
+ if (method === "PATCH")
4145
+ return controller.update(c);
3954
4146
  }
3955
4147
  }
3956
4148
  }
@@ -3958,7 +4150,7 @@ function registerRoutes(app, config) {
3958
4150
  });
3959
4151
  }
3960
4152
 
3961
- // src/utils/config.ts
4153
+ // src/utils/config.js
3962
4154
  var AUDIT_COLLECTION_SLUG = "__audit";
3963
4155
  var SYSTEM_FIELDS = [
3964
4156
  {
@@ -4172,7 +4364,8 @@ function normalizeConfig(config) {
4172
4364
  });
4173
4365
  const hasAuditCollection = normalizedCollections.some((col) => col.slug === AUDIT_COLLECTION_SLUG);
4174
4366
  const systemCollections = [];
4175
- if (needsAudit && !hasAuditCollection) systemCollections.push(AUDIT_COLLECTION);
4367
+ if (needsAudit && !hasAuditCollection)
4368
+ systemCollections.push(AUDIT_COLLECTION);
4176
4369
  if (needsWorkflow && !normalizedCollections.some((col) => col.slug === WORKFLOW_HISTORY_COLLECTION)) {
4177
4370
  systemCollections.push(WORKFLOW_HISTORY_COLLECTION_CONFIG);
4178
4371
  }
@@ -4186,7 +4379,7 @@ function normalizeConfig(config) {
4186
4379
  };
4187
4380
  }
4188
4381
 
4189
- // src/app.ts
4382
+ // src/app.js
4190
4383
  async function createDyrectedApp(rawConfig) {
4191
4384
  const config = normalizeConfig(rawConfig);
4192
4385
  const app = new import_hono.Hono();