@agntcms/next 0.2.0
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 +290 -0
- package/dist/assets-P8OCigDG.d.ts +290 -0
- package/dist/client.cjs +13244 -0
- package/dist/client.d.cts +806 -0
- package/dist/client.d.ts +806 -0
- package/dist/client.mjs +13234 -0
- package/dist/config.cjs +240 -0
- package/dist/config.d.cts +112 -0
- package/dist/config.d.ts +112 -0
- package/dist/config.mjs +194 -0
- package/dist/defineForm-Bp9vzW56.d.ts +71 -0
- package/dist/defineForm-CJ8KZC93.d.cts +71 -0
- package/dist/defineSection-9qQ5ulAH.d.cts +243 -0
- package/dist/defineSection-Kr0pWqMY.d.ts +243 -0
- package/dist/form-BqY0H1V5.d.cts +753 -0
- package/dist/form-BqY0H1V5.d.ts +753 -0
- package/dist/global-CV23g5Bn.d.cts +15 -0
- package/dist/global-CV23g5Bn.d.ts +15 -0
- package/dist/handlers.cjs +2525 -0
- package/dist/handlers.d.cts +330 -0
- package/dist/handlers.d.ts +330 -0
- package/dist/handlers.mjs +2473 -0
- package/dist/index.cjs +372 -0
- package/dist/index.d.cts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.mjs +319 -0
- package/dist/rateLimit-CXptRM_K.d.ts +391 -0
- package/dist/rateLimit-CiROGTLE.d.cts +391 -0
- package/dist/registry-CraTTwT7.d.cts +29 -0
- package/dist/registry-DMujGqt0.d.ts +29 -0
- package/dist/server.cjs +1970 -0
- package/dist/server.d.cts +153 -0
- package/dist/server.d.ts +153 -0
- package/dist/server.mjs +1889 -0
- package/package.json +62 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { a as FormSchema, b as FormDefinition } from './form-BqY0H1V5.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Input shape for `defineForm`. Split out so the factory can use a single
|
|
5
|
+
* generic parameter for both input and output, the same pattern as
|
|
6
|
+
* `defineSection`.
|
|
7
|
+
*/
|
|
8
|
+
interface DefineFormInput<S extends FormSchema> {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly schema: S;
|
|
11
|
+
/**
|
|
12
|
+
* Optional honeypot field name (out-of-schema). The route handler treats
|
|
13
|
+
* a non-empty value at this key in the submit payload as a bot
|
|
14
|
+
* indicator and silently drops the submission. The handler MUST NOT
|
|
15
|
+
* leak suppression to the client (returns 200 ok with `stored: false`).
|
|
16
|
+
*
|
|
17
|
+
* MUST NOT collide with any field name in `schema`.
|
|
18
|
+
*/
|
|
19
|
+
readonly honeypot?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Thrown when `defineForm` rejects a form schema that violates the v1 restrictions. */
|
|
22
|
+
declare class InvalidFormFieldError extends Error {
|
|
23
|
+
readonly formName: string;
|
|
24
|
+
readonly fieldName: string;
|
|
25
|
+
readonly fieldKind: string;
|
|
26
|
+
constructor(formName: string, fieldName: string, fieldKind: string);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Thrown when a `honeypot` value is unusable. Discriminated by `reason`:
|
|
30
|
+
*
|
|
31
|
+
* - `'collision'` — the honeypot name matches a field already declared
|
|
32
|
+
* in the schema. This would let bot traffic occupy a real field key.
|
|
33
|
+
* - `'empty'` — the honeypot name is the empty string. An empty key is
|
|
34
|
+
* never a valid identifier in the submit payload, so the honeypot
|
|
35
|
+
* check would silently always pass; this is treated as an authoring
|
|
36
|
+
* mistake.
|
|
37
|
+
*
|
|
38
|
+
* Single class with a discriminator (rather than a second error class)
|
|
39
|
+
* keeps the public error surface small while still letting callers
|
|
40
|
+
* distinguish the two failure modes through `err.reason`.
|
|
41
|
+
*/
|
|
42
|
+
declare class HoneypotCollisionError extends Error {
|
|
43
|
+
readonly formName: string;
|
|
44
|
+
readonly fieldName: string;
|
|
45
|
+
readonly reason: 'collision' | 'empty';
|
|
46
|
+
constructor(formName: string, fieldName: string, reason?: 'collision' | 'empty');
|
|
47
|
+
}
|
|
48
|
+
/** Thrown when `name` is empty or fails the conservative URL-segment check. */
|
|
49
|
+
declare class InvalidFormNameError extends Error {
|
|
50
|
+
constructor(name: string);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Type-safe factory for a `FormDefinition`.
|
|
54
|
+
*
|
|
55
|
+
* Compile-time guarantee: `S extends FormSchema` constrains each field
|
|
56
|
+
* to `FormFieldDescriptor` (the closed v1 subset). Runtime guarantee:
|
|
57
|
+
* the factory iterates `schema` and throws `InvalidFormFieldError` if any
|
|
58
|
+
* descriptor's `kind` is in `FORM_FORBIDDEN_KINDS`. Both layers exist on
|
|
59
|
+
* purpose — see file header.
|
|
60
|
+
*
|
|
61
|
+
* Allowed field kinds (see ARCHITECTURE.md §6.5):
|
|
62
|
+
* text, richText, number, boolean, select, link
|
|
63
|
+
*
|
|
64
|
+
* Forbidden in v1 (throws):
|
|
65
|
+
* image — file upload deferred (§6.5, §12)
|
|
66
|
+
* reference — semantic mismatch for user input
|
|
67
|
+
* list — recursive payload validation, YAGNI for v1
|
|
68
|
+
*/
|
|
69
|
+
declare function defineForm<S extends FormSchema>(input: DefineFormInput<S>): FormDefinition<S>;
|
|
70
|
+
|
|
71
|
+
export { type DefineFormInput as D, HoneypotCollisionError as H, InvalidFormFieldError as I, InvalidFormNameError as a, defineForm as d };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { a as FormSchema, b as FormDefinition } from './form-BqY0H1V5.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Input shape for `defineForm`. Split out so the factory can use a single
|
|
5
|
+
* generic parameter for both input and output, the same pattern as
|
|
6
|
+
* `defineSection`.
|
|
7
|
+
*/
|
|
8
|
+
interface DefineFormInput<S extends FormSchema> {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly schema: S;
|
|
11
|
+
/**
|
|
12
|
+
* Optional honeypot field name (out-of-schema). The route handler treats
|
|
13
|
+
* a non-empty value at this key in the submit payload as a bot
|
|
14
|
+
* indicator and silently drops the submission. The handler MUST NOT
|
|
15
|
+
* leak suppression to the client (returns 200 ok with `stored: false`).
|
|
16
|
+
*
|
|
17
|
+
* MUST NOT collide with any field name in `schema`.
|
|
18
|
+
*/
|
|
19
|
+
readonly honeypot?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Thrown when `defineForm` rejects a form schema that violates the v1 restrictions. */
|
|
22
|
+
declare class InvalidFormFieldError extends Error {
|
|
23
|
+
readonly formName: string;
|
|
24
|
+
readonly fieldName: string;
|
|
25
|
+
readonly fieldKind: string;
|
|
26
|
+
constructor(formName: string, fieldName: string, fieldKind: string);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Thrown when a `honeypot` value is unusable. Discriminated by `reason`:
|
|
30
|
+
*
|
|
31
|
+
* - `'collision'` — the honeypot name matches a field already declared
|
|
32
|
+
* in the schema. This would let bot traffic occupy a real field key.
|
|
33
|
+
* - `'empty'` — the honeypot name is the empty string. An empty key is
|
|
34
|
+
* never a valid identifier in the submit payload, so the honeypot
|
|
35
|
+
* check would silently always pass; this is treated as an authoring
|
|
36
|
+
* mistake.
|
|
37
|
+
*
|
|
38
|
+
* Single class with a discriminator (rather than a second error class)
|
|
39
|
+
* keeps the public error surface small while still letting callers
|
|
40
|
+
* distinguish the two failure modes through `err.reason`.
|
|
41
|
+
*/
|
|
42
|
+
declare class HoneypotCollisionError extends Error {
|
|
43
|
+
readonly formName: string;
|
|
44
|
+
readonly fieldName: string;
|
|
45
|
+
readonly reason: 'collision' | 'empty';
|
|
46
|
+
constructor(formName: string, fieldName: string, reason?: 'collision' | 'empty');
|
|
47
|
+
}
|
|
48
|
+
/** Thrown when `name` is empty or fails the conservative URL-segment check. */
|
|
49
|
+
declare class InvalidFormNameError extends Error {
|
|
50
|
+
constructor(name: string);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Type-safe factory for a `FormDefinition`.
|
|
54
|
+
*
|
|
55
|
+
* Compile-time guarantee: `S extends FormSchema` constrains each field
|
|
56
|
+
* to `FormFieldDescriptor` (the closed v1 subset). Runtime guarantee:
|
|
57
|
+
* the factory iterates `schema` and throws `InvalidFormFieldError` if any
|
|
58
|
+
* descriptor's `kind` is in `FORM_FORBIDDEN_KINDS`. Both layers exist on
|
|
59
|
+
* purpose — see file header.
|
|
60
|
+
*
|
|
61
|
+
* Allowed field kinds (see ARCHITECTURE.md §6.5):
|
|
62
|
+
* text, richText, number, boolean, select, link
|
|
63
|
+
*
|
|
64
|
+
* Forbidden in v1 (throws):
|
|
65
|
+
* image — file upload deferred (§6.5, §12)
|
|
66
|
+
* reference — semantic mismatch for user input
|
|
67
|
+
* list — recursive payload validation, YAGNI for v1
|
|
68
|
+
*/
|
|
69
|
+
declare function defineForm<S extends FormSchema>(input: DefineFormInput<S>): FormDefinition<S>;
|
|
70
|
+
|
|
71
|
+
export { type DefineFormInput as D, HoneypotCollisionError as H, InvalidFormFieldError as I, InvalidFormNameError as a, defineForm as d };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { S as SectionSchema, c as FieldDescriptor, T as TextField, R as RichTextField, I as ImageField, d as ImageValue, V as VideoField, e as VideoValue, f as ReferenceField, g as ReferenceValue, L as LinkField, h as LinkValue, B as ButtonField, i as ButtonValue, N as NumberField, j as BooleanField, k as SelectField, l as ListField, m as FormOverridesFieldDescriptor, n as FormFieldOverrides } from './form-BqY0H1V5.cjs';
|
|
2
|
+
|
|
3
|
+
declare const __slot: unique symbol;
|
|
4
|
+
/**
|
|
5
|
+
* Opaque editable slot. The runtime hands `<EditableText>` /
|
|
6
|
+
* `<EditableImage>` / etc. exactly this shape; the brand `[__slot]: K` is
|
|
7
|
+
* phantom (never set at runtime) and exists only to stop the slot from
|
|
8
|
+
* being assigned to `React.ReactNode` at JSX render sites. The slot kind
|
|
9
|
+
* `K` lets the matching editable component refuse a mismatched widget at
|
|
10
|
+
* compile time (`<EditableText field={imageSlot}>` is a TS error).
|
|
11
|
+
*
|
|
12
|
+
* The `value` is `V | PreviewFieldLike<V>` because the runtime wraps with
|
|
13
|
+
* `PreviewField` only in preview mode; published mode hands the bare
|
|
14
|
+
* value. The `read()` helper unwraps both branches.
|
|
15
|
+
*/
|
|
16
|
+
interface EditableSlot<K extends string, V> {
|
|
17
|
+
readonly [__slot]: K;
|
|
18
|
+
readonly value: V | PreviewFieldLike<V>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Local structural replica of `PreviewFieldLike<T>` from
|
|
22
|
+
* `react/editable/isPreviewField.ts`. Duplicated here (not imported) for
|
|
23
|
+
* the same reason that file exists: keeping `sections/` free of any
|
|
24
|
+
* dependency on `react/`. Both shapes must stay structurally identical —
|
|
25
|
+
* if one moves, both move.
|
|
26
|
+
*/
|
|
27
|
+
interface PreviewFieldLike<T> {
|
|
28
|
+
readonly __agntcmsPreview: true;
|
|
29
|
+
readonly value: T;
|
|
30
|
+
readonly origin: PreviewFieldOriginLike;
|
|
31
|
+
}
|
|
32
|
+
interface PreviewFieldOriginLike {
|
|
33
|
+
readonly pageSlug: string;
|
|
34
|
+
readonly sectionId: string;
|
|
35
|
+
readonly fieldPath: string;
|
|
36
|
+
readonly source: 'draft' | 'published';
|
|
37
|
+
readonly revision: string;
|
|
38
|
+
readonly kind?: 'page' | 'global';
|
|
39
|
+
readonly globalName?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Recursive item shape for `ListField`. Every field on a list item becomes
|
|
43
|
+
* its own slot, so a section author who writes `{item.text}` raw inside
|
|
44
|
+
* `<EditableList renderItem={…}>` gets the SAME compile error as a raw
|
|
45
|
+
* `{title}` at the section level. This closes the structural blind spot
|
|
46
|
+
* a heuristic gate could not (see EDITABILITY_DESIGN.md, decision #2).
|
|
47
|
+
*
|
|
48
|
+
* `_id` is preserved as a bare string — section components routinely use
|
|
49
|
+
* it as a React key, and there is no inline-editing surface for it.
|
|
50
|
+
*/
|
|
51
|
+
type SlotItem<S extends SectionSchema> = {
|
|
52
|
+
readonly _id: string;
|
|
53
|
+
} & {
|
|
54
|
+
readonly [K in keyof S]: FieldDataType<S[K]>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Maps a single `FieldDescriptor` to the type a section component receives
|
|
59
|
+
* on its `props` for that field.
|
|
60
|
+
*
|
|
61
|
+
* Built-in field → component-prop type:
|
|
62
|
+
* text → EditableSlot<'text', string>
|
|
63
|
+
* richText → EditableSlot<'richText', string>
|
|
64
|
+
* image → EditableSlot<'image', ImageValue>
|
|
65
|
+
* video → EditableSlot<'video', VideoValue>
|
|
66
|
+
* link → EditableSlot<'link', LinkValue>
|
|
67
|
+
* button → EditableSlot<'button', ButtonValue>
|
|
68
|
+
* number → EditableSlot<'number', number>
|
|
69
|
+
* boolean → EditableSlot<'boolean', boolean>
|
|
70
|
+
* select → EditableSlot<'select', string>
|
|
71
|
+
* list → EditableSlot<'list', ReadonlyArray<SlotItem<S>>>
|
|
72
|
+
* reference → ReferenceValue (raw — see decision #3)
|
|
73
|
+
* formOverrides → FormFieldOverrides (raw — see decision #4)
|
|
74
|
+
*
|
|
75
|
+
* EDITABILITY_DESIGN.md gates this design. The slot wrapping is the
|
|
76
|
+
* load-bearing change: a slot is not assignable to `React.ReactNode`, so
|
|
77
|
+
* `<h1>{title}</h1>` produces a TS error and forces the author into
|
|
78
|
+
* `<EditableText field={title}>`. `ListField` items recurse via
|
|
79
|
+
* `SlotItem<S>`, so raw rendering inside `renderItem` is also a TS error.
|
|
80
|
+
*
|
|
81
|
+
* `ReferenceField` stays raw because references are not inline-editable
|
|
82
|
+
* in v1 (decision #3): a slot would force a no-op `<EditableReference>`
|
|
83
|
+
* wrapper without UX value. `FormOverridesField` stays raw because
|
|
84
|
+
* forms render via a built-in `<Form>` component that reads the schema
|
|
85
|
+
* itself; there is no author-defined component receiving raw form-field
|
|
86
|
+
* values, and therefore no JSX surface where a raw render could leak
|
|
87
|
+
* (decision #4).
|
|
88
|
+
*
|
|
89
|
+
* `FieldDataType` and `FieldValueFor` (`domain/schema.ts`) NO LONGER
|
|
90
|
+
* agree element-by-element: `FieldValueFor` is the bare runtime payload
|
|
91
|
+
* shape (what storage carries, what `getContent` returns), and stays as
|
|
92
|
+
* it is; `FieldDataType` is the section-author-facing mapping that
|
|
93
|
+
* drives `DataOf<S>` and now wraps editable kinds in slots. The runtime
|
|
94
|
+
* wrapping happens at the SectionRenderer boundary (sub-task 2 wires
|
|
95
|
+
* `wrapAsSlot`) and is INTENTIONALLY NOT mirrored in `getContent` — see
|
|
96
|
+
* the comment in `runtime/getContent.ts` for why.
|
|
97
|
+
*
|
|
98
|
+
* The mapping is expressed per-descriptor so a future field type is one
|
|
99
|
+
* line of change instead of a redesign. That is not the same as
|
|
100
|
+
* "user-extensible field types" — invariant 5 (CLAUDE.md) forbids a
|
|
101
|
+
* plugin system; this is just the internal seam.
|
|
102
|
+
*/
|
|
103
|
+
type FieldDataType<F extends FieldDescriptor> = F extends TextField ? EditableSlot<'text', string> : F extends RichTextField ? EditableSlot<'richText', string> : F extends ImageField ? EditableSlot<'image', ImageValue> : F extends VideoField ? EditableSlot<'video', VideoValue> : F extends ReferenceField ? ReferenceValue : F extends LinkField ? EditableSlot<'link', LinkValue> : F extends ButtonField ? EditableSlot<'button', ButtonValue> : F extends NumberField ? EditableSlot<'number', number> : F extends BooleanField ? EditableSlot<'boolean', boolean> : F extends SelectField ? EditableSlot<'select', string> : F extends ListField<infer S> ? EditableSlot<'list', ReadonlyArray<SlotItem<S>>> : F extends FormOverridesFieldDescriptor ? FormFieldOverrides : never;
|
|
104
|
+
/**
|
|
105
|
+
* Derives the SECTION-COMPONENT prop shape from its schema.
|
|
106
|
+
*
|
|
107
|
+
* Per `FieldDataType`, every editable field becomes an `EditableSlot<K,V>`
|
|
108
|
+
* (so `<h1>{title}</h1>` is a TS error); `ReferenceValue` and
|
|
109
|
+
* `FormFieldOverrides` stay raw. Example:
|
|
110
|
+
*
|
|
111
|
+
* type HeroSchema = { title: TextField; image: ImageField; ref: ReferenceField }
|
|
112
|
+
* DataOf<HeroSchema> ≡ {
|
|
113
|
+
* readonly title: EditableSlot<'text', string>
|
|
114
|
+
* readonly image: EditableSlot<'image', ImageValue>
|
|
115
|
+
* readonly ref: ReferenceValue
|
|
116
|
+
* }
|
|
117
|
+
*
|
|
118
|
+
* NOTE: this is the COMPONENT-SIDE shape. The bare runtime payload (what
|
|
119
|
+
* `getContent` returns and what storage carries) is described by
|
|
120
|
+
* `FieldValueFor<F>` in `domain/schema.ts` — that mapping has not changed
|
|
121
|
+
* and must not change (see the slot-non-wrapping comment in
|
|
122
|
+
* `runtime/getContent.ts`).
|
|
123
|
+
*/
|
|
124
|
+
type DataOf<S extends SectionSchema> = {
|
|
125
|
+
readonly [K in keyof S]: FieldDataType<S[K]>;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* The contract a section's React component must satisfy: a function taking
|
|
129
|
+
* exactly the derived props for its schema. The return type is intentionally
|
|
130
|
+
* `unknown` — `sections/` may not name React types (see file header). The
|
|
131
|
+
* downstream `react/` module is where the stricter `ReactElement` typing
|
|
132
|
+
* lives; here we only care that the props line up with the schema.
|
|
133
|
+
*/
|
|
134
|
+
type SectionComponent<S extends SectionSchema> = (props: DataOf<S>) => unknown;
|
|
135
|
+
/**
|
|
136
|
+
* Registration record produced by `defineSection`. Consumers (the registry
|
|
137
|
+
* built by `defineConfig` in T-019, `SectionRenderer` in T-016) look up
|
|
138
|
+
* definitions by `name` and use `component` to render.
|
|
139
|
+
*
|
|
140
|
+
* Both generic parameters are required (no defaults). A heterogeneous
|
|
141
|
+
* registry — the list the runtime actually stores — is typed as
|
|
142
|
+
* `readonly AnySectionDefinition[]`, NOT `readonly SectionDefinition[]`.
|
|
143
|
+
*
|
|
144
|
+
* Why this split exists
|
|
145
|
+
* ---------------------
|
|
146
|
+
* `SectionComponent<S>` is a function whose parameter is contravariant. If
|
|
147
|
+
* we widened `SectionDefinition` to its default parameters and asked a
|
|
148
|
+
* concrete `SectionDefinition<{title: TextField}, (p: {title: string}) => unknown>`
|
|
149
|
+
* to be assignable to it, the function-parameter contravariance would
|
|
150
|
+
* refuse: the "widened" parameter type `DataOf<SectionSchema>` is stricter
|
|
151
|
+
* (more required keys, from TypeScript's perspective) than any concrete
|
|
152
|
+
* `DataOf<S>`. The erasure-safe form lives in `AnySectionDefinition`, which
|
|
153
|
+
* stores the component with `unknown` props. Every precise definition is
|
|
154
|
+
* assignable to `AnySectionDefinition`.
|
|
155
|
+
*/
|
|
156
|
+
interface SectionDefinition<S extends SectionSchema, C extends SectionComponent<S>> {
|
|
157
|
+
/** Stable type discriminator used to tag `Section.type` and in lookups. */
|
|
158
|
+
readonly name: string;
|
|
159
|
+
/** Optional grouping label for the section picker modal. */
|
|
160
|
+
readonly category?: string;
|
|
161
|
+
/** Field-descriptor schema. */
|
|
162
|
+
readonly schema: S;
|
|
163
|
+
/** React component reference. See file header on why not a thunk. */
|
|
164
|
+
readonly component: C;
|
|
165
|
+
/** Pre-computed placeholder values for each field, used when inserting a
|
|
166
|
+
* new section in preview mode. Computed from field descriptors' `default`
|
|
167
|
+
* property (when present) or from the built-in per-kind fallback.
|
|
168
|
+
*
|
|
169
|
+
* Typed as `Record<string, unknown>` so the registry type stays stable
|
|
170
|
+
* even if a future built-in field type introduces a non-string runtime
|
|
171
|
+
* value. In v1 every value here is a `string`. The registry itself
|
|
172
|
+
* never interprets these — consumers that read a specific field narrow
|
|
173
|
+
* at the call site. */
|
|
174
|
+
readonly defaults: Record<string, unknown>;
|
|
175
|
+
/** Optional list of layout names supported by this section. When present
|
|
176
|
+
* with length >= 2, the preview-mode section picker renders a "Layout"
|
|
177
|
+
* tab so authors can switch the layout without editing JSON. The section
|
|
178
|
+
* component reads the active layout from its `layout` prop; the picker
|
|
179
|
+
* writes that value via the existing per-field save round-trip.
|
|
180
|
+
*
|
|
181
|
+
* Layouts are a SECTION-level concern, not a field descriptor, because
|
|
182
|
+
* they gate structure inside a single component rather than introducing
|
|
183
|
+
* a new field type. See ARCHITECTURE.md §4. */
|
|
184
|
+
readonly layouts?: readonly string[];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The erased/heterogeneous form used by the registry and by
|
|
188
|
+
* `defineConfig({ sections: [...] })`. Precise definitions produced by
|
|
189
|
+
* `defineSection` are assignable to this type because:
|
|
190
|
+
* - `schema` widens to `SectionSchema` (covariant read),
|
|
191
|
+
* - `component` widens to a function whose parameter type is `never`.
|
|
192
|
+
* Under parameter contravariance, any concrete `(props: X) => unknown`
|
|
193
|
+
* is assignable to `(props: never) => unknown` (for any `X`, `never` is
|
|
194
|
+
* a subtype of `X`). Return widens to `unknown`.
|
|
195
|
+
*
|
|
196
|
+
* Callers that only need `name` / `schema` metadata treat a list of
|
|
197
|
+
* definitions as `readonly AnySectionDefinition[]`. Rendering code — which
|
|
198
|
+
* does need to INVOKE the component — recovers strong typing at the call
|
|
199
|
+
* site by looking up by name and casting to its section-specific
|
|
200
|
+
* `SectionComponent<S>` before calling. The registry itself never invokes
|
|
201
|
+
* components.
|
|
202
|
+
*/
|
|
203
|
+
interface AnySectionDefinition {
|
|
204
|
+
readonly name: string;
|
|
205
|
+
/** Optional grouping label for the section picker modal. */
|
|
206
|
+
readonly category?: string;
|
|
207
|
+
readonly schema: SectionSchema;
|
|
208
|
+
readonly component: (props: never) => unknown;
|
|
209
|
+
/** Pre-computed placeholder values — see `SectionDefinition.defaults`. */
|
|
210
|
+
readonly defaults: Record<string, unknown>;
|
|
211
|
+
/** Optional layout names — see `SectionDefinition.layouts`. */
|
|
212
|
+
readonly layouts?: readonly string[];
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Input shape for `defineSection`. Split out so the factory can use a single
|
|
216
|
+
* generic parameter set for both input and output.
|
|
217
|
+
*/
|
|
218
|
+
interface DefineSectionInput<S extends SectionSchema, C extends SectionComponent<S>> {
|
|
219
|
+
readonly name: string;
|
|
220
|
+
/** Optional grouping label for the section picker modal. */
|
|
221
|
+
readonly category?: string;
|
|
222
|
+
readonly schema: S;
|
|
223
|
+
readonly component: C;
|
|
224
|
+
/** Optional layout names — see `SectionDefinition.layouts`. */
|
|
225
|
+
readonly layouts?: readonly string[];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Type-safe factory for a `SectionDefinition`.
|
|
229
|
+
*
|
|
230
|
+
* The compile-time win is the constraint `C extends SectionComponent<S>`: if
|
|
231
|
+
* the component's props do not match the derived `DataOf<S>`, TypeScript
|
|
232
|
+
* rejects the call. The factory itself is purely about binding — it does no
|
|
233
|
+
* validation at runtime, because in v1 there is nothing to validate (the
|
|
234
|
+
* schema is plain descriptor metadata, not a parser).
|
|
235
|
+
*
|
|
236
|
+
* Object and schema are not deep-frozen: doing so would add a runtime cost on
|
|
237
|
+
* every module load without preventing anything the type system does not
|
|
238
|
+
* already prevent in strict mode. `as const` at the author's call site is
|
|
239
|
+
* enough for literal inference.
|
|
240
|
+
*/
|
|
241
|
+
declare function defineSection<S extends SectionSchema, C extends SectionComponent<S>>(input: DefineSectionInput<S, C>): SectionDefinition<S, C>;
|
|
242
|
+
|
|
243
|
+
export { type AnySectionDefinition as A, type DataOf as D, type EditableSlot as E, type FieldDataType as F, type SectionComponent as S, type DefineSectionInput as a, type SectionDefinition as b, type SlotItem as c, defineSection as d };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { S as SectionSchema, c as FieldDescriptor, T as TextField, R as RichTextField, I as ImageField, d as ImageValue, V as VideoField, e as VideoValue, f as ReferenceField, g as ReferenceValue, L as LinkField, h as LinkValue, B as ButtonField, i as ButtonValue, N as NumberField, j as BooleanField, k as SelectField, l as ListField, m as FormOverridesFieldDescriptor, n as FormFieldOverrides } from './form-BqY0H1V5.js';
|
|
2
|
+
|
|
3
|
+
declare const __slot: unique symbol;
|
|
4
|
+
/**
|
|
5
|
+
* Opaque editable slot. The runtime hands `<EditableText>` /
|
|
6
|
+
* `<EditableImage>` / etc. exactly this shape; the brand `[__slot]: K` is
|
|
7
|
+
* phantom (never set at runtime) and exists only to stop the slot from
|
|
8
|
+
* being assigned to `React.ReactNode` at JSX render sites. The slot kind
|
|
9
|
+
* `K` lets the matching editable component refuse a mismatched widget at
|
|
10
|
+
* compile time (`<EditableText field={imageSlot}>` is a TS error).
|
|
11
|
+
*
|
|
12
|
+
* The `value` is `V | PreviewFieldLike<V>` because the runtime wraps with
|
|
13
|
+
* `PreviewField` only in preview mode; published mode hands the bare
|
|
14
|
+
* value. The `read()` helper unwraps both branches.
|
|
15
|
+
*/
|
|
16
|
+
interface EditableSlot<K extends string, V> {
|
|
17
|
+
readonly [__slot]: K;
|
|
18
|
+
readonly value: V | PreviewFieldLike<V>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Local structural replica of `PreviewFieldLike<T>` from
|
|
22
|
+
* `react/editable/isPreviewField.ts`. Duplicated here (not imported) for
|
|
23
|
+
* the same reason that file exists: keeping `sections/` free of any
|
|
24
|
+
* dependency on `react/`. Both shapes must stay structurally identical —
|
|
25
|
+
* if one moves, both move.
|
|
26
|
+
*/
|
|
27
|
+
interface PreviewFieldLike<T> {
|
|
28
|
+
readonly __agntcmsPreview: true;
|
|
29
|
+
readonly value: T;
|
|
30
|
+
readonly origin: PreviewFieldOriginLike;
|
|
31
|
+
}
|
|
32
|
+
interface PreviewFieldOriginLike {
|
|
33
|
+
readonly pageSlug: string;
|
|
34
|
+
readonly sectionId: string;
|
|
35
|
+
readonly fieldPath: string;
|
|
36
|
+
readonly source: 'draft' | 'published';
|
|
37
|
+
readonly revision: string;
|
|
38
|
+
readonly kind?: 'page' | 'global';
|
|
39
|
+
readonly globalName?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Recursive item shape for `ListField`. Every field on a list item becomes
|
|
43
|
+
* its own slot, so a section author who writes `{item.text}` raw inside
|
|
44
|
+
* `<EditableList renderItem={…}>` gets the SAME compile error as a raw
|
|
45
|
+
* `{title}` at the section level. This closes the structural blind spot
|
|
46
|
+
* a heuristic gate could not (see EDITABILITY_DESIGN.md, decision #2).
|
|
47
|
+
*
|
|
48
|
+
* `_id` is preserved as a bare string — section components routinely use
|
|
49
|
+
* it as a React key, and there is no inline-editing surface for it.
|
|
50
|
+
*/
|
|
51
|
+
type SlotItem<S extends SectionSchema> = {
|
|
52
|
+
readonly _id: string;
|
|
53
|
+
} & {
|
|
54
|
+
readonly [K in keyof S]: FieldDataType<S[K]>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Maps a single `FieldDescriptor` to the type a section component receives
|
|
59
|
+
* on its `props` for that field.
|
|
60
|
+
*
|
|
61
|
+
* Built-in field → component-prop type:
|
|
62
|
+
* text → EditableSlot<'text', string>
|
|
63
|
+
* richText → EditableSlot<'richText', string>
|
|
64
|
+
* image → EditableSlot<'image', ImageValue>
|
|
65
|
+
* video → EditableSlot<'video', VideoValue>
|
|
66
|
+
* link → EditableSlot<'link', LinkValue>
|
|
67
|
+
* button → EditableSlot<'button', ButtonValue>
|
|
68
|
+
* number → EditableSlot<'number', number>
|
|
69
|
+
* boolean → EditableSlot<'boolean', boolean>
|
|
70
|
+
* select → EditableSlot<'select', string>
|
|
71
|
+
* list → EditableSlot<'list', ReadonlyArray<SlotItem<S>>>
|
|
72
|
+
* reference → ReferenceValue (raw — see decision #3)
|
|
73
|
+
* formOverrides → FormFieldOverrides (raw — see decision #4)
|
|
74
|
+
*
|
|
75
|
+
* EDITABILITY_DESIGN.md gates this design. The slot wrapping is the
|
|
76
|
+
* load-bearing change: a slot is not assignable to `React.ReactNode`, so
|
|
77
|
+
* `<h1>{title}</h1>` produces a TS error and forces the author into
|
|
78
|
+
* `<EditableText field={title}>`. `ListField` items recurse via
|
|
79
|
+
* `SlotItem<S>`, so raw rendering inside `renderItem` is also a TS error.
|
|
80
|
+
*
|
|
81
|
+
* `ReferenceField` stays raw because references are not inline-editable
|
|
82
|
+
* in v1 (decision #3): a slot would force a no-op `<EditableReference>`
|
|
83
|
+
* wrapper without UX value. `FormOverridesField` stays raw because
|
|
84
|
+
* forms render via a built-in `<Form>` component that reads the schema
|
|
85
|
+
* itself; there is no author-defined component receiving raw form-field
|
|
86
|
+
* values, and therefore no JSX surface where a raw render could leak
|
|
87
|
+
* (decision #4).
|
|
88
|
+
*
|
|
89
|
+
* `FieldDataType` and `FieldValueFor` (`domain/schema.ts`) NO LONGER
|
|
90
|
+
* agree element-by-element: `FieldValueFor` is the bare runtime payload
|
|
91
|
+
* shape (what storage carries, what `getContent` returns), and stays as
|
|
92
|
+
* it is; `FieldDataType` is the section-author-facing mapping that
|
|
93
|
+
* drives `DataOf<S>` and now wraps editable kinds in slots. The runtime
|
|
94
|
+
* wrapping happens at the SectionRenderer boundary (sub-task 2 wires
|
|
95
|
+
* `wrapAsSlot`) and is INTENTIONALLY NOT mirrored in `getContent` — see
|
|
96
|
+
* the comment in `runtime/getContent.ts` for why.
|
|
97
|
+
*
|
|
98
|
+
* The mapping is expressed per-descriptor so a future field type is one
|
|
99
|
+
* line of change instead of a redesign. That is not the same as
|
|
100
|
+
* "user-extensible field types" — invariant 5 (CLAUDE.md) forbids a
|
|
101
|
+
* plugin system; this is just the internal seam.
|
|
102
|
+
*/
|
|
103
|
+
type FieldDataType<F extends FieldDescriptor> = F extends TextField ? EditableSlot<'text', string> : F extends RichTextField ? EditableSlot<'richText', string> : F extends ImageField ? EditableSlot<'image', ImageValue> : F extends VideoField ? EditableSlot<'video', VideoValue> : F extends ReferenceField ? ReferenceValue : F extends LinkField ? EditableSlot<'link', LinkValue> : F extends ButtonField ? EditableSlot<'button', ButtonValue> : F extends NumberField ? EditableSlot<'number', number> : F extends BooleanField ? EditableSlot<'boolean', boolean> : F extends SelectField ? EditableSlot<'select', string> : F extends ListField<infer S> ? EditableSlot<'list', ReadonlyArray<SlotItem<S>>> : F extends FormOverridesFieldDescriptor ? FormFieldOverrides : never;
|
|
104
|
+
/**
|
|
105
|
+
* Derives the SECTION-COMPONENT prop shape from its schema.
|
|
106
|
+
*
|
|
107
|
+
* Per `FieldDataType`, every editable field becomes an `EditableSlot<K,V>`
|
|
108
|
+
* (so `<h1>{title}</h1>` is a TS error); `ReferenceValue` and
|
|
109
|
+
* `FormFieldOverrides` stay raw. Example:
|
|
110
|
+
*
|
|
111
|
+
* type HeroSchema = { title: TextField; image: ImageField; ref: ReferenceField }
|
|
112
|
+
* DataOf<HeroSchema> ≡ {
|
|
113
|
+
* readonly title: EditableSlot<'text', string>
|
|
114
|
+
* readonly image: EditableSlot<'image', ImageValue>
|
|
115
|
+
* readonly ref: ReferenceValue
|
|
116
|
+
* }
|
|
117
|
+
*
|
|
118
|
+
* NOTE: this is the COMPONENT-SIDE shape. The bare runtime payload (what
|
|
119
|
+
* `getContent` returns and what storage carries) is described by
|
|
120
|
+
* `FieldValueFor<F>` in `domain/schema.ts` — that mapping has not changed
|
|
121
|
+
* and must not change (see the slot-non-wrapping comment in
|
|
122
|
+
* `runtime/getContent.ts`).
|
|
123
|
+
*/
|
|
124
|
+
type DataOf<S extends SectionSchema> = {
|
|
125
|
+
readonly [K in keyof S]: FieldDataType<S[K]>;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* The contract a section's React component must satisfy: a function taking
|
|
129
|
+
* exactly the derived props for its schema. The return type is intentionally
|
|
130
|
+
* `unknown` — `sections/` may not name React types (see file header). The
|
|
131
|
+
* downstream `react/` module is where the stricter `ReactElement` typing
|
|
132
|
+
* lives; here we only care that the props line up with the schema.
|
|
133
|
+
*/
|
|
134
|
+
type SectionComponent<S extends SectionSchema> = (props: DataOf<S>) => unknown;
|
|
135
|
+
/**
|
|
136
|
+
* Registration record produced by `defineSection`. Consumers (the registry
|
|
137
|
+
* built by `defineConfig` in T-019, `SectionRenderer` in T-016) look up
|
|
138
|
+
* definitions by `name` and use `component` to render.
|
|
139
|
+
*
|
|
140
|
+
* Both generic parameters are required (no defaults). A heterogeneous
|
|
141
|
+
* registry — the list the runtime actually stores — is typed as
|
|
142
|
+
* `readonly AnySectionDefinition[]`, NOT `readonly SectionDefinition[]`.
|
|
143
|
+
*
|
|
144
|
+
* Why this split exists
|
|
145
|
+
* ---------------------
|
|
146
|
+
* `SectionComponent<S>` is a function whose parameter is contravariant. If
|
|
147
|
+
* we widened `SectionDefinition` to its default parameters and asked a
|
|
148
|
+
* concrete `SectionDefinition<{title: TextField}, (p: {title: string}) => unknown>`
|
|
149
|
+
* to be assignable to it, the function-parameter contravariance would
|
|
150
|
+
* refuse: the "widened" parameter type `DataOf<SectionSchema>` is stricter
|
|
151
|
+
* (more required keys, from TypeScript's perspective) than any concrete
|
|
152
|
+
* `DataOf<S>`. The erasure-safe form lives in `AnySectionDefinition`, which
|
|
153
|
+
* stores the component with `unknown` props. Every precise definition is
|
|
154
|
+
* assignable to `AnySectionDefinition`.
|
|
155
|
+
*/
|
|
156
|
+
interface SectionDefinition<S extends SectionSchema, C extends SectionComponent<S>> {
|
|
157
|
+
/** Stable type discriminator used to tag `Section.type` and in lookups. */
|
|
158
|
+
readonly name: string;
|
|
159
|
+
/** Optional grouping label for the section picker modal. */
|
|
160
|
+
readonly category?: string;
|
|
161
|
+
/** Field-descriptor schema. */
|
|
162
|
+
readonly schema: S;
|
|
163
|
+
/** React component reference. See file header on why not a thunk. */
|
|
164
|
+
readonly component: C;
|
|
165
|
+
/** Pre-computed placeholder values for each field, used when inserting a
|
|
166
|
+
* new section in preview mode. Computed from field descriptors' `default`
|
|
167
|
+
* property (when present) or from the built-in per-kind fallback.
|
|
168
|
+
*
|
|
169
|
+
* Typed as `Record<string, unknown>` so the registry type stays stable
|
|
170
|
+
* even if a future built-in field type introduces a non-string runtime
|
|
171
|
+
* value. In v1 every value here is a `string`. The registry itself
|
|
172
|
+
* never interprets these — consumers that read a specific field narrow
|
|
173
|
+
* at the call site. */
|
|
174
|
+
readonly defaults: Record<string, unknown>;
|
|
175
|
+
/** Optional list of layout names supported by this section. When present
|
|
176
|
+
* with length >= 2, the preview-mode section picker renders a "Layout"
|
|
177
|
+
* tab so authors can switch the layout without editing JSON. The section
|
|
178
|
+
* component reads the active layout from its `layout` prop; the picker
|
|
179
|
+
* writes that value via the existing per-field save round-trip.
|
|
180
|
+
*
|
|
181
|
+
* Layouts are a SECTION-level concern, not a field descriptor, because
|
|
182
|
+
* they gate structure inside a single component rather than introducing
|
|
183
|
+
* a new field type. See ARCHITECTURE.md §4. */
|
|
184
|
+
readonly layouts?: readonly string[];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The erased/heterogeneous form used by the registry and by
|
|
188
|
+
* `defineConfig({ sections: [...] })`. Precise definitions produced by
|
|
189
|
+
* `defineSection` are assignable to this type because:
|
|
190
|
+
* - `schema` widens to `SectionSchema` (covariant read),
|
|
191
|
+
* - `component` widens to a function whose parameter type is `never`.
|
|
192
|
+
* Under parameter contravariance, any concrete `(props: X) => unknown`
|
|
193
|
+
* is assignable to `(props: never) => unknown` (for any `X`, `never` is
|
|
194
|
+
* a subtype of `X`). Return widens to `unknown`.
|
|
195
|
+
*
|
|
196
|
+
* Callers that only need `name` / `schema` metadata treat a list of
|
|
197
|
+
* definitions as `readonly AnySectionDefinition[]`. Rendering code — which
|
|
198
|
+
* does need to INVOKE the component — recovers strong typing at the call
|
|
199
|
+
* site by looking up by name and casting to its section-specific
|
|
200
|
+
* `SectionComponent<S>` before calling. The registry itself never invokes
|
|
201
|
+
* components.
|
|
202
|
+
*/
|
|
203
|
+
interface AnySectionDefinition {
|
|
204
|
+
readonly name: string;
|
|
205
|
+
/** Optional grouping label for the section picker modal. */
|
|
206
|
+
readonly category?: string;
|
|
207
|
+
readonly schema: SectionSchema;
|
|
208
|
+
readonly component: (props: never) => unknown;
|
|
209
|
+
/** Pre-computed placeholder values — see `SectionDefinition.defaults`. */
|
|
210
|
+
readonly defaults: Record<string, unknown>;
|
|
211
|
+
/** Optional layout names — see `SectionDefinition.layouts`. */
|
|
212
|
+
readonly layouts?: readonly string[];
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Input shape for `defineSection`. Split out so the factory can use a single
|
|
216
|
+
* generic parameter set for both input and output.
|
|
217
|
+
*/
|
|
218
|
+
interface DefineSectionInput<S extends SectionSchema, C extends SectionComponent<S>> {
|
|
219
|
+
readonly name: string;
|
|
220
|
+
/** Optional grouping label for the section picker modal. */
|
|
221
|
+
readonly category?: string;
|
|
222
|
+
readonly schema: S;
|
|
223
|
+
readonly component: C;
|
|
224
|
+
/** Optional layout names — see `SectionDefinition.layouts`. */
|
|
225
|
+
readonly layouts?: readonly string[];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Type-safe factory for a `SectionDefinition`.
|
|
229
|
+
*
|
|
230
|
+
* The compile-time win is the constraint `C extends SectionComponent<S>`: if
|
|
231
|
+
* the component's props do not match the derived `DataOf<S>`, TypeScript
|
|
232
|
+
* rejects the call. The factory itself is purely about binding — it does no
|
|
233
|
+
* validation at runtime, because in v1 there is nothing to validate (the
|
|
234
|
+
* schema is plain descriptor metadata, not a parser).
|
|
235
|
+
*
|
|
236
|
+
* Object and schema are not deep-frozen: doing so would add a runtime cost on
|
|
237
|
+
* every module load without preventing anything the type system does not
|
|
238
|
+
* already prevent in strict mode. `as const` at the author's call site is
|
|
239
|
+
* enough for literal inference.
|
|
240
|
+
*/
|
|
241
|
+
declare function defineSection<S extends SectionSchema, C extends SectionComponent<S>>(input: DefineSectionInput<S, C>): SectionDefinition<S, C>;
|
|
242
|
+
|
|
243
|
+
export { type AnySectionDefinition as A, type DataOf as D, type EditableSlot as E, type FieldDataType as F, type SectionComponent as S, type DefineSectionInput as a, type SectionDefinition as b, type SlotItem as c, defineSection as d };
|