@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.js CHANGED
@@ -13,14 +13,14 @@ import {
13
13
  runCollectionHooks,
14
14
  saveWorkflowDraft,
15
15
  transitionWorkflow
16
- } from "./chunk-FCXE5CVC.js";
16
+ } from "./chunk-44O2LDPT.js";
17
17
 
18
- // src/app.ts
18
+ // src/app.js
19
19
  import { Hono } from "hono";
20
20
  import { cors } from "hono/cors";
21
21
  import { requestId } from "hono/request-id";
22
22
 
23
- // src/services/defaults.service.ts
23
+ // src/services/defaults.service.js
24
24
  var DefaultsService = class {
25
25
  /**
26
26
  * Recursively apply default values to a data object based on field definitions.
@@ -28,12 +28,14 @@ var DefaultsService = class {
28
28
  static apply(fields, data = {}) {
29
29
  const result = { ...data || {} };
30
30
  fields.forEach((field) => {
31
- if (field.type === "join") return;
31
+ if (field.type === "join")
32
+ return;
32
33
  if (field.type === "row" && field.fields) {
33
34
  Object.assign(result, this.apply(field.fields, data));
34
35
  return;
35
36
  }
36
- if (!field.name) return;
37
+ if (!field.name)
38
+ return;
37
39
  let value = result[field.name];
38
40
  if ((value === void 0 || value === null) && field.renameTo) {
39
41
  const legacyValue = result[field.renameTo];
@@ -46,9 +48,12 @@ var DefaultsService = class {
46
48
  if (field.defaultValue !== void 0) {
47
49
  result[field.name] = field.defaultValue;
48
50
  } else {
49
- if (field.type === "boolean") result[field.name] = false;
50
- else if (field.type === "array") result[field.name] = [];
51
- else if (field.type === "multiSelect") result[field.name] = [];
51
+ if (field.type === "boolean")
52
+ result[field.name] = false;
53
+ else if (field.type === "array")
54
+ result[field.name] = [];
55
+ else if (field.type === "multiSelect")
56
+ result[field.name] = [];
52
57
  else if (field.type === "object") {
53
58
  result[field.name] = this.apply(field.fields || [], {});
54
59
  }
@@ -63,7 +68,7 @@ var DefaultsService = class {
63
68
  }
64
69
  };
65
70
 
66
- // src/services/population.service.ts
71
+ // src/services/population.service.js
67
72
  var PopulationService = class {
68
73
  db;
69
74
  collections;
@@ -116,29 +121,31 @@ var PopulationService = class {
116
121
  Object.assign(populatedDoc, rowPopulated);
117
122
  continue;
118
123
  }
119
- if (!field.name) continue;
124
+ if (!field.name)
125
+ continue;
120
126
  const value = populatedDoc[field.name];
121
127
  if (field.type === "relationship" && field.relationTo && value) {
122
128
  const relatedCollection = this.collections.find((c) => c.slug === field.relationTo);
123
- if (!relatedCollection) continue;
129
+ if (!relatedCollection)
130
+ continue;
124
131
  if (Array.isArray(value)) {
125
- populatedDoc[field.name] = await Promise.all(
126
- value.map(async (id) => {
127
- if (!id) return id;
128
- let doc = id;
129
- if (typeof id === "string") {
130
- doc = await this.db.findOne({ collection: field.relationTo, id });
131
- }
132
- if (!doc || typeof doc !== "object") return id;
133
- const docWithDefaults = DefaultsService.apply(relatedCollection.fields, doc);
134
- return this.populate({
135
- data: docWithDefaults,
136
- fields: relatedCollection.fields,
137
- currentDepth: currentDepth + 1,
138
- maxDepth
139
- });
140
- })
141
- );
132
+ populatedDoc[field.name] = await Promise.all(value.map(async (id) => {
133
+ if (!id)
134
+ return id;
135
+ let doc = id;
136
+ if (typeof id === "string") {
137
+ doc = await this.db.findOne({ collection: field.relationTo, id });
138
+ }
139
+ if (!doc || typeof doc !== "object")
140
+ return id;
141
+ const docWithDefaults = DefaultsService.apply(relatedCollection.fields, doc);
142
+ return this.populate({
143
+ data: docWithDefaults,
144
+ fields: relatedCollection.fields,
145
+ currentDepth: currentDepth + 1,
146
+ maxDepth
147
+ });
148
+ }));
142
149
  } else if (value) {
143
150
  let doc = value;
144
151
  if (typeof value === "string") {
@@ -187,18 +194,17 @@ var PopulationService = class {
187
194
  });
188
195
  }
189
196
  if (field.type === "blocks" && field.blocks && Array.isArray(value)) {
190
- populatedDoc[field.name] = await Promise.all(
191
- value.map(async (blockData) => {
192
- const blockConfig = field.blocks.find((b) => b.slug === blockData.blockType);
193
- if (!blockConfig) return blockData;
194
- return this.populate({
195
- data: blockData,
196
- fields: blockConfig.fields,
197
- currentDepth,
198
- maxDepth
199
- });
200
- })
201
- );
197
+ populatedDoc[field.name] = await Promise.all(value.map(async (blockData) => {
198
+ const blockConfig = field.blocks.find((b) => b.slug === blockData.blockType);
199
+ if (!blockConfig)
200
+ return blockData;
201
+ return this.populate({
202
+ data: blockData,
203
+ fields: blockConfig.fields,
204
+ currentDepth,
205
+ maxDepth
206
+ });
207
+ }));
202
208
  }
203
209
  }
204
210
  return populatedDoc;
@@ -207,7 +213,8 @@ var PopulationService = class {
207
213
  * Helper to populate a PaginatedResult
208
214
  */
209
215
  async populateResult(result, fields, maxDepth) {
210
- if (maxDepth <= 0) return result;
216
+ if (maxDepth <= 0)
217
+ return result;
211
218
  const populatedDocs = await this.populate({
212
219
  data: result.docs,
213
220
  fields,
@@ -221,7 +228,7 @@ var PopulationService = class {
221
228
  }
222
229
  };
223
230
 
224
- // src/services/audit.service.ts
231
+ // src/services/audit.service.js
225
232
  var AuditService = class {
226
233
  /**
227
234
  * Writes a single entry to the __audit collection.
@@ -249,7 +256,7 @@ var AuditService = class {
249
256
  }
250
257
  };
251
258
 
252
- // src/auth/password.ts
259
+ // src/auth/password.js
253
260
  import { promisify } from "util";
254
261
  import { scrypt, randomBytes, timingSafeEqual } from "crypto";
255
262
  var scryptAsync = promisify(scrypt);
@@ -262,23 +269,23 @@ async function hashPassword(plain) {
262
269
  }
263
270
  async function verifyPassword(plain, stored) {
264
271
  const [salt, storedHash] = stored.split(":");
265
- if (!salt || !storedHash) return false;
272
+ if (!salt || !storedHash)
273
+ return false;
266
274
  const derivedKey = await scryptAsync(plain, salt, KEY_LEN);
267
275
  const storedBuffer = Buffer.from(storedHash, "hex");
268
- if (derivedKey.length !== storedBuffer.length) return false;
276
+ if (derivedKey.length !== storedBuffer.length)
277
+ return false;
269
278
  return timingSafeEqual(derivedKey, storedBuffer);
270
279
  }
271
280
 
272
- // src/utils/readonly-db.ts
281
+ // src/utils/readonly-db.js
273
282
  var WRITE_METHODS = ["create", "update", "delete", "updateGlobal", "execute"];
274
283
  function createReadonlyDb(db) {
275
284
  return new Proxy(db, {
276
285
  get(target, prop) {
277
286
  if (WRITE_METHODS.includes(prop)) {
278
287
  return () => {
279
- throw new Error(
280
- `[dyrected] Write operation "${String(prop)}" is not allowed in this hook phase. Use afterChange/afterDelete hooks for write operations.`
281
- );
288
+ throw new Error(`[dyrected] Write operation "${String(prop)}" is not allowed in this hook phase. Use afterChange/afterDelete hooks for write operations.`);
282
289
  };
283
290
  }
284
291
  return Reflect.get(target, prop);
@@ -286,11 +293,13 @@ function createReadonlyDb(db) {
286
293
  });
287
294
  }
288
295
 
289
- // src/auth/jexl.ts
296
+ // src/auth/jexl.js
290
297
  import jexl from "jexl";
291
298
  async function evaluateAccess(expression, context) {
292
- if (expression === void 0 || expression === null) return false;
293
- if (typeof expression === "boolean") return expression;
299
+ if (expression === void 0 || expression === null)
300
+ return false;
301
+ if (typeof expression === "boolean")
302
+ return expression;
294
303
  try {
295
304
  const result = await jexl.eval(expression, context);
296
305
  return !!result;
@@ -300,16 +309,61 @@ async function evaluateAccess(expression, context) {
300
309
  }
301
310
  }
302
311
 
303
- // src/controllers/collection.controller.ts
312
+ // src/controllers/collection.controller.js
304
313
  var CollectionController = class {
305
314
  collection;
306
315
  constructor(collection) {
307
316
  this.collection = collection;
308
317
  }
318
+ getDelegatedProvider(c) {
319
+ const config = c.get("config");
320
+ const authCollectionSlug = config.adminAuth?.collectionSlug || "__admins";
321
+ if (this.collection.slug !== authCollectionSlug) {
322
+ return null;
323
+ }
324
+ return config.adminAuth?.providers?.find((p) => p.members) || null;
325
+ }
309
326
  async find(c) {
310
327
  const config = c.get("config");
311
328
  const db = config.db;
312
- if (!db) return c.json({ message: "Database not configured" }, 500);
329
+ if (!db)
330
+ return c.json({ message: "Database not configured" }, 500);
331
+ const provider = this.getDelegatedProvider(c);
332
+ if (provider && provider.members?.list) {
333
+ const limit2 = Number(c.req.query("limit")) || 10;
334
+ const page2 = Number(c.req.query("page")) || 1;
335
+ const sort2 = c.req.query("sort") || void 0;
336
+ let where2 = void 0;
337
+ const whereRaw2 = c.req.query("where");
338
+ if (whereRaw2) {
339
+ try {
340
+ where2 = JSON.parse(decodeURIComponent(whereRaw2));
341
+ } catch {
342
+ }
343
+ }
344
+ const paginatedResult = await provider.members.list({ limit: limit2, page: page2, sort: sort2, where: where2, req: c.req });
345
+ const mappedDocs = [];
346
+ for (const m of paginatedResult.docs) {
347
+ let localId = m.id;
348
+ const localDoc = await db.find({
349
+ collection: this.collection.slug,
350
+ where: m.id ? { externalSubject: { equals: m.id } } : { email: { equals: m.email } },
351
+ limit: 1
352
+ });
353
+ if (localDoc.docs[0]) {
354
+ localId = localDoc.docs[0].id;
355
+ }
356
+ mappedDocs.push({
357
+ ...m,
358
+ id: localId,
359
+ externalSubject: m.id
360
+ });
361
+ }
362
+ return c.json({
363
+ ...paginatedResult,
364
+ docs: mappedDocs
365
+ });
366
+ }
313
367
  const readonlyDb = createReadonlyDb(db);
314
368
  const limit = Number(c.req.query("limit")) || 10;
315
369
  const page = Number(c.req.query("page")) || 1;
@@ -328,7 +382,7 @@ var CollectionController = class {
328
382
  if (this.collection.admin?.filterable === false) {
329
383
  where = void 0;
330
384
  } else {
331
- const { sanitizeWhereClause } = await import("./where-sanitizer-WJ2W2QCE.js");
385
+ const { sanitizeWhereClause } = await import("./where-sanitizer-7Q4JXMX6.js");
332
386
  where = sanitizeWhereClause(where, this.collection.fields);
333
387
  if (Object.keys(where).length === 0) {
334
388
  where = void 0;
@@ -367,11 +421,13 @@ var CollectionController = class {
367
421
  where
368
422
  });
369
423
  }
370
- 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));
424
+ result.docs = result.docs.map((doc) => this.collection.workflow ? materializeWorkflowDocument(doc, this.collection.workflow, user) : doc).filter((doc) => doc !== null);
371
425
  const processedDocs = [];
426
+ const readonlyDbForHooks = createReadonlyDb(db);
372
427
  for (const doc of result.docs) {
428
+ const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
373
429
  const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
374
- doc,
430
+ doc: docWithDefaults,
375
431
  req: c.req,
376
432
  user,
377
433
  db: readonlyDb
@@ -389,17 +445,43 @@ var CollectionController = class {
389
445
  async findOne(c) {
390
446
  const config = c.get("config");
391
447
  const db = config.db;
392
- if (!db) return c.json({ message: "Database not configured" }, 500);
448
+ if (!db)
449
+ return c.json({ message: "Database not configured" }, 500);
450
+ const provider = this.getDelegatedProvider(c);
451
+ if (provider && (provider.members?.get || provider.members?.list)) {
452
+ const id2 = c.req.param("id");
453
+ if (!id2)
454
+ return c.json({ message: "Missing ID" }, 400);
455
+ const localDoc = await db.findOne({ collection: this.collection.slug, id: id2 });
456
+ const externalSubject = localDoc?.externalSubject || id2;
457
+ let member = null;
458
+ if (provider.members.get) {
459
+ member = await provider.members.get({ externalSubject, req: c.req });
460
+ } else if (provider.members.list) {
461
+ const listResult = await provider.members.list({ req: c.req });
462
+ member = listResult.docs.find((m) => m.id === externalSubject) || null;
463
+ }
464
+ if (!member)
465
+ return c.json({ message: "Not Found" }, 404);
466
+ return c.json({
467
+ ...member,
468
+ id: localDoc ? localDoc.id : member.id,
469
+ externalSubject: member.id
470
+ });
471
+ }
393
472
  const readonlyDb = createReadonlyDb(db);
394
473
  const id = c.req.param("id");
395
474
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
396
475
  const user = c.get("user");
397
- if (!id) return c.json({ message: "Missing ID" }, 400);
476
+ if (!id)
477
+ return c.json({ message: "Missing ID" }, 400);
398
478
  let doc = await db.findOne({ collection: this.collection.slug, id });
399
- if (!doc) return c.json({ message: "Not Found" }, 404);
479
+ if (!doc)
480
+ return c.json({ message: "Not Found" }, 404);
400
481
  if (this.collection.workflow) {
401
482
  doc = materializeWorkflowDocument(doc, this.collection.workflow, user);
402
- if (!doc) return c.json({ message: "Not Found" }, 404);
483
+ if (!doc)
484
+ return c.json({ message: "Not Found" }, 404);
403
485
  }
404
486
  const docWithDefaults = DefaultsService.apply(this.collection.fields, doc);
405
487
  const docWithCollectionHooks = await runCollectionHooks(this.collection.hooks?.afterRead, {
@@ -424,7 +506,22 @@ var CollectionController = class {
424
506
  async create(c) {
425
507
  const config = c.get("config");
426
508
  const db = config.db;
427
- if (!db) return c.json({ message: "Database not configured" }, 500);
509
+ if (!db)
510
+ return c.json({ message: "Database not configured" }, 500);
511
+ const provider = this.getDelegatedProvider(c);
512
+ if (provider && provider.members?.create) {
513
+ const contentType2 = c.req.header("Content-Type") || "";
514
+ if (contentType2.toLowerCase().includes("multipart/form-data")) {
515
+ return this.upload(c);
516
+ }
517
+ const body2 = await c.req.json();
518
+ const member = await provider.members.create({ data: body2, req: c.req });
519
+ return c.json({
520
+ ...member,
521
+ id: member.id,
522
+ externalSubject: member.id
523
+ }, 201);
524
+ }
428
525
  const readonlyDb = createReadonlyDb(db);
429
526
  const contentType = c.req.header("Content-Type") || "";
430
527
  if (contentType.toLowerCase().includes("multipart/form-data")) {
@@ -485,13 +582,16 @@ var CollectionController = class {
485
582
  async upload(c) {
486
583
  const config = c.get("config");
487
584
  const storage = config.storage;
488
- if (!storage) return c.json({ message: "Storage not configured" }, 500);
585
+ if (!storage)
586
+ return c.json({ message: "Storage not configured" }, 500);
489
587
  const db = config.db;
490
- if (!db) return c.json({ message: "Database not configured" }, 500);
588
+ if (!db)
589
+ return c.json({ message: "Database not configured" }, 500);
491
590
  const readonlyDb = createReadonlyDb(db);
492
591
  const formData = await c.req.formData();
493
592
  const file = formData.get("file");
494
- if (!file) return c.json({ message: "No file uploaded" }, 400);
593
+ if (!file)
594
+ return c.json({ message: "No file uploaded" }, 400);
495
595
  const buffer = new Uint8Array(await file.arrayBuffer());
496
596
  const siteId = c.get("siteId");
497
597
  const workspaceId = c.get("workspaceId");
@@ -550,10 +650,27 @@ var CollectionController = class {
550
650
  async update(c) {
551
651
  const config = c.get("config");
552
652
  const db = config.db;
553
- if (!db) return c.json({ message: "Database not configured" }, 500);
653
+ if (!db)
654
+ return c.json({ message: "Database not configured" }, 500);
655
+ const provider = this.getDelegatedProvider(c);
656
+ if (provider && provider.members?.update) {
657
+ const id2 = c.req.param("id");
658
+ if (!id2)
659
+ return c.json({ message: "Missing ID" }, 400);
660
+ const body2 = await c.req.json();
661
+ const localDoc = await db.findOne({ collection: this.collection.slug, id: id2 });
662
+ const externalSubject = localDoc?.externalSubject || id2;
663
+ const member = await provider.members.update({ externalSubject, data: body2, req: c.req });
664
+ return c.json({
665
+ ...member,
666
+ id: localDoc ? localDoc.id : member.id,
667
+ externalSubject: member.id
668
+ });
669
+ }
554
670
  const readonlyDb = createReadonlyDb(db);
555
671
  const id = c.req.param("id");
556
- if (!id) return c.json({ message: "Missing ID" }, 400);
672
+ if (!id)
673
+ return c.json({ message: "Missing ID" }, 400);
557
674
  const body = await c.req.json();
558
675
  const user = c.get("user");
559
676
  let data = { ...body };
@@ -567,7 +684,8 @@ var CollectionController = class {
567
684
  updatedBy: user?.sub ?? null
568
685
  });
569
686
  const originalDoc = await db.findOne({ collection: this.collection.slug, id });
570
- if (!originalDoc) return c.json({ message: "Not Found" }, 404);
687
+ if (!originalDoc)
688
+ return c.json({ message: "Not Found" }, 404);
571
689
  let before = null;
572
690
  if (this.collection.audit) {
573
691
  before = originalDoc;
@@ -611,8 +729,10 @@ var CollectionController = class {
611
729
  }
612
730
  async transition(c) {
613
731
  const config = c.get("config");
614
- if (!config.db) return c.json({ message: "Database not configured" }, 500);
615
- if (!this.collection.workflow) return c.json({ message: "Workflows are not enabled for this collection" }, 404);
732
+ if (!config.db)
733
+ return c.json({ message: "Database not configured" }, 500);
734
+ if (!this.collection.workflow)
735
+ return c.json({ message: "Workflows are not enabled for this collection" }, 404);
616
736
  const id = c.req.param("id");
617
737
  const transitionName = c.req.param("transition");
618
738
  const body = await c.req.json().catch(() => ({}));
@@ -635,12 +755,16 @@ var CollectionController = class {
635
755
  }
636
756
  async workflowHistory(c) {
637
757
  const config = c.get("config");
638
- if (!config.db) return c.json({ message: "Database not configured" }, 500);
639
- if (!this.collection.workflow) return c.json({ message: "Workflows are not enabled for this collection" }, 404);
758
+ if (!config.db)
759
+ return c.json({ message: "Database not configured" }, 500);
760
+ if (!this.collection.workflow)
761
+ return c.json({ message: "Workflows are not enabled for this collection" }, 404);
640
762
  const documentId = c.req.param("id");
641
- if (!documentId) return c.json({ message: "Missing ID" }, 400);
763
+ if (!documentId)
764
+ return c.json({ message: "Missing ID" }, 400);
642
765
  const document = await config.db.findOne({ collection: this.collection.slug, id: documentId });
643
- if (!document) return c.json({ message: "Not Found" }, 404);
766
+ if (!document)
767
+ return c.json({ message: "Not Found" }, 404);
644
768
  const readAccess = this.collection.access?.read;
645
769
  if (readAccess !== void 0 && readAccess !== null) {
646
770
  const args = { user: c.get("user"), req: c.req, doc: document };
@@ -654,7 +778,8 @@ var CollectionController = class {
654
778
  });
655
779
  allowed = match.total > 0;
656
780
  }
657
- if (!allowed) return c.json({ error: true, message: `Access denied: read on ${this.collection.slug}` }, 403);
781
+ if (!allowed)
782
+ return c.json({ error: true, message: `Access denied: read on ${this.collection.slug}` }, 403);
658
783
  }
659
784
  const result = await config.db.find({
660
785
  collection: WORKFLOW_HISTORY_COLLECTION,
@@ -678,14 +803,17 @@ var CollectionController = class {
678
803
  async changePassword(c) {
679
804
  const config = c.get("config");
680
805
  const db = config.db;
681
- if (!db) return c.json({ message: "Database not configured" }, 500);
806
+ if (!db)
807
+ return c.json({ message: "Database not configured" }, 500);
682
808
  if (!this.collection.auth) {
683
809
  return c.json({ message: "This collection does not support authentication" }, 400);
684
810
  }
685
811
  const id = c.req.param("id");
686
- if (!id) return c.json({ message: "Missing ID" }, 400);
812
+ if (!id)
813
+ return c.json({ message: "Missing ID" }, 400);
687
814
  const user = c.get("user");
688
- if (!user) return c.json({ message: "Authentication required" }, 401);
815
+ if (!user)
816
+ return c.json({ message: "Authentication required" }, 401);
689
817
  const body = await c.req.json().catch(() => null);
690
818
  const { oldPassword, newPassword, confirmPassword } = body ?? {};
691
819
  if (!newPassword) {
@@ -707,7 +835,8 @@ var CollectionController = class {
707
835
  return c.json({ message: "Current password is required" }, 400);
708
836
  }
709
837
  const existing = await db.findOne({ collection: this.collection.slug, id });
710
- if (!existing) return c.json({ message: "User not found" }, 404);
838
+ if (!existing)
839
+ return c.json({ message: "User not found" }, 404);
711
840
  const valid = await verifyPassword(oldPassword, existing.password);
712
841
  if (!valid) {
713
842
  return c.json({ message: "Invalid current password" }, 400);
@@ -738,13 +867,26 @@ var CollectionController = class {
738
867
  async delete(c) {
739
868
  const config = c.get("config");
740
869
  const db = config.db;
741
- if (!db) return c.json({ message: "Database not configured" }, 500);
870
+ if (!db)
871
+ return c.json({ message: "Database not configured" }, 500);
872
+ const provider = this.getDelegatedProvider(c);
873
+ if (provider && provider.members?.delete) {
874
+ const id2 = c.req.param("id");
875
+ if (!id2)
876
+ return c.json({ message: "Missing ID" }, 400);
877
+ const localDoc = await db.findOne({ collection: this.collection.slug, id: id2 });
878
+ const externalSubject = localDoc?.externalSubject || id2;
879
+ await provider.members.delete({ externalSubject, req: c.req });
880
+ return c.json({ message: "Deleted" });
881
+ }
742
882
  const readonlyDb = createReadonlyDb(db);
743
883
  const id = c.req.param("id");
744
- if (!id) return c.json({ message: "Missing ID" }, 400);
884
+ if (!id)
885
+ return c.json({ message: "Missing ID" }, 400);
745
886
  const user = c.get("user");
746
887
  const doc = await db.findOne({ collection: this.collection.slug, id });
747
- if (!doc) return c.json({ message: "Not Found" }, 404);
888
+ if (!doc)
889
+ return c.json({ message: "Not Found" }, 404);
748
890
  let before = null;
749
891
  if (this.collection.audit) {
750
892
  before = doc;
@@ -779,7 +921,8 @@ var CollectionController = class {
779
921
  async deleteMany(c) {
780
922
  const config = c.get("config");
781
923
  const db = config.db;
782
- if (!db) return c.json({ message: "Database not configured" }, 500);
924
+ if (!db)
925
+ return c.json({ message: "Database not configured" }, 500);
783
926
  const readonlyDb = createReadonlyDb(db);
784
927
  const user = c.get("user");
785
928
  let ids = [];
@@ -794,7 +937,8 @@ var CollectionController = class {
794
937
  const raw = c.req.queries("ids") ?? c.req.queries("ids[]") ?? [];
795
938
  ids = raw.filter(Boolean);
796
939
  }
797
- if (!ids.length) return c.json({ message: "No IDs provided" }, 400);
940
+ if (!ids.length)
941
+ return c.json({ message: "No IDs provided" }, 400);
798
942
  const deleted = [];
799
943
  const failed = [];
800
944
  for (const id of ids) {
@@ -847,7 +991,8 @@ var CollectionController = class {
847
991
  async seed(c) {
848
992
  const config = c.get("config");
849
993
  const db = config.db;
850
- if (!db) return c.json({ message: "Database not configured" }, 500);
994
+ if (!db)
995
+ return c.json({ message: "Database not configured" }, 500);
851
996
  const body = await c.req.json();
852
997
  const initialData = body.data;
853
998
  if (!initialData || !Array.isArray(initialData)) {
@@ -867,7 +1012,7 @@ var CollectionController = class {
867
1012
  }
868
1013
  };
869
1014
 
870
- // src/controllers/global.controller.ts
1015
+ // src/controllers/global.controller.js
871
1016
  var GlobalController = class {
872
1017
  global;
873
1018
  constructor(global) {
@@ -876,7 +1021,8 @@ var GlobalController = class {
876
1021
  async get(c) {
877
1022
  const config = c.get("config");
878
1023
  const db = config.db;
879
- if (!db) return c.json({ message: "Database not configured" }, 500);
1024
+ if (!db)
1025
+ return c.json({ message: "Database not configured" }, 500);
880
1026
  const readonlyDb = createReadonlyDb(db);
881
1027
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 10;
882
1028
  const user = c.get("user");
@@ -920,7 +1066,8 @@ var GlobalController = class {
920
1066
  async update(c) {
921
1067
  const config = c.get("config");
922
1068
  const db = config.db;
923
- if (!db) return c.json({ message: "Database not configured" }, 500);
1069
+ if (!db)
1070
+ return c.json({ message: "Database not configured" }, 500);
924
1071
  const readonlyDb = createReadonlyDb(db);
925
1072
  const body = await c.req.json();
926
1073
  const user = c.get("user");
@@ -955,7 +1102,8 @@ var GlobalController = class {
955
1102
  async seed(c) {
956
1103
  const config = c.get("config");
957
1104
  const db = config.db;
958
- if (!db) return c.json({ message: "Database not configured" }, 500);
1105
+ if (!db)
1106
+ return c.json({ message: "Database not configured" }, 500);
959
1107
  const body = await c.req.json();
960
1108
  const initialData = body.data;
961
1109
  if (!initialData) {
@@ -971,20 +1119,23 @@ var GlobalController = class {
971
1119
  }
972
1120
  };
973
1121
  function isFunctionallyEmpty(obj) {
974
- if (obj === null || obj === void 0 || obj === "") return true;
1122
+ if (obj === null || obj === void 0 || obj === "")
1123
+ return true;
975
1124
  if (Array.isArray(obj)) {
976
- if (obj.length === 0) return true;
1125
+ if (obj.length === 0)
1126
+ return true;
977
1127
  return obj.every(isFunctionallyEmpty);
978
1128
  }
979
1129
  if (typeof obj === "object") {
980
1130
  const keys = Object.keys(obj);
981
- if (keys.length === 0) return true;
1131
+ if (keys.length === 0)
1132
+ return true;
982
1133
  return keys.every((key) => isFunctionallyEmpty(obj[key]));
983
1134
  }
984
1135
  return false;
985
1136
  }
986
1137
 
987
- // src/controllers/media.controller.ts
1138
+ // src/controllers/media.controller.js
988
1139
  var MediaController = class {
989
1140
  collection;
990
1141
  constructor(collection = "media") {
@@ -1064,7 +1215,8 @@ var MediaController = class {
1064
1215
  }
1065
1216
  }
1066
1217
  const db = config.db;
1067
- if (!db) return c.json({ message: "Database not configured" }, 500);
1218
+ if (!db)
1219
+ return c.json({ message: "Database not configured" }, 500);
1068
1220
  const doc = await db.create({
1069
1221
  collection: this.collection,
1070
1222
  data: finalFileData
@@ -1073,7 +1225,8 @@ var MediaController = class {
1073
1225
  }
1074
1226
  async find(c) {
1075
1227
  const db = c.get("config").db;
1076
- if (!db) return c.json({ message: "Database not configured" }, 500);
1228
+ if (!db)
1229
+ return c.json({ message: "Database not configured" }, 500);
1077
1230
  const limit = Number(c.req.query("limit")) || 10;
1078
1231
  const page = Number(c.req.query("page")) || 1;
1079
1232
  const result = await db.find({
@@ -1087,11 +1240,14 @@ var MediaController = class {
1087
1240
  const config = c.get("config");
1088
1241
  const storage = config.storage;
1089
1242
  const db = config.db;
1090
- if (!db) return c.json({ message: "Database not configured" }, 500);
1243
+ if (!db)
1244
+ return c.json({ message: "Database not configured" }, 500);
1091
1245
  const id = c.req.param("id");
1092
- if (!id) return c.json({ message: "Missing ID" }, 400);
1246
+ if (!id)
1247
+ return c.json({ message: "Missing ID" }, 400);
1093
1248
  const doc = await db.findOne({ collection: this.collection, id });
1094
- if (!doc) return c.json({ message: "Not Found" }, 404);
1249
+ if (!doc)
1250
+ return c.json({ message: "Not Found" }, 404);
1095
1251
  if (storage) {
1096
1252
  await storage.delete({ filename: doc.filename });
1097
1253
  if (doc.sizes) {
@@ -1112,26 +1268,26 @@ var MediaController = class {
1112
1268
  return c.json({ message: "Storage not configured for serving" }, 404);
1113
1269
  }
1114
1270
  const filename = c.req.param("filename");
1115
- if (!filename) return c.json({ message: "Missing filename" }, 400);
1271
+ if (!filename)
1272
+ return c.json({ message: "Missing filename" }, 400);
1116
1273
  let res = await storage.resolve({ filename });
1117
1274
  if (!res && !filename.includes("/")) {
1118
1275
  res = await storage.resolve({ filename: `default/${filename}` });
1119
1276
  }
1120
- if (!res) return c.json({ message: "Not Found" }, 404);
1277
+ if (!res)
1278
+ return c.json({ message: "Not Found" }, 404);
1121
1279
  c.header("Content-Type", res.mimeType);
1122
1280
  return c.body(res.buffer);
1123
1281
  }
1124
1282
  };
1125
1283
 
1126
- // src/auth/token.ts
1284
+ // src/auth/token.js
1127
1285
  import { SignJWT, jwtVerify, decodeJwt } from "jose";
1128
1286
  import { TextEncoder } from "util";
1129
1287
  function getSecret() {
1130
1288
  const secret = process.env.DYRECTED_JWT_SECRET || process.env.JWT_SECRET;
1131
1289
  if (!secret) {
1132
- throw new Error(
1133
- "[dyrected/core] DYRECTED_JWT_SECRET is not set. Add it to your environment variables to enable auth collections."
1134
- );
1290
+ throw new Error("[dyrected/core] DYRECTED_JWT_SECRET is not set. Add it to your environment variables to enable auth collections.");
1135
1291
  }
1136
1292
  return new TextEncoder().encode(secret);
1137
1293
  }
@@ -1144,7 +1300,7 @@ async function verifyCollectionToken(token) {
1144
1300
  return payload;
1145
1301
  }
1146
1302
 
1147
- // src/services/email-template.ts
1303
+ // src/services/email-template.js
1148
1304
  var emailTokens = {
1149
1305
  colors: {
1150
1306
  canvas: "#f6f7f2",
@@ -1208,11 +1364,9 @@ function detailBox(content, monospace = false) {
1208
1364
  }
1209
1365
  function ctaButton(label, href) {
1210
1366
  const safeHref = safeHttpUrl(href);
1211
- if (!safeHref) return "";
1212
- return table(row(
1213
- `<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>`,
1214
- "padding:8px 0 24px"
1215
- ), "width:auto");
1367
+ if (!safeHref)
1368
+ return "";
1369
+ 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");
1216
1370
  }
1217
1371
  function alertBox(content) {
1218
1372
  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}`);
@@ -1223,23 +1377,19 @@ function layout({ preheader, title, content, footer }) {
1223
1377
  <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"></head>
1224
1378
  <body style="margin:0;padding:0;background:${emailTokens.colors.canvas}">
1225
1379
  <div style="display:none;max-height:0;overflow:hidden;opacity:0;color:transparent">${escapeHtml(preheader)}</div>
1226
- ${table(row(
1227
- table(
1228
- 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"),
1229
- `width:100%;max-width:${emailTokens.width};background:${emailTokens.colors.surface};border:1px solid ${emailTokens.colors.border};border-radius:${emailTokens.radius.card};overflow:hidden`
1230
- ),
1231
- "padding:32px 12px"
1232
- ), `width:100%;background:${emailTokens.colors.canvas}`)}
1380
+ ${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}`)}
1233
1381
  </body>
1234
1382
  </html>`;
1235
1383
  }
1236
1384
 
1237
- // src/services/email.service.ts
1385
+ // src/services/email.service.js
1238
1386
  var _devSend = null;
1239
1387
  var _devSendPromise = null;
1240
1388
  async function getDevSend() {
1241
- if (_devSend) return _devSend;
1242
- if (_devSendPromise) return _devSendPromise;
1389
+ if (_devSend)
1390
+ return _devSend;
1391
+ if (_devSendPromise)
1392
+ return _devSendPromise;
1243
1393
  _devSendPromise = (async () => {
1244
1394
  try {
1245
1395
  const nodemailer = await import("nodemailer");
@@ -1323,7 +1473,7 @@ function buildPasswordChangedEmail(config, args) {
1323
1473
  };
1324
1474
  }
1325
1475
 
1326
- // src/controllers/auth.controller.ts
1476
+ // src/controllers/auth.controller.js
1327
1477
  var AuthController = class {
1328
1478
  collection;
1329
1479
  constructor(collection) {
@@ -1335,7 +1485,8 @@ var AuthController = class {
1335
1485
  // ---------------------------------------------------------------------------
1336
1486
  async init(c) {
1337
1487
  const db = c.get("config").db;
1338
- if (!db) return c.json({ message: "Database not configured" }, 500);
1488
+ if (!db)
1489
+ return c.json({ message: "Database not configured" }, 500);
1339
1490
  const result = await db.find({
1340
1491
  collection: this.collection.slug,
1341
1492
  limit: 1
@@ -1351,7 +1502,8 @@ var AuthController = class {
1351
1502
  async registerFirstUser(c) {
1352
1503
  const config = c.get("config");
1353
1504
  const db = config.db;
1354
- if (!db) return c.json({ message: "Database not configured" }, 500);
1505
+ if (!db)
1506
+ return c.json({ message: "Database not configured" }, 500);
1355
1507
  const check = await db.find({
1356
1508
  collection: this.collection.slug,
1357
1509
  limit: 1
@@ -1379,9 +1531,7 @@ var AuthController = class {
1379
1531
  collection: this.collection.slug
1380
1532
  });
1381
1533
  const { subject, html } = buildWelcomeEmail(config, { email: body.email });
1382
- sendEmail(config, { to: body.email, subject, html }).catch(
1383
- (err) => console.error("[dyrected/core] Failed to send welcome email:", err)
1384
- );
1534
+ sendEmail(config, { to: body.email, subject, html }).catch((err) => console.error("[dyrected/core] Failed to send welcome email:", err));
1385
1535
  const { password: _, ...safeUser } = user;
1386
1536
  return c.json({ token, user: safeUser });
1387
1537
  }
@@ -1390,7 +1540,8 @@ var AuthController = class {
1390
1540
  // ---------------------------------------------------------------------------
1391
1541
  async login(c) {
1392
1542
  const db = c.get("config").db;
1393
- if (!db) return c.json({ message: "Database not configured" }, 500);
1543
+ if (!db)
1544
+ return c.json({ message: "Database not configured" }, 500);
1394
1545
  const body = await c.req.json().catch(() => null);
1395
1546
  if (!body?.email || !body?.password) {
1396
1547
  return c.json({ error: true, message: "email and password are required." }, 400);
@@ -1429,7 +1580,8 @@ var AuthController = class {
1429
1580
  // ---------------------------------------------------------------------------
1430
1581
  async me(c) {
1431
1582
  const db = c.get("config").db;
1432
- if (!db) return c.json({ message: "Database not configured" }, 500);
1583
+ if (!db)
1584
+ return c.json({ message: "Database not configured" }, 500);
1433
1585
  const requestUser = c.get("user");
1434
1586
  if (!requestUser) {
1435
1587
  return c.json({ error: true, message: "Authentication required." }, 401);
@@ -1464,7 +1616,8 @@ var AuthController = class {
1464
1616
  async forgotPassword(c) {
1465
1617
  const config = c.get("config");
1466
1618
  const db = config.db;
1467
- if (!db) return c.json({ message: "Database not configured" }, 500);
1619
+ if (!db)
1620
+ return c.json({ message: "Database not configured" }, 500);
1468
1621
  const body = await c.req.json().catch(() => null);
1469
1622
  if (!body?.email) {
1470
1623
  return c.json({ error: true, message: "email is required." }, 400);
@@ -1476,10 +1629,7 @@ var AuthController = class {
1476
1629
  });
1477
1630
  const user = result.docs[0];
1478
1631
  if (user) {
1479
- const resetToken = await signCollectionToken(
1480
- { sub: user.id, email: user.email, collection: this.collection.slug, purpose: "reset" },
1481
- "1h"
1482
- );
1632
+ const resetToken = await signCollectionToken({ sub: user.id, email: user.email, collection: this.collection.slug, purpose: "reset" }, "1h");
1483
1633
  const resetUrl = body?.resetUrl;
1484
1634
  const url = resetUrl ? `${resetUrl}${resetUrl.includes("?") ? "&" : "?"}token=${encodeURIComponent(resetToken)}` : void 0;
1485
1635
  try {
@@ -1502,7 +1652,8 @@ var AuthController = class {
1502
1652
  async resetPassword(c) {
1503
1653
  const config = c.get("config");
1504
1654
  const db = config.db;
1505
- if (!db) return c.json({ message: "Database not configured" }, 500);
1655
+ if (!db)
1656
+ return c.json({ message: "Database not configured" }, 500);
1506
1657
  const body = await c.req.json().catch(() => null);
1507
1658
  if (!body?.token || !body?.password) {
1508
1659
  return c.json({ error: true, message: "token and password are required." }, 400);
@@ -1523,9 +1674,7 @@ var AuthController = class {
1523
1674
  data: { password: hashedPassword }
1524
1675
  });
1525
1676
  const { subject, html } = buildPasswordChangedEmail(config, { email: payload.email });
1526
- sendEmail(config, { to: payload.email, subject, html }).catch(
1527
- (err) => console.error("[dyrected/core] Failed to send password-changed email:", err)
1528
- );
1677
+ sendEmail(config, { to: payload.email, subject, html }).catch((err) => console.error("[dyrected/core] Failed to send password-changed email:", err));
1529
1678
  return c.json({ success: true, message: "Password has been reset. You can now log in." });
1530
1679
  }
1531
1680
  // ---------------------------------------------------------------------------
@@ -1535,7 +1684,8 @@ var AuthController = class {
1535
1684
  async invite(c) {
1536
1685
  const config = c.get("config");
1537
1686
  const db = config.db;
1538
- if (!db) return c.json({ message: "Database not configured" }, 500);
1687
+ if (!db)
1688
+ return c.json({ message: "Database not configured" }, 500);
1539
1689
  const requestUser = c.get("user");
1540
1690
  if (!requestUser) {
1541
1691
  return c.json({ error: true, message: "Authentication required." }, 401);
@@ -1552,10 +1702,7 @@ var AuthController = class {
1552
1702
  if (existing.total > 0) {
1553
1703
  return c.json({ error: true, message: "An account with that email already exists." }, 409);
1554
1704
  }
1555
- const inviteToken = await signCollectionToken(
1556
- { sub: body.email, email: body.email, collection: this.collection.slug, purpose: "invite" },
1557
- "7d"
1558
- );
1705
+ const inviteToken = await signCollectionToken({ sub: body.email, email: body.email, collection: this.collection.slug, purpose: "invite" }, "7d");
1559
1706
  try {
1560
1707
  const { subject, html } = buildInviteEmail(config, {
1561
1708
  token: inviteToken,
@@ -1575,7 +1722,8 @@ var AuthController = class {
1575
1722
  async acceptInvite(c) {
1576
1723
  const config = c.get("config");
1577
1724
  const db = config.db;
1578
- if (!db) return c.json({ message: "Database not configured" }, 500);
1725
+ if (!db)
1726
+ return c.json({ message: "Database not configured" }, 500);
1579
1727
  const body = await c.req.json().catch(() => null);
1580
1728
  if (!body?.token || !body?.password) {
1581
1729
  return c.json({ error: true, message: "token and password are required." }, 400);
@@ -1610,28 +1758,21 @@ var AuthController = class {
1610
1758
  collection: this.collection.slug
1611
1759
  });
1612
1760
  const { subject, html } = buildWelcomeEmail(config, { email: inviteeEmail });
1613
- sendEmail(config, { to: inviteeEmail, subject, html }).catch(
1614
- (err) => console.error("[dyrected/core] Failed to send welcome email:", err)
1615
- );
1761
+ sendEmail(config, { to: inviteeEmail, subject, html }).catch((err) => console.error("[dyrected/core] Failed to send welcome email:", err));
1616
1762
  const { password: _, ...safeUser } = user;
1617
1763
  return c.json({ token: sessionToken, user: safeUser }, 201);
1618
1764
  }
1619
1765
  };
1620
1766
 
1621
- // src/controllers/admin-auth.controller.ts
1767
+ // src/controllers/admin-auth.controller.js
1622
1768
  import { randomBytes as randomBytes2 } from "crypto";
1623
1769
  import { TextEncoder as TextEncoder2 } from "util";
1624
- import {
1625
- SignJWT as SignJWT2,
1626
- createRemoteJWKSet,
1627
- jwtVerify as jwtVerify2,
1628
- decodeJwt as decodeJwt2
1629
- } from "jose";
1770
+ import { SignJWT as SignJWT2, createRemoteJWKSet, jwtVerify as jwtVerify2, decodeJwt as decodeJwt2 } from "jose";
1630
1771
  var AdminAuthController = class {
1772
+ config;
1631
1773
  constructor(config) {
1632
1774
  this.config = config;
1633
1775
  }
1634
- config;
1635
1776
  providers(c) {
1636
1777
  return c.json(getPublicAdminAuthConfig(this.config.adminAuth));
1637
1778
  }
@@ -1644,20 +1785,22 @@ var AdminAuthController = class {
1644
1785
  const siteId = this.getSiteId(c);
1645
1786
  const returnTo = this.normalizeReturnTo(c.req.query("returnTo"), c);
1646
1787
  if (provider.type === "oidc") {
1647
- const redirectUrl = await this.buildOidcStartUrl(
1648
- provider,
1649
- returnTo,
1650
- siteId,
1651
- new URL(c.req.url).origin
1652
- );
1788
+ const redirectUrl = await this.buildOidcStartUrl(provider, returnTo, siteId, new URL(c.req.url).origin);
1653
1789
  return c.redirect(redirectUrl, 302);
1654
1790
  }
1655
1791
  if (!provider.startUrl) {
1656
1792
  return c.json({ error: true, message: "This provider does not expose a start URL." }, 400);
1657
1793
  }
1658
- const url = new URL(provider.startUrl);
1794
+ const url = this.buildCustomProviderStartUrl(provider.startUrl, new URL(c.req.url).origin);
1795
+ if (!url) {
1796
+ return c.json({
1797
+ error: true,
1798
+ message: `Admin auth provider "${provider.id}" has an invalid start URL.`
1799
+ }, 500);
1800
+ }
1659
1801
  url.searchParams.set("returnTo", returnTo);
1660
- if (siteId) url.searchParams.set("siteId", siteId);
1802
+ if (siteId)
1803
+ url.searchParams.set("siteId", siteId);
1661
1804
  url.searchParams.set("provider", provider.id);
1662
1805
  return c.redirect(url.toString(), 302);
1663
1806
  }
@@ -1704,7 +1847,8 @@ var AdminAuthController = class {
1704
1847
  return c.json({ success: true, message: "Logged out. Discard your token." });
1705
1848
  }
1706
1849
  getProvider(config, id) {
1707
- if (config.adminAuth?.mode !== "external") return void 0;
1850
+ if (config.adminAuth?.mode !== "external")
1851
+ return void 0;
1708
1852
  return config.adminAuth.providers.find((provider) => provider.id === id);
1709
1853
  }
1710
1854
  async completeProviderAuth(requestConfig, provider, c) {
@@ -1716,7 +1860,8 @@ var AdminAuthController = class {
1716
1860
  throw new Error("Admin auth collection is not configured.");
1717
1861
  }
1718
1862
  const db = c.get("config").db;
1719
- if (!db) throw new Error("Database not configured.");
1863
+ if (!db)
1864
+ throw new Error("Database not configured.");
1720
1865
  let user = await this.findUserByExternalIdentity(db, adminCollection.slug, provider.id, identity.sub) ?? (identity.email ? (await db.find({
1721
1866
  collection: adminCollection.slug,
1722
1867
  where: { email: identity.email },
@@ -1776,7 +1921,8 @@ var AdminAuthController = class {
1776
1921
  },
1777
1922
  user
1778
1923
  });
1779
- if (resolved) return resolved;
1924
+ if (resolved)
1925
+ return resolved;
1780
1926
  return { allowed: true, roles: identity.roles, data: {} };
1781
1927
  }
1782
1928
  async buildUserData(identity, providerId, roles, extraData) {
@@ -1807,7 +1953,8 @@ var AdminAuthController = class {
1807
1953
  }
1808
1954
  async getRequestConfig(c) {
1809
1955
  const siteId = this.getSiteId(c);
1810
- if (!siteId || !this.config.onSchemaFetch) return this.config;
1956
+ if (!siteId || !this.config.onSchemaFetch)
1957
+ return this.config;
1811
1958
  const dynamic = await this.config.onSchemaFetch(siteId);
1812
1959
  return {
1813
1960
  ...this.config,
@@ -1838,7 +1985,8 @@ var AdminAuthController = class {
1838
1985
  async resolveOidcIdentity(provider, c) {
1839
1986
  const code = c.req.query("code");
1840
1987
  const state = c.req.query("state");
1841
- if (!code || !state) throw new Error("Missing OIDC callback parameters.");
1988
+ if (!code || !state)
1989
+ throw new Error("Missing OIDC callback parameters.");
1842
1990
  const parsedState = await this.verifyState(state, provider.id);
1843
1991
  const discovery = await this.getOidcDiscovery(provider);
1844
1992
  const redirectUri = provider.redirectUri || this.defaultCallbackPath(provider.id, new URL(c.req.url).origin);
@@ -1873,7 +2021,8 @@ var AdminAuthController = class {
1873
2021
  const userInfo = await fetch(provider.userInfoEndpoint || discovery.userinfo_endpoint, {
1874
2022
  headers: { Authorization: `Bearer ${tokenBody.access_token}` }
1875
2023
  });
1876
- if (!userInfo.ok) throw new Error("OIDC userinfo request failed.");
2024
+ if (!userInfo.ok)
2025
+ throw new Error("OIDC userinfo request failed.");
1877
2026
  claims = await userInfo.json();
1878
2027
  } else {
1879
2028
  throw new Error("OIDC provider did not return usable identity claims.");
@@ -1913,15 +2062,13 @@ var AdminAuthController = class {
1913
2062
  if (provider.audience) {
1914
2063
  const aud = claims.aud;
1915
2064
  const matches = Array.isArray(aud) ? aud.includes(provider.audience) : aud === provider.audience;
1916
- if (!matches) throw new Error("External auth audience mismatch.");
2065
+ if (!matches)
2066
+ throw new Error("External auth audience mismatch.");
1917
2067
  }
1918
2068
  }
1919
2069
  return {
1920
2070
  identity: this.mapClaims(claims, provider.claimMapping),
1921
- returnTo: this.normalizeReturnTo(
1922
- body?.returnTo || c.req.query("returnTo"),
1923
- c
1924
- )
2071
+ returnTo: this.normalizeReturnTo(body?.returnTo || c.req.query("returnTo"), c)
1925
2072
  };
1926
2073
  }
1927
2074
  mapClaims(claims, mapping) {
@@ -1951,24 +2098,38 @@ var AdminAuthController = class {
1951
2098
  return typeof value === "string" ? value : void 0;
1952
2099
  }
1953
2100
  readStringArrayClaim(value) {
1954
- if (!value) return void 0;
2101
+ if (!value)
2102
+ return void 0;
1955
2103
  if (Array.isArray(value)) {
1956
2104
  return value.filter((entry) => typeof entry === "string");
1957
2105
  }
1958
- if (typeof value === "string") return [value];
2106
+ if (typeof value === "string")
2107
+ return [value];
1959
2108
  return void 0;
1960
2109
  }
1961
2110
  async getOidcDiscovery(provider) {
1962
- const discoveryUrl = new URL(
1963
- provider.issuer.replace(/\/$/, "") + "/.well-known/openid-configuration"
1964
- );
2111
+ const discoveryUrl = new URL(provider.issuer.replace(/\/$/, "") + "/.well-known/openid-configuration");
1965
2112
  const response = await fetch(discoveryUrl.toString());
1966
- if (!response.ok) throw new Error("Failed to load OIDC discovery document.");
2113
+ if (!response.ok)
2114
+ throw new Error("Failed to load OIDC discovery document.");
1967
2115
  return response.json();
1968
2116
  }
1969
2117
  defaultCallbackPath(providerId, origin = "") {
1970
2118
  return `${origin}/api/admin/auth/${providerId}/callback`;
1971
2119
  }
2120
+ buildCustomProviderStartUrl(startUrl, origin) {
2121
+ const value = startUrl.trim();
2122
+ if (!value || /^(undefined|null)(\/|$)/i.test(value))
2123
+ return null;
2124
+ try {
2125
+ const url = new URL(value, origin);
2126
+ if (url.protocol !== "http:" && url.protocol !== "https:")
2127
+ return null;
2128
+ return url;
2129
+ } catch {
2130
+ return null;
2131
+ }
2132
+ }
1972
2133
  async signState(payload) {
1973
2134
  return new SignJWT2(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime("15m").sign(this.getStateSecret());
1974
2135
  }
@@ -1988,13 +2149,14 @@ var AdminAuthController = class {
1988
2149
  return new TextEncoder2().encode(secret);
1989
2150
  }
1990
2151
  normalizeReturnTo(returnTo, c) {
1991
- if (returnTo) return returnTo;
2152
+ if (returnTo)
2153
+ return returnTo;
1992
2154
  const url = new URL(c.req.url);
1993
2155
  return `${url.origin}/admin`;
1994
2156
  }
1995
2157
  };
1996
2158
 
1997
- // src/controllers/preview.controller.ts
2159
+ // src/controllers/preview.controller.js
1998
2160
  import { SignJWT as SignJWT3, jwtVerify as jwtVerify3 } from "jose";
1999
2161
  import { TextEncoder as TextEncoder3 } from "util";
2000
2162
  var PreviewController = class {
@@ -2037,7 +2199,7 @@ var PreviewController = class {
2037
2199
  }
2038
2200
  };
2039
2201
 
2040
- // src/middleware/auth.ts
2202
+ // src/middleware/auth.js
2041
2203
  function requireAuth() {
2042
2204
  return async (c, next) => {
2043
2205
  const authHeader = c.req.header("Authorization");
@@ -2069,7 +2231,7 @@ function optionalAuth() {
2069
2231
  };
2070
2232
  }
2071
2233
 
2072
- // src/utils/swagger.ts
2234
+ // src/utils/swagger.js
2073
2235
  function getSwaggerHtml(specUrl = "/api/openapi.json") {
2074
2236
  return `
2075
2237
  <!DOCTYPE html>
@@ -2118,7 +2280,7 @@ function getSwaggerHtml(specUrl = "/api/openapi.json") {
2118
2280
  `;
2119
2281
  }
2120
2282
 
2121
- // src/router.ts
2283
+ // src/router.js
2122
2284
  function accessGate(target, action) {
2123
2285
  return async (c, next) => {
2124
2286
  const user = c.get("user");
@@ -2135,7 +2297,8 @@ function accessGate(target, action) {
2135
2297
  };
2136
2298
  }
2137
2299
  async function checkAccess(access, accessArgs) {
2138
- if (access === void 0 || access === null) return true;
2300
+ if (access === void 0 || access === null)
2301
+ return true;
2139
2302
  if (typeof access === "function") {
2140
2303
  try {
2141
2304
  const result = await access(accessArgs);
@@ -2151,7 +2314,8 @@ async function checkAccess(access, accessArgs) {
2151
2314
  return true;
2152
2315
  }
2153
2316
  function serializeFieldForApi(f) {
2154
- if (!f) return f;
2317
+ if (!f)
2318
+ return f;
2155
2319
  const serialized = { ...f };
2156
2320
  if (serialized.admin?.hooks) {
2157
2321
  const hooks = { ...serialized.admin.hooks };
@@ -2184,8 +2348,10 @@ function registerRoutes(app, config) {
2184
2348
  let globals = [...config.globals];
2185
2349
  if (siteId && config.onSchemaFetch) {
2186
2350
  const dynamic = await config.onSchemaFetch(siteId);
2187
- if (dynamic.collections) collections = [...collections, ...dynamic.collections];
2188
- if (dynamic.globals) globals = [...globals, ...dynamic.globals];
2351
+ if (dynamic.collections)
2352
+ collections = [...collections, ...dynamic.collections];
2353
+ if (dynamic.globals)
2354
+ globals = [...globals, ...dynamic.globals];
2189
2355
  if (dynamic.admin) {
2190
2356
  config.admin = { ...config.admin, ...dynamic.admin };
2191
2357
  }
@@ -2196,8 +2362,10 @@ function registerRoutes(app, config) {
2196
2362
  const user = c.get("user");
2197
2363
  const accessArgs = { user, req: c.req, doc: null };
2198
2364
  const serializeAccess = async (access) => {
2199
- if (typeof access === "string") return access;
2200
- if (typeof access === "boolean") return access;
2365
+ if (typeof access === "string")
2366
+ return access;
2367
+ if (typeof access === "boolean")
2368
+ return access;
2201
2369
  return checkAccess(access, accessArgs);
2202
2370
  };
2203
2371
  const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
@@ -2290,7 +2458,8 @@ function registerRoutes(app, config) {
2290
2458
  let collections = [...config.collections];
2291
2459
  if (siteId && config.onSchemaFetch) {
2292
2460
  const dynamic = await config.onSchemaFetch(siteId);
2293
- if (dynamic.collections) collections = [...collections, ...dynamic.collections];
2461
+ if (dynamic.collections)
2462
+ collections = [...collections, ...dynamic.collections];
2294
2463
  }
2295
2464
  const user = c.get("user");
2296
2465
  let collection = collections.find((col) => col.slug === colSlug);
@@ -2309,7 +2478,8 @@ function registerRoutes(app, config) {
2309
2478
  let globals = [...config.globals];
2310
2479
  if (siteId && config.onSchemaFetch) {
2311
2480
  const dynamic = await config.onSchemaFetch(siteId);
2312
- if (dynamic.globals) globals = [...globals, ...dynamic.globals];
2481
+ if (dynamic.globals)
2482
+ globals = [...globals, ...dynamic.globals];
2313
2483
  }
2314
2484
  const glb = globals.find((g) => g.slug === colSlug);
2315
2485
  if (!glb) {
@@ -2367,9 +2537,12 @@ function registerRoutes(app, config) {
2367
2537
  const user = c.get("user");
2368
2538
  const key = c.req.param("key");
2369
2539
  const scope = c.req.query("scope");
2370
- if (!db) return c.json({ message: "Database not configured" }, 500);
2371
- if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
2372
- if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
2540
+ if (!db)
2541
+ return c.json({ message: "Database not configured" }, 500);
2542
+ if (!user?.collection || !user.sub)
2543
+ return c.json({ error: true, message: "Authentication required." }, 401);
2544
+ if (!key)
2545
+ return c.json({ error: true, message: "Preference key is required." }, 400);
2373
2546
  const getGlobalPreference = async () => {
2374
2547
  const globalDoc = await db.findOne({ collection: "__global_preferences", id: key });
2375
2548
  return globalDoc ? globalDoc.value : null;
@@ -2379,7 +2552,8 @@ function registerRoutes(app, config) {
2379
2552
  return c.json({ key, value: globalValue2 });
2380
2553
  }
2381
2554
  const doc = await db.findOne({ collection: user.collection, id: user.sub });
2382
- if (!doc) return c.json({ error: true, message: "User not found." }, 404);
2555
+ if (!doc)
2556
+ return c.json({ error: true, message: "User not found." }, 404);
2383
2557
  const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? doc.__preferences : {};
2384
2558
  if (key in preferences) {
2385
2559
  return c.json({ key, value: preferences[key] ?? null });
@@ -2392,9 +2566,12 @@ function registerRoutes(app, config) {
2392
2566
  const user = c.get("user");
2393
2567
  const key = c.req.param("key");
2394
2568
  const scope = c.req.query("scope");
2395
- if (!db) return c.json({ message: "Database not configured" }, 500);
2396
- if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
2397
- if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
2569
+ if (!db)
2570
+ return c.json({ message: "Database not configured" }, 500);
2571
+ if (!user?.collection || !user.sub)
2572
+ return c.json({ error: true, message: "Authentication required." }, 401);
2573
+ if (!key)
2574
+ return c.json({ error: true, message: "Preference key is required." }, 400);
2398
2575
  const body = await c.req.json().catch(() => ({}));
2399
2576
  if (scope === "global") {
2400
2577
  const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
@@ -2417,7 +2594,8 @@ function registerRoutes(app, config) {
2417
2594
  return c.json({ key, value: body.value });
2418
2595
  }
2419
2596
  const doc = await db.findOne({ collection: user.collection, id: user.sub });
2420
- if (!doc) return c.json({ error: true, message: "User not found." }, 404);
2597
+ if (!doc)
2598
+ return c.json({ error: true, message: "User not found." }, 404);
2421
2599
  const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? doc.__preferences : {};
2422
2600
  const nextPreferences = { ...preferences, [key]: body.value };
2423
2601
  await db.update({
@@ -2432,9 +2610,12 @@ function registerRoutes(app, config) {
2432
2610
  const user = c.get("user");
2433
2611
  const key = c.req.param("key");
2434
2612
  const scope = c.req.query("scope");
2435
- if (!db) return c.json({ message: "Database not configured" }, 500);
2436
- if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
2437
- if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
2613
+ if (!db)
2614
+ return c.json({ message: "Database not configured" }, 500);
2615
+ if (!user?.collection || !user.sub)
2616
+ return c.json({ error: true, message: "Authentication required." }, 401);
2617
+ if (!key)
2618
+ return c.json({ error: true, message: "Preference key is required." }, 400);
2438
2619
  if (scope === "global") {
2439
2620
  const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
2440
2621
  if (!isAdminUser) {
@@ -2444,7 +2625,8 @@ function registerRoutes(app, config) {
2444
2625
  return c.json({ success: true });
2445
2626
  }
2446
2627
  const doc = await db.findOne({ collection: user.collection, id: user.sub });
2447
- if (!doc) return c.json({ error: true, message: "User not found." }, 404);
2628
+ if (!doc)
2629
+ return c.json({ error: true, message: "User not found." }, 404);
2448
2630
  const preferences = typeof doc.__preferences === "object" && doc.__preferences !== null ? { ...doc.__preferences } : {};
2449
2631
  delete preferences[key];
2450
2632
  await db.update({
@@ -2480,7 +2662,8 @@ function registerRoutes(app, config) {
2480
2662
  app.post("/api/admin/auth/:provider/exchange", (c) => adminAuthController.exchange(c));
2481
2663
  app.post("/api/admin/logout", (c) => adminAuthController.logout(c));
2482
2664
  for (const collection of config.collections) {
2483
- if (!collection.auth) continue;
2665
+ if (!collection.auth)
2666
+ continue;
2484
2667
  const path = `/api/collections/${collection.slug}`;
2485
2668
  const authController = new AuthController(collection);
2486
2669
  app.post(`${path}/login`, (c) => authController.login(c));
@@ -2582,25 +2765,39 @@ function registerRoutes(app, config) {
2582
2765
  if (collection.auth && id) {
2583
2766
  const authController = new AuthController(collection);
2584
2767
  const method2 = c.req.method;
2585
- if (method2 === "POST" && id === "login") return authController.login(c);
2586
- if (method2 === "POST" && id === "logout") return authController.logout(c);
2587
- if (method2 === "GET" && id === "me") return authController.me(c);
2588
- if (method2 === "POST" && id === "refresh-token") return authController.refreshToken(c);
2589
- if (method2 === "POST" && id === "forgot-password") return authController.forgotPassword(c);
2590
- if (method2 === "POST" && id === "reset-password") return authController.resetPassword(c);
2768
+ if (method2 === "POST" && id === "login")
2769
+ return authController.login(c);
2770
+ if (method2 === "POST" && id === "logout")
2771
+ return authController.logout(c);
2772
+ if (method2 === "GET" && id === "me")
2773
+ return authController.me(c);
2774
+ if (method2 === "POST" && id === "refresh-token")
2775
+ return authController.refreshToken(c);
2776
+ if (method2 === "POST" && id === "forgot-password")
2777
+ return authController.forgotPassword(c);
2778
+ if (method2 === "POST" && id === "reset-password")
2779
+ return authController.resetPassword(c);
2591
2780
  }
2592
2781
  const controller = new CollectionController(collection);
2593
2782
  const method = c.req.method;
2594
2783
  if (id) {
2595
- if (method === "GET") return controller.findOne(c);
2596
- if (method === "PATCH") return controller.update(c);
2597
- if (method === "DELETE" && id === "delete-many") return controller.deleteMany(c);
2598
- if (method === "DELETE") return controller.delete(c);
2599
- if (method === "POST" && id === "media") return controller.create(c);
2600
- if (method === "POST" && id === "seed") return controller.seed(c);
2784
+ if (method === "GET")
2785
+ return controller.findOne(c);
2786
+ if (method === "PATCH")
2787
+ return controller.update(c);
2788
+ if (method === "DELETE" && id === "delete-many")
2789
+ return controller.deleteMany(c);
2790
+ if (method === "DELETE")
2791
+ return controller.delete(c);
2792
+ if (method === "POST" && id === "media")
2793
+ return controller.create(c);
2794
+ if (method === "POST" && id === "seed")
2795
+ return controller.seed(c);
2601
2796
  } else {
2602
- if (method === "GET") return controller.find(c);
2603
- if (method === "POST") return controller.create(c);
2797
+ if (method === "GET")
2798
+ return controller.find(c);
2799
+ if (method === "POST")
2800
+ return controller.create(c);
2604
2801
  }
2605
2802
  }
2606
2803
  }
@@ -2621,10 +2818,13 @@ function registerRoutes(app, config) {
2621
2818
  const controller = new GlobalController(global);
2622
2819
  const method = c.req.method;
2623
2820
  if (id) {
2624
- if (method === "POST" && id === "seed") return controller.seed(c);
2821
+ if (method === "POST" && id === "seed")
2822
+ return controller.seed(c);
2625
2823
  } else {
2626
- if (method === "GET") return controller.get(c);
2627
- if (method === "PATCH") return controller.update(c);
2824
+ if (method === "GET")
2825
+ return controller.get(c);
2826
+ if (method === "PATCH")
2827
+ return controller.update(c);
2628
2828
  }
2629
2829
  }
2630
2830
  }
@@ -2632,7 +2832,7 @@ function registerRoutes(app, config) {
2632
2832
  });
2633
2833
  }
2634
2834
 
2635
- // src/app.ts
2835
+ // src/app.js
2636
2836
  async function createDyrectedApp(rawConfig) {
2637
2837
  const config = normalizeConfig(rawConfig);
2638
2838
  const app = new Hono();