@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/{assets-Cyt9upqW.d.cts → assets-B3oNeLdj.d.cts} +1 -1
- package/dist/{assets-P8OCigDG.d.ts → assets-DHumg-X7.d.ts} +1 -1
- package/dist/client.cjs +2831 -4747
- package/dist/client.d.cts +11 -153
- package/dist/client.d.ts +11 -153
- package/dist/client.mjs +4750 -6675
- package/dist/config.cjs +25 -121
- package/dist/config.d.cts +6 -59
- package/dist/config.d.ts +6 -59
- package/dist/config.mjs +25 -114
- package/dist/{defineSection-Kr0pWqMY.d.ts → defineSection-ByG5uwiR.d.cts} +5 -24
- package/dist/{defineSection-9qQ5ulAH.d.cts → defineSection-ChkZCQyQ.d.ts} +5 -24
- package/dist/{rateLimit-CXptRM_K.d.ts → getContent-DAgAn095.d.ts} +3 -132
- package/dist/{rateLimit-CiROGTLE.d.cts → getContent-yK-sARoc.d.cts} +3 -132
- package/dist/handlers.cjs +19 -382
- package/dist/handlers.d.cts +4 -73
- package/dist/handlers.d.ts +4 -73
- package/dist/handlers.mjs +19 -377
- package/dist/index.cjs +1 -109
- package/dist/index.d.cts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.mjs +1 -103
- package/dist/{form-BqY0H1V5.d.cts → page-DXF0_SrY.d.cts} +3 -293
- package/dist/{form-BqY0H1V5.d.ts → page-DXF0_SrY.d.ts} +3 -293
- package/dist/server.cjs +15 -635
- package/dist/server.d.cts +8 -75
- package/dist/server.d.ts +8 -75
- package/dist/server.mjs +11 -618
- package/package.json +1 -1
- package/dist/defineForm-Bp9vzW56.d.ts +0 -71
- package/dist/defineForm-CJ8KZC93.d.cts +0 -71
- package/dist/registry-CraTTwT7.d.cts +0 -29
- package/dist/registry-DMujGqt0.d.ts +0 -29
package/dist/config.cjs
CHANGED
|
@@ -22,23 +22,16 @@ var config_exports = {};
|
|
|
22
22
|
__export(config_exports, {
|
|
23
23
|
BooleanField: () => BooleanField,
|
|
24
24
|
ButtonField: () => ButtonField,
|
|
25
|
-
DuplicateFormNameError: () => DuplicateFormNameError,
|
|
26
|
-
FormOverridesField: () => FormOverridesField,
|
|
27
|
-
HoneypotCollisionError: () => HoneypotCollisionError,
|
|
28
25
|
ImageField: () => ImageField,
|
|
29
|
-
InvalidFormFieldError: () => InvalidFormFieldError,
|
|
30
|
-
InvalidFormNameError: () => InvalidFormNameError,
|
|
31
26
|
LinkField: () => LinkField,
|
|
32
27
|
ListField: () => ListField,
|
|
33
28
|
NumberField: () => NumberField,
|
|
34
29
|
ReferenceField: () => ReferenceField,
|
|
35
30
|
RichTextField: () => RichTextField,
|
|
36
31
|
SelectField: () => SelectField,
|
|
37
|
-
SubmissionsNotReadableError: () => SubmissionsNotReadableError,
|
|
38
32
|
TextField: () => TextField,
|
|
39
33
|
VideoField: () => VideoField,
|
|
40
34
|
defineConfig: () => defineConfig,
|
|
41
|
-
defineForm: () => defineForm,
|
|
42
35
|
withagntcms: () => withagntcms
|
|
43
36
|
});
|
|
44
37
|
module.exports = __toCommonJS(config_exports);
|
|
@@ -52,7 +45,7 @@ function getDefaultAdapterFactories(requested) {
|
|
|
52
45
|
const f = holder()[SLOT];
|
|
53
46
|
if (f === void 0) {
|
|
54
47
|
throw new Error(
|
|
55
|
-
`[agntcms] defineConfig: cannot fill the default ${requested} adapter because the server-only adapter factories were never registered. Import \`@agntcms/next/server\` (or \`@agntcms/next/handlers\`) before evaluating \`agntcms/config.ts\`, or pass an explicit adapter to \`defineConfig({ contentAdapter, assetAdapter
|
|
48
|
+
`[agntcms] defineConfig: cannot fill the default ${requested} adapter because the server-only adapter factories were never registered. Import \`@agntcms/next/server\` (or \`@agntcms/next/handlers\`) before evaluating \`agntcms/config.ts\`, or pass an explicit adapter to \`defineConfig({ contentAdapter, assetAdapter })\`.`
|
|
56
49
|
);
|
|
57
50
|
}
|
|
58
51
|
return f;
|
|
@@ -67,126 +60,51 @@ function defineConfig(config) {
|
|
|
67
60
|
}
|
|
68
61
|
const contentAdapter = config.contentAdapter ?? getDefaultAdapterFactories("content").content();
|
|
69
62
|
const assetAdapter = config.assetAdapter ?? getDefaultAdapterFactories("asset").asset();
|
|
70
|
-
const submissionAdapter = config.submissionAdapter ?? getDefaultAdapterFactories("submission").submission();
|
|
71
63
|
return {
|
|
72
64
|
sections: config.sections,
|
|
73
65
|
contentAdapter,
|
|
74
|
-
assetAdapter
|
|
75
|
-
templates: config.templates ?? [],
|
|
76
|
-
forms: config.forms ?? [],
|
|
77
|
-
submissionAdapter,
|
|
78
|
-
submissionRateLimit: config.submissionRateLimit ?? { perMinute: 5 }
|
|
66
|
+
assetAdapter
|
|
79
67
|
};
|
|
80
68
|
}
|
|
81
69
|
|
|
82
70
|
// src/config/withagntcms.ts
|
|
83
71
|
var DEV_PAGE_EXTENSIONS = ["dev.ts", "dev.tsx", "ts", "tsx", "js", "jsx"];
|
|
84
72
|
var PROD_PAGE_EXTENSIONS = ["ts", "tsx", "js", "jsx"];
|
|
73
|
+
var CONTENT_TRACE_GLOB = "./content/**/*";
|
|
85
74
|
function withagntcms(nextConfig) {
|
|
86
75
|
const isProd = process.env.NODE_ENV === "production";
|
|
87
76
|
const defaultPageExtensions = isProd ? PROD_PAGE_EXTENSIONS : DEV_PAGE_EXTENSIONS;
|
|
88
77
|
const userPageExtensions = nextConfig.pageExtensions;
|
|
89
78
|
const pageExtensions = Array.isArray(userPageExtensions) ? userPageExtensions : [...defaultPageExtensions];
|
|
79
|
+
const userTracingIncludes = isPlainRecordOfStringArrays(
|
|
80
|
+
nextConfig.outputFileTracingIncludes
|
|
81
|
+
) ? nextConfig.outputFileTracingIncludes : {};
|
|
82
|
+
const outputFileTracingIncludes = mergeTracingIncludes(userTracingIncludes, {
|
|
83
|
+
"/**": [CONTENT_TRACE_GLOB]
|
|
84
|
+
});
|
|
90
85
|
return {
|
|
91
86
|
...nextConfig,
|
|
92
|
-
pageExtensions
|
|
87
|
+
pageExtensions,
|
|
88
|
+
outputFileTracingIncludes
|
|
93
89
|
};
|
|
94
90
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
"reference",
|
|
101
|
-
"list",
|
|
102
|
-
// `formOverrides` is a section-only descriptor (it overrides another
|
|
103
|
-
// form schema instance). Putting it inside a form would mean a form's
|
|
104
|
-
// payload could carry overrides for itself or another form — a
|
|
105
|
-
// recursive shape with no ergonomic editor UI. Section-only by design.
|
|
106
|
-
"formOverrides",
|
|
107
|
-
// `button` is a section-only descriptor: a styled CTA with an
|
|
108
|
-
// optional link. Public-form payloads collect user input — a button
|
|
109
|
-
// value is authored content, not a submitted answer. Section-only
|
|
110
|
-
// by design (mirrors `formOverrides`).
|
|
111
|
-
"button"
|
|
112
|
-
]);
|
|
113
|
-
var SubmissionsNotReadableError = class extends Error {
|
|
114
|
-
constructor(message = "submission adapter does not support reading") {
|
|
115
|
-
super(message);
|
|
116
|
-
this.name = "SubmissionsNotReadableError";
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// src/forms/defineForm.ts
|
|
121
|
-
var InvalidFormFieldError = class extends Error {
|
|
122
|
-
formName;
|
|
123
|
-
fieldName;
|
|
124
|
-
fieldKind;
|
|
125
|
-
constructor(formName, fieldName, fieldKind) {
|
|
126
|
-
super(
|
|
127
|
-
`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.`
|
|
128
|
-
);
|
|
129
|
-
this.name = "InvalidFormFieldError";
|
|
130
|
-
this.formName = formName;
|
|
131
|
-
this.fieldName = fieldName;
|
|
132
|
-
this.fieldKind = fieldKind;
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
var HoneypotCollisionError = class extends Error {
|
|
136
|
-
formName;
|
|
137
|
-
fieldName;
|
|
138
|
-
reason;
|
|
139
|
-
constructor(formName, fieldName, reason = "collision") {
|
|
140
|
-
super(
|
|
141
|
-
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.`
|
|
142
|
-
);
|
|
143
|
-
this.name = "HoneypotCollisionError";
|
|
144
|
-
this.formName = formName;
|
|
145
|
-
this.fieldName = fieldName;
|
|
146
|
-
this.reason = reason;
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
var InvalidFormNameError = class extends Error {
|
|
150
|
-
constructor(name) {
|
|
151
|
-
super(
|
|
152
|
-
`Invalid form name ${JSON.stringify(name)}. Use letters, digits, hyphen, and underscore only (no path separators).`
|
|
153
|
-
);
|
|
154
|
-
this.name = "InvalidFormNameError";
|
|
91
|
+
function isPlainRecordOfStringArrays(value) {
|
|
92
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
93
|
+
for (const v of Object.values(value)) {
|
|
94
|
+
if (!Array.isArray(v)) return false;
|
|
95
|
+
if (!v.every((item) => typeof item === "string")) return false;
|
|
155
96
|
}
|
|
156
|
-
|
|
157
|
-
var FORM_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
158
|
-
function defineForm(input) {
|
|
159
|
-
if (typeof input.name !== "string" || !FORM_NAME_PATTERN.test(input.name)) {
|
|
160
|
-
throw new InvalidFormNameError(input.name);
|
|
161
|
-
}
|
|
162
|
-
for (const [fieldName, descriptor] of Object.entries(input.schema)) {
|
|
163
|
-
const kind = descriptor.kind;
|
|
164
|
-
if (FORM_FORBIDDEN_KINDS.has(kind)) {
|
|
165
|
-
throw new InvalidFormFieldError(input.name, fieldName, kind);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (input.honeypot !== void 0) {
|
|
169
|
-
if (typeof input.honeypot !== "string" || input.honeypot === "") {
|
|
170
|
-
throw new HoneypotCollisionError(input.name, input.honeypot, "empty");
|
|
171
|
-
}
|
|
172
|
-
if (Object.prototype.hasOwnProperty.call(input.schema, input.honeypot)) {
|
|
173
|
-
throw new HoneypotCollisionError(input.name, input.honeypot, "collision");
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return input.honeypot !== void 0 ? { name: input.name, schema: input.schema, honeypot: input.honeypot } : { name: input.name, schema: input.schema };
|
|
97
|
+
return true;
|
|
177
98
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
`Form name "${name}" is registered more than once. Form names must be unique within a config.`
|
|
185
|
-
);
|
|
186
|
-
this.name = "DuplicateFormNameError";
|
|
187
|
-
this.formName = name;
|
|
99
|
+
function mergeTracingIncludes(user, framework) {
|
|
100
|
+
const result = {};
|
|
101
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(user), ...Object.keys(framework)]);
|
|
102
|
+
for (const key of keys) {
|
|
103
|
+
const merged = [...user[key] ?? [], ...framework[key] ?? []];
|
|
104
|
+
result[key] = Array.from(new Set(merged));
|
|
188
105
|
}
|
|
189
|
-
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
190
108
|
|
|
191
109
|
// src/domain/fields.ts
|
|
192
110
|
var TextField = { kind: "text" };
|
|
@@ -214,33 +132,19 @@ var ListField = (itemSchema, opts) => ({
|
|
|
214
132
|
...opts?.max !== void 0 ? { max: opts.max } : {},
|
|
215
133
|
...opts?.default !== void 0 ? { default: opts.default } : {}
|
|
216
134
|
});
|
|
217
|
-
|
|
218
|
-
// src/domain/formOverrides.ts
|
|
219
|
-
var FormOverridesField = (formName, opts) => ({
|
|
220
|
-
kind: "formOverrides",
|
|
221
|
-
formName,
|
|
222
|
-
...opts?.default !== void 0 ? { default: opts.default } : {}
|
|
223
|
-
});
|
|
224
135
|
// Annotate the CommonJS export names for ESM import in node:
|
|
225
136
|
0 && (module.exports = {
|
|
226
137
|
BooleanField,
|
|
227
138
|
ButtonField,
|
|
228
|
-
DuplicateFormNameError,
|
|
229
|
-
FormOverridesField,
|
|
230
|
-
HoneypotCollisionError,
|
|
231
139
|
ImageField,
|
|
232
|
-
InvalidFormFieldError,
|
|
233
|
-
InvalidFormNameError,
|
|
234
140
|
LinkField,
|
|
235
141
|
ListField,
|
|
236
142
|
NumberField,
|
|
237
143
|
ReferenceField,
|
|
238
144
|
RichTextField,
|
|
239
145
|
SelectField,
|
|
240
|
-
SubmissionsNotReadableError,
|
|
241
146
|
TextField,
|
|
242
147
|
VideoField,
|
|
243
148
|
defineConfig,
|
|
244
|
-
defineForm,
|
|
245
149
|
withagntcms
|
|
246
150
|
});
|
package/dist/config.d.cts
CHANGED
|
@@ -1,36 +1,8 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { C as ContentStorageAdapter, A as AssetStorageAdapter } from './assets-Cyt9upqW.cjs';
|
|
5
|
-
export { D as DefineFormInput, H as HoneypotCollisionError, I as InvalidFormFieldError, a as InvalidFormNameError, d as defineForm } from './defineForm-CJ8KZC93.cjs';
|
|
6
|
-
export { D as DuplicateFormNameError } from './registry-CraTTwT7.cjs';
|
|
1
|
+
import { A as AnySectionDefinition } from './defineSection-ByG5uwiR.cjs';
|
|
2
|
+
import { C as ContentStorageAdapter, A as AssetStorageAdapter } from './assets-B3oNeLdj.cjs';
|
|
3
|
+
export { g as BooleanField, B as ButtonField, I as ImageField, L as LinkField, i as ListField, N as NumberField, c as ReferenceField, R as RichTextField, h as SelectField, T as TextField, V as VideoField } from './page-DXF0_SrY.cjs';
|
|
7
4
|
import './global-CV23g5Bn.cjs';
|
|
8
5
|
|
|
9
|
-
/**
|
|
10
|
-
* A named preset that pre-fills sections when creating a new page.
|
|
11
|
-
*
|
|
12
|
-
* Templates are a config-level concept only — after creation, the page is a
|
|
13
|
-
* normal page with no template constraints. The `sectionTypes` array
|
|
14
|
-
* references section names from the registered `sections` list; unknown
|
|
15
|
-
* names are silently skipped at list time.
|
|
16
|
-
*/
|
|
17
|
-
interface PageTemplate {
|
|
18
|
-
readonly name: string;
|
|
19
|
-
readonly description?: string;
|
|
20
|
-
/** Ordered list of section type names to pre-fill. */
|
|
21
|
-
readonly sectionTypes: readonly string[];
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Submission rate-limit configuration knob (ARCHITECTURE.md §6.5).
|
|
25
|
-
*
|
|
26
|
-
* Single field on purpose — multiplexing perWindow/windowMs/strategy into
|
|
27
|
-
* a struct here would invite YAGNI bikeshedding. The window is fixed at
|
|
28
|
-
* 60 seconds in v1; only the per-window count is user-tunable.
|
|
29
|
-
*/
|
|
30
|
-
interface SubmissionRateLimit {
|
|
31
|
-
/** Max submissions per minute per (IP, form) pair. Default: 5. */
|
|
32
|
-
readonly perMinute: number;
|
|
33
|
-
}
|
|
34
6
|
/**
|
|
35
7
|
* User-facing configuration shape passed to `defineConfig`.
|
|
36
8
|
*
|
|
@@ -45,25 +17,6 @@ interface AgentCmsConfig {
|
|
|
45
17
|
readonly contentAdapter?: ContentStorageAdapter;
|
|
46
18
|
/** Asset storage adapter. Defaults to FS adapter with standard paths. */
|
|
47
19
|
readonly assetAdapter?: AssetStorageAdapter;
|
|
48
|
-
/** Page creation templates. Optional — defaults to [] (blank pages only). */
|
|
49
|
-
readonly templates?: readonly PageTemplate[];
|
|
50
|
-
/**
|
|
51
|
-
* Registered form definitions (ARCHITECTURE.md §6.5). Optional — when
|
|
52
|
-
* omitted, no forms are registered and the submit handler returns 404
|
|
53
|
-
* for every form name.
|
|
54
|
-
*/
|
|
55
|
-
readonly forms?: readonly AnyFormDefinition[];
|
|
56
|
-
/**
|
|
57
|
-
* Submission storage adapter. Defaults to the FS adapter rooted at
|
|
58
|
-
* `<projectRoot>/content/submissions`. Use `createWebhookSubmissionAdapter`
|
|
59
|
-
* (or a custom implementation) to send submissions to an external service.
|
|
60
|
-
*/
|
|
61
|
-
readonly submissionAdapter?: SubmissionStorageAdapter;
|
|
62
|
-
/**
|
|
63
|
-
* Rate-limit knob for the submit endpoint. Defaults to `{ perMinute: 5 }`.
|
|
64
|
-
* The window is always 60 seconds in v1 — only the count is user-tunable.
|
|
65
|
-
*/
|
|
66
|
-
readonly submissionRateLimit?: SubmissionRateLimit;
|
|
67
20
|
}
|
|
68
21
|
/**
|
|
69
22
|
* Fully-resolved configuration with all defaults filled in.
|
|
@@ -74,14 +27,6 @@ interface ResolvedConfig {
|
|
|
74
27
|
readonly sections: readonly AnySectionDefinition[];
|
|
75
28
|
readonly contentAdapter: ContentStorageAdapter;
|
|
76
29
|
readonly assetAdapter: AssetStorageAdapter;
|
|
77
|
-
/** Page creation templates. Always present (defaults to []). */
|
|
78
|
-
readonly templates: readonly PageTemplate[];
|
|
79
|
-
/** Registered forms. Always present (defaults to []). */
|
|
80
|
-
readonly forms: readonly AnyFormDefinition[];
|
|
81
|
-
/** Submission storage adapter. Always present. */
|
|
82
|
-
readonly submissionAdapter: SubmissionStorageAdapter;
|
|
83
|
-
/** Resolved rate-limit knob. Always present. */
|
|
84
|
-
readonly submissionRateLimit: SubmissionRateLimit;
|
|
85
30
|
}
|
|
86
31
|
/**
|
|
87
32
|
* Resolve a user config into a fully-populated config with defaults.
|
|
@@ -96,6 +41,7 @@ interface ResolvedConfig {
|
|
|
96
41
|
*/
|
|
97
42
|
declare function defineConfig(config: AgentCmsConfig): ResolvedConfig;
|
|
98
43
|
|
|
44
|
+
type TracingIncludes = Record<string, string[]>;
|
|
99
45
|
/**
|
|
100
46
|
* Wrap a Next.js configuration object for agntcms.
|
|
101
47
|
*
|
|
@@ -111,6 +57,7 @@ declare function defineConfig(config: AgentCmsConfig): ResolvedConfig;
|
|
|
111
57
|
*/
|
|
112
58
|
declare function withagntcms<T extends Record<string, unknown>>(nextConfig: T): T & {
|
|
113
59
|
pageExtensions: string[];
|
|
60
|
+
outputFileTracingIncludes: TracingIncludes;
|
|
114
61
|
};
|
|
115
62
|
|
|
116
|
-
export { type AgentCmsConfig,
|
|
63
|
+
export { type AgentCmsConfig, type ResolvedConfig, defineConfig, withagntcms };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,36 +1,8 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { C as ContentStorageAdapter, A as AssetStorageAdapter } from './assets-P8OCigDG.js';
|
|
5
|
-
export { D as DefineFormInput, H as HoneypotCollisionError, I as InvalidFormFieldError, a as InvalidFormNameError, d as defineForm } from './defineForm-Bp9vzW56.js';
|
|
6
|
-
export { D as DuplicateFormNameError } from './registry-DMujGqt0.js';
|
|
1
|
+
import { A as AnySectionDefinition } from './defineSection-ChkZCQyQ.js';
|
|
2
|
+
import { C as ContentStorageAdapter, A as AssetStorageAdapter } from './assets-DHumg-X7.js';
|
|
3
|
+
export { g as BooleanField, B as ButtonField, I as ImageField, L as LinkField, i as ListField, N as NumberField, c as ReferenceField, R as RichTextField, h as SelectField, T as TextField, V as VideoField } from './page-DXF0_SrY.js';
|
|
7
4
|
import './global-CV23g5Bn.js';
|
|
8
5
|
|
|
9
|
-
/**
|
|
10
|
-
* A named preset that pre-fills sections when creating a new page.
|
|
11
|
-
*
|
|
12
|
-
* Templates are a config-level concept only — after creation, the page is a
|
|
13
|
-
* normal page with no template constraints. The `sectionTypes` array
|
|
14
|
-
* references section names from the registered `sections` list; unknown
|
|
15
|
-
* names are silently skipped at list time.
|
|
16
|
-
*/
|
|
17
|
-
interface PageTemplate {
|
|
18
|
-
readonly name: string;
|
|
19
|
-
readonly description?: string;
|
|
20
|
-
/** Ordered list of section type names to pre-fill. */
|
|
21
|
-
readonly sectionTypes: readonly string[];
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Submission rate-limit configuration knob (ARCHITECTURE.md §6.5).
|
|
25
|
-
*
|
|
26
|
-
* Single field on purpose — multiplexing perWindow/windowMs/strategy into
|
|
27
|
-
* a struct here would invite YAGNI bikeshedding. The window is fixed at
|
|
28
|
-
* 60 seconds in v1; only the per-window count is user-tunable.
|
|
29
|
-
*/
|
|
30
|
-
interface SubmissionRateLimit {
|
|
31
|
-
/** Max submissions per minute per (IP, form) pair. Default: 5. */
|
|
32
|
-
readonly perMinute: number;
|
|
33
|
-
}
|
|
34
6
|
/**
|
|
35
7
|
* User-facing configuration shape passed to `defineConfig`.
|
|
36
8
|
*
|
|
@@ -45,25 +17,6 @@ interface AgentCmsConfig {
|
|
|
45
17
|
readonly contentAdapter?: ContentStorageAdapter;
|
|
46
18
|
/** Asset storage adapter. Defaults to FS adapter with standard paths. */
|
|
47
19
|
readonly assetAdapter?: AssetStorageAdapter;
|
|
48
|
-
/** Page creation templates. Optional — defaults to [] (blank pages only). */
|
|
49
|
-
readonly templates?: readonly PageTemplate[];
|
|
50
|
-
/**
|
|
51
|
-
* Registered form definitions (ARCHITECTURE.md §6.5). Optional — when
|
|
52
|
-
* omitted, no forms are registered and the submit handler returns 404
|
|
53
|
-
* for every form name.
|
|
54
|
-
*/
|
|
55
|
-
readonly forms?: readonly AnyFormDefinition[];
|
|
56
|
-
/**
|
|
57
|
-
* Submission storage adapter. Defaults to the FS adapter rooted at
|
|
58
|
-
* `<projectRoot>/content/submissions`. Use `createWebhookSubmissionAdapter`
|
|
59
|
-
* (or a custom implementation) to send submissions to an external service.
|
|
60
|
-
*/
|
|
61
|
-
readonly submissionAdapter?: SubmissionStorageAdapter;
|
|
62
|
-
/**
|
|
63
|
-
* Rate-limit knob for the submit endpoint. Defaults to `{ perMinute: 5 }`.
|
|
64
|
-
* The window is always 60 seconds in v1 — only the count is user-tunable.
|
|
65
|
-
*/
|
|
66
|
-
readonly submissionRateLimit?: SubmissionRateLimit;
|
|
67
20
|
}
|
|
68
21
|
/**
|
|
69
22
|
* Fully-resolved configuration with all defaults filled in.
|
|
@@ -74,14 +27,6 @@ interface ResolvedConfig {
|
|
|
74
27
|
readonly sections: readonly AnySectionDefinition[];
|
|
75
28
|
readonly contentAdapter: ContentStorageAdapter;
|
|
76
29
|
readonly assetAdapter: AssetStorageAdapter;
|
|
77
|
-
/** Page creation templates. Always present (defaults to []). */
|
|
78
|
-
readonly templates: readonly PageTemplate[];
|
|
79
|
-
/** Registered forms. Always present (defaults to []). */
|
|
80
|
-
readonly forms: readonly AnyFormDefinition[];
|
|
81
|
-
/** Submission storage adapter. Always present. */
|
|
82
|
-
readonly submissionAdapter: SubmissionStorageAdapter;
|
|
83
|
-
/** Resolved rate-limit knob. Always present. */
|
|
84
|
-
readonly submissionRateLimit: SubmissionRateLimit;
|
|
85
30
|
}
|
|
86
31
|
/**
|
|
87
32
|
* Resolve a user config into a fully-populated config with defaults.
|
|
@@ -96,6 +41,7 @@ interface ResolvedConfig {
|
|
|
96
41
|
*/
|
|
97
42
|
declare function defineConfig(config: AgentCmsConfig): ResolvedConfig;
|
|
98
43
|
|
|
44
|
+
type TracingIncludes = Record<string, string[]>;
|
|
99
45
|
/**
|
|
100
46
|
* Wrap a Next.js configuration object for agntcms.
|
|
101
47
|
*
|
|
@@ -111,6 +57,7 @@ declare function defineConfig(config: AgentCmsConfig): ResolvedConfig;
|
|
|
111
57
|
*/
|
|
112
58
|
declare function withagntcms<T extends Record<string, unknown>>(nextConfig: T): T & {
|
|
113
59
|
pageExtensions: string[];
|
|
60
|
+
outputFileTracingIncludes: TracingIncludes;
|
|
114
61
|
};
|
|
115
62
|
|
|
116
|
-
export { type AgentCmsConfig,
|
|
63
|
+
export { type AgentCmsConfig, type ResolvedConfig, defineConfig, withagntcms };
|
package/dist/config.mjs
CHANGED
|
@@ -7,7 +7,7 @@ function getDefaultAdapterFactories(requested) {
|
|
|
7
7
|
const f = holder()[SLOT];
|
|
8
8
|
if (f === void 0) {
|
|
9
9
|
throw new Error(
|
|
10
|
-
`[agntcms] defineConfig: cannot fill the default ${requested} adapter because the server-only adapter factories were never registered. Import \`@agntcms/next/server\` (or \`@agntcms/next/handlers\`) before evaluating \`agntcms/config.ts\`, or pass an explicit adapter to \`defineConfig({ contentAdapter, assetAdapter
|
|
10
|
+
`[agntcms] defineConfig: cannot fill the default ${requested} adapter because the server-only adapter factories were never registered. Import \`@agntcms/next/server\` (or \`@agntcms/next/handlers\`) before evaluating \`agntcms/config.ts\`, or pass an explicit adapter to \`defineConfig({ contentAdapter, assetAdapter })\`.`
|
|
11
11
|
);
|
|
12
12
|
}
|
|
13
13
|
return f;
|
|
@@ -22,126 +22,51 @@ function defineConfig(config) {
|
|
|
22
22
|
}
|
|
23
23
|
const contentAdapter = config.contentAdapter ?? getDefaultAdapterFactories("content").content();
|
|
24
24
|
const assetAdapter = config.assetAdapter ?? getDefaultAdapterFactories("asset").asset();
|
|
25
|
-
const submissionAdapter = config.submissionAdapter ?? getDefaultAdapterFactories("submission").submission();
|
|
26
25
|
return {
|
|
27
26
|
sections: config.sections,
|
|
28
27
|
contentAdapter,
|
|
29
|
-
assetAdapter
|
|
30
|
-
templates: config.templates ?? [],
|
|
31
|
-
forms: config.forms ?? [],
|
|
32
|
-
submissionAdapter,
|
|
33
|
-
submissionRateLimit: config.submissionRateLimit ?? { perMinute: 5 }
|
|
28
|
+
assetAdapter
|
|
34
29
|
};
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
// src/config/withagntcms.ts
|
|
38
33
|
var DEV_PAGE_EXTENSIONS = ["dev.ts", "dev.tsx", "ts", "tsx", "js", "jsx"];
|
|
39
34
|
var PROD_PAGE_EXTENSIONS = ["ts", "tsx", "js", "jsx"];
|
|
35
|
+
var CONTENT_TRACE_GLOB = "./content/**/*";
|
|
40
36
|
function withagntcms(nextConfig) {
|
|
41
37
|
const isProd = process.env.NODE_ENV === "production";
|
|
42
38
|
const defaultPageExtensions = isProd ? PROD_PAGE_EXTENSIONS : DEV_PAGE_EXTENSIONS;
|
|
43
39
|
const userPageExtensions = nextConfig.pageExtensions;
|
|
44
40
|
const pageExtensions = Array.isArray(userPageExtensions) ? userPageExtensions : [...defaultPageExtensions];
|
|
41
|
+
const userTracingIncludes = isPlainRecordOfStringArrays(
|
|
42
|
+
nextConfig.outputFileTracingIncludes
|
|
43
|
+
) ? nextConfig.outputFileTracingIncludes : {};
|
|
44
|
+
const outputFileTracingIncludes = mergeTracingIncludes(userTracingIncludes, {
|
|
45
|
+
"/**": [CONTENT_TRACE_GLOB]
|
|
46
|
+
});
|
|
45
47
|
return {
|
|
46
48
|
...nextConfig,
|
|
47
|
-
pageExtensions
|
|
49
|
+
pageExtensions,
|
|
50
|
+
outputFileTracingIncludes
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"reference",
|
|
56
|
-
"list",
|
|
57
|
-
// `formOverrides` is a section-only descriptor (it overrides another
|
|
58
|
-
// form schema instance). Putting it inside a form would mean a form's
|
|
59
|
-
// payload could carry overrides for itself or another form — a
|
|
60
|
-
// recursive shape with no ergonomic editor UI. Section-only by design.
|
|
61
|
-
"formOverrides",
|
|
62
|
-
// `button` is a section-only descriptor: a styled CTA with an
|
|
63
|
-
// optional link. Public-form payloads collect user input — a button
|
|
64
|
-
// value is authored content, not a submitted answer. Section-only
|
|
65
|
-
// by design (mirrors `formOverrides`).
|
|
66
|
-
"button"
|
|
67
|
-
]);
|
|
68
|
-
var SubmissionsNotReadableError = class extends Error {
|
|
69
|
-
constructor(message = "submission adapter does not support reading") {
|
|
70
|
-
super(message);
|
|
71
|
-
this.name = "SubmissionsNotReadableError";
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// src/forms/defineForm.ts
|
|
76
|
-
var InvalidFormFieldError = class extends Error {
|
|
77
|
-
formName;
|
|
78
|
-
fieldName;
|
|
79
|
-
fieldKind;
|
|
80
|
-
constructor(formName, fieldName, fieldKind) {
|
|
81
|
-
super(
|
|
82
|
-
`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.`
|
|
83
|
-
);
|
|
84
|
-
this.name = "InvalidFormFieldError";
|
|
85
|
-
this.formName = formName;
|
|
86
|
-
this.fieldName = fieldName;
|
|
87
|
-
this.fieldKind = fieldKind;
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
var HoneypotCollisionError = class extends Error {
|
|
91
|
-
formName;
|
|
92
|
-
fieldName;
|
|
93
|
-
reason;
|
|
94
|
-
constructor(formName, fieldName, reason = "collision") {
|
|
95
|
-
super(
|
|
96
|
-
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.`
|
|
97
|
-
);
|
|
98
|
-
this.name = "HoneypotCollisionError";
|
|
99
|
-
this.formName = formName;
|
|
100
|
-
this.fieldName = fieldName;
|
|
101
|
-
this.reason = reason;
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
var InvalidFormNameError = class extends Error {
|
|
105
|
-
constructor(name) {
|
|
106
|
-
super(
|
|
107
|
-
`Invalid form name ${JSON.stringify(name)}. Use letters, digits, hyphen, and underscore only (no path separators).`
|
|
108
|
-
);
|
|
109
|
-
this.name = "InvalidFormNameError";
|
|
53
|
+
function isPlainRecordOfStringArrays(value) {
|
|
54
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
55
|
+
for (const v of Object.values(value)) {
|
|
56
|
+
if (!Array.isArray(v)) return false;
|
|
57
|
+
if (!v.every((item) => typeof item === "string")) return false;
|
|
110
58
|
}
|
|
111
|
-
|
|
112
|
-
var FORM_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
113
|
-
function defineForm(input) {
|
|
114
|
-
if (typeof input.name !== "string" || !FORM_NAME_PATTERN.test(input.name)) {
|
|
115
|
-
throw new InvalidFormNameError(input.name);
|
|
116
|
-
}
|
|
117
|
-
for (const [fieldName, descriptor] of Object.entries(input.schema)) {
|
|
118
|
-
const kind = descriptor.kind;
|
|
119
|
-
if (FORM_FORBIDDEN_KINDS.has(kind)) {
|
|
120
|
-
throw new InvalidFormFieldError(input.name, fieldName, kind);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (input.honeypot !== void 0) {
|
|
124
|
-
if (typeof input.honeypot !== "string" || input.honeypot === "") {
|
|
125
|
-
throw new HoneypotCollisionError(input.name, input.honeypot, "empty");
|
|
126
|
-
}
|
|
127
|
-
if (Object.prototype.hasOwnProperty.call(input.schema, input.honeypot)) {
|
|
128
|
-
throw new HoneypotCollisionError(input.name, input.honeypot, "collision");
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return input.honeypot !== void 0 ? { name: input.name, schema: input.schema, honeypot: input.honeypot } : { name: input.name, schema: input.schema };
|
|
59
|
+
return true;
|
|
132
60
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
`Form name "${name}" is registered more than once. Form names must be unique within a config.`
|
|
140
|
-
);
|
|
141
|
-
this.name = "DuplicateFormNameError";
|
|
142
|
-
this.formName = name;
|
|
61
|
+
function mergeTracingIncludes(user, framework) {
|
|
62
|
+
const result = {};
|
|
63
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(user), ...Object.keys(framework)]);
|
|
64
|
+
for (const key of keys) {
|
|
65
|
+
const merged = [...user[key] ?? [], ...framework[key] ?? []];
|
|
66
|
+
result[key] = Array.from(new Set(merged));
|
|
143
67
|
}
|
|
144
|
-
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
145
70
|
|
|
146
71
|
// src/domain/fields.ts
|
|
147
72
|
var TextField = { kind: "text" };
|
|
@@ -169,32 +94,18 @@ var ListField = (itemSchema, opts) => ({
|
|
|
169
94
|
...opts?.max !== void 0 ? { max: opts.max } : {},
|
|
170
95
|
...opts?.default !== void 0 ? { default: opts.default } : {}
|
|
171
96
|
});
|
|
172
|
-
|
|
173
|
-
// src/domain/formOverrides.ts
|
|
174
|
-
var FormOverridesField = (formName, opts) => ({
|
|
175
|
-
kind: "formOverrides",
|
|
176
|
-
formName,
|
|
177
|
-
...opts?.default !== void 0 ? { default: opts.default } : {}
|
|
178
|
-
});
|
|
179
97
|
export {
|
|
180
98
|
BooleanField,
|
|
181
99
|
ButtonField,
|
|
182
|
-
DuplicateFormNameError,
|
|
183
|
-
FormOverridesField,
|
|
184
|
-
HoneypotCollisionError,
|
|
185
100
|
ImageField,
|
|
186
|
-
InvalidFormFieldError,
|
|
187
|
-
InvalidFormNameError,
|
|
188
101
|
LinkField,
|
|
189
102
|
ListField,
|
|
190
103
|
NumberField,
|
|
191
104
|
ReferenceField,
|
|
192
105
|
RichTextField,
|
|
193
106
|
SelectField,
|
|
194
|
-
SubmissionsNotReadableError,
|
|
195
107
|
TextField,
|
|
196
108
|
VideoField,
|
|
197
109
|
defineConfig,
|
|
198
|
-
defineForm,
|
|
199
110
|
withagntcms
|
|
200
111
|
};
|