@agntcms/next 0.3.1 → 0.3.4

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.mjs CHANGED
@@ -335,37 +335,6 @@ function normalizeLinkValue(raw) {
335
335
  return { type: "internal", slug: "", label: "" };
336
336
  }
337
337
 
338
- // src/domain/form.ts
339
- var FORM_FORBIDDEN_KINDS = /* @__PURE__ */ new Set([
340
- "image",
341
- "video",
342
- "reference",
343
- "list",
344
- // `formOverrides` is a section-only descriptor (it overrides another
345
- // form schema instance). Putting it inside a form would mean a form's
346
- // payload could carry overrides for itself or another form — a
347
- // recursive shape with no ergonomic editor UI. Section-only by design.
348
- "formOverrides",
349
- // `button` is a section-only descriptor: a styled CTA with an
350
- // optional link. Public-form payloads collect user input — a button
351
- // value is authored content, not a submitted answer. Section-only
352
- // by design (mirrors `formOverrides`).
353
- "button"
354
- ]);
355
- var SubmissionsNotReadableError = class extends Error {
356
- constructor(message = "submission adapter does not support reading") {
357
- super(message);
358
- this.name = "SubmissionsNotReadableError";
359
- }
360
- };
361
-
362
- // src/domain/formOverrides.ts
363
- var FormOverridesField = (formName, opts) => ({
364
- kind: "formOverrides",
365
- formName,
366
- ...opts?.default !== void 0 ? { default: opts.default } : {}
367
- });
368
-
369
338
  // src/storage/fs/content.ts
370
339
  var SLUG_PATTERN2 = /^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*$/;
371
340
  var assertValidSlug = (slug) => {
@@ -426,7 +395,7 @@ var createFsContentAdapter = (options) => {
426
395
  throw err;
427
396
  }
428
397
  };
429
- const listJsonFiles2 = async (dir) => {
398
+ const listJsonFiles = async (dir) => {
430
399
  let entries;
431
400
  try {
432
401
  entries = await fs3.readdir(dir, { withFileTypes: true });
@@ -440,7 +409,7 @@ var createFsContentAdapter = (options) => {
440
409
  results.push(entry.name);
441
410
  } else if (entry.isDirectory()) {
442
411
  const subDir = path3.join(dir, entry.name);
443
- const subFiles = await listJsonFiles2(subDir);
412
+ const subFiles = await listJsonFiles(subDir);
444
413
  for (const sub of subFiles) {
445
414
  results.push(`${entry.name}/${sub}`);
446
415
  }
@@ -479,7 +448,7 @@ var createFsContentAdapter = (options) => {
479
448
  await writeAtomic(filePath, JSON.stringify(page, null, 2));
480
449
  };
481
450
  const listDrafts = async () => {
482
- const files = await listJsonFiles2(draftsDir);
451
+ const files = await listJsonFiles(draftsDir);
483
452
  const results = [];
484
453
  for (const relPath of files) {
485
454
  const slug = relPath.slice(0, -".json".length);
@@ -489,7 +458,7 @@ var createFsContentAdapter = (options) => {
489
458
  return results;
490
459
  };
491
460
  const listPages = async () => {
492
- const files = await listJsonFiles2(pagesDir);
461
+ const files = await listJsonFiles(pagesDir);
493
462
  const results = [];
494
463
  for (const relPath of files) {
495
464
  const slug = relPath.slice(0, -".json".length);
@@ -499,7 +468,7 @@ var createFsContentAdapter = (options) => {
499
468
  return results;
500
469
  };
501
470
  const listPageSummaries = async () => {
502
- const files = await listJsonFiles2(pagesDir);
471
+ const files = await listJsonFiles(pagesDir);
503
472
  const results = [];
504
473
  for (const relPath of files) {
505
474
  const slug = relPath.slice(0, -".json".length);
@@ -797,151 +766,6 @@ var createFsContentAdapter = (options) => {
797
766
  };
798
767
  };
799
768
 
800
- // src/storage/fs/submissions.ts
801
- import { randomUUID } from "crypto";
802
- import * as fs4 from "fs/promises";
803
- import * as path4 from "path";
804
- var DEFAULT_MAX_LIST = 500;
805
- var FORM_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
806
- var parseSubmission = (raw) => {
807
- let parsed;
808
- try {
809
- parsed = JSON.parse(raw);
810
- } catch {
811
- return null;
812
- }
813
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
814
- return null;
815
- }
816
- const obj = parsed;
817
- if (typeof obj["formName"] !== "string") return null;
818
- if (typeof obj["id"] !== "string") return null;
819
- if (typeof obj["submittedAt"] !== "string") return null;
820
- if (obj["payload"] === null || typeof obj["payload"] !== "object" || Array.isArray(obj["payload"])) {
821
- return null;
822
- }
823
- return {
824
- formName: obj["formName"],
825
- id: obj["id"],
826
- submittedAt: obj["submittedAt"],
827
- payload: obj["payload"]
828
- };
829
- };
830
- var assertValidFormName = (name) => {
831
- if (typeof name !== "string" || !FORM_NAME_PATTERN.test(name)) {
832
- throw new Error(`invalid form name: ${JSON.stringify(name)}`);
833
- }
834
- };
835
- var submissionFilename = (submittedAt, id) => {
836
- const ts = submittedAt.replace(/:/g, "-");
837
- return `${ts}-${id}.json`;
838
- };
839
- var listJsonFiles = async (dir) => {
840
- let entries;
841
- try {
842
- entries = await fs4.readdir(dir, { withFileTypes: true });
843
- } catch (err) {
844
- if (isEnoent(err)) return [];
845
- throw err;
846
- }
847
- const results = [];
848
- for (const entry of entries) {
849
- if (entry.isFile() && entry.name.endsWith(".json")) {
850
- results.push(entry.name);
851
- }
852
- }
853
- return results;
854
- };
855
- var createFsSubmissionAdapter = (options) => {
856
- const { submissionsRoot } = options;
857
- if (!path4.isAbsolute(submissionsRoot)) {
858
- throw new Error(
859
- `submissionsRoot must be an absolute path, got: ${JSON.stringify(submissionsRoot)}`
860
- );
861
- }
862
- const maxList = options.maxList ?? DEFAULT_MAX_LIST;
863
- if (!Number.isFinite(maxList) || maxList <= 0 || !Number.isInteger(maxList)) {
864
- throw new Error(
865
- `maxList must be a positive integer, got: ${maxList}`
866
- );
867
- }
868
- const rootResolved = path4.resolve(submissionsRoot);
869
- const rootWithSep = rootResolved.endsWith(path4.sep) ? rootResolved : rootResolved + path4.sep;
870
- const formDir = (formName) => {
871
- assertValidFormName(formName);
872
- const dir = path4.resolve(rootResolved, formName);
873
- if (!dir.startsWith(rootWithSep)) {
874
- throw new Error(`form name escapes submissions root: ${JSON.stringify(formName)}`);
875
- }
876
- return dir;
877
- };
878
- const cryptoSuffix = () => randomUUID();
879
- const store = async (submission) => {
880
- const dir = formDir(submission.formName);
881
- const filename = submissionFilename(submission.submittedAt, submission.id);
882
- if (filename.includes("/") || filename.includes("\\")) {
883
- throw new Error(`submission id contains path separator: ${JSON.stringify(submission.id)}`);
884
- }
885
- const target = path4.join(dir, filename);
886
- await writeAtomic(target, JSON.stringify(submission, null, 2), cryptoSuffix);
887
- };
888
- const list = async (formName) => {
889
- const dir = formDir(formName);
890
- const files = await listJsonFiles(dir);
891
- const summaries = [];
892
- for (const filename of files) {
893
- const stem = filename.slice(0, -".json".length);
894
- const lastDash = stem.lastIndexOf("-");
895
- if (lastDash < 0) continue;
896
- const tsRaw = stem.slice(0, lastDash);
897
- const id = stem.slice(lastDash + 1);
898
- if (id === "") continue;
899
- const tIndex = tsRaw.indexOf("T");
900
- if (tIndex < 0) continue;
901
- const datePart = tsRaw.slice(0, tIndex);
902
- const timePart = tsRaw.slice(tIndex + 1).replace(/-/g, ":");
903
- const submittedAt = `${datePart}T${timePart}`;
904
- summaries.push({ id, submittedAt });
905
- }
906
- summaries.sort((a, b) => {
907
- if (a.submittedAt !== b.submittedAt) {
908
- return a.submittedAt < b.submittedAt ? 1 : -1;
909
- }
910
- if (a.id !== b.id) {
911
- return a.id < b.id ? 1 : -1;
912
- }
913
- return 0;
914
- });
915
- return summaries.length > maxList ? summaries.slice(0, maxList) : summaries;
916
- };
917
- const read = async (formName, id) => {
918
- if (id.includes("/") || id.includes("\\") || id.includes("..")) {
919
- return null;
920
- }
921
- const dir = formDir(formName);
922
- let entries;
923
- try {
924
- entries = (await fs4.readdir(dir)).filter((n) => n.endsWith(".json"));
925
- } catch (err) {
926
- if (isEnoent(err)) return null;
927
- throw err;
928
- }
929
- const suffix = `-${id}.json`;
930
- const found = entries.find((n) => n.endsWith(suffix));
931
- if (!found) return null;
932
- const target = path4.join(dir, found);
933
- let raw;
934
- try {
935
- raw = await fs4.readFile(target, { encoding: "utf8" });
936
- } catch (err) {
937
- if (isEnoent(err)) return null;
938
- throw err;
939
- }
940
- return parseSubmission(raw);
941
- };
942
- return { info: { kind: "fs" }, store, list, read };
943
- };
944
-
945
769
  // src/config/defaults-registry.ts
946
770
  var SLOT = /* @__PURE__ */ Symbol.for("@agntcms/next/default-adapter-factories");
947
771
  function holder() {
@@ -952,31 +776,24 @@ function registerDefaultAdapterFactories(factories) {
952
776
  }
953
777
 
954
778
  // src/config/defaults.ts
955
- import * as path5 from "path";
779
+ import * as path4 from "path";
956
780
  function createDefaultContentAdapter(options) {
957
781
  const root = options?.projectRoot ?? process.cwd();
958
782
  return createFsContentAdapter({
959
- contentRoot: path5.resolve(root, "content")
783
+ contentRoot: path4.resolve(root, "content")
960
784
  });
961
785
  }
962
786
  function createDefaultAssetAdapter(options) {
963
787
  const root = options?.projectRoot ?? process.cwd();
964
788
  return createFsAssetAdapter({
965
- assetsRoot: path5.resolve(root, "public/assets"),
789
+ assetsRoot: path4.resolve(root, "public/assets"),
966
790
  publicUrlBase: "/assets"
967
791
  });
968
792
  }
969
- function createDefaultSubmissionAdapter(options) {
970
- const root = options?.projectRoot ?? process.cwd();
971
- return createFsSubmissionAdapter({
972
- submissionsRoot: path5.resolve(root, "content/submissions")
973
- });
974
- }
975
793
  function installDefaultAdapterFactories() {
976
794
  const factories = {
977
795
  content: createDefaultContentAdapter,
978
- asset: createDefaultAssetAdapter,
979
- submission: createDefaultSubmissionAdapter
796
+ asset: createDefaultAssetAdapter
980
797
  };
981
798
  registerDefaultAdapterFactories(factories);
982
799
  }
@@ -1085,160 +902,6 @@ var createListPages = ({
1085
902
  };
1086
903
  };
1087
904
 
1088
- // src/runtime/submitForm.ts
1089
- import { randomBytes } from "crypto";
1090
- var CROCKFORD32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
1091
- var defaultGenerateId = () => {
1092
- const bytes = randomBytes(16);
1093
- let out = "";
1094
- for (let i = 0; i < 16; i++) {
1095
- out += CROCKFORD32[bytes[i] & 31];
1096
- }
1097
- return out;
1098
- };
1099
- var validateNonEmptyString = (value) => {
1100
- if (typeof value !== "string") return "must be a string";
1101
- if (value.trim() === "") return "must not be empty";
1102
- return null;
1103
- };
1104
- var validateNumber = (value, descriptor) => {
1105
- if (typeof value !== "number" || !Number.isFinite(value)) {
1106
- return "must be a finite number";
1107
- }
1108
- if (descriptor.min !== void 0 && value < descriptor.min) {
1109
- return `must be >= ${descriptor.min}`;
1110
- }
1111
- if (descriptor.max !== void 0 && value > descriptor.max) {
1112
- return `must be <= ${descriptor.max}`;
1113
- }
1114
- return null;
1115
- };
1116
- var validateBoolean = (value) => {
1117
- if (typeof value !== "boolean") return "must be true or false";
1118
- return null;
1119
- };
1120
- var validateSelect = (value, descriptor) => {
1121
- if (typeof value !== "string") return "must be a string";
1122
- if (!descriptor.options.some((opt) => opt.value === value)) {
1123
- return "must be one of the declared options";
1124
- }
1125
- return null;
1126
- };
1127
- var validateLinkPayload = (value) => {
1128
- if (value === null || typeof value !== "object") return "must be a link object";
1129
- const obj = value;
1130
- if (typeof obj["label"] !== "string") return "label must be a string";
1131
- if (obj["type"] === "internal") {
1132
- const slug = obj["slug"];
1133
- if (typeof slug !== "string") return "slug must be a string";
1134
- if (slug.trim() === "") return "slug must be a non-empty string";
1135
- return validateInternalSlug(slug);
1136
- }
1137
- if (obj["type"] === "external") {
1138
- const url = obj["url"];
1139
- if (typeof url !== "string") return "url must be a string";
1140
- if (url.trim() === "") return "url must be a non-empty string";
1141
- return validateExternalUrl(url);
1142
- }
1143
- if (obj["type"] === "email") {
1144
- const email = obj["email"];
1145
- if (typeof email !== "string") return "email must be a string";
1146
- if (email.trim() === "") return "email must be a non-empty string";
1147
- return validateEmail(email);
1148
- }
1149
- if (obj["type"] === "phone") {
1150
- const phone = obj["phone"];
1151
- if (typeof phone !== "string") return "phone must be a string";
1152
- if (phone.trim() === "") return "phone must be a non-empty string";
1153
- return validatePhone(phone);
1154
- }
1155
- return 'type must be "internal", "external", "email", or "phone"';
1156
- };
1157
- var validateAndNormalisePayload = (schema, payload) => {
1158
- const errors = {};
1159
- const normalised = {};
1160
- for (const [fieldName, descriptor] of Object.entries(schema)) {
1161
- if (!Object.prototype.hasOwnProperty.call(payload, fieldName)) {
1162
- errors[fieldName] = "is required";
1163
- continue;
1164
- }
1165
- const value = payload[fieldName];
1166
- let err = null;
1167
- switch (descriptor.kind) {
1168
- case "text":
1169
- case "richText": {
1170
- err = validateNonEmptyString(value);
1171
- if (!err) {
1172
- normalised[fieldName] = value.trim();
1173
- }
1174
- break;
1175
- }
1176
- case "number": {
1177
- err = validateNumber(value, descriptor);
1178
- if (!err) normalised[fieldName] = value;
1179
- break;
1180
- }
1181
- case "boolean": {
1182
- err = validateBoolean(value);
1183
- if (!err) normalised[fieldName] = value;
1184
- break;
1185
- }
1186
- case "select": {
1187
- err = validateSelect(value, descriptor);
1188
- if (!err) normalised[fieldName] = value;
1189
- break;
1190
- }
1191
- case "link": {
1192
- err = validateLinkPayload(value);
1193
- if (!err) {
1194
- const linkObj = value;
1195
- const label = typeof linkObj["label"] === "string" ? linkObj["label"] : "";
1196
- const normalisedLink = linkObj["type"] === "external" ? { type: "external", url: linkObj["url"], label } : linkObj["type"] === "email" ? { type: "email", email: linkObj["email"], label } : linkObj["type"] === "phone" ? { type: "phone", phone: linkObj["phone"], label } : { type: "internal", slug: linkObj["slug"], label };
1197
- normalised[fieldName] = normalisedLink;
1198
- }
1199
- break;
1200
- }
1201
- default: {
1202
- const _exhaustive = descriptor;
1203
- void _exhaustive;
1204
- err = "unsupported field type";
1205
- }
1206
- }
1207
- if (err) errors[fieldName] = err;
1208
- }
1209
- if (Object.keys(errors).length > 0) return { ok: false, errors };
1210
- return { ok: true, payload: normalised };
1211
- };
1212
- function createSubmitForm(deps) {
1213
- const { forms, submissionAdapter } = deps;
1214
- const generateId = deps.generateId ?? defaultGenerateId;
1215
- const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
1216
- return async function submitForm(input) {
1217
- const def = forms.get(input.formName);
1218
- if (!def) {
1219
- return { ok: false, error: "unknown_form" };
1220
- }
1221
- if (def.honeypot !== void 0) {
1222
- const trapValue = input.payload[def.honeypot];
1223
- if (typeof trapValue === "string" && trapValue.length > 0) {
1224
- return { ok: true, stored: false, suppressed: "honeypot" };
1225
- }
1226
- }
1227
- const validated = validateAndNormalisePayload(def.schema, input.payload);
1228
- if (!validated.ok) {
1229
- return { ok: false, error: "validation_failed", errors: validated.errors };
1230
- }
1231
- const submission = {
1232
- formName: def.name,
1233
- payload: validated.payload,
1234
- submittedAt: now(),
1235
- id: generateId()
1236
- };
1237
- await submissionAdapter.store(submission);
1238
- return { ok: true, stored: true, id: submission.id };
1239
- };
1240
- }
1241
-
1242
905
  // src/runtime/getContent.ts
1243
906
  var isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
1244
907
  var looksLikeLink = (obj) => {
@@ -1398,83 +1061,7 @@ function createRuntime(options) {
1398
1061
  };
1399
1062
  const getGlobal = createGetGlobal(contentAdapter);
1400
1063
  const listPages = createListPages({ contentAdapter });
1401
- const noopAdapter = {
1402
- info: { kind: "fs" },
1403
- store: async () => {
1404
- throw new Error("submissionAdapter not configured; pass one to createRuntime");
1405
- },
1406
- list: async () => [],
1407
- read: async () => null
1408
- };
1409
- const submitForm = createSubmitForm({
1410
- forms: options.forms ?? emptyFormRegistry,
1411
- submissionAdapter: options.submissionAdapter ?? noopAdapter
1412
- });
1413
- return { getContent, publishDraft, getGlobal, submitForm, listPages };
1414
- }
1415
- var emptyFormRegistry = Object.freeze({
1416
- definitions: [],
1417
- get: () => void 0,
1418
- has: () => false
1419
- });
1420
-
1421
- // src/runtime/rateLimit.ts
1422
- var DEFAULT_MAX_BUCKETS = 1e4;
1423
- function createRateLimit(options) {
1424
- const { perWindow, windowMs } = options;
1425
- if (!Number.isFinite(perWindow) || perWindow <= 0) {
1426
- throw new Error(`perWindow must be a positive number, got: ${perWindow}`);
1427
- }
1428
- if (!Number.isFinite(windowMs) || windowMs <= 0) {
1429
- throw new Error(`windowMs must be a positive number, got: ${windowMs}`);
1430
- }
1431
- const maxBuckets = options.maxBuckets ?? DEFAULT_MAX_BUCKETS;
1432
- if (!Number.isFinite(maxBuckets) || maxBuckets <= 0 || !Number.isInteger(maxBuckets)) {
1433
- throw new Error(`maxBuckets must be a positive integer, got: ${maxBuckets}`);
1434
- }
1435
- const now = options.now ?? Date.now;
1436
- const buckets = /* @__PURE__ */ new Map();
1437
- const keyOf = (ip, formName) => `${ip}:${formName}`;
1438
- const sweepExpired = (t) => {
1439
- for (const [k, b] of buckets) {
1440
- if (t >= b.resetAt) buckets.delete(k);
1441
- }
1442
- };
1443
- const evictOldest = () => {
1444
- let oldestKey = null;
1445
- let oldestResetAt = Number.POSITIVE_INFINITY;
1446
- for (const [k, b] of buckets) {
1447
- if (b.resetAt < oldestResetAt) {
1448
- oldestResetAt = b.resetAt;
1449
- oldestKey = k;
1450
- }
1451
- }
1452
- if (oldestKey !== null) buckets.delete(oldestKey);
1453
- };
1454
- return {
1455
- check: (ip, formName) => {
1456
- const key = keyOf(ip, formName);
1457
- const t = now();
1458
- const existing = buckets.get(key);
1459
- if (!existing || t >= existing.resetAt) {
1460
- if (!existing && buckets.size >= maxBuckets) {
1461
- sweepExpired(t);
1462
- while (buckets.size >= maxBuckets) {
1463
- evictOldest();
1464
- }
1465
- }
1466
- const bucket = { count: 1, resetAt: t + windowMs };
1467
- buckets.set(key, bucket);
1468
- return { allowed: true, count: 1, resetAt: bucket.resetAt };
1469
- }
1470
- existing.count += 1;
1471
- const allowed = existing.count <= perWindow;
1472
- return { allowed, count: existing.count, resetAt: existing.resetAt };
1473
- },
1474
- reset: () => {
1475
- buckets.clear();
1476
- }
1477
- };
1064
+ return { getContent, publishDraft, getGlobal, listPages };
1478
1065
  }
1479
1066
 
1480
1067
  // src/runtime/systemPages.ts
@@ -1512,95 +1099,6 @@ var isSitemapEligibleSlug = (slug) => {
1512
1099
  return !SITEMAP_EXCLUDED_TERMINAL_SLUGS.has(terminal);
1513
1100
  };
1514
1101
 
1515
- // src/storage/webhook/submissions.ts
1516
- var URL_PATTERN = /^https?:\/\//;
1517
- var DEFAULT_TIMEOUT_MS = 1e4;
1518
- var createWebhookSubmissionAdapter = (options) => {
1519
- if (typeof options.url !== "string" || !URL_PATTERN.test(options.url)) {
1520
- throw new Error(
1521
- `webhook url must start with http:// or https://, got: ${JSON.stringify(options.url)}`
1522
- );
1523
- }
1524
- let parsedHost;
1525
- try {
1526
- parsedHost = new URL(options.url).host;
1527
- } catch {
1528
- throw new Error(
1529
- `webhook url is not a valid URL: ${JSON.stringify(options.url)}`
1530
- );
1531
- }
1532
- if (options.url.startsWith("http://") && options.headers !== void 0 && Object.keys(options.headers).length > 0) {
1533
- console.warn(
1534
- "[agntcms] webhook url uses http:// with custom headers \u2014 secrets will be transmitted in plaintext"
1535
- );
1536
- }
1537
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1538
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || !Number.isInteger(timeoutMs)) {
1539
- throw new Error(
1540
- `webhook timeoutMs must be a positive integer (ms), got: ${timeoutMs}`
1541
- );
1542
- }
1543
- const doFetch = options.fetch ?? ((url, init) => (
1544
- // The DOM/Node fetch types differ; cast to the minimal shape we declared.
1545
- // Using `globalThis.fetch` keeps this neutral across Node, Edge, and
1546
- // jsdom test environments.
1547
- globalThis.fetch(url, init)
1548
- ));
1549
- const baseHeaders = {
1550
- "Content-Type": "application/json",
1551
- ...options.headers
1552
- };
1553
- const store = async (submission) => {
1554
- const controller = new AbortController();
1555
- const timer = setTimeout(() => controller.abort(), timeoutMs);
1556
- let res;
1557
- try {
1558
- res = await doFetch(options.url, {
1559
- method: "POST",
1560
- headers: baseHeaders,
1561
- body: JSON.stringify(submission),
1562
- signal: controller.signal
1563
- });
1564
- } catch (err) {
1565
- const isAbort = controller.signal.aborted || err instanceof Error && err.name === "AbortError";
1566
- if (isAbort) {
1567
- console.error(
1568
- `[agntcms] webhook submission timed out after ${timeoutMs}ms`
1569
- );
1570
- throw new Error(`webhook request timed out after ${timeoutMs}ms`);
1571
- }
1572
- console.error(
1573
- `[agntcms] webhook submission failed: ${err instanceof Error ? err.message : String(err)}`
1574
- );
1575
- throw err;
1576
- } finally {
1577
- clearTimeout(timer);
1578
- }
1579
- if (!res.ok) {
1580
- let detail = "";
1581
- try {
1582
- detail = await res.text();
1583
- } catch {
1584
- }
1585
- console.error(
1586
- `[agntcms] webhook returned ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
1587
- );
1588
- throw new Error(`webhook responded with status ${res.status}`);
1589
- }
1590
- };
1591
- const list = async (_formName) => {
1592
- throw new SubmissionsNotReadableError(
1593
- "webhook submission adapter has no local copy; configure an FS adapter to enable listing"
1594
- );
1595
- };
1596
- const read = async (_formName, _id) => {
1597
- throw new SubmissionsNotReadableError(
1598
- "webhook submission adapter has no local copy; configure an FS adapter to enable reading"
1599
- );
1600
- };
1601
- return { info: { kind: "webhook", host: parsedHost }, store, list, read };
1602
- };
1603
-
1604
1102
  // src/sections/defineSection.ts
1605
1103
  function builtInDefault(fieldName, descriptor) {
1606
1104
  switch (descriptor.kind) {
@@ -1628,8 +1126,6 @@ function builtInDefault(fieldName, descriptor) {
1628
1126
  return descriptor.options[0]?.value ?? "";
1629
1127
  case "list":
1630
1128
  return [];
1631
- case "formOverrides":
1632
- return {};
1633
1129
  default: {
1634
1130
  const _exhaustive = descriptor;
1635
1131
  void _exhaustive;
@@ -1647,11 +1143,7 @@ function defineSection(input) {
1647
1143
  ...input.category !== void 0 ? { category: input.category } : {},
1648
1144
  schema: input.schema,
1649
1145
  component: input.component,
1650
- defaults,
1651
- // Conditional spread mirrors the `category` pattern — required so
1652
- // `exactOptionalPropertyTypes` does not see `layouts: undefined` being
1653
- // assigned to an optional-only property.
1654
- ...input.layouts !== void 0 ? { layouts: input.layouts } : {}
1146
+ defaults
1655
1147
  };
1656
1148
  }
1657
1149
 
@@ -1752,104 +1244,13 @@ function renderFallback(fallback) {
1752
1244
  return /* @__PURE__ */ jsx(Fragment, { children: fallback });
1753
1245
  }
1754
1246
 
1755
- // src/forms/defineForm.ts
1756
- var InvalidFormFieldError = class extends Error {
1757
- formName;
1758
- fieldName;
1759
- fieldKind;
1760
- constructor(formName, fieldName, fieldKind) {
1761
- super(
1762
- `Form "${formName}": field "${fieldName}" uses kind "${fieldKind}", which is not allowed in a form schema in v1. Allowed kinds: text, richText, number, boolean, select, link. Forbidden kinds: image, reference, list, formOverrides. See ARCHITECTURE.md \xA76.5.`
1763
- );
1764
- this.name = "InvalidFormFieldError";
1765
- this.formName = formName;
1766
- this.fieldName = fieldName;
1767
- this.fieldKind = fieldKind;
1768
- }
1769
- };
1770
- var HoneypotCollisionError = class extends Error {
1771
- formName;
1772
- fieldName;
1773
- reason;
1774
- constructor(formName, fieldName, reason = "collision") {
1775
- super(
1776
- reason === "empty" ? `Form "${formName}": honeypot name must be a non-empty string.` : `Form "${formName}": honeypot name "${fieldName}" collides with a real field. Choose a honeypot name that does not appear in the schema.`
1777
- );
1778
- this.name = "HoneypotCollisionError";
1779
- this.formName = formName;
1780
- this.fieldName = fieldName;
1781
- this.reason = reason;
1782
- }
1783
- };
1784
- var InvalidFormNameError = class extends Error {
1785
- constructor(name) {
1786
- super(
1787
- `Invalid form name ${JSON.stringify(name)}. Use letters, digits, hyphen, and underscore only (no path separators).`
1788
- );
1789
- this.name = "InvalidFormNameError";
1790
- }
1791
- };
1792
- var FORM_NAME_PATTERN2 = /^[a-zA-Z0-9_-]+$/;
1793
- function defineForm(input) {
1794
- if (typeof input.name !== "string" || !FORM_NAME_PATTERN2.test(input.name)) {
1795
- throw new InvalidFormNameError(input.name);
1796
- }
1797
- for (const [fieldName, descriptor] of Object.entries(input.schema)) {
1798
- const kind = descriptor.kind;
1799
- if (FORM_FORBIDDEN_KINDS.has(kind)) {
1800
- throw new InvalidFormFieldError(input.name, fieldName, kind);
1801
- }
1802
- }
1803
- if (input.honeypot !== void 0) {
1804
- if (typeof input.honeypot !== "string" || input.honeypot === "") {
1805
- throw new HoneypotCollisionError(input.name, input.honeypot, "empty");
1806
- }
1807
- if (Object.prototype.hasOwnProperty.call(input.schema, input.honeypot)) {
1808
- throw new HoneypotCollisionError(input.name, input.honeypot, "collision");
1809
- }
1810
- }
1811
- return input.honeypot !== void 0 ? { name: input.name, schema: input.schema, honeypot: input.honeypot } : { name: input.name, schema: input.schema };
1812
- }
1813
-
1814
- // src/forms/registry.ts
1815
- var DuplicateFormNameError = class extends Error {
1816
- formName;
1817
- constructor(name) {
1818
- super(
1819
- `Form name "${name}" is registered more than once. Form names must be unique within a config.`
1820
- );
1821
- this.name = "DuplicateFormNameError";
1822
- this.formName = name;
1823
- }
1824
- };
1825
- function buildFormRegistry(definitions) {
1826
- const byName = /* @__PURE__ */ new Map();
1827
- for (const def of definitions) {
1828
- if (byName.has(def.name)) {
1829
- throw new DuplicateFormNameError(def.name);
1830
- }
1831
- byName.set(def.name, def);
1832
- }
1833
- const registry = {
1834
- definitions,
1835
- get: (name) => byName.get(name),
1836
- has: (name) => byName.has(name)
1837
- };
1838
- return Object.freeze(registry);
1839
- }
1840
-
1841
1247
  // src/server.ts
1842
1248
  installDefaultAdapterFactories();
1843
1249
  export {
1844
1250
  BooleanField,
1845
1251
  ButtonField,
1846
- DuplicateFormNameError,
1847
- FormOverridesField,
1848
1252
  GlobalSlot,
1849
- HoneypotCollisionError,
1850
1253
  ImageField,
1851
- InvalidFormFieldError,
1852
- InvalidFormNameError,
1853
1254
  LinkField,
1854
1255
  ListField,
1855
1256
  NOT_FOUND_PAGE_SLUG,
@@ -1858,22 +1259,14 @@ export {
1858
1259
  RichTextField,
1859
1260
  SERVER_ERROR_PAGE_SLUG,
1860
1261
  SelectField,
1861
- SubmissionsNotReadableError,
1862
1262
  TextField,
1863
1263
  VideoField,
1864
- buildFormRegistry,
1865
1264
  createDefaultAssetAdapter,
1866
1265
  createDefaultContentAdapter,
1867
- createDefaultSubmissionAdapter,
1868
1266
  createFsAssetAdapter,
1869
1267
  createFsContentAdapter,
1870
- createFsSubmissionAdapter,
1871
1268
  createListPages,
1872
- createRateLimit,
1873
1269
  createRuntime,
1874
- createSubmitForm,
1875
- createWebhookSubmissionAdapter,
1876
- defineForm,
1877
1270
  defineSection,
1878
1271
  getReservedPageSlugViolation,
1879
1272
  hasUniqueSectionIds,