@agntcms/next 0.3.2 → 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.cjs CHANGED
@@ -32,13 +32,8 @@ var server_exports = {};
32
32
  __export(server_exports, {
33
33
  BooleanField: () => BooleanField,
34
34
  ButtonField: () => ButtonField,
35
- DuplicateFormNameError: () => DuplicateFormNameError,
36
- FormOverridesField: () => FormOverridesField,
37
35
  GlobalSlot: () => GlobalSlot,
38
- HoneypotCollisionError: () => HoneypotCollisionError,
39
36
  ImageField: () => ImageField,
40
- InvalidFormFieldError: () => InvalidFormFieldError,
41
- InvalidFormNameError: () => InvalidFormNameError,
42
37
  LinkField: () => LinkField,
43
38
  ListField: () => ListField,
44
39
  NOT_FOUND_PAGE_SLUG: () => NOT_FOUND_PAGE_SLUG,
@@ -47,22 +42,14 @@ __export(server_exports, {
47
42
  RichTextField: () => RichTextField,
48
43
  SERVER_ERROR_PAGE_SLUG: () => SERVER_ERROR_PAGE_SLUG,
49
44
  SelectField: () => SelectField,
50
- SubmissionsNotReadableError: () => SubmissionsNotReadableError,
51
45
  TextField: () => TextField,
52
46
  VideoField: () => VideoField,
53
- buildFormRegistry: () => buildFormRegistry,
54
47
  createDefaultAssetAdapter: () => createDefaultAssetAdapter,
55
48
  createDefaultContentAdapter: () => createDefaultContentAdapter,
56
- createDefaultSubmissionAdapter: () => createDefaultSubmissionAdapter,
57
49
  createFsAssetAdapter: () => createFsAssetAdapter,
58
50
  createFsContentAdapter: () => createFsContentAdapter,
59
- createFsSubmissionAdapter: () => createFsSubmissionAdapter,
60
51
  createListPages: () => createListPages,
61
- createRateLimit: () => createRateLimit,
62
52
  createRuntime: () => createRuntime,
63
- createSubmitForm: () => createSubmitForm,
64
- createWebhookSubmissionAdapter: () => createWebhookSubmissionAdapter,
65
- defineForm: () => defineForm,
66
53
  defineSection: () => defineSection,
67
54
  getReservedPageSlugViolation: () => getReservedPageSlugViolation,
68
55
  hasUniqueSectionIds: () => hasUniqueSectionIds,
@@ -415,37 +402,6 @@ function normalizeLinkValue(raw) {
415
402
  return { type: "internal", slug: "", label: "" };
416
403
  }
417
404
 
418
- // src/domain/form.ts
419
- var FORM_FORBIDDEN_KINDS = /* @__PURE__ */ new Set([
420
- "image",
421
- "video",
422
- "reference",
423
- "list",
424
- // `formOverrides` is a section-only descriptor (it overrides another
425
- // form schema instance). Putting it inside a form would mean a form's
426
- // payload could carry overrides for itself or another form — a
427
- // recursive shape with no ergonomic editor UI. Section-only by design.
428
- "formOverrides",
429
- // `button` is a section-only descriptor: a styled CTA with an
430
- // optional link. Public-form payloads collect user input — a button
431
- // value is authored content, not a submitted answer. Section-only
432
- // by design (mirrors `formOverrides`).
433
- "button"
434
- ]);
435
- var SubmissionsNotReadableError = class extends Error {
436
- constructor(message = "submission adapter does not support reading") {
437
- super(message);
438
- this.name = "SubmissionsNotReadableError";
439
- }
440
- };
441
-
442
- // src/domain/formOverrides.ts
443
- var FormOverridesField = (formName, opts) => ({
444
- kind: "formOverrides",
445
- formName,
446
- ...opts?.default !== void 0 ? { default: opts.default } : {}
447
- });
448
-
449
405
  // src/storage/fs/content.ts
450
406
  var SLUG_PATTERN2 = /^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*$/;
451
407
  var assertValidSlug = (slug) => {
@@ -506,7 +462,7 @@ var createFsContentAdapter = (options) => {
506
462
  throw err;
507
463
  }
508
464
  };
509
- const listJsonFiles2 = async (dir) => {
465
+ const listJsonFiles = async (dir) => {
510
466
  let entries;
511
467
  try {
512
468
  entries = await fs3.readdir(dir, { withFileTypes: true });
@@ -520,7 +476,7 @@ var createFsContentAdapter = (options) => {
520
476
  results.push(entry.name);
521
477
  } else if (entry.isDirectory()) {
522
478
  const subDir = path3.join(dir, entry.name);
523
- const subFiles = await listJsonFiles2(subDir);
479
+ const subFiles = await listJsonFiles(subDir);
524
480
  for (const sub of subFiles) {
525
481
  results.push(`${entry.name}/${sub}`);
526
482
  }
@@ -559,7 +515,7 @@ var createFsContentAdapter = (options) => {
559
515
  await writeAtomic(filePath, JSON.stringify(page, null, 2));
560
516
  };
561
517
  const listDrafts = async () => {
562
- const files = await listJsonFiles2(draftsDir);
518
+ const files = await listJsonFiles(draftsDir);
563
519
  const results = [];
564
520
  for (const relPath of files) {
565
521
  const slug = relPath.slice(0, -".json".length);
@@ -569,7 +525,7 @@ var createFsContentAdapter = (options) => {
569
525
  return results;
570
526
  };
571
527
  const listPages = async () => {
572
- const files = await listJsonFiles2(pagesDir);
528
+ const files = await listJsonFiles(pagesDir);
573
529
  const results = [];
574
530
  for (const relPath of files) {
575
531
  const slug = relPath.slice(0, -".json".length);
@@ -579,7 +535,7 @@ var createFsContentAdapter = (options) => {
579
535
  return results;
580
536
  };
581
537
  const listPageSummaries = async () => {
582
- const files = await listJsonFiles2(pagesDir);
538
+ const files = await listJsonFiles(pagesDir);
583
539
  const results = [];
584
540
  for (const relPath of files) {
585
541
  const slug = relPath.slice(0, -".json".length);
@@ -877,151 +833,6 @@ var createFsContentAdapter = (options) => {
877
833
  };
878
834
  };
879
835
 
880
- // src/storage/fs/submissions.ts
881
- var import_node_crypto2 = require("crypto");
882
- var fs4 = __toESM(require("fs/promises"), 1);
883
- var path4 = __toESM(require("path"), 1);
884
- var DEFAULT_MAX_LIST = 500;
885
- var FORM_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
886
- var parseSubmission = (raw) => {
887
- let parsed;
888
- try {
889
- parsed = JSON.parse(raw);
890
- } catch {
891
- return null;
892
- }
893
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
894
- return null;
895
- }
896
- const obj = parsed;
897
- if (typeof obj["formName"] !== "string") return null;
898
- if (typeof obj["id"] !== "string") return null;
899
- if (typeof obj["submittedAt"] !== "string") return null;
900
- if (obj["payload"] === null || typeof obj["payload"] !== "object" || Array.isArray(obj["payload"])) {
901
- return null;
902
- }
903
- return {
904
- formName: obj["formName"],
905
- id: obj["id"],
906
- submittedAt: obj["submittedAt"],
907
- payload: obj["payload"]
908
- };
909
- };
910
- var assertValidFormName = (name) => {
911
- if (typeof name !== "string" || !FORM_NAME_PATTERN.test(name)) {
912
- throw new Error(`invalid form name: ${JSON.stringify(name)}`);
913
- }
914
- };
915
- var submissionFilename = (submittedAt, id) => {
916
- const ts = submittedAt.replace(/:/g, "-");
917
- return `${ts}-${id}.json`;
918
- };
919
- var listJsonFiles = async (dir) => {
920
- let entries;
921
- try {
922
- entries = await fs4.readdir(dir, { withFileTypes: true });
923
- } catch (err) {
924
- if (isEnoent(err)) return [];
925
- throw err;
926
- }
927
- const results = [];
928
- for (const entry of entries) {
929
- if (entry.isFile() && entry.name.endsWith(".json")) {
930
- results.push(entry.name);
931
- }
932
- }
933
- return results;
934
- };
935
- var createFsSubmissionAdapter = (options) => {
936
- const { submissionsRoot } = options;
937
- if (!path4.isAbsolute(submissionsRoot)) {
938
- throw new Error(
939
- `submissionsRoot must be an absolute path, got: ${JSON.stringify(submissionsRoot)}`
940
- );
941
- }
942
- const maxList = options.maxList ?? DEFAULT_MAX_LIST;
943
- if (!Number.isFinite(maxList) || maxList <= 0 || !Number.isInteger(maxList)) {
944
- throw new Error(
945
- `maxList must be a positive integer, got: ${maxList}`
946
- );
947
- }
948
- const rootResolved = path4.resolve(submissionsRoot);
949
- const rootWithSep = rootResolved.endsWith(path4.sep) ? rootResolved : rootResolved + path4.sep;
950
- const formDir = (formName) => {
951
- assertValidFormName(formName);
952
- const dir = path4.resolve(rootResolved, formName);
953
- if (!dir.startsWith(rootWithSep)) {
954
- throw new Error(`form name escapes submissions root: ${JSON.stringify(formName)}`);
955
- }
956
- return dir;
957
- };
958
- const cryptoSuffix = () => (0, import_node_crypto2.randomUUID)();
959
- const store = async (submission) => {
960
- const dir = formDir(submission.formName);
961
- const filename = submissionFilename(submission.submittedAt, submission.id);
962
- if (filename.includes("/") || filename.includes("\\")) {
963
- throw new Error(`submission id contains path separator: ${JSON.stringify(submission.id)}`);
964
- }
965
- const target = path4.join(dir, filename);
966
- await writeAtomic(target, JSON.stringify(submission, null, 2), cryptoSuffix);
967
- };
968
- const list = async (formName) => {
969
- const dir = formDir(formName);
970
- const files = await listJsonFiles(dir);
971
- const summaries = [];
972
- for (const filename of files) {
973
- const stem = filename.slice(0, -".json".length);
974
- const lastDash = stem.lastIndexOf("-");
975
- if (lastDash < 0) continue;
976
- const tsRaw = stem.slice(0, lastDash);
977
- const id = stem.slice(lastDash + 1);
978
- if (id === "") continue;
979
- const tIndex = tsRaw.indexOf("T");
980
- if (tIndex < 0) continue;
981
- const datePart = tsRaw.slice(0, tIndex);
982
- const timePart = tsRaw.slice(tIndex + 1).replace(/-/g, ":");
983
- const submittedAt = `${datePart}T${timePart}`;
984
- summaries.push({ id, submittedAt });
985
- }
986
- summaries.sort((a, b) => {
987
- if (a.submittedAt !== b.submittedAt) {
988
- return a.submittedAt < b.submittedAt ? 1 : -1;
989
- }
990
- if (a.id !== b.id) {
991
- return a.id < b.id ? 1 : -1;
992
- }
993
- return 0;
994
- });
995
- return summaries.length > maxList ? summaries.slice(0, maxList) : summaries;
996
- };
997
- const read = async (formName, id) => {
998
- if (id.includes("/") || id.includes("\\") || id.includes("..")) {
999
- return null;
1000
- }
1001
- const dir = formDir(formName);
1002
- let entries;
1003
- try {
1004
- entries = (await fs4.readdir(dir)).filter((n) => n.endsWith(".json"));
1005
- } catch (err) {
1006
- if (isEnoent(err)) return null;
1007
- throw err;
1008
- }
1009
- const suffix = `-${id}.json`;
1010
- const found = entries.find((n) => n.endsWith(suffix));
1011
- if (!found) return null;
1012
- const target = path4.join(dir, found);
1013
- let raw;
1014
- try {
1015
- raw = await fs4.readFile(target, { encoding: "utf8" });
1016
- } catch (err) {
1017
- if (isEnoent(err)) return null;
1018
- throw err;
1019
- }
1020
- return parseSubmission(raw);
1021
- };
1022
- return { info: { kind: "fs" }, store, list, read };
1023
- };
1024
-
1025
836
  // src/config/defaults-registry.ts
1026
837
  var SLOT = /* @__PURE__ */ Symbol.for("@agntcms/next/default-adapter-factories");
1027
838
  function holder() {
@@ -1032,43 +843,36 @@ function registerDefaultAdapterFactories(factories) {
1032
843
  }
1033
844
 
1034
845
  // src/config/defaults.ts
1035
- var path5 = __toESM(require("path"), 1);
846
+ var path4 = __toESM(require("path"), 1);
1036
847
  function createDefaultContentAdapter(options) {
1037
848
  const root = options?.projectRoot ?? process.cwd();
1038
849
  return createFsContentAdapter({
1039
- contentRoot: path5.resolve(root, "content")
850
+ contentRoot: path4.resolve(root, "content")
1040
851
  });
1041
852
  }
1042
853
  function createDefaultAssetAdapter(options) {
1043
854
  const root = options?.projectRoot ?? process.cwd();
1044
855
  return createFsAssetAdapter({
1045
- assetsRoot: path5.resolve(root, "public/assets"),
856
+ assetsRoot: path4.resolve(root, "public/assets"),
1046
857
  publicUrlBase: "/assets"
1047
858
  });
1048
859
  }
1049
- function createDefaultSubmissionAdapter(options) {
1050
- const root = options?.projectRoot ?? process.cwd();
1051
- return createFsSubmissionAdapter({
1052
- submissionsRoot: path5.resolve(root, "content/submissions")
1053
- });
1054
- }
1055
860
  function installDefaultAdapterFactories() {
1056
861
  const factories = {
1057
862
  content: createDefaultContentAdapter,
1058
- asset: createDefaultAssetAdapter,
1059
- submission: createDefaultSubmissionAdapter
863
+ asset: createDefaultAssetAdapter
1060
864
  };
1061
865
  registerDefaultAdapterFactories(factories);
1062
866
  }
1063
867
 
1064
868
  // src/runtime/getContent.ts
1065
- var import_node_crypto5 = require("crypto");
869
+ var import_node_crypto3 = require("crypto");
1066
870
 
1067
871
  // src/runtime/getGlobal.ts
1068
- var import_node_crypto3 = require("crypto");
872
+ var import_node_crypto2 = require("crypto");
1069
873
  var computeRevision = (global) => {
1070
874
  const serialized = JSON.stringify(global);
1071
- return (0, import_node_crypto3.createHash)("sha256").update(serialized).digest("hex");
875
+ return (0, import_node_crypto2.createHash)("sha256").update(serialized).digest("hex");
1072
876
  };
1073
877
  var wrapGlobalData = (global, revision) => {
1074
878
  const data = global.data;
@@ -1165,160 +969,6 @@ var createListPages = ({
1165
969
  };
1166
970
  };
1167
971
 
1168
- // src/runtime/submitForm.ts
1169
- var import_node_crypto4 = require("crypto");
1170
- var CROCKFORD32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
1171
- var defaultGenerateId = () => {
1172
- const bytes = (0, import_node_crypto4.randomBytes)(16);
1173
- let out = "";
1174
- for (let i = 0; i < 16; i++) {
1175
- out += CROCKFORD32[bytes[i] & 31];
1176
- }
1177
- return out;
1178
- };
1179
- var validateNonEmptyString = (value) => {
1180
- if (typeof value !== "string") return "must be a string";
1181
- if (value.trim() === "") return "must not be empty";
1182
- return null;
1183
- };
1184
- var validateNumber = (value, descriptor) => {
1185
- if (typeof value !== "number" || !Number.isFinite(value)) {
1186
- return "must be a finite number";
1187
- }
1188
- if (descriptor.min !== void 0 && value < descriptor.min) {
1189
- return `must be >= ${descriptor.min}`;
1190
- }
1191
- if (descriptor.max !== void 0 && value > descriptor.max) {
1192
- return `must be <= ${descriptor.max}`;
1193
- }
1194
- return null;
1195
- };
1196
- var validateBoolean = (value) => {
1197
- if (typeof value !== "boolean") return "must be true or false";
1198
- return null;
1199
- };
1200
- var validateSelect = (value, descriptor) => {
1201
- if (typeof value !== "string") return "must be a string";
1202
- if (!descriptor.options.some((opt) => opt.value === value)) {
1203
- return "must be one of the declared options";
1204
- }
1205
- return null;
1206
- };
1207
- var validateLinkPayload = (value) => {
1208
- if (value === null || typeof value !== "object") return "must be a link object";
1209
- const obj = value;
1210
- if (typeof obj["label"] !== "string") return "label must be a string";
1211
- if (obj["type"] === "internal") {
1212
- const slug = obj["slug"];
1213
- if (typeof slug !== "string") return "slug must be a string";
1214
- if (slug.trim() === "") return "slug must be a non-empty string";
1215
- return validateInternalSlug(slug);
1216
- }
1217
- if (obj["type"] === "external") {
1218
- const url = obj["url"];
1219
- if (typeof url !== "string") return "url must be a string";
1220
- if (url.trim() === "") return "url must be a non-empty string";
1221
- return validateExternalUrl(url);
1222
- }
1223
- if (obj["type"] === "email") {
1224
- const email = obj["email"];
1225
- if (typeof email !== "string") return "email must be a string";
1226
- if (email.trim() === "") return "email must be a non-empty string";
1227
- return validateEmail(email);
1228
- }
1229
- if (obj["type"] === "phone") {
1230
- const phone = obj["phone"];
1231
- if (typeof phone !== "string") return "phone must be a string";
1232
- if (phone.trim() === "") return "phone must be a non-empty string";
1233
- return validatePhone(phone);
1234
- }
1235
- return 'type must be "internal", "external", "email", or "phone"';
1236
- };
1237
- var validateAndNormalisePayload = (schema, payload) => {
1238
- const errors = {};
1239
- const normalised = {};
1240
- for (const [fieldName, descriptor] of Object.entries(schema)) {
1241
- if (!Object.prototype.hasOwnProperty.call(payload, fieldName)) {
1242
- errors[fieldName] = "is required";
1243
- continue;
1244
- }
1245
- const value = payload[fieldName];
1246
- let err = null;
1247
- switch (descriptor.kind) {
1248
- case "text":
1249
- case "richText": {
1250
- err = validateNonEmptyString(value);
1251
- if (!err) {
1252
- normalised[fieldName] = value.trim();
1253
- }
1254
- break;
1255
- }
1256
- case "number": {
1257
- err = validateNumber(value, descriptor);
1258
- if (!err) normalised[fieldName] = value;
1259
- break;
1260
- }
1261
- case "boolean": {
1262
- err = validateBoolean(value);
1263
- if (!err) normalised[fieldName] = value;
1264
- break;
1265
- }
1266
- case "select": {
1267
- err = validateSelect(value, descriptor);
1268
- if (!err) normalised[fieldName] = value;
1269
- break;
1270
- }
1271
- case "link": {
1272
- err = validateLinkPayload(value);
1273
- if (!err) {
1274
- const linkObj = value;
1275
- const label = typeof linkObj["label"] === "string" ? linkObj["label"] : "";
1276
- 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 };
1277
- normalised[fieldName] = normalisedLink;
1278
- }
1279
- break;
1280
- }
1281
- default: {
1282
- const _exhaustive = descriptor;
1283
- void _exhaustive;
1284
- err = "unsupported field type";
1285
- }
1286
- }
1287
- if (err) errors[fieldName] = err;
1288
- }
1289
- if (Object.keys(errors).length > 0) return { ok: false, errors };
1290
- return { ok: true, payload: normalised };
1291
- };
1292
- function createSubmitForm(deps) {
1293
- const { forms, submissionAdapter } = deps;
1294
- const generateId = deps.generateId ?? defaultGenerateId;
1295
- const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
1296
- return async function submitForm(input) {
1297
- const def = forms.get(input.formName);
1298
- if (!def) {
1299
- return { ok: false, error: "unknown_form" };
1300
- }
1301
- if (def.honeypot !== void 0) {
1302
- const trapValue = input.payload[def.honeypot];
1303
- if (typeof trapValue === "string" && trapValue.length > 0) {
1304
- return { ok: true, stored: false, suppressed: "honeypot" };
1305
- }
1306
- }
1307
- const validated = validateAndNormalisePayload(def.schema, input.payload);
1308
- if (!validated.ok) {
1309
- return { ok: false, error: "validation_failed", errors: validated.errors };
1310
- }
1311
- const submission = {
1312
- formName: def.name,
1313
- payload: validated.payload,
1314
- submittedAt: now(),
1315
- id: generateId()
1316
- };
1317
- await submissionAdapter.store(submission);
1318
- return { ok: true, stored: true, id: submission.id };
1319
- };
1320
- }
1321
-
1322
972
  // src/runtime/getContent.ts
1323
973
  var isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
1324
974
  var looksLikeLink = (obj) => {
@@ -1367,7 +1017,7 @@ var normalizeLinksInSections = (sections) => {
1367
1017
  };
1368
1018
  var computeRevision2 = (page) => {
1369
1019
  const serialized = JSON.stringify(page);
1370
- return (0, import_node_crypto5.createHash)("sha256").update(serialized).digest("hex");
1020
+ return (0, import_node_crypto3.createHash)("sha256").update(serialized).digest("hex");
1371
1021
  };
1372
1022
  var withSections = (page, sections) => ({
1373
1023
  ...page,
@@ -1478,83 +1128,7 @@ function createRuntime(options) {
1478
1128
  };
1479
1129
  const getGlobal = createGetGlobal(contentAdapter);
1480
1130
  const listPages = createListPages({ contentAdapter });
1481
- const noopAdapter = {
1482
- info: { kind: "fs" },
1483
- store: async () => {
1484
- throw new Error("submissionAdapter not configured; pass one to createRuntime");
1485
- },
1486
- list: async () => [],
1487
- read: async () => null
1488
- };
1489
- const submitForm = createSubmitForm({
1490
- forms: options.forms ?? emptyFormRegistry,
1491
- submissionAdapter: options.submissionAdapter ?? noopAdapter
1492
- });
1493
- return { getContent, publishDraft, getGlobal, submitForm, listPages };
1494
- }
1495
- var emptyFormRegistry = Object.freeze({
1496
- definitions: [],
1497
- get: () => void 0,
1498
- has: () => false
1499
- });
1500
-
1501
- // src/runtime/rateLimit.ts
1502
- var DEFAULT_MAX_BUCKETS = 1e4;
1503
- function createRateLimit(options) {
1504
- const { perWindow, windowMs } = options;
1505
- if (!Number.isFinite(perWindow) || perWindow <= 0) {
1506
- throw new Error(`perWindow must be a positive number, got: ${perWindow}`);
1507
- }
1508
- if (!Number.isFinite(windowMs) || windowMs <= 0) {
1509
- throw new Error(`windowMs must be a positive number, got: ${windowMs}`);
1510
- }
1511
- const maxBuckets = options.maxBuckets ?? DEFAULT_MAX_BUCKETS;
1512
- if (!Number.isFinite(maxBuckets) || maxBuckets <= 0 || !Number.isInteger(maxBuckets)) {
1513
- throw new Error(`maxBuckets must be a positive integer, got: ${maxBuckets}`);
1514
- }
1515
- const now = options.now ?? Date.now;
1516
- const buckets = /* @__PURE__ */ new Map();
1517
- const keyOf = (ip, formName) => `${ip}:${formName}`;
1518
- const sweepExpired = (t) => {
1519
- for (const [k, b] of buckets) {
1520
- if (t >= b.resetAt) buckets.delete(k);
1521
- }
1522
- };
1523
- const evictOldest = () => {
1524
- let oldestKey = null;
1525
- let oldestResetAt = Number.POSITIVE_INFINITY;
1526
- for (const [k, b] of buckets) {
1527
- if (b.resetAt < oldestResetAt) {
1528
- oldestResetAt = b.resetAt;
1529
- oldestKey = k;
1530
- }
1531
- }
1532
- if (oldestKey !== null) buckets.delete(oldestKey);
1533
- };
1534
- return {
1535
- check: (ip, formName) => {
1536
- const key = keyOf(ip, formName);
1537
- const t = now();
1538
- const existing = buckets.get(key);
1539
- if (!existing || t >= existing.resetAt) {
1540
- if (!existing && buckets.size >= maxBuckets) {
1541
- sweepExpired(t);
1542
- while (buckets.size >= maxBuckets) {
1543
- evictOldest();
1544
- }
1545
- }
1546
- const bucket = { count: 1, resetAt: t + windowMs };
1547
- buckets.set(key, bucket);
1548
- return { allowed: true, count: 1, resetAt: bucket.resetAt };
1549
- }
1550
- existing.count += 1;
1551
- const allowed = existing.count <= perWindow;
1552
- return { allowed, count: existing.count, resetAt: existing.resetAt };
1553
- },
1554
- reset: () => {
1555
- buckets.clear();
1556
- }
1557
- };
1131
+ return { getContent, publishDraft, getGlobal, listPages };
1558
1132
  }
1559
1133
 
1560
1134
  // src/runtime/systemPages.ts
@@ -1592,95 +1166,6 @@ var isSitemapEligibleSlug = (slug) => {
1592
1166
  return !SITEMAP_EXCLUDED_TERMINAL_SLUGS.has(terminal);
1593
1167
  };
1594
1168
 
1595
- // src/storage/webhook/submissions.ts
1596
- var URL_PATTERN = /^https?:\/\//;
1597
- var DEFAULT_TIMEOUT_MS = 1e4;
1598
- var createWebhookSubmissionAdapter = (options) => {
1599
- if (typeof options.url !== "string" || !URL_PATTERN.test(options.url)) {
1600
- throw new Error(
1601
- `webhook url must start with http:// or https://, got: ${JSON.stringify(options.url)}`
1602
- );
1603
- }
1604
- let parsedHost;
1605
- try {
1606
- parsedHost = new URL(options.url).host;
1607
- } catch {
1608
- throw new Error(
1609
- `webhook url is not a valid URL: ${JSON.stringify(options.url)}`
1610
- );
1611
- }
1612
- if (options.url.startsWith("http://") && options.headers !== void 0 && Object.keys(options.headers).length > 0) {
1613
- console.warn(
1614
- "[agntcms] webhook url uses http:// with custom headers \u2014 secrets will be transmitted in plaintext"
1615
- );
1616
- }
1617
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1618
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || !Number.isInteger(timeoutMs)) {
1619
- throw new Error(
1620
- `webhook timeoutMs must be a positive integer (ms), got: ${timeoutMs}`
1621
- );
1622
- }
1623
- const doFetch = options.fetch ?? ((url, init) => (
1624
- // The DOM/Node fetch types differ; cast to the minimal shape we declared.
1625
- // Using `globalThis.fetch` keeps this neutral across Node, Edge, and
1626
- // jsdom test environments.
1627
- globalThis.fetch(url, init)
1628
- ));
1629
- const baseHeaders = {
1630
- "Content-Type": "application/json",
1631
- ...options.headers
1632
- };
1633
- const store = async (submission) => {
1634
- const controller = new AbortController();
1635
- const timer = setTimeout(() => controller.abort(), timeoutMs);
1636
- let res;
1637
- try {
1638
- res = await doFetch(options.url, {
1639
- method: "POST",
1640
- headers: baseHeaders,
1641
- body: JSON.stringify(submission),
1642
- signal: controller.signal
1643
- });
1644
- } catch (err) {
1645
- const isAbort = controller.signal.aborted || err instanceof Error && err.name === "AbortError";
1646
- if (isAbort) {
1647
- console.error(
1648
- `[agntcms] webhook submission timed out after ${timeoutMs}ms`
1649
- );
1650
- throw new Error(`webhook request timed out after ${timeoutMs}ms`);
1651
- }
1652
- console.error(
1653
- `[agntcms] webhook submission failed: ${err instanceof Error ? err.message : String(err)}`
1654
- );
1655
- throw err;
1656
- } finally {
1657
- clearTimeout(timer);
1658
- }
1659
- if (!res.ok) {
1660
- let detail = "";
1661
- try {
1662
- detail = await res.text();
1663
- } catch {
1664
- }
1665
- console.error(
1666
- `[agntcms] webhook returned ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
1667
- );
1668
- throw new Error(`webhook responded with status ${res.status}`);
1669
- }
1670
- };
1671
- const list = async (_formName) => {
1672
- throw new SubmissionsNotReadableError(
1673
- "webhook submission adapter has no local copy; configure an FS adapter to enable listing"
1674
- );
1675
- };
1676
- const read = async (_formName, _id) => {
1677
- throw new SubmissionsNotReadableError(
1678
- "webhook submission adapter has no local copy; configure an FS adapter to enable reading"
1679
- );
1680
- };
1681
- return { info: { kind: "webhook", host: parsedHost }, store, list, read };
1682
- };
1683
-
1684
1169
  // src/sections/defineSection.ts
1685
1170
  function builtInDefault(fieldName, descriptor) {
1686
1171
  switch (descriptor.kind) {
@@ -1708,8 +1193,6 @@ function builtInDefault(fieldName, descriptor) {
1708
1193
  return descriptor.options[0]?.value ?? "";
1709
1194
  case "list":
1710
1195
  return [];
1711
- case "formOverrides":
1712
- return {};
1713
1196
  default: {
1714
1197
  const _exhaustive = descriptor;
1715
1198
  void _exhaustive;
@@ -1727,11 +1210,7 @@ function defineSection(input) {
1727
1210
  ...input.category !== void 0 ? { category: input.category } : {},
1728
1211
  schema: input.schema,
1729
1212
  component: input.component,
1730
- defaults,
1731
- // Conditional spread mirrors the `category` pattern — required so
1732
- // `exactOptionalPropertyTypes` does not see `layouts: undefined` being
1733
- // assigned to an optional-only property.
1734
- ...input.layouts !== void 0 ? { layouts: input.layouts } : {}
1213
+ defaults
1735
1214
  };
1736
1215
  }
1737
1216
 
@@ -1832,105 +1311,14 @@ function renderFallback(fallback) {
1832
1311
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback });
1833
1312
  }
1834
1313
 
1835
- // src/forms/defineForm.ts
1836
- var InvalidFormFieldError = class extends Error {
1837
- formName;
1838
- fieldName;
1839
- fieldKind;
1840
- constructor(formName, fieldName, fieldKind) {
1841
- super(
1842
- `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.`
1843
- );
1844
- this.name = "InvalidFormFieldError";
1845
- this.formName = formName;
1846
- this.fieldName = fieldName;
1847
- this.fieldKind = fieldKind;
1848
- }
1849
- };
1850
- var HoneypotCollisionError = class extends Error {
1851
- formName;
1852
- fieldName;
1853
- reason;
1854
- constructor(formName, fieldName, reason = "collision") {
1855
- super(
1856
- 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.`
1857
- );
1858
- this.name = "HoneypotCollisionError";
1859
- this.formName = formName;
1860
- this.fieldName = fieldName;
1861
- this.reason = reason;
1862
- }
1863
- };
1864
- var InvalidFormNameError = class extends Error {
1865
- constructor(name) {
1866
- super(
1867
- `Invalid form name ${JSON.stringify(name)}. Use letters, digits, hyphen, and underscore only (no path separators).`
1868
- );
1869
- this.name = "InvalidFormNameError";
1870
- }
1871
- };
1872
- var FORM_NAME_PATTERN2 = /^[a-zA-Z0-9_-]+$/;
1873
- function defineForm(input) {
1874
- if (typeof input.name !== "string" || !FORM_NAME_PATTERN2.test(input.name)) {
1875
- throw new InvalidFormNameError(input.name);
1876
- }
1877
- for (const [fieldName, descriptor] of Object.entries(input.schema)) {
1878
- const kind = descriptor.kind;
1879
- if (FORM_FORBIDDEN_KINDS.has(kind)) {
1880
- throw new InvalidFormFieldError(input.name, fieldName, kind);
1881
- }
1882
- }
1883
- if (input.honeypot !== void 0) {
1884
- if (typeof input.honeypot !== "string" || input.honeypot === "") {
1885
- throw new HoneypotCollisionError(input.name, input.honeypot, "empty");
1886
- }
1887
- if (Object.prototype.hasOwnProperty.call(input.schema, input.honeypot)) {
1888
- throw new HoneypotCollisionError(input.name, input.honeypot, "collision");
1889
- }
1890
- }
1891
- return input.honeypot !== void 0 ? { name: input.name, schema: input.schema, honeypot: input.honeypot } : { name: input.name, schema: input.schema };
1892
- }
1893
-
1894
- // src/forms/registry.ts
1895
- var DuplicateFormNameError = class extends Error {
1896
- formName;
1897
- constructor(name) {
1898
- super(
1899
- `Form name "${name}" is registered more than once. Form names must be unique within a config.`
1900
- );
1901
- this.name = "DuplicateFormNameError";
1902
- this.formName = name;
1903
- }
1904
- };
1905
- function buildFormRegistry(definitions) {
1906
- const byName = /* @__PURE__ */ new Map();
1907
- for (const def of definitions) {
1908
- if (byName.has(def.name)) {
1909
- throw new DuplicateFormNameError(def.name);
1910
- }
1911
- byName.set(def.name, def);
1912
- }
1913
- const registry = {
1914
- definitions,
1915
- get: (name) => byName.get(name),
1916
- has: (name) => byName.has(name)
1917
- };
1918
- return Object.freeze(registry);
1919
- }
1920
-
1921
1314
  // src/server.ts
1922
1315
  installDefaultAdapterFactories();
1923
1316
  // Annotate the CommonJS export names for ESM import in node:
1924
1317
  0 && (module.exports = {
1925
1318
  BooleanField,
1926
1319
  ButtonField,
1927
- DuplicateFormNameError,
1928
- FormOverridesField,
1929
1320
  GlobalSlot,
1930
- HoneypotCollisionError,
1931
1321
  ImageField,
1932
- InvalidFormFieldError,
1933
- InvalidFormNameError,
1934
1322
  LinkField,
1935
1323
  ListField,
1936
1324
  NOT_FOUND_PAGE_SLUG,
@@ -1939,22 +1327,14 @@ installDefaultAdapterFactories();
1939
1327
  RichTextField,
1940
1328
  SERVER_ERROR_PAGE_SLUG,
1941
1329
  SelectField,
1942
- SubmissionsNotReadableError,
1943
1330
  TextField,
1944
1331
  VideoField,
1945
- buildFormRegistry,
1946
1332
  createDefaultAssetAdapter,
1947
1333
  createDefaultContentAdapter,
1948
- createDefaultSubmissionAdapter,
1949
1334
  createFsAssetAdapter,
1950
1335
  createFsContentAdapter,
1951
- createFsSubmissionAdapter,
1952
1336
  createListPages,
1953
- createRateLimit,
1954
1337
  createRuntime,
1955
- createSubmitForm,
1956
- createWebhookSubmissionAdapter,
1957
- defineForm,
1958
1338
  defineSection,
1959
1339
  getReservedPageSlugViolation,
1960
1340
  hasUniqueSectionIds,