@elytracms/next 0.0.1
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/LICENSE +21 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +47 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +2616 -0
- package/dist/index.js +4484 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2616 @@
|
|
|
1
|
+
import { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export { z } from 'zod';
|
|
4
|
+
import { Metadata, MetadataRoute } from 'next';
|
|
5
|
+
import { ImageLoader } from 'next/image';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A structured CMS validation issue. Same conventions as
|
|
9
|
+
* `@elytracms/project-graph`'s `ValidationIssue`: a path locating the offending
|
|
10
|
+
* data, optional document/collection identity, and free-form metadata.
|
|
11
|
+
*/
|
|
12
|
+
declare const cmsValidationIssueSchema: z.ZodObject<{
|
|
13
|
+
code: z.ZodEnum<{
|
|
14
|
+
"duplicate-collection": "duplicate-collection";
|
|
15
|
+
"duplicate-field": "duplicate-field";
|
|
16
|
+
"unknown-collection": "unknown-collection";
|
|
17
|
+
"invalid-collection-config": "invalid-collection-config";
|
|
18
|
+
"invalid-field-config": "invalid-field-config";
|
|
19
|
+
"missing-required-field": "missing-required-field";
|
|
20
|
+
"invalid-field-value": "invalid-field-value";
|
|
21
|
+
"unknown-relation-target": "unknown-relation-target";
|
|
22
|
+
"unpublished-relation-target": "unpublished-relation-target";
|
|
23
|
+
"cardinality-violation": "cardinality-violation";
|
|
24
|
+
"unknown-asset": "unknown-asset";
|
|
25
|
+
"duplicate-document": "duplicate-document";
|
|
26
|
+
"unknown-locale": "unknown-locale";
|
|
27
|
+
"missing-localized-value": "missing-localized-value";
|
|
28
|
+
"route-conflict": "route-conflict";
|
|
29
|
+
"redirect-loop": "redirect-loop";
|
|
30
|
+
"unknown-route-target": "unknown-route-target";
|
|
31
|
+
"hierarchy-cycle": "hierarchy-cycle";
|
|
32
|
+
"unknown-version": "unknown-version";
|
|
33
|
+
}>;
|
|
34
|
+
severity: z.ZodEnum<{
|
|
35
|
+
error: "error";
|
|
36
|
+
warning: "warning";
|
|
37
|
+
}>;
|
|
38
|
+
message: z.ZodString;
|
|
39
|
+
path: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
40
|
+
collectionId: z.ZodOptional<z.ZodString>;
|
|
41
|
+
documentId: z.ZodOptional<z.ZodString>;
|
|
42
|
+
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
type CmsValidationIssue = z.infer<typeof cmsValidationIssueSchema>;
|
|
45
|
+
type CmsPath = ReadonlyArray<string | number>;
|
|
46
|
+
|
|
47
|
+
/** How many related documents a relation field points at. */
|
|
48
|
+
declare const cardinalitySchema: z.ZodEnum<{
|
|
49
|
+
one: "one";
|
|
50
|
+
many: "many";
|
|
51
|
+
}>;
|
|
52
|
+
type Cardinality = z.infer<typeof cardinalitySchema>;
|
|
53
|
+
/** A single allowed option for a `select` field. */
|
|
54
|
+
declare const selectOptionSchema: z.ZodObject<{
|
|
55
|
+
value: z.ZodString;
|
|
56
|
+
label: z.ZodOptional<z.ZodString>;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
type SelectOption = z.infer<typeof selectOptionSchema>;
|
|
59
|
+
/** A schema of just the shared base attributes — the source for {@link BaseFieldDef}. */
|
|
60
|
+
declare const baseFieldObjectSchema: z.ZodObject<{
|
|
61
|
+
name: z.ZodString;
|
|
62
|
+
localized: z.ZodOptional<z.ZodBoolean>;
|
|
63
|
+
filterable: z.ZodOptional<z.ZodBoolean>;
|
|
64
|
+
context: z.ZodOptional<z.ZodEnum<{
|
|
65
|
+
document: "document";
|
|
66
|
+
prop: "prop";
|
|
67
|
+
both: "both";
|
|
68
|
+
}>>;
|
|
69
|
+
default: z.ZodOptional<z.ZodUnknown>;
|
|
70
|
+
form: z.ZodOptional<z.ZodObject<{
|
|
71
|
+
label: z.ZodOptional<z.ZodString>;
|
|
72
|
+
description: z.ZodOptional<z.ZodString>;
|
|
73
|
+
placeholder: z.ZodOptional<z.ZodString>;
|
|
74
|
+
group: z.ZodOptional<z.ZodString>;
|
|
75
|
+
tab: z.ZodOptional<z.ZodString>;
|
|
76
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
77
|
+
readOnly: z.ZodOptional<z.ZodBoolean>;
|
|
78
|
+
hidden: z.ZodOptional<z.ZodBoolean>;
|
|
79
|
+
control: z.ZodOptional<z.ZodEnum<{
|
|
80
|
+
input: "input";
|
|
81
|
+
textarea: "textarea";
|
|
82
|
+
color: "color";
|
|
83
|
+
json: "json";
|
|
84
|
+
}>>;
|
|
85
|
+
inlineEditable: z.ZodOptional<z.ZodBoolean>;
|
|
86
|
+
}, z.core.$strip>>;
|
|
87
|
+
validation: z.ZodOptional<z.ZodObject<{
|
|
88
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
89
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
90
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
91
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
92
|
+
unique: z.ZodOptional<z.ZodBoolean>;
|
|
93
|
+
}, z.core.$strip>>;
|
|
94
|
+
}, z.core.$strip>;
|
|
95
|
+
/** Attributes every field-def carries, derived from {@link baseFieldShape}. */
|
|
96
|
+
type BaseFieldDef = z.infer<typeof baseFieldObjectSchema>;
|
|
97
|
+
/**
|
|
98
|
+
* A field definition (the shape {@link fieldDefSchema} parses to). Written
|
|
99
|
+
* explicitly rather than via `z.infer` because the `object` variant is recursive
|
|
100
|
+
* — its `fields` reference `FieldDef` itself — and TypeScript cannot infer a
|
|
101
|
+
* self-referential `z.infer`. The schema below is annotated with this type and the
|
|
102
|
+
* two are kept in lock-step (`object-field.test.ts` round-trips against drift).
|
|
103
|
+
*
|
|
104
|
+
* A discriminated union on `type` so relation/asset/select/object carry exactly
|
|
105
|
+
* the extra config they need and nothing more.
|
|
106
|
+
*/
|
|
107
|
+
type FieldDef = (BaseFieldDef & {
|
|
108
|
+
type: 'text';
|
|
109
|
+
}) | (BaseFieldDef & {
|
|
110
|
+
type: 'number';
|
|
111
|
+
}) | (BaseFieldDef & {
|
|
112
|
+
type: 'boolean';
|
|
113
|
+
}) | (BaseFieldDef & {
|
|
114
|
+
type: 'date';
|
|
115
|
+
}) | (BaseFieldDef & {
|
|
116
|
+
type: 'select';
|
|
117
|
+
options: SelectOption[];
|
|
118
|
+
multiple?: boolean;
|
|
119
|
+
}) | (BaseFieldDef & {
|
|
120
|
+
type: 'richText';
|
|
121
|
+
allow?: string[];
|
|
122
|
+
}) | (BaseFieldDef & {
|
|
123
|
+
type: 'relation';
|
|
124
|
+
target: string;
|
|
125
|
+
cardinality: Cardinality;
|
|
126
|
+
populate?: boolean;
|
|
127
|
+
strict?: boolean;
|
|
128
|
+
}) | (BaseFieldDef & {
|
|
129
|
+
type: 'asset';
|
|
130
|
+
cardinality: Cardinality;
|
|
131
|
+
accept?: string[];
|
|
132
|
+
}) | (BaseFieldDef & {
|
|
133
|
+
type: 'blocks';
|
|
134
|
+
allow?: string[];
|
|
135
|
+
cardinality: Cardinality;
|
|
136
|
+
}) | (BaseFieldDef & {
|
|
137
|
+
type: 'object';
|
|
138
|
+
fields: FieldDef[];
|
|
139
|
+
cardinality: Cardinality;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* A collection definition: an id, a kind, an ordered list of fields, and
|
|
144
|
+
* optional form metadata. The field whose value identifies a document for
|
|
145
|
+
* routing/display defaults to `title` when present (see `titleFieldOf`).
|
|
146
|
+
*/
|
|
147
|
+
declare const collectionDefSchema: z.ZodObject<{
|
|
148
|
+
id: z.ZodString;
|
|
149
|
+
kind: z.ZodDefault<z.ZodEnum<{
|
|
150
|
+
asset: "asset";
|
|
151
|
+
document: "document";
|
|
152
|
+
}>>;
|
|
153
|
+
fields: z.ZodDefault<z.ZodArray<z.ZodType<FieldDef, unknown, z.core.$ZodTypeInternals<FieldDef, unknown>>>>;
|
|
154
|
+
form: z.ZodOptional<z.ZodObject<{
|
|
155
|
+
label: z.ZodOptional<z.ZodString>;
|
|
156
|
+
labelSingular: z.ZodOptional<z.ZodString>;
|
|
157
|
+
description: z.ZodOptional<z.ZodString>;
|
|
158
|
+
groups: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
159
|
+
tabs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
160
|
+
}, z.core.$strip>>;
|
|
161
|
+
localized: z.ZodOptional<z.ZodBoolean>;
|
|
162
|
+
titleField: z.ZodOptional<z.ZodString>;
|
|
163
|
+
singleton: z.ZodOptional<z.ZodBoolean>;
|
|
164
|
+
}, z.core.$strip>;
|
|
165
|
+
type CollectionDef = z.infer<typeof collectionDefSchema>;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A locale code (BCP-47-ish, e.g. `en`, `en-US`, `de`). Kept as a plain
|
|
169
|
+
* non-empty string so unusual locales still parse; validity against a project's
|
|
170
|
+
* declared locale set is checked semantically (EC-018).
|
|
171
|
+
*/
|
|
172
|
+
declare const localeSchema: z.ZodString;
|
|
173
|
+
type Locale = z.infer<typeof localeSchema>;
|
|
174
|
+
/**
|
|
175
|
+
* A document identity (EC-017): the stable pointer to a piece of content. A
|
|
176
|
+
* document belongs to exactly one collection and has a stable id. For localized
|
|
177
|
+
* content the *identity* is collection+id; a specific localized variant is
|
|
178
|
+
* addressed by additionally carrying a locale (see `LocaleAwareDocumentRef`).
|
|
179
|
+
*/
|
|
180
|
+
declare const documentRefSchema: z.ZodObject<{
|
|
181
|
+
collection: z.ZodString;
|
|
182
|
+
id: z.ZodString;
|
|
183
|
+
}, z.core.$strip>;
|
|
184
|
+
type DocumentRef = z.infer<typeof documentRefSchema>;
|
|
185
|
+
/**
|
|
186
|
+
* A stored document (EC-016/017/018). The live row is the continuous DRAFT
|
|
187
|
+
* (the Sanity model, EC-224). Holds:
|
|
188
|
+
* - identity (`collection` + `id`),
|
|
189
|
+
* - non-localized field values in `values`,
|
|
190
|
+
* - per-locale field values in `localized` (only for localized fields).
|
|
191
|
+
*
|
|
192
|
+
* Publish-ness is NOT a field here: a document is published when an append-only
|
|
193
|
+
* version is pinned via the record's `publishedVersion` pointer (EC-224). The
|
|
194
|
+
* delivery `published` perspective serves that pinned snapshot; this live row
|
|
195
|
+
* is always the working draft.
|
|
196
|
+
*/
|
|
197
|
+
declare const documentSchema: z.ZodObject<{
|
|
198
|
+
collection: z.ZodString;
|
|
199
|
+
id: z.ZodString;
|
|
200
|
+
values: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
201
|
+
localized: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
202
|
+
defaultLocale: z.ZodOptional<z.ZodString>;
|
|
203
|
+
}, z.core.$strip>;
|
|
204
|
+
type CmsDocument = z.infer<typeof documentSchema>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Locale configuration for a project (EC-018): the set of supported locales and
|
|
208
|
+
* the default used for fallback. Provider-neutral.
|
|
209
|
+
*/
|
|
210
|
+
declare const localeConfigSchema: z.ZodObject<{
|
|
211
|
+
default: z.ZodString;
|
|
212
|
+
locales: z.ZodArray<z.ZodString>;
|
|
213
|
+
}, z.core.$strip>;
|
|
214
|
+
type LocaleConfig = z.infer<typeof localeConfigSchema>;
|
|
215
|
+
|
|
216
|
+
/** A JSON-serializable value. The project graph contains only these — never functions or runtime handles. */
|
|
217
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
218
|
+
[key: string]: JsonValue;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/** A pointer from the graph into a data source via a stable path token. */
|
|
222
|
+
declare const bindingReferenceSchema: z.ZodObject<{
|
|
223
|
+
sourceId: z.ZodString;
|
|
224
|
+
token: z.ZodString;
|
|
225
|
+
mode: z.ZodEnum<{
|
|
226
|
+
object: "object";
|
|
227
|
+
value: "value";
|
|
228
|
+
spread: "spread";
|
|
229
|
+
repeaterItem: "repeaterItem";
|
|
230
|
+
condition: "condition";
|
|
231
|
+
}>;
|
|
232
|
+
}, z.core.$strip>;
|
|
233
|
+
type BindingReference = z.infer<typeof bindingReferenceSchema>;
|
|
234
|
+
/** Gates whether a node renders. The left operand is resolved from a binding. */
|
|
235
|
+
declare const conditionSchema: z.ZodObject<{
|
|
236
|
+
source: z.ZodObject<{
|
|
237
|
+
sourceId: z.ZodString;
|
|
238
|
+
token: z.ZodString;
|
|
239
|
+
mode: z.ZodEnum<{
|
|
240
|
+
object: "object";
|
|
241
|
+
value: "value";
|
|
242
|
+
spread: "spread";
|
|
243
|
+
repeaterItem: "repeaterItem";
|
|
244
|
+
condition: "condition";
|
|
245
|
+
}>;
|
|
246
|
+
}, z.core.$strip>;
|
|
247
|
+
operator: z.ZodEnum<{
|
|
248
|
+
truthy: "truthy";
|
|
249
|
+
exists: "exists";
|
|
250
|
+
eq: "eq";
|
|
251
|
+
neq: "neq";
|
|
252
|
+
gt: "gt";
|
|
253
|
+
gte: "gte";
|
|
254
|
+
lt: "lt";
|
|
255
|
+
lte: "lte";
|
|
256
|
+
}>;
|
|
257
|
+
value: z.ZodOptional<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>;
|
|
258
|
+
}, z.core.$strip>;
|
|
259
|
+
type Condition = z.infer<typeof conditionSchema>;
|
|
260
|
+
|
|
261
|
+
/** A prop value is either a static JSON value or a binding into a data source. */
|
|
262
|
+
declare const propValueSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
263
|
+
kind: z.ZodLiteral<"static">;
|
|
264
|
+
value: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
|
|
265
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
266
|
+
kind: z.ZodLiteral<"binding">;
|
|
267
|
+
binding: z.ZodObject<{
|
|
268
|
+
sourceId: z.ZodString;
|
|
269
|
+
token: z.ZodString;
|
|
270
|
+
mode: z.ZodEnum<{
|
|
271
|
+
object: "object";
|
|
272
|
+
value: "value";
|
|
273
|
+
spread: "spread";
|
|
274
|
+
repeaterItem: "repeaterItem";
|
|
275
|
+
condition: "condition";
|
|
276
|
+
}>;
|
|
277
|
+
}, z.core.$strip>;
|
|
278
|
+
}, z.core.$strip>], "kind">;
|
|
279
|
+
type PropValue = z.infer<typeof propValueSchema>;
|
|
280
|
+
/**
|
|
281
|
+
* An instance of a registered component. Recursive via `slots`: each named slot
|
|
282
|
+
* holds an ordered list of child nodes. The reserved slot `children` is the default
|
|
283
|
+
* insertion point.
|
|
284
|
+
*/
|
|
285
|
+
interface ComponentNode {
|
|
286
|
+
id: string;
|
|
287
|
+
/** Namespaced registered component id (validated semantically, not at parse time). */
|
|
288
|
+
componentId: string;
|
|
289
|
+
props?: Record<string, PropValue>;
|
|
290
|
+
slots?: Record<string, ComponentNode[]>;
|
|
291
|
+
condition?: Condition;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Schema version 2 (EC-187): the graph is LAYOUTS-ONLY. Version 1 carried a
|
|
296
|
+
* `pages` array (page-as-graph-node); v2 removes it — a page is now a document
|
|
297
|
+
* in the `page` collection whose `body` composition renders into a layout via
|
|
298
|
+
* `renderCompositionInLayout`. A deployment NOT reseeded to v2 serves a null
|
|
299
|
+
* graph, so the host degrades to its visible fallback rather than crashing.
|
|
300
|
+
*/
|
|
301
|
+
declare const PROJECT_GRAPH_SCHEMA_VERSION = 2;
|
|
302
|
+
/**
|
|
303
|
+
* The canonical project graph — LAYOUTS-ONLY (EC-187). The single source of
|
|
304
|
+
* truth for the dev-frame layout scaffolding; page content lives in the `page`
|
|
305
|
+
* collection.
|
|
306
|
+
*/
|
|
307
|
+
declare const projectGraphSchema: z.ZodObject<{
|
|
308
|
+
id: z.ZodString;
|
|
309
|
+
schemaVersion: z.ZodNumber;
|
|
310
|
+
metadata: z.ZodOptional<z.ZodObject<{
|
|
311
|
+
name: z.ZodOptional<z.ZodString>;
|
|
312
|
+
}, z.core.$strip>>;
|
|
313
|
+
settingsRef: z.ZodOptional<z.ZodString>;
|
|
314
|
+
layouts: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
315
|
+
id: z.ZodString;
|
|
316
|
+
name: z.ZodString;
|
|
317
|
+
root: z.ZodType<ComponentNode, unknown, z.core.$ZodTypeInternals<ComponentNode, unknown>>;
|
|
318
|
+
}, z.core.$strip>>>;
|
|
319
|
+
}, z.core.$strip>;
|
|
320
|
+
type ProjectGraph = z.infer<typeof projectGraphSchema>;
|
|
321
|
+
|
|
322
|
+
/** A structured validation issue carrying a JSON path into the graph. */
|
|
323
|
+
declare const validationIssueSchema: z.ZodObject<{
|
|
324
|
+
code: z.ZodEnum<{
|
|
325
|
+
"route-conflict": "route-conflict";
|
|
326
|
+
"invalid-component-name": "invalid-component-name";
|
|
327
|
+
"unknown-component": "unknown-component";
|
|
328
|
+
"duplicate-node-id": "duplicate-node-id";
|
|
329
|
+
"unknown-prop": "unknown-prop";
|
|
330
|
+
"invalid-prop": "invalid-prop";
|
|
331
|
+
"binding-not-allowed": "binding-not-allowed";
|
|
332
|
+
"unresolved-binding": "unresolved-binding";
|
|
333
|
+
"missing-required-slot": "missing-required-slot";
|
|
334
|
+
"unknown-slot": "unknown-slot";
|
|
335
|
+
"forbidden-component": "forbidden-component";
|
|
336
|
+
"server-only-component": "server-only-component";
|
|
337
|
+
"unknown-layout": "unknown-layout";
|
|
338
|
+
"invalid-container-direction": "invalid-container-direction";
|
|
339
|
+
"invalid-container-alignment": "invalid-container-alignment";
|
|
340
|
+
"invalid-container-gap": "invalid-container-gap";
|
|
341
|
+
"invalid-container-columns": "invalid-container-columns";
|
|
342
|
+
"invalid-container-responsive": "invalid-container-responsive";
|
|
343
|
+
}>;
|
|
344
|
+
severity: z.ZodEnum<{
|
|
345
|
+
error: "error";
|
|
346
|
+
warning: "warning";
|
|
347
|
+
}>;
|
|
348
|
+
message: z.ZodString;
|
|
349
|
+
path: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
350
|
+
nodeId: z.ZodOptional<z.ZodString>;
|
|
351
|
+
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>>;
|
|
352
|
+
}, z.core.$strip>;
|
|
353
|
+
type ValidationIssue = z.infer<typeof validationIssueSchema>;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Structural views of a component manifest, supplied by the registry. The graph
|
|
357
|
+
* package depends only on these shapes (not on `@elytracms/component-registry`) so it
|
|
358
|
+
* stays free of cycles. The registry's Manifest satisfies these structurally.
|
|
359
|
+
*/
|
|
360
|
+
interface PropView {
|
|
361
|
+
/** Whether this prop accepts a binding. Defaults to allowed when omitted. */
|
|
362
|
+
bindable?: boolean;
|
|
363
|
+
/** Optional static-value validator (typically backed by the manifest's Zod schema). */
|
|
364
|
+
validate?: (value: unknown) => boolean;
|
|
365
|
+
}
|
|
366
|
+
interface SlotView {
|
|
367
|
+
name: string;
|
|
368
|
+
required?: boolean;
|
|
369
|
+
/**
|
|
370
|
+
* Allowed child component ids (EC-186). Omit to allow any registered
|
|
371
|
+
* component; when present, a child whose `componentId` is not listed is a
|
|
372
|
+
* `forbidden-component` error. Mirrors the registry's `SlotSpec.allow`.
|
|
373
|
+
*/
|
|
374
|
+
allow?: readonly string[];
|
|
375
|
+
}
|
|
376
|
+
interface ManifestView {
|
|
377
|
+
id: string;
|
|
378
|
+
props?: Record<string, PropView>;
|
|
379
|
+
slots?: SlotView[];
|
|
380
|
+
/**
|
|
381
|
+
* Canvas-eligibility (EC-191). `false` marks a server-only / RSC component that
|
|
382
|
+
* is NOT client-renderable, so it may not appear in an editor composition (the
|
|
383
|
+
* canvas is client-rendered). Omitted/true = canvas-eligible. Only enforced when
|
|
384
|
+
* `requireCanvasEligible` is set (i.e. validating a composition value, not a
|
|
385
|
+
* dev-authored page/layout frame).
|
|
386
|
+
*/
|
|
387
|
+
clientRenderable?: boolean;
|
|
388
|
+
}
|
|
389
|
+
interface ComponentLookup {
|
|
390
|
+
has(componentId: string): boolean;
|
|
391
|
+
get(componentId: string): ManifestView | undefined;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
type ParseResult = {
|
|
395
|
+
ok: true;
|
|
396
|
+
graph: ProjectGraph;
|
|
397
|
+
} | {
|
|
398
|
+
ok: false;
|
|
399
|
+
error: z.ZodError;
|
|
400
|
+
};
|
|
401
|
+
/** Parse a project graph from a JSON string or already-parsed value. Never throws on invalid input. */
|
|
402
|
+
declare function parseProjectGraph(input: string | unknown): ParseResult;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* A route record (EC-019, reshaped by EC-187). A path pattern may contain
|
|
406
|
+
* dynamic segments written `:name` (e.g. `/blog/:slug`). A route maps a concrete
|
|
407
|
+
* URL to a **render target = a collection + document** via its `document` ref
|
|
408
|
+
* — `/` → `{ collection: 'page', id: 'home' }`, `/blog/:slug` →
|
|
409
|
+
* `{ collection: 'post', id: ':slug' }`. The catch-all dispatches on the
|
|
410
|
+
* resolved document's collection. There is no `templateId`/`pageId` anymore:
|
|
411
|
+
* page content is a `page`-collection document, not a graph page.
|
|
412
|
+
*/
|
|
413
|
+
declare const routeRecordSchema: z.ZodObject<{
|
|
414
|
+
id: z.ZodString;
|
|
415
|
+
pattern: z.ZodString;
|
|
416
|
+
locale: z.ZodOptional<z.ZodString>;
|
|
417
|
+
document: z.ZodOptional<z.ZodObject<{
|
|
418
|
+
collection: z.ZodString;
|
|
419
|
+
id: z.ZodString;
|
|
420
|
+
}, z.core.$strip>>;
|
|
421
|
+
}, z.core.$strip>;
|
|
422
|
+
type RouteRecord = z.infer<typeof routeRecordSchema>;
|
|
423
|
+
/** A redirect record (EC-019/020). */
|
|
424
|
+
declare const redirectRecordSchema: z.ZodObject<{
|
|
425
|
+
id: z.ZodString;
|
|
426
|
+
from: z.ZodString;
|
|
427
|
+
to: z.ZodString;
|
|
428
|
+
permanent: z.ZodDefault<z.ZodBoolean>;
|
|
429
|
+
}, z.core.$strip>;
|
|
430
|
+
type RedirectRecord = z.infer<typeof redirectRecordSchema>;
|
|
431
|
+
/** Result of `resolveUrl` (EC-019). */
|
|
432
|
+
type ResolveStatus = 'ok' | 'redirect' | 'notFound';
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* A snapshot of a document at a point in time (EC-021 version history). Stores a
|
|
436
|
+
* full copy of the document's stored state plus bookkeeping. Snapshots are
|
|
437
|
+
* immutable records of prior states.
|
|
438
|
+
*/
|
|
439
|
+
declare const documentVersionSchema: z.ZodObject<{
|
|
440
|
+
version: z.ZodNumber;
|
|
441
|
+
snapshot: z.ZodObject<{
|
|
442
|
+
collection: z.ZodString;
|
|
443
|
+
id: z.ZodString;
|
|
444
|
+
values: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
445
|
+
localized: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
446
|
+
defaultLocale: z.ZodOptional<z.ZodString>;
|
|
447
|
+
}, z.core.$strip>;
|
|
448
|
+
createdAt: z.ZodNumber;
|
|
449
|
+
label: z.ZodOptional<z.ZodString>;
|
|
450
|
+
}, z.core.$strip>;
|
|
451
|
+
type DocumentVersion = z.infer<typeof documentVersionSchema>;
|
|
452
|
+
/**
|
|
453
|
+
* Draft/published primitives + version history for a single document (EC-021).
|
|
454
|
+
*
|
|
455
|
+
* Holds the live `draft` state, the last `published` state (if any), and an
|
|
456
|
+
* ordered history of recorded versions. All mutations return new states or
|
|
457
|
+
* append history; nothing is silently dropped, and retrieving an unknown
|
|
458
|
+
* version yields a structured issue rather than throwing.
|
|
459
|
+
*/
|
|
460
|
+
interface DocumentHistory {
|
|
461
|
+
/** The current working draft. */
|
|
462
|
+
readonly draft: CmsDocument;
|
|
463
|
+
/** The last published snapshot, if the document has ever been published. */
|
|
464
|
+
readonly published: CmsDocument | undefined;
|
|
465
|
+
/** Recorded versions, oldest first. */
|
|
466
|
+
readonly versions: readonly DocumentVersion[];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Distributive `Omit` that preserves a discriminated union (one variant per member). */
|
|
470
|
+
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
|
471
|
+
/**
|
|
472
|
+
* A component prop is a **field-def** (EC-190 "props as fields", AD-12): the SAME
|
|
473
|
+
* vocabulary the CMS uses for document fields (`@elytracms/cms-core` `FieldDef`) —
|
|
474
|
+
* text/number/boolean/select/date/relation/asset/richText/blocks — minus the
|
|
475
|
+
* `name` (the record key on `ComponentManifest.props` IS the prop name). One
|
|
476
|
+
* schema vocabulary, one inspector/form engine, recursively: a prop of type
|
|
477
|
+
* `blocks` carries a nested composition. Authoring metadata (label, default,
|
|
478
|
+
* inline-editing, control hint) lives on the field-def's `form` and `default`.
|
|
479
|
+
* Use `context: 'prop'` (or `'both'`); document-only attributes (`filterable`,
|
|
480
|
+
* `unique`) are flagged by cms-core's `assertPropFieldDef`, never silently honoured.
|
|
481
|
+
*/
|
|
482
|
+
type PropField = DistributiveOmit<FieldDef, 'name'>;
|
|
483
|
+
/** A named child insertion point. */
|
|
484
|
+
interface SlotSpec {
|
|
485
|
+
name: string;
|
|
486
|
+
label?: string;
|
|
487
|
+
required?: boolean;
|
|
488
|
+
/** Restrict which component ids may be inserted (omit to allow any). */
|
|
489
|
+
allow?: string[];
|
|
490
|
+
}
|
|
491
|
+
/** A deterministic preview fixture for a component state. */
|
|
492
|
+
interface ComponentFixture {
|
|
493
|
+
name: string;
|
|
494
|
+
props?: Record<string, JsonValue>;
|
|
495
|
+
/** Named slots rendered with literal child component ids for preview. */
|
|
496
|
+
slotChildren?: Record<string, string[]>;
|
|
497
|
+
}
|
|
498
|
+
/** Declares that a component iterates a list prop over an item slot (e.g. Repeater). */
|
|
499
|
+
interface IterateSpec {
|
|
500
|
+
itemsProp: string;
|
|
501
|
+
itemSlot: string;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Declares that a component renders a composition FIELD VALUE (EC-188, AD-12): the
|
|
505
|
+
* `valueProp` carries a stored composition (one `ComponentNode` root or an ordered
|
|
506
|
+
* list — a `blocks` field's value), which the renderer renders as the component's
|
|
507
|
+
* children via `renderComposition`, with the live `RenderContext`. The mirror of
|
|
508
|
+
* `IterateSpec`: a manifest opts a host component into rendering a composition tree
|
|
509
|
+
* wherever it is placed (e.g. a post template's body), so the same renderer/export
|
|
510
|
+
* pipeline that renders pages renders block fields too.
|
|
511
|
+
*/
|
|
512
|
+
interface CompositionSpec {
|
|
513
|
+
valueProp: string;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Declares that a component renders a LISTING — a live, query-resolved set of
|
|
517
|
+
* documents (EC-255, un-parks AD-9 "this section repeats over a collection,
|
|
518
|
+
* filtered, sorted"). The `prop` receives the matching documents (delivery-shaped),
|
|
519
|
+
* injected by the host exactly like a composition's children — NOT a static
|
|
520
|
+
* relation pick the editor maintains by hand. The filter binds the listed
|
|
521
|
+
* collection's `field` to the ref a SIBLING authored prop (`fromProp`) carries:
|
|
522
|
+
* a ProductGrid picks one `category`, and the listing lists `product` where its
|
|
523
|
+
* `categories` (a `many` relation) contains that pick. `sort`/`limit` are the
|
|
524
|
+
* closed AD-3 query envelope. The mirror of {@link CompositionSpec} for a
|
|
525
|
+
* query-resolved prop — so the same renderer/export pipeline that renders pages
|
|
526
|
+
* renders dynamic grids, and adding/removing a member re-renders them.
|
|
527
|
+
*/
|
|
528
|
+
interface ListingSpec {
|
|
529
|
+
/** Prop the resolved documents are injected as (read by the implementation). */
|
|
530
|
+
prop: string;
|
|
531
|
+
/** Collection to list (e.g. `product`). */
|
|
532
|
+
collection: string;
|
|
533
|
+
/**
|
|
534
|
+
* The membership filter: the listed collection's `field` must contain the ref
|
|
535
|
+
* the sibling authored prop `fromProp` holds (a relation pick, reduced to its
|
|
536
|
+
* target id). Closed to a single equality/contains term — the AD-3 vocabulary.
|
|
537
|
+
*/
|
|
538
|
+
filter: {
|
|
539
|
+
field: string;
|
|
540
|
+
fromProp: string;
|
|
541
|
+
};
|
|
542
|
+
/** Optional single-field sort (ties break by id, like every list — EC-143). */
|
|
543
|
+
sort?: {
|
|
544
|
+
field: string;
|
|
545
|
+
direction?: 'asc' | 'desc';
|
|
546
|
+
};
|
|
547
|
+
/** Optional page-size cap. */
|
|
548
|
+
limit?: number;
|
|
549
|
+
}
|
|
550
|
+
/** The typed manifest each editable component declares (brief §7.2). */
|
|
551
|
+
interface ComponentManifest {
|
|
552
|
+
/** Namespaced id, e.g. `base.primitives.Stack` or `project.MarketingHero`. */
|
|
553
|
+
id: string;
|
|
554
|
+
/** Source namespace: `base.primitives` for platform primitives, `project` for project code. */
|
|
555
|
+
namespace: string;
|
|
556
|
+
title?: string;
|
|
557
|
+
description?: string;
|
|
558
|
+
category?: string;
|
|
559
|
+
/** Editable props as field-defs (EC-190), keyed by prop name. */
|
|
560
|
+
props: Record<string, PropField>;
|
|
561
|
+
slots: SlotSpec[];
|
|
562
|
+
/** Design-token usage hints (e.g. `['color.surface', 'space.gap']`). */
|
|
563
|
+
designTokens?: string[];
|
|
564
|
+
fixtures?: ComponentFixture[];
|
|
565
|
+
iterate?: IterateSpec;
|
|
566
|
+
/** Marks the component as a composition host: `valueProp` holds a block-field tree (EC-188). */
|
|
567
|
+
composition?: CompositionSpec;
|
|
568
|
+
/** Marks the component as a listing host: `prop` receives a live query's documents (EC-255). */
|
|
569
|
+
listing?: ListingSpec;
|
|
570
|
+
/**
|
|
571
|
+
* Canvas-eligibility (EC-191). Editors place components on a **client-rendered**
|
|
572
|
+
* canvas, so a placeable component must be client-renderable (isomorphic React).
|
|
573
|
+
* Defaults to eligible; set `clientRenderable: false` for a server-only / RSC
|
|
574
|
+
* component — it stays usable in dev frames (`page.tsx` / `layout.tsx`) but the
|
|
575
|
+
* blocks panel won't offer it and a composition that contains it is invalid.
|
|
576
|
+
*/
|
|
577
|
+
clientRenderable?: boolean;
|
|
578
|
+
}
|
|
579
|
+
/** Identity helper that infers nothing but documents intent at call sites. */
|
|
580
|
+
declare function defineComponent(manifest: ComponentManifest): ComponentManifest;
|
|
581
|
+
|
|
582
|
+
/** Structured issues surfaced by the registry itself (distinct from graph issues). */
|
|
583
|
+
interface ComponentIssue {
|
|
584
|
+
code: 'duplicate-id' | 'invalid-namespace' | 'invalid-id';
|
|
585
|
+
componentId: string;
|
|
586
|
+
message: string;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
interface ListFilter {
|
|
590
|
+
namespace?: string;
|
|
591
|
+
category?: string;
|
|
592
|
+
/** Case-insensitive substring match over id / title / category. */
|
|
593
|
+
query?: string;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* The component registry serves the Builder, renderer, AI tools, export pipeline, and
|
|
597
|
+
* CLI. It distinguishes platform primitives from project components, supports lookup,
|
|
598
|
+
* filtering, required lookups, and reports duplicate ids as structured issues.
|
|
599
|
+
*/
|
|
600
|
+
declare class ComponentRegistry {
|
|
601
|
+
private readonly byId;
|
|
602
|
+
private readonly viewCache;
|
|
603
|
+
readonly issues: ComponentIssue[];
|
|
604
|
+
constructor(manifests?: Iterable<ComponentManifest>);
|
|
605
|
+
register(manifest: ComponentManifest): void;
|
|
606
|
+
has(id: string): boolean;
|
|
607
|
+
getManifest(id: string): ComponentManifest | undefined;
|
|
608
|
+
/** Required lookup — throws when the component is not registered. */
|
|
609
|
+
require(id: string): ComponentManifest;
|
|
610
|
+
list(filter?: ListFilter): ComponentManifest[];
|
|
611
|
+
/** Platform primitives (`base.primitives.*`). */
|
|
612
|
+
primitives(): ComponentManifest[];
|
|
613
|
+
/** Project-level components (everything not under `base.primitives.*`). */
|
|
614
|
+
projectComponents(): ComponentManifest[];
|
|
615
|
+
get size(): number;
|
|
616
|
+
/** A `ComponentLookup` view for the project-graph validator. */
|
|
617
|
+
get lookup(): ComponentLookup;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* A single repeater item scope. When set, `repeaterItem`-mode bindings resolve against
|
|
622
|
+
* `item` rather than the root data source (brief §4.6 — uniform binding model).
|
|
623
|
+
*/
|
|
624
|
+
interface ItemContext {
|
|
625
|
+
item: unknown;
|
|
626
|
+
index: number;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Resolves a binding to a runtime value. INJECTED by the host (builder preview, export
|
|
630
|
+
* runtime, tests) so the renderer never imports `@elytracms/data-binding`. When an
|
|
631
|
+
* `ItemContext` is active, it is passed through so `repeaterItem` bindings resolve
|
|
632
|
+
* relative to the current item.
|
|
633
|
+
*/
|
|
634
|
+
type ResolveBinding = (ref: BindingReference, item?: ItemContext) => unknown;
|
|
635
|
+
/**
|
|
636
|
+
* A resolved asset, ready to hand to an image implementation (EC-195). The
|
|
637
|
+
* delivery-shaped subset of `@elytracms/content`'s `ResolvedAsset` the renderer
|
|
638
|
+
* needs — kept minimal so the renderer takes no dependency on the content or
|
|
639
|
+
* persistence packages. `width`/`height` are intrinsic dimensions (so the host
|
|
640
|
+
* can reserve space and `next/image` can size variants); `null` when unknown.
|
|
641
|
+
*/
|
|
642
|
+
interface RenderAsset {
|
|
643
|
+
url: string;
|
|
644
|
+
width: number | null;
|
|
645
|
+
height: number | null;
|
|
646
|
+
alt: string | null;
|
|
647
|
+
/**
|
|
648
|
+
* Normalized focal point `{ x, y }` in 0–1 (EC-230) → CSS `object-position` so
|
|
649
|
+
* an art-directed image keeps its subject in frame when cropped; `null` when
|
|
650
|
+
* none. Kept as a plain pair so the renderer stays free of content/persistence.
|
|
651
|
+
*/
|
|
652
|
+
focalPoint: {
|
|
653
|
+
x: number;
|
|
654
|
+
y: number;
|
|
655
|
+
} | null;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* A relation target populated to delivery shape (EC-254): identity plus the
|
|
659
|
+
* target document's resolved fields (relations inside stay `{ id, collection }`
|
|
660
|
+
* stubs at depth-1; assets inside resolve to objects). Opaque to the renderer —
|
|
661
|
+
* the host's content layer produces it, the component implementation reads its
|
|
662
|
+
* fields. The minimal structural face is `{ id, collection }`.
|
|
663
|
+
*/
|
|
664
|
+
type RelationTarget = {
|
|
665
|
+
id: string;
|
|
666
|
+
collection: string;
|
|
667
|
+
} & Record<string, unknown>;
|
|
668
|
+
/**
|
|
669
|
+
* Maps namespaced component ids to their React implementations. Implementations receive
|
|
670
|
+
* resolved props plus rendered slot children: the default slot `children` maps to React
|
|
671
|
+
* `children`, and every other named slot is passed as a prop named after the slot.
|
|
672
|
+
*/
|
|
673
|
+
type ComponentImplementations = Record<string, ComponentType<Record<string, unknown>>> | Map<string, ComponentType<Record<string, unknown>>>;
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* One host-repo component: the manifest the builder edits against plus the
|
|
677
|
+
* real React implementation that renders it (vision AD-2 — project components
|
|
678
|
+
* are real code in the host repo, never generated).
|
|
679
|
+
*/
|
|
680
|
+
interface HostComponent {
|
|
681
|
+
manifest: ComponentManifest;
|
|
682
|
+
implementation: ComponentType<Record<string, unknown>>;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* The registered component surface a host app hands to `<CanvasRenderer />`
|
|
686
|
+
* and the catch-all route helper: a manifest registry plus the implementation
|
|
687
|
+
* map, with registration problems surfaced as structured issues (never
|
|
688
|
+
* thrown — an unknown component renders the EC-015 fallback).
|
|
689
|
+
*/
|
|
690
|
+
interface HostComponents {
|
|
691
|
+
registry: ComponentRegistry;
|
|
692
|
+
implementations: ComponentImplementations;
|
|
693
|
+
/** Structural registration issues (duplicate ids, invalid namespaces). */
|
|
694
|
+
issues: readonly ComponentIssue[];
|
|
695
|
+
}
|
|
696
|
+
interface DefineHostComponentsOptions {
|
|
697
|
+
/**
|
|
698
|
+
* Include the platform primitives (`base.primitives.*`) that ship with
|
|
699
|
+
* `@elytracms/runtime-renderer`. Defaults to `true` — graphs authored in the
|
|
700
|
+
* builder assume them.
|
|
701
|
+
*/
|
|
702
|
+
includeBasePrimitives?: boolean;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Component registration API for the embedded runtime (EC-144). The host repo
|
|
706
|
+
* supplies real implementations + manifests; base primitives are included by
|
|
707
|
+
* default. A host implementation registered under a primitive id replaces the
|
|
708
|
+
* default implementation (the manifest of the first registration wins, which
|
|
709
|
+
* is the primitive's — by design, so prop schemas stay canonical).
|
|
710
|
+
*/
|
|
711
|
+
declare function defineHostComponents(components?: readonly HostComponent[], options?: DefineHostComponentsOptions): HostComponents;
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Which side of the draft/published split a delivery request reads from
|
|
715
|
+
* (EC-021, vision AD-4). `published` never exposes draft content; `draft` is
|
|
716
|
+
* the editor/preview view.
|
|
717
|
+
*/
|
|
718
|
+
declare const perspectiveSchema: z.ZodEnum<{
|
|
719
|
+
draft: "draft";
|
|
720
|
+
published: "published";
|
|
721
|
+
}>;
|
|
722
|
+
type Perspective = z.infer<typeof perspectiveSchema>;
|
|
723
|
+
/**
|
|
724
|
+
* Ambient request context (vision AD-4): locale and perspective are set once
|
|
725
|
+
* per request and threaded through every resolution function — never per-call
|
|
726
|
+
* ceremony. The locale config travels along so EC-018 fallback needs no extra
|
|
727
|
+
* arguments either.
|
|
728
|
+
*/
|
|
729
|
+
interface ContentContext {
|
|
730
|
+
/** The locale requested for this delivery (EC-018 fallback applies). */
|
|
731
|
+
readonly locale: Locale;
|
|
732
|
+
/** Draft or published perspective (EC-021). */
|
|
733
|
+
readonly perspective: Perspective;
|
|
734
|
+
/** The project's locale configuration (default + supported set). */
|
|
735
|
+
readonly locales: LocaleConfig;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* What delivery resolution accepts as a document source: either a bare stored
|
|
740
|
+
* row (`CmsDocument`, always a working draft — EC-224 retired the `state` flag)
|
|
741
|
+
* or the full draft/published history (`DocumentHistory`, EC-021) whose
|
|
742
|
+
* `published` field carries the pinned snapshot. Lookups hand these in;
|
|
743
|
+
* resolution stays pure — no fetching at this layer.
|
|
744
|
+
*/
|
|
745
|
+
type ContentDocumentSource = CmsDocument | DocumentHistory;
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* A report entry for one field whose value was served from a different locale
|
|
749
|
+
* than the one requested (EC-018 default-locale fallback). Paths are rooted at
|
|
750
|
+
* the resolved document, e.g. `['title']` or `['author', 'bio']` for a field
|
|
751
|
+
* inside a populated relation.
|
|
752
|
+
*/
|
|
753
|
+
interface FieldLocaleFallback {
|
|
754
|
+
path: CmsPath;
|
|
755
|
+
/** The field name (last path segment, repeated for convenience). */
|
|
756
|
+
field: string;
|
|
757
|
+
/** The locale the request asked for. */
|
|
758
|
+
requestedLocale: Locale;
|
|
759
|
+
/** The locale the value was actually read from. */
|
|
760
|
+
sourceLocale: Locale;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Which backend a persistence adapter is talking to, and whether it's reachable.
|
|
765
|
+
* Lets the Studio surface degraded/offline states without leaking provider types.
|
|
766
|
+
*/
|
|
767
|
+
declare const backendKindSchema: z.ZodEnum<{
|
|
768
|
+
convex: "convex";
|
|
769
|
+
"in-memory": "in-memory";
|
|
770
|
+
}>;
|
|
771
|
+
type BackendKind = z.infer<typeof backendKindSchema>;
|
|
772
|
+
interface BackendStatus {
|
|
773
|
+
backend: BackendKind;
|
|
774
|
+
available: boolean;
|
|
775
|
+
detail?: string;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* An immutable record of one saved graph state (brief §7.1, §10). Each save
|
|
780
|
+
* appends a revision; the highest revision is the latest. The graph is stored
|
|
781
|
+
* serialized so reload is provably lossless (serialize → parse round-trip).
|
|
782
|
+
*/
|
|
783
|
+
declare const graphRevisionRecordSchema: z.ZodObject<{
|
|
784
|
+
id: z.ZodString;
|
|
785
|
+
projectId: z.ZodString;
|
|
786
|
+
revision: z.ZodNumber;
|
|
787
|
+
serializedGraph: z.ZodString;
|
|
788
|
+
schemaVersion: z.ZodNumber;
|
|
789
|
+
createdAt: z.ZodString;
|
|
790
|
+
label: z.ZodOptional<z.ZodString>;
|
|
791
|
+
message: z.ZodOptional<z.ZodString>;
|
|
792
|
+
}, z.core.$strip>;
|
|
793
|
+
type GraphRevisionRecord = z.infer<typeof graphRevisionRecordSchema>;
|
|
794
|
+
/** A revision paired with its parsed graph. */
|
|
795
|
+
interface LoadedGraph {
|
|
796
|
+
graph: ProjectGraph;
|
|
797
|
+
revision: GraphRevisionRecord;
|
|
798
|
+
}
|
|
799
|
+
interface SaveGraphOptions {
|
|
800
|
+
label?: string;
|
|
801
|
+
message?: string;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Persists project graphs and their revision history (brief §7.1, §10).
|
|
805
|
+
* Provider-neutral: an in-memory and a Convex implementation both satisfy it.
|
|
806
|
+
*/
|
|
807
|
+
interface GraphRepository {
|
|
808
|
+
/** Append a new revision for the project's graph and return it. */
|
|
809
|
+
saveGraph(projectId: string, graph: ProjectGraph, options?: SaveGraphOptions): Promise<GraphRevisionRecord>;
|
|
810
|
+
/** Load the latest graph + revision. Throws `RecordNotFoundError` if none. */
|
|
811
|
+
getLatest(projectId: string): Promise<LoadedGraph>;
|
|
812
|
+
/** Load a specific revision. Throws `RecordNotFoundError` if absent. */
|
|
813
|
+
getRevision(projectId: string, revision: number): Promise<LoadedGraph>;
|
|
814
|
+
/** All revisions for a project, newest first. */
|
|
815
|
+
listRevisions(projectId: string): Promise<GraphRevisionRecord[]>;
|
|
816
|
+
/** Whether any graph revision exists for the project. */
|
|
817
|
+
hasGraph(projectId: string): Promise<boolean>;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/** The persisted CMS schema for a project: its collection definitions (brief §7.4, §10). */
|
|
821
|
+
declare const cmsSchemaRecordSchema: z.ZodObject<{
|
|
822
|
+
createdAt: z.ZodString;
|
|
823
|
+
updatedAt: z.ZodString;
|
|
824
|
+
projectId: z.ZodString;
|
|
825
|
+
collections: z.ZodArray<z.ZodObject<{
|
|
826
|
+
id: z.ZodString;
|
|
827
|
+
kind: z.ZodDefault<z.ZodEnum<{
|
|
828
|
+
asset: "asset";
|
|
829
|
+
document: "document";
|
|
830
|
+
}>>;
|
|
831
|
+
fields: z.ZodDefault<z.ZodArray<z.ZodType<FieldDef, unknown, z.core.$ZodTypeInternals<FieldDef, unknown>>>>;
|
|
832
|
+
form: z.ZodOptional<z.ZodObject<{
|
|
833
|
+
label: z.ZodOptional<z.ZodString>;
|
|
834
|
+
labelSingular: z.ZodOptional<z.ZodString>;
|
|
835
|
+
description: z.ZodOptional<z.ZodString>;
|
|
836
|
+
groups: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
837
|
+
tabs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
838
|
+
}, z.core.$strip>>;
|
|
839
|
+
localized: z.ZodOptional<z.ZodBoolean>;
|
|
840
|
+
titleField: z.ZodOptional<z.ZodString>;
|
|
841
|
+
singleton: z.ZodOptional<z.ZodBoolean>;
|
|
842
|
+
}, z.core.$strip>>;
|
|
843
|
+
}, z.core.$strip>;
|
|
844
|
+
type CmsSchemaRecord = z.infer<typeof cmsSchemaRecordSchema>;
|
|
845
|
+
/**
|
|
846
|
+
* A persisted CMS document. The stored `document` is the continuous working
|
|
847
|
+
* **draft** (every edit updates it); `publishedVersion` pins which recorded
|
|
848
|
+
* version is currently live (the Sanity draft/published model, EC-224). Absent
|
|
849
|
+
* `publishedVersion` means the document has never been published / is unpublished.
|
|
850
|
+
* Localized variants ride along on the `CmsDocument` itself.
|
|
851
|
+
*/
|
|
852
|
+
declare const cmsDocumentRecordSchema: z.ZodObject<{
|
|
853
|
+
createdAt: z.ZodString;
|
|
854
|
+
updatedAt: z.ZodString;
|
|
855
|
+
projectId: z.ZodString;
|
|
856
|
+
document: z.ZodObject<{
|
|
857
|
+
collection: z.ZodString;
|
|
858
|
+
id: z.ZodString;
|
|
859
|
+
values: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
860
|
+
localized: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
861
|
+
defaultLocale: z.ZodOptional<z.ZodString>;
|
|
862
|
+
}, z.core.$strip>;
|
|
863
|
+
publishedVersion: z.ZodOptional<z.ZodNumber>;
|
|
864
|
+
}, z.core.$strip>;
|
|
865
|
+
type CmsDocumentRecord = z.infer<typeof cmsDocumentRecordSchema>;
|
|
866
|
+
/**
|
|
867
|
+
* An immutable snapshot of a document at one recorded version (EC-224). Append-only,
|
|
868
|
+
* mirroring {@link GraphRevisionRecord} but document-scoped: one row per version,
|
|
869
|
+
* keyed by (projectId, collection, docId, version). Created on publish (the promoted
|
|
870
|
+
* draft) and by an explicit pre-restore snapshot, never on a plain draft edit.
|
|
871
|
+
*/
|
|
872
|
+
declare const documentVersionRecordSchema: z.ZodObject<{
|
|
873
|
+
projectId: z.ZodString;
|
|
874
|
+
collection: z.ZodString;
|
|
875
|
+
docId: z.ZodString;
|
|
876
|
+
version: z.ZodNumber;
|
|
877
|
+
snapshot: z.ZodObject<{
|
|
878
|
+
collection: z.ZodString;
|
|
879
|
+
id: z.ZodString;
|
|
880
|
+
values: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
881
|
+
localized: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
882
|
+
defaultLocale: z.ZodOptional<z.ZodString>;
|
|
883
|
+
}, z.core.$strip>;
|
|
884
|
+
createdAt: z.ZodString;
|
|
885
|
+
label: z.ZodOptional<z.ZodString>;
|
|
886
|
+
}, z.core.$strip>;
|
|
887
|
+
type DocumentVersionRecord = z.infer<typeof documentVersionRecordSchema>;
|
|
888
|
+
interface SaveDocumentVersionOptions {
|
|
889
|
+
label?: string;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Persists CMS collection schemas and documents (brief §7.4, §10). Localized
|
|
893
|
+
* variants and draft/published state ride along on the `CmsDocument` itself.
|
|
894
|
+
*/
|
|
895
|
+
interface CmsRepository {
|
|
896
|
+
/** Persist (replace) the project's collection schema; validates structural shape. */
|
|
897
|
+
saveSchema(projectId: string, collections: readonly CollectionDef[]): Promise<CmsSchemaRecord>;
|
|
898
|
+
getSchema(projectId: string): Promise<CmsSchemaRecord>;
|
|
899
|
+
findSchema(projectId: string): Promise<CmsSchemaRecord | undefined>;
|
|
900
|
+
/** Insert or update a document (keyed by collection + id). */
|
|
901
|
+
upsertDocument(projectId: string, document: CmsDocument): Promise<CmsDocumentRecord>;
|
|
902
|
+
getDocument(projectId: string, ref: DocumentRef): Promise<CmsDocumentRecord>;
|
|
903
|
+
findDocument(projectId: string, ref: DocumentRef): Promise<CmsDocumentRecord | undefined>;
|
|
904
|
+
/** All documents for a project, optionally filtered to one collection. */
|
|
905
|
+
listDocuments(projectId: string, collection?: string): Promise<CmsDocumentRecord[]>;
|
|
906
|
+
deleteDocument(projectId: string, ref: DocumentRef): Promise<void>;
|
|
907
|
+
/**
|
|
908
|
+
* Append a snapshot of a document as a new monotonic version (EC-224). Used by
|
|
909
|
+
* publish (the promoted draft) and the pre-restore snapshot so a rollback is
|
|
910
|
+
* itself undoable. The (collection, docId) is derived from the snapshot.
|
|
911
|
+
*/
|
|
912
|
+
saveDocumentVersion(projectId: string, snapshot: CmsDocument, options?: SaveDocumentVersionOptions): Promise<DocumentVersionRecord>;
|
|
913
|
+
/** All recorded versions for a document, newest first. Empty when none. */
|
|
914
|
+
listDocumentVersions(projectId: string, ref: DocumentRef): Promise<DocumentVersionRecord[]>;
|
|
915
|
+
/**
|
|
916
|
+
* Every recorded version across all documents in a project (EC-224). Backs the
|
|
917
|
+
* delivery feeders, which reconstruct each document's `DocumentHistory` from
|
|
918
|
+
* one bulk fetch rather than a per-document query. Order is deterministic
|
|
919
|
+
* (by document key, then version ascending).
|
|
920
|
+
*/
|
|
921
|
+
listAllDocumentVersions(projectId: string): Promise<DocumentVersionRecord[]>;
|
|
922
|
+
/** A specific recorded version. Throws `RecordNotFoundError` if absent. */
|
|
923
|
+
getDocumentVersion(projectId: string, ref: DocumentRef, version: number): Promise<DocumentVersionRecord>;
|
|
924
|
+
/**
|
|
925
|
+
* Pin an existing recorded version live (EC-224 publish). Throws
|
|
926
|
+
* `RecordNotFoundError` if the document or the version is unknown.
|
|
927
|
+
*/
|
|
928
|
+
publishDocumentVersion(projectId: string, ref: DocumentRef, version: number): Promise<CmsDocumentRecord>;
|
|
929
|
+
/**
|
|
930
|
+
* Clear the live pin (EC-224 unpublish). Throws `PersistenceValidationError`
|
|
931
|
+
* if the document is not currently published.
|
|
932
|
+
*/
|
|
933
|
+
unpublishDocument(projectId: string, ref: DocumentRef): Promise<CmsDocumentRecord>;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Persisted asset metadata (brief §7.4, §8.6, §10). The bytes themselves live in
|
|
938
|
+
* a {@link BlobStorageAdapter}; this record is the queryable metadata that CMS
|
|
939
|
+
* `asset` fields point at by id.
|
|
940
|
+
*/
|
|
941
|
+
declare const assetRecordSchema: z.ZodObject<{
|
|
942
|
+
createdAt: z.ZodString;
|
|
943
|
+
updatedAt: z.ZodString;
|
|
944
|
+
id: z.ZodString;
|
|
945
|
+
projectId: z.ZodString;
|
|
946
|
+
filename: z.ZodString;
|
|
947
|
+
contentType: z.ZodString;
|
|
948
|
+
byteSize: z.ZodNumber;
|
|
949
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
950
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
951
|
+
alt: z.ZodOptional<z.ZodString>;
|
|
952
|
+
title: z.ZodOptional<z.ZodString>;
|
|
953
|
+
storageKey: z.ZodString;
|
|
954
|
+
url: z.ZodOptional<z.ZodString>;
|
|
955
|
+
focalPoint: z.ZodOptional<z.ZodObject<{
|
|
956
|
+
x: z.ZodNumber;
|
|
957
|
+
y: z.ZodNumber;
|
|
958
|
+
}, z.core.$strip>>;
|
|
959
|
+
}, z.core.$strip>;
|
|
960
|
+
type AssetRecord = z.infer<typeof assetRecordSchema>;
|
|
961
|
+
interface NewAssetInput {
|
|
962
|
+
id?: string;
|
|
963
|
+
filename: string;
|
|
964
|
+
contentType: string;
|
|
965
|
+
byteSize: number;
|
|
966
|
+
width?: number;
|
|
967
|
+
height?: number;
|
|
968
|
+
alt?: string;
|
|
969
|
+
/** Editable display title (EC-152 asset library metadata). */
|
|
970
|
+
title?: string;
|
|
971
|
+
/** Defaults to a key derived from the asset id. */
|
|
972
|
+
storageKey?: string;
|
|
973
|
+
/** Resolvable serve URL for the stored bytes (EC-151). */
|
|
974
|
+
url?: string;
|
|
975
|
+
/** Normalized image focal point `{ x, y }` in 0–1 (EC-230). */
|
|
976
|
+
focalPoint?: {
|
|
977
|
+
x: number;
|
|
978
|
+
y: number;
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Storage boundary for asset *bytes* (brief §8.6). Project-owned and swappable
|
|
983
|
+
* (Convex file storage, S3, etc.); the in-memory implementation backs tests and
|
|
984
|
+
* local inspection. Metadata stays in the {@link AssetRepository}.
|
|
985
|
+
*/
|
|
986
|
+
interface BlobStorageAdapter {
|
|
987
|
+
put(key: string, data: Uint8Array, contentType?: string): Promise<void>;
|
|
988
|
+
get(key: string): Promise<Uint8Array | undefined>;
|
|
989
|
+
/** A resolvable locator for the blob (e.g. a CDN/file URL). */
|
|
990
|
+
url(key: string): string;
|
|
991
|
+
has(key: string): Promise<boolean>;
|
|
992
|
+
delete(key: string): Promise<void>;
|
|
993
|
+
/**
|
|
994
|
+
* Store bytes under a backend-assigned key and return it (EC-151). Backends
|
|
995
|
+
* like Convex file storage mint their own ids, so callers cannot choose the
|
|
996
|
+
* key up front the way {@link put} assumes. Optional; in-memory implements it
|
|
997
|
+
* deterministically.
|
|
998
|
+
*/
|
|
999
|
+
store?(data: Uint8Array, contentType?: string): Promise<string>;
|
|
1000
|
+
/**
|
|
1001
|
+
* Mint a short-lived URL the client can POST bytes to directly (EC-151,
|
|
1002
|
+
* Convex `storage.generateUploadUrl()`). Optional — backends without
|
|
1003
|
+
* client-direct uploads omit it and callers fall back to {@link store}/{@link put}.
|
|
1004
|
+
*/
|
|
1005
|
+
createUploadUrl?(): Promise<string>;
|
|
1006
|
+
/**
|
|
1007
|
+
* Resolve the serve URL for a stored blob asynchronously (EC-151, Convex
|
|
1008
|
+
* `storage.getUrl()`). Returns `undefined` when the blob does not exist.
|
|
1009
|
+
* Optional; backends with synchronous stable URLs can rely on {@link url}.
|
|
1010
|
+
*/
|
|
1011
|
+
resolveUrl?(key: string): Promise<string | undefined>;
|
|
1012
|
+
}
|
|
1013
|
+
/** Persists asset metadata records (brief §7.4, §8.6, §10). */
|
|
1014
|
+
interface AssetRepository {
|
|
1015
|
+
upsertAsset(projectId: string, input: NewAssetInput): Promise<AssetRecord>;
|
|
1016
|
+
getAsset(projectId: string, id: string): Promise<AssetRecord>;
|
|
1017
|
+
findAsset(projectId: string, id: string): Promise<AssetRecord | undefined>;
|
|
1018
|
+
listAssets(projectId: string): Promise<AssetRecord[]>;
|
|
1019
|
+
removeAsset(projectId: string, id: string): Promise<void>;
|
|
1020
|
+
/** Resolve persisted metadata for the asset ids referenced by CMS fields. */
|
|
1021
|
+
resolveMany(projectId: string, ids: readonly string[]): Promise<Map<string, AssetRecord>>;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Publishing state (brief §10). Single-environment (EC-179, vision AD-8): a
|
|
1026
|
+
* deployment IS one environment, so there is exactly ONE publishing-state
|
|
1027
|
+
* record per project. A `published` record pins the graph revision that is
|
|
1028
|
+
* live. Moving a release between environments is a deployment/code-pipeline
|
|
1029
|
+
* concern, not an in-deployment dimension — see `env-is-deployment-boundary`.
|
|
1030
|
+
*/
|
|
1031
|
+
declare const publishingStateRecordSchema: z.ZodObject<{
|
|
1032
|
+
createdAt: z.ZodString;
|
|
1033
|
+
updatedAt: z.ZodString;
|
|
1034
|
+
projectId: z.ZodString;
|
|
1035
|
+
status: z.ZodEnum<{
|
|
1036
|
+
draft: "draft";
|
|
1037
|
+
published: "published";
|
|
1038
|
+
}>;
|
|
1039
|
+
publishedRevisionId: z.ZodOptional<z.ZodString>;
|
|
1040
|
+
publishedRevision: z.ZodOptional<z.ZodNumber>;
|
|
1041
|
+
publishedAt: z.ZodOptional<z.ZodString>;
|
|
1042
|
+
}, z.core.$strip>;
|
|
1043
|
+
type PublishingStateRecord = z.infer<typeof publishingStateRecordSchema>;
|
|
1044
|
+
interface PublishInput {
|
|
1045
|
+
revisionId: string;
|
|
1046
|
+
revision: number;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Persists and transitions publishing state (brief §10). Transitions are
|
|
1050
|
+
* validated: publishing requires a revision; unpublishing requires a
|
|
1051
|
+
* currently-published deployment.
|
|
1052
|
+
*/
|
|
1053
|
+
interface PublishingRepository {
|
|
1054
|
+
getState(projectId: string): Promise<PublishingStateRecord>;
|
|
1055
|
+
findState(projectId: string): Promise<PublishingStateRecord | undefined>;
|
|
1056
|
+
/** Publish a graph revision. */
|
|
1057
|
+
publish(projectId: string, input: PublishInput): Promise<PublishingStateRecord>;
|
|
1058
|
+
/** Revert to draft (must currently be published). */
|
|
1059
|
+
unpublish(projectId: string): Promise<PublishingStateRecord>;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Reference-index substrate (EC-155, vision AD-6 / pillar 3.6).
|
|
1064
|
+
*
|
|
1065
|
+
* On every write the operations layer extracts *outbound* references — relation
|
|
1066
|
+
* fields, rich-text link marks and inline refs, page-graph bindings and
|
|
1067
|
+
* component usage, asset usages — into this index, for draft and published
|
|
1068
|
+
* variants separately. One index powers many features: "used by" panels, safe
|
|
1069
|
+
* unpublish (the queryable answer to Sanity's hidden-draft-reference failure),
|
|
1070
|
+
* component/asset where-used, and broken-link audits (UX lands in Milestone B).
|
|
1071
|
+
*
|
|
1072
|
+
* Records are **derived data**: they carry no timestamps and are deterministic
|
|
1073
|
+
* functions of the indexed content, so backfill is idempotent and two runs over
|
|
1074
|
+
* the same data are byte-for-byte identical.
|
|
1075
|
+
*/
|
|
1076
|
+
/** Which content variant an entry was extracted from (the Sanity failure-case axis). */
|
|
1077
|
+
declare const referenceVariantSchema: z.ZodEnum<{
|
|
1078
|
+
draft: "draft";
|
|
1079
|
+
published: "published";
|
|
1080
|
+
}>;
|
|
1081
|
+
type ReferenceVariant = z.infer<typeof referenceVariantSchema>;
|
|
1082
|
+
/**
|
|
1083
|
+
* What kind of entity *holds* the reference. Documents are keyed by their
|
|
1084
|
+
* `documentKey` (`collection:id`); pages/layouts by their graph ids.
|
|
1085
|
+
*/
|
|
1086
|
+
declare const referenceSourceTypeSchema: z.ZodEnum<{
|
|
1087
|
+
document: "document";
|
|
1088
|
+
page: "page";
|
|
1089
|
+
layout: "layout";
|
|
1090
|
+
}>;
|
|
1091
|
+
type ReferenceSourceType = z.infer<typeof referenceSourceTypeSchema>;
|
|
1092
|
+
declare const referenceSourceSchema: z.ZodObject<{
|
|
1093
|
+
type: z.ZodEnum<{
|
|
1094
|
+
document: "document";
|
|
1095
|
+
page: "page";
|
|
1096
|
+
layout: "layout";
|
|
1097
|
+
}>;
|
|
1098
|
+
id: z.ZodString;
|
|
1099
|
+
}, z.core.$strip>;
|
|
1100
|
+
type ReferenceSource = z.infer<typeof referenceSourceSchema>;
|
|
1101
|
+
declare const referenceTargetSchema: z.ZodObject<{
|
|
1102
|
+
type: z.ZodEnum<{
|
|
1103
|
+
asset: "asset";
|
|
1104
|
+
document: "document";
|
|
1105
|
+
component: "component";
|
|
1106
|
+
}>;
|
|
1107
|
+
id: z.ZodString;
|
|
1108
|
+
}, z.core.$strip>;
|
|
1109
|
+
type ReferenceTarget = z.infer<typeof referenceTargetSchema>;
|
|
1110
|
+
/**
|
|
1111
|
+
* One outbound reference found *inside* a source, before it is attributed to
|
|
1112
|
+
* that source: the exact field/node path, the target, and which structure
|
|
1113
|
+
* produced it. `path` is a `/`-joined token path into the stored value, e.g.
|
|
1114
|
+
* `values/author`, `localized/en/body/doc/content/0/marks/0`,
|
|
1115
|
+
* `root/slots/children/2/props/items/binding`.
|
|
1116
|
+
*/
|
|
1117
|
+
declare const outboundReferenceSchema: z.ZodObject<{
|
|
1118
|
+
path: z.ZodString;
|
|
1119
|
+
target: z.ZodObject<{
|
|
1120
|
+
type: z.ZodEnum<{
|
|
1121
|
+
asset: "asset";
|
|
1122
|
+
document: "document";
|
|
1123
|
+
component: "component";
|
|
1124
|
+
}>;
|
|
1125
|
+
id: z.ZodString;
|
|
1126
|
+
}, z.core.$strip>;
|
|
1127
|
+
kind: z.ZodEnum<{
|
|
1128
|
+
"relation-field": "relation-field";
|
|
1129
|
+
"asset-field": "asset-field";
|
|
1130
|
+
"rich-text-link": "rich-text-link";
|
|
1131
|
+
"rich-text-node": "rich-text-node";
|
|
1132
|
+
"component-usage": "component-usage";
|
|
1133
|
+
"prop-binding": "prop-binding";
|
|
1134
|
+
"condition-binding": "condition-binding";
|
|
1135
|
+
}>;
|
|
1136
|
+
}, z.core.$strip>;
|
|
1137
|
+
type OutboundReference = z.infer<typeof outboundReferenceSchema>;
|
|
1138
|
+
/** An outbound reference attributed to its holding source entity. */
|
|
1139
|
+
declare const referenceEntrySchema: z.ZodObject<{
|
|
1140
|
+
path: z.ZodString;
|
|
1141
|
+
target: z.ZodObject<{
|
|
1142
|
+
type: z.ZodEnum<{
|
|
1143
|
+
asset: "asset";
|
|
1144
|
+
document: "document";
|
|
1145
|
+
component: "component";
|
|
1146
|
+
}>;
|
|
1147
|
+
id: z.ZodString;
|
|
1148
|
+
}, z.core.$strip>;
|
|
1149
|
+
kind: z.ZodEnum<{
|
|
1150
|
+
"relation-field": "relation-field";
|
|
1151
|
+
"asset-field": "asset-field";
|
|
1152
|
+
"rich-text-link": "rich-text-link";
|
|
1153
|
+
"rich-text-node": "rich-text-node";
|
|
1154
|
+
"component-usage": "component-usage";
|
|
1155
|
+
"prop-binding": "prop-binding";
|
|
1156
|
+
"condition-binding": "condition-binding";
|
|
1157
|
+
}>;
|
|
1158
|
+
source: z.ZodObject<{
|
|
1159
|
+
type: z.ZodEnum<{
|
|
1160
|
+
document: "document";
|
|
1161
|
+
page: "page";
|
|
1162
|
+
layout: "layout";
|
|
1163
|
+
}>;
|
|
1164
|
+
id: z.ZodString;
|
|
1165
|
+
}, z.core.$strip>;
|
|
1166
|
+
}, z.core.$strip>;
|
|
1167
|
+
type ReferenceEntry = z.infer<typeof referenceEntrySchema>;
|
|
1168
|
+
/** The persisted index record: entry + project + variant. Derived, timestamp-free. */
|
|
1169
|
+
declare const referenceEntryRecordSchema: z.ZodObject<{
|
|
1170
|
+
path: z.ZodString;
|
|
1171
|
+
target: z.ZodObject<{
|
|
1172
|
+
type: z.ZodEnum<{
|
|
1173
|
+
asset: "asset";
|
|
1174
|
+
document: "document";
|
|
1175
|
+
component: "component";
|
|
1176
|
+
}>;
|
|
1177
|
+
id: z.ZodString;
|
|
1178
|
+
}, z.core.$strip>;
|
|
1179
|
+
kind: z.ZodEnum<{
|
|
1180
|
+
"relation-field": "relation-field";
|
|
1181
|
+
"asset-field": "asset-field";
|
|
1182
|
+
"rich-text-link": "rich-text-link";
|
|
1183
|
+
"rich-text-node": "rich-text-node";
|
|
1184
|
+
"component-usage": "component-usage";
|
|
1185
|
+
"prop-binding": "prop-binding";
|
|
1186
|
+
"condition-binding": "condition-binding";
|
|
1187
|
+
}>;
|
|
1188
|
+
source: z.ZodObject<{
|
|
1189
|
+
type: z.ZodEnum<{
|
|
1190
|
+
document: "document";
|
|
1191
|
+
page: "page";
|
|
1192
|
+
layout: "layout";
|
|
1193
|
+
}>;
|
|
1194
|
+
id: z.ZodString;
|
|
1195
|
+
}, z.core.$strip>;
|
|
1196
|
+
projectId: z.ZodString;
|
|
1197
|
+
variant: z.ZodEnum<{
|
|
1198
|
+
draft: "draft";
|
|
1199
|
+
published: "published";
|
|
1200
|
+
}>;
|
|
1201
|
+
}, z.core.$strip>;
|
|
1202
|
+
type ReferenceEntryRecord = z.infer<typeof referenceEntryRecordSchema>;
|
|
1203
|
+
interface ReferenceLookupOptions {
|
|
1204
|
+
/** Restrict the lookup to one variant; omit for both (draft + published). */
|
|
1205
|
+
variant?: ReferenceVariant;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Persists the reference index (EC-155). Writes are **replace-all-for-source**:
|
|
1209
|
+
* saving an entity recomputes every outbound reference it holds and replaces the
|
|
1210
|
+
* previous set atomically, so edits/deletes/unpublishes can never leave stale
|
|
1211
|
+
* entries behind. Lookups work in both directions with field/node-path
|
|
1212
|
+
* granularity and are variant-aware.
|
|
1213
|
+
*/
|
|
1214
|
+
interface ReferenceIndexRepository {
|
|
1215
|
+
/**
|
|
1216
|
+
* Replace all entries held by `source` in `variant` with the given outbound
|
|
1217
|
+
* references. An empty list removes the source's entries entirely.
|
|
1218
|
+
*/
|
|
1219
|
+
replaceForSource(projectId: string, variant: ReferenceVariant, source: ReferenceSource, references: readonly OutboundReference[]): Promise<ReferenceEntryRecord[]>;
|
|
1220
|
+
/**
|
|
1221
|
+
* Replace all entries held by sources of the given types in `variant` (the
|
|
1222
|
+
* graph recompute path: one revision yields entries across many pages/layouts,
|
|
1223
|
+
* and pages removed from the graph must drop their entries too).
|
|
1224
|
+
*/
|
|
1225
|
+
replaceForSourceTypes(projectId: string, variant: ReferenceVariant, sourceTypes: readonly ReferenceSourceType[], entries: readonly ReferenceEntry[]): Promise<ReferenceEntryRecord[]>;
|
|
1226
|
+
/** Remove all entries held by `source` (in one variant, or both when omitted). */
|
|
1227
|
+
removeForSource(projectId: string, source: ReferenceSource, variant?: ReferenceVariant): Promise<void>;
|
|
1228
|
+
/** Outbound: what does `source` reference? */
|
|
1229
|
+
outbound(projectId: string, source: ReferenceSource, options?: ReferenceLookupOptions): Promise<ReferenceEntryRecord[]>;
|
|
1230
|
+
/** Inbound: what references `target`? (The "used by" / safe-unpublish direction.) */
|
|
1231
|
+
inbound(projectId: string, target: ReferenceTarget, options?: ReferenceLookupOptions): Promise<ReferenceEntryRecord[]>;
|
|
1232
|
+
/** Every entry of a project (optionally one variant), deterministically ordered. */
|
|
1233
|
+
listEntries(projectId: string, variant?: ReferenceVariant): Promise<ReferenceEntryRecord[]>;
|
|
1234
|
+
/** Drop the whole index of a project (backfill starts from a clean slate). */
|
|
1235
|
+
clearProject(projectId: string): Promise<void>;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Per-project membership records (EC-150, vision AD-7). The role model is
|
|
1240
|
+
* deliberately minimal for v1 — three roles, project-scoped, no field-level
|
|
1241
|
+
* permissions. Workspace-level grants (who may create projects, default
|
|
1242
|
+
* memberships) land with EC-154; everything here stays project-scoped.
|
|
1243
|
+
*
|
|
1244
|
+
* Roles:
|
|
1245
|
+
* - `viewer` — read-only: queries succeed, every command is denied.
|
|
1246
|
+
* - `editor` — edits and publishes content, but cannot administer the project
|
|
1247
|
+
* (lifecycle, settings, members, CLI tokens).
|
|
1248
|
+
* - `admin` — everything within the project.
|
|
1249
|
+
*
|
|
1250
|
+
* Enforcement lives at the operations chokepoint
|
|
1251
|
+
* (`@elytracms/operations` `createRoleAuthorization`) and server-side in the
|
|
1252
|
+
* Convex functions (`apps/builder/convex/guard.ts`); this module only defines
|
|
1253
|
+
* the storage contract.
|
|
1254
|
+
*/
|
|
1255
|
+
declare const projectRoleSchema: z.ZodEnum<{
|
|
1256
|
+
admin: "admin";
|
|
1257
|
+
editor: "editor";
|
|
1258
|
+
viewer: "viewer";
|
|
1259
|
+
}>;
|
|
1260
|
+
type ProjectRole = z.infer<typeof projectRoleSchema>;
|
|
1261
|
+
declare const projectMemberRecordSchema: z.ZodObject<{
|
|
1262
|
+
projectId: z.ZodString;
|
|
1263
|
+
userId: z.ZodString;
|
|
1264
|
+
role: z.ZodEnum<{
|
|
1265
|
+
admin: "admin";
|
|
1266
|
+
editor: "editor";
|
|
1267
|
+
viewer: "viewer";
|
|
1268
|
+
}>;
|
|
1269
|
+
createdAt: z.ZodString;
|
|
1270
|
+
updatedAt: z.ZodString;
|
|
1271
|
+
}, z.core.$strip>;
|
|
1272
|
+
type ProjectMemberRecord = z.infer<typeof projectMemberRecordSchema>;
|
|
1273
|
+
/**
|
|
1274
|
+
* Membership storage boundary. Mirrors the other repository contracts: the
|
|
1275
|
+
* in-memory implementation below and the Convex-backed implementation in
|
|
1276
|
+
* `apps/builder` both satisfy it.
|
|
1277
|
+
*/
|
|
1278
|
+
interface MembersRepository {
|
|
1279
|
+
/** Upsert a membership (add a member or change their role). */
|
|
1280
|
+
setMember(projectId: string, userId: string, role: ProjectRole): Promise<ProjectMemberRecord>;
|
|
1281
|
+
/** Look up one membership; `undefined` when the user is not a member. */
|
|
1282
|
+
findMember(projectId: string, userId: string): Promise<ProjectMemberRecord | undefined>;
|
|
1283
|
+
/** All members of a project, ordered by userId for determinism. */
|
|
1284
|
+
listMembers(projectId: string): Promise<ProjectMemberRecord[]>;
|
|
1285
|
+
/** All memberships of a user across projects, ordered by projectId. */
|
|
1286
|
+
listMembershipsForUser(userId: string): Promise<ProjectMemberRecord[]>;
|
|
1287
|
+
/** Remove a membership. Throws `RecordNotFoundError` when absent. */
|
|
1288
|
+
removeMember(projectId: string, userId: string): Promise<void>;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* Revocable per-project CLI tokens (EC-150). The EC-147 push CLI authenticates
|
|
1293
|
+
* with `Authorization: Bearer <token>`; the builder stores only a SHA-256 hash
|
|
1294
|
+
* of the token (`tokenHash`) — the plaintext is shown exactly once at issuance
|
|
1295
|
+
* and never persisted. Verification (`verifyCliToken` in `@elytracms/operations`)
|
|
1296
|
+
* hashes the presented token and looks it up here; revoked tokens stay stored
|
|
1297
|
+
* (with `revokedAt`) so revocation is auditable and ids stay stable.
|
|
1298
|
+
*/
|
|
1299
|
+
declare const cliTokenRecordSchema: z.ZodObject<{
|
|
1300
|
+
id: z.ZodString;
|
|
1301
|
+
label: z.ZodString;
|
|
1302
|
+
tokenHash: z.ZodString;
|
|
1303
|
+
createdAt: z.ZodString;
|
|
1304
|
+
revokedAt: z.ZodOptional<z.ZodString>;
|
|
1305
|
+
}, z.core.$strip>;
|
|
1306
|
+
type CliTokenRecord = z.infer<typeof cliTokenRecordSchema>;
|
|
1307
|
+
interface NewCliTokenInput {
|
|
1308
|
+
label: string;
|
|
1309
|
+
tokenHash: string;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Token storage boundary. The in-memory implementation below and the
|
|
1313
|
+
* Convex-backed implementation in `apps/builder` both satisfy it.
|
|
1314
|
+
*/
|
|
1315
|
+
interface CliTokenRepository {
|
|
1316
|
+
/** Persist a freshly issued token (hash only). */
|
|
1317
|
+
createToken(input: NewCliTokenInput): Promise<CliTokenRecord>;
|
|
1318
|
+
/** All tokens of this deployment (revoked included), ordered by id. */
|
|
1319
|
+
listTokens(): Promise<CliTokenRecord[]>;
|
|
1320
|
+
/** Mark a token revoked (idempotent). Throws `RecordNotFoundError` when absent. */
|
|
1321
|
+
revokeToken(tokenId: string): Promise<CliTokenRecord>;
|
|
1322
|
+
/** Look a token up by its hash — the verification path. */
|
|
1323
|
+
findByHash(tokenHash: string): Promise<CliTokenRecord | undefined>;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* Inputs to the backend validation service (brief §4.11, §10). Any subset may be
|
|
1328
|
+
* provided; only supplied domains are validated.
|
|
1329
|
+
*/
|
|
1330
|
+
interface BackendValidationInput {
|
|
1331
|
+
graph?: ProjectGraph;
|
|
1332
|
+
/** Registry lookup for component/prop/slot checks against the graph. */
|
|
1333
|
+
components?: ComponentLookup;
|
|
1334
|
+
/** Known data-source ids for binding resolution. */
|
|
1335
|
+
knownSourceIds?: ReadonlySet<string>;
|
|
1336
|
+
collections?: readonly CollectionDef[];
|
|
1337
|
+
routes?: readonly RouteRecord[];
|
|
1338
|
+
redirects?: readonly RedirectRecord[];
|
|
1339
|
+
defaultLocale?: string;
|
|
1340
|
+
}
|
|
1341
|
+
/** Structured, typed validation results returned to clients (brief §4.11). */
|
|
1342
|
+
interface BackendValidationResult {
|
|
1343
|
+
ok: boolean;
|
|
1344
|
+
/** Graph + binding + component issues (from `@elytracms/project-graph`). */
|
|
1345
|
+
graphIssues: ValidationIssue[];
|
|
1346
|
+
/** Collection/field schema issues (from `@elytracms/cms-core`). */
|
|
1347
|
+
schemaIssues: CmsValidationIssue[];
|
|
1348
|
+
/** Route conflict + redirect loop issues (from `@elytracms/cms-core`). */
|
|
1349
|
+
routeIssues: CmsValidationIssue[];
|
|
1350
|
+
/** Count of error-severity issues across all domains. */
|
|
1351
|
+
errorCount: number;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
/**
|
|
1355
|
+
* The aggregate persistence boundary (brief §4.10, §10). Core packages depend on
|
|
1356
|
+
* this interface — never on Convex directly. A Convex-backed implementation and
|
|
1357
|
+
* the in-memory implementation below both satisfy it, so the Studio, seeds, and
|
|
1358
|
+
* tests are provider-agnostic.
|
|
1359
|
+
*/
|
|
1360
|
+
interface PersistenceAdapter {
|
|
1361
|
+
readonly graphs: GraphRepository;
|
|
1362
|
+
readonly cms: CmsRepository;
|
|
1363
|
+
readonly assets: AssetRepository;
|
|
1364
|
+
readonly publishing: PublishingRepository;
|
|
1365
|
+
/** Reference index — outbound/inbound reference entries per variant (EC-155, AD-6). */
|
|
1366
|
+
readonly references: ReferenceIndexRepository;
|
|
1367
|
+
/** Per-project membership roles — admin/editor/viewer (EC-150, AD-7). */
|
|
1368
|
+
readonly members: MembersRepository;
|
|
1369
|
+
/** Revocable per-project CLI sync tokens, stored hashed (EC-150). */
|
|
1370
|
+
readonly cliTokens: CliTokenRepository;
|
|
1371
|
+
/** Blob storage boundary for asset bytes. */
|
|
1372
|
+
readonly blobs: BlobStorageAdapter;
|
|
1373
|
+
/** Backend identity + reachability, for degraded-state UI. */
|
|
1374
|
+
status(): Promise<BackendStatus>;
|
|
1375
|
+
/** Server-side validation service (brief §4.11, EC-047). */
|
|
1376
|
+
validate(input: BackendValidationInput): BackendValidationResult;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* The delivery shape of an asset (vision AD-4): a directly usable object, not
|
|
1381
|
+
* an id. Optional metadata that the stored record lacks is `null` (never
|
|
1382
|
+
* `undefined`) so resolved documents stay JSON-stable.
|
|
1383
|
+
*/
|
|
1384
|
+
interface ResolvedAsset {
|
|
1385
|
+
/** The asset id the field stored (kept for cache keys and re-lookup). */
|
|
1386
|
+
id: string;
|
|
1387
|
+
/** Resolvable URL for the asset bytes (EC-044 blob storage locator). */
|
|
1388
|
+
url: string;
|
|
1389
|
+
width: number | null;
|
|
1390
|
+
height: number | null;
|
|
1391
|
+
alt: string | null;
|
|
1392
|
+
mimeType: string;
|
|
1393
|
+
/**
|
|
1394
|
+
* Normalized image focal point `{ x, y }` in 0–1 (EC-230), or `null`. Threaded
|
|
1395
|
+
* to the image primitive's CSS `object-position` so a migrated hotspot keeps
|
|
1396
|
+
* the subject in frame when a layout crops the image (`object-fit: cover`).
|
|
1397
|
+
*/
|
|
1398
|
+
focalPoint: {
|
|
1399
|
+
x: number;
|
|
1400
|
+
y: number;
|
|
1401
|
+
} | null;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/**
|
|
1405
|
+
* A document in delivery shape: identity plus resolved field values, flat —
|
|
1406
|
+
* `post.title`, `post.author.name`, `post.cover.url`. The precise per-collection
|
|
1407
|
+
* shape is what `generateDeliveryTypes` emits (EC-142 codegen).
|
|
1408
|
+
*/
|
|
1409
|
+
type ResolvedDocument = {
|
|
1410
|
+
id: string;
|
|
1411
|
+
collection: string;
|
|
1412
|
+
} & Record<string, unknown>;
|
|
1413
|
+
/**
|
|
1414
|
+
* The minimal structural face of any delivery-shaped document — what the typed
|
|
1415
|
+
* accessors (EC-143) constrain their per-collection generics against. The
|
|
1416
|
+
* generated interfaces from `generateDeliveryTypes` (e.g. `Post`) satisfy this
|
|
1417
|
+
* where they cannot satisfy `ResolvedDocument`'s index signature.
|
|
1418
|
+
*/
|
|
1419
|
+
interface DeliveryDocument {
|
|
1420
|
+
id: string;
|
|
1421
|
+
collection: string;
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Structured information about *how* a document was resolved — most notably
|
|
1425
|
+
* which fields served their value via EC-018 locale fallback.
|
|
1426
|
+
*/
|
|
1427
|
+
interface ResolutionInfo {
|
|
1428
|
+
locale: Locale;
|
|
1429
|
+
perspective: Perspective;
|
|
1430
|
+
localeFallbacks: FieldLocaleFallback[];
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
/** A scalar a filter can compare against (relation/asset filters use the id). */
|
|
1434
|
+
type FilterScalar = string | number | boolean;
|
|
1435
|
+
/** One field's filter: every present operator must hold (AND). */
|
|
1436
|
+
interface FilterCondition {
|
|
1437
|
+
eq?: FilterScalar;
|
|
1438
|
+
gt?: FilterScalar;
|
|
1439
|
+
gte?: FilterScalar;
|
|
1440
|
+
in?: readonly FilterScalar[];
|
|
1441
|
+
lt?: FilterScalar;
|
|
1442
|
+
lte?: FilterScalar;
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* The `where` input: field name → condition. A bare scalar is shorthand for
|
|
1446
|
+
* `{ eq: value }`. Generated per-collection types (`PostWhere`) narrow the
|
|
1447
|
+
* keys to the fields declared `filterable` in the schema.
|
|
1448
|
+
*/
|
|
1449
|
+
type WhereInput = Readonly<Record<string, FilterScalar | FilterCondition | undefined>>;
|
|
1450
|
+
type SortDirection = 'asc' | 'desc';
|
|
1451
|
+
/** Single-field sort; ties always break by document id ascending. */
|
|
1452
|
+
interface SortInput {
|
|
1453
|
+
field: string;
|
|
1454
|
+
direction?: SortDirection;
|
|
1455
|
+
}
|
|
1456
|
+
/** The full `listDocuments` query envelope (vision AD-3, rung 3). */
|
|
1457
|
+
interface ListDocumentsQuery<TWhere extends WhereInput = WhereInput> {
|
|
1458
|
+
where?: TWhere;
|
|
1459
|
+
sort?: SortInput;
|
|
1460
|
+
limit?: number;
|
|
1461
|
+
cursor?: string;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Codes for structured accessor query errors (EC-143). These describe *caller*
|
|
1465
|
+
* mistakes (bad filter, bad cursor), as opposed to `CmsValidationIssue`s which
|
|
1466
|
+
* describe content/schema problems. Accessors never throw — invalid queries
|
|
1467
|
+
* come back as an error result carrying these.
|
|
1468
|
+
*/
|
|
1469
|
+
type ContentQueryErrorCode = 'unknown-collection' | 'list-not-supported' | 'non-filterable-field' | 'invalid-filter' | 'invalid-sort' | 'invalid-limit' | 'invalid-cursor';
|
|
1470
|
+
interface ContentQueryError {
|
|
1471
|
+
code: ContentQueryErrorCode;
|
|
1472
|
+
message: string;
|
|
1473
|
+
/** Path into the query input, e.g. ['where', 'title'] or ['sort', 'field']. */
|
|
1474
|
+
path: CmsPath;
|
|
1475
|
+
meta?: Record<string, unknown>;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* Cache-tag metadata carried by every accessor result (EC-143, vision AD-5):
|
|
1480
|
+
* fetch-time tagging of what was *actually returned*, so EC-146 can map each
|
|
1481
|
+
* entry to a `revalidateTag` call. Deterministic by construction — every list
|
|
1482
|
+
* is sorted and deduplicated.
|
|
1483
|
+
*/
|
|
1484
|
+
interface CacheTags {
|
|
1485
|
+
/**
|
|
1486
|
+
* Identities of every document whose content is present in the result —
|
|
1487
|
+
* the returned documents themselves plus their depth-1 populated relation
|
|
1488
|
+
* targets. Entries are `documentKey` strings (`collection:id`), sorted.
|
|
1489
|
+
*/
|
|
1490
|
+
docs: string[];
|
|
1491
|
+
/**
|
|
1492
|
+
* Collections the accessor read from (the coarse membership tags of AD-5:
|
|
1493
|
+
* create/delete/publish in a collection sweeps them). Sorted.
|
|
1494
|
+
*/
|
|
1495
|
+
collections: string[];
|
|
1496
|
+
/**
|
|
1497
|
+
* Normalized URL paths the accessor touched (`resolvePage` only): the
|
|
1498
|
+
* requested path, plus every hop of a redirect chain — editing any hop
|
|
1499
|
+
* changes the result. Sorted.
|
|
1500
|
+
*/
|
|
1501
|
+
routes: string[];
|
|
1502
|
+
/**
|
|
1503
|
+
* Asset ids the result *baked* its delivery shape from (EC-227): the page's
|
|
1504
|
+
* page-scoped `ResolvedAsset` set (url/dimensions/alt) + its `seoOgImage`.
|
|
1505
|
+
* Editing the asset record (alt/dimensions/re-upload) must drop the page that
|
|
1506
|
+
* baked it — without this dimension the only invalidation triggers are
|
|
1507
|
+
* unrelated (doc/collection/project), so an asset-only edit serves stale
|
|
1508
|
+
* until one of those fires. Sorted.
|
|
1509
|
+
*/
|
|
1510
|
+
assets: string[];
|
|
1511
|
+
/** The locale the result was resolved for. */
|
|
1512
|
+
locale: Locale;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* The closed-form v1 query surface (EC-143, vision AD-3): `resolvePage`,
|
|
1517
|
+
* `getDocument`, `listDocuments` — no query language. Every result is
|
|
1518
|
+
* delivery-shaped (EC-142) and carries deterministic `CacheTags` for EC-146.
|
|
1519
|
+
*
|
|
1520
|
+
* Pure and synchronous like the rest of the package: the client reads through
|
|
1521
|
+
* a `ContentLookup`; EC-144/EC-145 bind it to Convex/Next later. The ambient
|
|
1522
|
+
* `{ locale, perspective }` context is taken once at creation — never per-call
|
|
1523
|
+
* ceremony (per-call `locale` overrides exist for route-level needs only).
|
|
1524
|
+
*/
|
|
1525
|
+
interface ResolvePageResult {
|
|
1526
|
+
/** Route resolution status (EC-019 parity): ok | redirect | notFound. */
|
|
1527
|
+
status: ResolveStatus;
|
|
1528
|
+
/** The matched route record, when status is `ok`. */
|
|
1529
|
+
route: RouteRecord | null;
|
|
1530
|
+
/** Extracted dynamic params, e.g. `{ slug: 'hello-world' }`. */
|
|
1531
|
+
dynamicParams: Record<string, string>;
|
|
1532
|
+
/** True when route matching fell back to the default locale (EC-018). */
|
|
1533
|
+
localeFallback: boolean;
|
|
1534
|
+
/** Terminal redirect target + permanence, when status is `redirect`. */
|
|
1535
|
+
redirect: {
|
|
1536
|
+
target: string;
|
|
1537
|
+
permanent: boolean;
|
|
1538
|
+
} | null;
|
|
1539
|
+
/**
|
|
1540
|
+
* The document this URL resolves to, delivery-shaped (EC-142, EC-187). A
|
|
1541
|
+
* `page`-collection document (its `body` composition renders into a layout)
|
|
1542
|
+
* or a content-collection document (rendered by a dev route component). SEO
|
|
1543
|
+
* for a page is read from this document's flat `seo*` fields — there is no
|
|
1544
|
+
* separate page-graph `seo` block anymore.
|
|
1545
|
+
*/
|
|
1546
|
+
document: ResolvedDocument | null;
|
|
1547
|
+
issues: CmsValidationIssue[];
|
|
1548
|
+
tags: CacheTags;
|
|
1549
|
+
}
|
|
1550
|
+
interface GetDocumentResult<TDoc extends DeliveryDocument = ResolvedDocument> {
|
|
1551
|
+
/**
|
|
1552
|
+
* The delivery-shaped document, or `null` when not found / not visible in
|
|
1553
|
+
* the context perspective (correct behavior, not an error — see EC-142).
|
|
1554
|
+
*/
|
|
1555
|
+
document: TDoc | null;
|
|
1556
|
+
info: ResolutionInfo | null;
|
|
1557
|
+
issues: CmsValidationIssue[];
|
|
1558
|
+
tags: CacheTags;
|
|
1559
|
+
}
|
|
1560
|
+
type ListDocumentsResult<TDoc extends DeliveryDocument = ResolvedDocument> = {
|
|
1561
|
+
ok: true;
|
|
1562
|
+
documents: TDoc[];
|
|
1563
|
+
/** Cursor for the next page, `null` when this page exhausts the list. */
|
|
1564
|
+
nextCursor: string | null;
|
|
1565
|
+
issues: CmsValidationIssue[];
|
|
1566
|
+
tags: CacheTags;
|
|
1567
|
+
} | {
|
|
1568
|
+
/** The query itself was invalid (never thrown — structured errors). */
|
|
1569
|
+
ok: false;
|
|
1570
|
+
errors: ContentQueryError[];
|
|
1571
|
+
tags: CacheTags;
|
|
1572
|
+
};
|
|
1573
|
+
interface ContentClient {
|
|
1574
|
+
readonly context: ContentContext;
|
|
1575
|
+
/** Structural route issues from construction (conflicts, redirect loops). */
|
|
1576
|
+
readonly routerIssues: readonly CmsValidationIssue[];
|
|
1577
|
+
resolvePage(url: string, locale?: Locale): ResolvePageResult;
|
|
1578
|
+
getDocument<TDoc extends DeliveryDocument = ResolvedDocument>(collection: string, idOrSlug: string, locale?: Locale): GetDocumentResult<TDoc>;
|
|
1579
|
+
listDocuments<TDoc extends DeliveryDocument = ResolvedDocument, TWhere extends WhereInput = WhereInput>(collection: string, query?: ListDocumentsQuery<TWhere>): ListDocumentsResult<TDoc>;
|
|
1580
|
+
/**
|
|
1581
|
+
* Populate a relation reference to depth-1 delivery shape (EC-254), or `null`
|
|
1582
|
+
* when the target is missing or not visible in the active perspective. A
|
|
1583
|
+
* renderer host wires this as `RenderContext.resolveRelation` to populate the
|
|
1584
|
+
* COMPOSITION-NODE relation props of the nodes inside a page's `blocks` field
|
|
1585
|
+
* (e.g. a ProductGrid block's picked `product`s) — which `resolvePage` leaves
|
|
1586
|
+
* raw, since document resolution does not descend into a composition value.
|
|
1587
|
+
*/
|
|
1588
|
+
resolveRelation(ref: DocumentRef): ResolvedDocument | null;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/**
|
|
1592
|
+
* Binding payloads for the embedded runtime (EC-144): one payload value per
|
|
1593
|
+
* binding `sourceId`. The catch-all route helper provides `document` (the
|
|
1594
|
+
* route-bound resolved document) and `params` (dynamic route params) by
|
|
1595
|
+
* default; hosts add more through the route helper's `payloads` factory
|
|
1596
|
+
* (e.g. a `listDocuments` result for a posts repeater).
|
|
1597
|
+
*
|
|
1598
|
+
* This is a deliberately small, pure resolver — the embedded runtime never
|
|
1599
|
+
* imports `@elytracms/data-binding` (a builder-side package). Semantics mirror
|
|
1600
|
+
* EC-023 tokens: a binding token is a JSON Pointer (RFC 6901) into the
|
|
1601
|
+
* payload; a miss resolves to `undefined`, never a throw (the renderer turns
|
|
1602
|
+
* unresolvable bindings into visible fallback behavior, EC-015).
|
|
1603
|
+
*/
|
|
1604
|
+
type BindingPayloads = Record<string, unknown>;
|
|
1605
|
+
/**
|
|
1606
|
+
* Marker key of an {@link SourcePayloadError} payload. A plain string property
|
|
1607
|
+
* (not a Symbol) on purpose: the route helper's cached path JSON-serializes
|
|
1608
|
+
* payloads through `unstable_cache`, and the marker must survive that
|
|
1609
|
+
* round-trip.
|
|
1610
|
+
*/
|
|
1611
|
+
declare const SOURCE_PAYLOAD_ERROR_KEY = "__elytraSourcePayloadError";
|
|
1612
|
+
/**
|
|
1613
|
+
* Explicit issue payload for a source whose stored query was REJECTED at
|
|
1614
|
+
* delivery (EC-177 gap 3) — e.g. a sort/filter field that is no longer
|
|
1615
|
+
* filterable in the live schema. Materialization stores this instead of a
|
|
1616
|
+
* silently-absent payload; `createBindingResolver` throws on it, which the
|
|
1617
|
+
* renderer converts into its visible per-node render-error fallback (EC-015).
|
|
1618
|
+
* Validation principle: a broken stored query is a visible state, never an
|
|
1619
|
+
* empty section.
|
|
1620
|
+
*/
|
|
1621
|
+
interface SourcePayloadError {
|
|
1622
|
+
[SOURCE_PAYLOAD_ERROR_KEY]: true;
|
|
1623
|
+
sourceId: string;
|
|
1624
|
+
/** Human-readable messages of the structured query errors. */
|
|
1625
|
+
messages: string[];
|
|
1626
|
+
}
|
|
1627
|
+
/** Build the explicit issue payload for one failed source query. */
|
|
1628
|
+
declare function sourcePayloadError(sourceId: string, messages: readonly string[]): SourcePayloadError;
|
|
1629
|
+
/** `true` when a payload value is an explicit {@link SourcePayloadError}. */
|
|
1630
|
+
declare function isSourcePayloadError(value: unknown): value is SourcePayloadError;
|
|
1631
|
+
/**
|
|
1632
|
+
* Resolve a JSON Pointer token within a payload value. Numeric segments index
|
|
1633
|
+
* arrays; string segments index plain objects. Any miss (unknown key,
|
|
1634
|
+
* out-of-range index, traversal through a primitive) yields `undefined`.
|
|
1635
|
+
*/
|
|
1636
|
+
declare function resolvePayloadToken(payload: unknown, token: string): unknown;
|
|
1637
|
+
/**
|
|
1638
|
+
* Create the renderer's `ResolveBinding` over a payload map. Mode semantics
|
|
1639
|
+
* (brief §4.6, uniform binding model):
|
|
1640
|
+
*
|
|
1641
|
+
* - `value` / `object` / `condition` — the value at the token within the
|
|
1642
|
+
* payload named by `sourceId`.
|
|
1643
|
+
* - `spread` — same lookup; the renderer spreads the resulting object.
|
|
1644
|
+
* - `repeaterItem` — the token resolves against the active repeater item,
|
|
1645
|
+
* not the root payload.
|
|
1646
|
+
*
|
|
1647
|
+
* Total for data misses: an unknown `sourceId` or missing path resolves to
|
|
1648
|
+
* `undefined` — the renderer (EC-015) degrades visibly instead of crashing.
|
|
1649
|
+
* An explicit {@link SourcePayloadError} payload (a REJECTED stored query, not
|
|
1650
|
+
* merely missing data) throws instead: the renderer catches per node and
|
|
1651
|
+
* renders its visible render-error fallback with the query error message;
|
|
1652
|
+
* conditions treat the throw as `false` (the node hides) — never a page crash.
|
|
1653
|
+
*/
|
|
1654
|
+
declare function createBindingResolver(payloads: BindingPayloads): ResolveBinding;
|
|
1655
|
+
|
|
1656
|
+
/**
|
|
1657
|
+
* `<CanvasRenderer />` (EC-144, vision AD-1): an async React Server Component
|
|
1658
|
+
* that renders a resolved page (from `resolvePage`, EC-143) through
|
|
1659
|
+
* `@elytracms/runtime-renderer` with the host app's component registry. EC-015
|
|
1660
|
+
* fallback behavior is identical to the builder: a missing component, invalid
|
|
1661
|
+
* prop, or missing required slot renders a visible fallback — a page never
|
|
1662
|
+
* crashes and never silently omits a node.
|
|
1663
|
+
*/
|
|
1664
|
+
interface CanvasRendererProps {
|
|
1665
|
+
/** The `resolvePage` result for the current URL. */
|
|
1666
|
+
result: ResolvePageResult;
|
|
1667
|
+
/** Host component surface from `defineHostComponents`. */
|
|
1668
|
+
components: HostComponents;
|
|
1669
|
+
/**
|
|
1670
|
+
* The full project graph for the active perspective (from
|
|
1671
|
+
* `ContentSource.graph`). Needed for layout composition; without it the
|
|
1672
|
+
* page root renders without its layout.
|
|
1673
|
+
*/
|
|
1674
|
+
graph?: ProjectGraph | null;
|
|
1675
|
+
/**
|
|
1676
|
+
* Binding payloads keyed by `sourceId`. Defaults to
|
|
1677
|
+
* `{ document, params }` derived from the result.
|
|
1678
|
+
*/
|
|
1679
|
+
payloads?: BindingPayloads;
|
|
1680
|
+
/** Full custom binding resolver — overrides `payloads` when provided. */
|
|
1681
|
+
resolveBinding?: ResolveBinding;
|
|
1682
|
+
/**
|
|
1683
|
+
* Delivery-shaped assets the page may reference (EC-195, from the render
|
|
1684
|
+
* outcome). An `asset`-typed Image prop resolves through these to a url +
|
|
1685
|
+
* intrinsic dimensions; an unknown id degrades to the visible fallback.
|
|
1686
|
+
*/
|
|
1687
|
+
assets?: readonly ResolvedAsset[];
|
|
1688
|
+
/**
|
|
1689
|
+
* Populated composition-node `relation` props, keyed `collection:id` (EC-254,
|
|
1690
|
+
* from the render outcome). A block's `relation` prop (e.g. a ProductGrid's
|
|
1691
|
+
* picked products) resolves through these to the populated target; an unknown
|
|
1692
|
+
* key degrades to the block's own empty state. Absent → relation props pass
|
|
1693
|
+
* through unresolved (raw ref), the same degradation as a missing asset list.
|
|
1694
|
+
*/
|
|
1695
|
+
relations?: Record<string, RelationTarget>;
|
|
1696
|
+
/**
|
|
1697
|
+
* Documents each `listing` block resolved to, keyed by serialized query (EC-255,
|
|
1698
|
+
* from the render outcome). A ProductGrid keyed to a picked `category` reads its
|
|
1699
|
+
* products through this; an unknown key (or absent map) degrades to the block's
|
|
1700
|
+
* own empty state — the same degradation as a missing relation.
|
|
1701
|
+
*/
|
|
1702
|
+
listings?: Record<string, RelationTarget[]>;
|
|
1703
|
+
}
|
|
1704
|
+
/** Default payloads: the route-bound document and the dynamic route params. */
|
|
1705
|
+
declare function defaultBindingPayloads(result: ResolvePageResult): BindingPayloads;
|
|
1706
|
+
/**
|
|
1707
|
+
* Render a resolved page server-side. Async so hosts can compose it like any
|
|
1708
|
+
* other RSC; resolution itself is synchronous (the content was prefetched by
|
|
1709
|
+
* the `ContentSource`).
|
|
1710
|
+
*/
|
|
1711
|
+
declare function CanvasRenderer(props: CanvasRendererProps): Promise<ReactNode>;
|
|
1712
|
+
|
|
1713
|
+
/**
|
|
1714
|
+
* The content seam of the embedded runtime (EC-144, vision AD-1/AD-4): the
|
|
1715
|
+
* catch-all route helper consumes this interface, so where the content comes
|
|
1716
|
+
* from is swappable. EC-144 ships the in-memory snapshot bridge
|
|
1717
|
+
* (`createContentSnapshot` over a `PersistenceAdapter`); EC-145/146 plug a
|
|
1718
|
+
* Convex-backed source into the exact same seam.
|
|
1719
|
+
*
|
|
1720
|
+
* Accessors are synchronous over a prefetched `ContentLookup` (vision AD-4);
|
|
1721
|
+
* any fetching happens when the source is loaded, before clients are minted.
|
|
1722
|
+
*/
|
|
1723
|
+
/**
|
|
1724
|
+
* One data-source definition the published graph's bindings reference
|
|
1725
|
+
* (EC-166 delivery): the structural shape of an `@elytracms/data-binding`
|
|
1726
|
+
* `DataSource`, declared here so the embedded runtime stays free of
|
|
1727
|
+
* builder-side packages. Only `cms`-kind sources with the EC-166 per-scope id
|
|
1728
|
+
* prefixes (`cms.section.<nodeId>` / `cms.template.<pageId>`) participate in
|
|
1729
|
+
* automatic payload materialization; everything else is ignored.
|
|
1730
|
+
*/
|
|
1731
|
+
interface CanvasDataSource {
|
|
1732
|
+
kind: string;
|
|
1733
|
+
id: string;
|
|
1734
|
+
label?: string;
|
|
1735
|
+
/** Kind-specific config; cms query configs parse via `@elytracms/content`. */
|
|
1736
|
+
config?: unknown;
|
|
1737
|
+
}
|
|
1738
|
+
interface ContentSource {
|
|
1739
|
+
/** The project's locale configuration (default + supported set, EC-018). */
|
|
1740
|
+
readonly locales: LocaleConfig;
|
|
1741
|
+
/**
|
|
1742
|
+
* Mint a typed accessor client (EC-143) bound to a perspective. The locale
|
|
1743
|
+
* defaults to the project default; the route helper overrides it per
|
|
1744
|
+
* request from the URL's locale prefix.
|
|
1745
|
+
*/
|
|
1746
|
+
client(init: {
|
|
1747
|
+
perspective: Perspective;
|
|
1748
|
+
locale?: Locale;
|
|
1749
|
+
}): ContentClient;
|
|
1750
|
+
/**
|
|
1751
|
+
* The full project graph visible in a perspective — `draft` is the latest
|
|
1752
|
+
* working revision, `published` the revision pinned by publishing state.
|
|
1753
|
+
* `null` when nothing is visible (e.g. never published). The renderer needs
|
|
1754
|
+
* the graph (not just the page) for layout composition.
|
|
1755
|
+
*/
|
|
1756
|
+
graph(perspective: Perspective): ProjectGraph | null;
|
|
1757
|
+
/**
|
|
1758
|
+
* The route records the source's clients resolve against (EC-171, additive).
|
|
1759
|
+
* Lets enumerating consumers — the sitemap helper — walk the same route
|
|
1760
|
+
* table `resolvePage` uses, instead of keeping a second list. Optional so
|
|
1761
|
+
* existing custom sources stay valid; without it the sitemap helper needs
|
|
1762
|
+
* explicit routes.
|
|
1763
|
+
*/
|
|
1764
|
+
readonly routes?: readonly RouteRecord[];
|
|
1765
|
+
/**
|
|
1766
|
+
* Data-source definitions the graph's bindings reference (EC-166 delivery,
|
|
1767
|
+
* additive). When present, the route helper materializes per-section list
|
|
1768
|
+
* sources automatically by replaying their stored where/sort/limit through
|
|
1769
|
+
* `listDocuments` — see `materializeSourcePayloads`. Optional so existing
|
|
1770
|
+
* custom sources stay valid; absent definitions degrade to unresolved
|
|
1771
|
+
* bindings (the renderer's visible fallbacks), never a crash.
|
|
1772
|
+
*/
|
|
1773
|
+
readonly sources?: readonly CanvasDataSource[];
|
|
1774
|
+
/**
|
|
1775
|
+
* The project's delivery-shaped assets (EC-195, additive). Lets the route
|
|
1776
|
+
* helper bake the assets a page may reference into the (serializable) render
|
|
1777
|
+
* outcome, so the renderer can resolve an `asset`-typed Image prop to a usable
|
|
1778
|
+
* url + intrinsic dimensions — and a cached page keeps its images during a
|
|
1779
|
+
* backend outage. Optional so existing custom sources stay valid; without it,
|
|
1780
|
+
* asset-typed props fall back to their raw value (the renderer's visible
|
|
1781
|
+
* degradation), never a crash.
|
|
1782
|
+
*/
|
|
1783
|
+
assets?(): readonly ResolvedAsset[];
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Merge accessor cache tags into one deterministic set (sorted, deduplicated)
|
|
1787
|
+
* — used by the route helper to combine the `resolvePage` tags with tags
|
|
1788
|
+
* added by the host's payload factory (e.g. a `listDocuments` call). The
|
|
1789
|
+
* merged set is what the EC-146 `onTags` hook receives.
|
|
1790
|
+
*/
|
|
1791
|
+
declare function mergeCacheTags(first: CacheTags, ...rest: readonly CacheTags[]): CacheTags;
|
|
1792
|
+
|
|
1793
|
+
/**
|
|
1794
|
+
* Automatic payload materialization for EC-166 repeating sections — the
|
|
1795
|
+
* delivery half of the builder's collection-driven LAYOUTS (EC-187: layouts
|
|
1796
|
+
* are the only graph trees that can bind section sources now). A published
|
|
1797
|
+
* layout may carry bindings against per-scope CMS sources; the renderer needs
|
|
1798
|
+
* the source PAYLOADS:
|
|
1799
|
+
*
|
|
1800
|
+
* - **Repeating sections** (`cms.section.<nodeId>`): the stored source config
|
|
1801
|
+
* carries the closed AD-3 query. For every section source a referenced
|
|
1802
|
+
* layout tree actually binds, the stored where/sort/limit replays through
|
|
1803
|
+
* the request client's `listDocuments` — published perspective and ambient
|
|
1804
|
+
* locale come from the client itself. The list result's `CacheTags` (the
|
|
1805
|
+
* fetch-time doc tags of what was actually returned plus the coarse
|
|
1806
|
+
* collection tag — the EC-146 contract) are surfaced so the route helper
|
|
1807
|
+
* merges them into the request tag set.
|
|
1808
|
+
*
|
|
1809
|
+
* The `cms.template.<pageId>` "current document" source is RETIRED (EC-187):
|
|
1810
|
+
* there is no template page, and a routed page document's `body` uses static
|
|
1811
|
+
* props (v1). A collection detail route's document is read by the dev route
|
|
1812
|
+
* component directly via the route's `document` ref.
|
|
1813
|
+
*
|
|
1814
|
+
* Everything degrades visibly, never a crash:
|
|
1815
|
+
*
|
|
1816
|
+
* - a missing source definition, a non-cms kind, or a malformed config leaves
|
|
1817
|
+
* the payload absent, so the binding resolves to `undefined` and the
|
|
1818
|
+
* renderer shows its EC-015 fallback (an empty list renders the section's
|
|
1819
|
+
* empty state);
|
|
1820
|
+
* - a structured query error (the stored query was REJECTED at delivery, e.g.
|
|
1821
|
+
* a sort field no longer filterable in the live schema) stores an explicit
|
|
1822
|
+
* {@link sourcePayloadError} payload instead — the binding resolver throws
|
|
1823
|
+
* on it and the renderer shows its visible per-node render-error fallback,
|
|
1824
|
+
* not a silently empty section (EC-177 gap 3).
|
|
1825
|
+
*/
|
|
1826
|
+
/** Collect every binding `sourceId` referenced in a node tree (props, conditions, slots). */
|
|
1827
|
+
declare function collectBindingSourceIds(node: ComponentNode, into?: Set<string>): Set<string>;
|
|
1828
|
+
interface MaterializeSourcePayloadsInput {
|
|
1829
|
+
/** Accessor client bound to this request's perspective + locale. */
|
|
1830
|
+
client: ContentClient;
|
|
1831
|
+
result: ResolvePageResult;
|
|
1832
|
+
/** The graph for the active perspective (layout trees may bind sections too). */
|
|
1833
|
+
graph: ProjectGraph | null;
|
|
1834
|
+
/** Source definitions from the `ContentSource` (absent ones simply skip). */
|
|
1835
|
+
sources: readonly CanvasDataSource[];
|
|
1836
|
+
}
|
|
1837
|
+
interface MaterializedSourcePayloads {
|
|
1838
|
+
payloads: BindingPayloads;
|
|
1839
|
+
/** `CacheTags` of every accessor call made here (one entry per section list). */
|
|
1840
|
+
tags: CacheTags[];
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Materialize the EC-166 source payloads for one resolved page. Pure over the
|
|
1844
|
+
* prefetched source — `listDocuments` is synchronous; tags are returned (not
|
|
1845
|
+
* applied) so the caller owns the merge.
|
|
1846
|
+
*/
|
|
1847
|
+
declare function materializeSourcePayloads(input: MaterializeSourcePayloadsInput): MaterializedSourcePayloads;
|
|
1848
|
+
|
|
1849
|
+
interface ContentSnapshotOptions {
|
|
1850
|
+
/** Route records served by `resolvePage` (EC-019). */
|
|
1851
|
+
routes?: readonly RouteRecord[];
|
|
1852
|
+
/** Redirect records served by `resolvePage` (EC-019/020). */
|
|
1853
|
+
redirects?: readonly RedirectRecord[];
|
|
1854
|
+
/** Locale configuration; defaults to English-only. */
|
|
1855
|
+
locales?: LocaleConfig;
|
|
1856
|
+
/**
|
|
1857
|
+
* Data-source definitions the graph's bindings reference (EC-166): section
|
|
1858
|
+
* sources (`cms.section.<nodeId>`) carry their stored where/sort/limit, and
|
|
1859
|
+
* the route helper materializes their payloads automatically.
|
|
1860
|
+
*/
|
|
1861
|
+
sources?: readonly CanvasDataSource[];
|
|
1862
|
+
}
|
|
1863
|
+
interface ContentSnapshot extends ContentSource {
|
|
1864
|
+
readonly projectId: string;
|
|
1865
|
+
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Plain prefetched data for {@link createStaticContentSource} (EC-156): the
|
|
1868
|
+
* same inputs `createContentSnapshot` reads from a `PersistenceAdapter`, as
|
|
1869
|
+
* already-loaded values. Everything content-bearing is optional — an absent
|
|
1870
|
+
* piece simply resolves to "not visible", with the runtime's explicit
|
|
1871
|
+
* fallbacks (never a crash).
|
|
1872
|
+
*/
|
|
1873
|
+
interface StaticContentData {
|
|
1874
|
+
projectId: string;
|
|
1875
|
+
/** Locale configuration; defaults to English-only. */
|
|
1876
|
+
locales?: LocaleConfig;
|
|
1877
|
+
/** Route records served by `resolvePage` (EC-019). */
|
|
1878
|
+
routes?: readonly RouteRecord[];
|
|
1879
|
+
/** Redirect records served by `resolvePage` (EC-019/020). */
|
|
1880
|
+
redirects?: readonly RedirectRecord[];
|
|
1881
|
+
collections?: readonly CollectionDef[];
|
|
1882
|
+
/**
|
|
1883
|
+
* Stored document sources. Either a bare `CmsDocument` (gated by its own
|
|
1884
|
+
* `state`) or a `DocumentHistory` carrying the live draft + the pinned
|
|
1885
|
+
* published snapshot (EC-224). Perspective gating happens in the pure
|
|
1886
|
+
* accessors (`selectPerspective`, EC-142): a `published` client sees a
|
|
1887
|
+
* history's pinned snapshot (or nothing), and never a bare `draft` row.
|
|
1888
|
+
*/
|
|
1889
|
+
documents?: readonly ContentDocumentSource[];
|
|
1890
|
+
assets?: readonly AssetRecord[];
|
|
1891
|
+
/**
|
|
1892
|
+
* The graph visible per perspective: `draft` is the latest working
|
|
1893
|
+
* revision, `published` the revision pinned by publishing state. `null` /
|
|
1894
|
+
* absent means "nothing visible in that perspective".
|
|
1895
|
+
*/
|
|
1896
|
+
graphs?: {
|
|
1897
|
+
draft?: ProjectGraph | null;
|
|
1898
|
+
published?: ProjectGraph | null;
|
|
1899
|
+
};
|
|
1900
|
+
/**
|
|
1901
|
+
* Resolve a serve URL for assets without a stored `url`. Defaults to an
|
|
1902
|
+
* explicit unresolved-placeholder scheme (never a silent empty string).
|
|
1903
|
+
*/
|
|
1904
|
+
assetUrl?: (asset: AssetRecord) => string;
|
|
1905
|
+
/**
|
|
1906
|
+
* Data-source definitions the graph's bindings reference (EC-166 delivery).
|
|
1907
|
+
* Absent definitions degrade to unresolved bindings, never a crash.
|
|
1908
|
+
*/
|
|
1909
|
+
sources?: readonly CanvasDataSource[];
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Merge stored route records with derived fallback routes (EC-177 gap 2):
|
|
1913
|
+
* stored records win per pattern+locale; fallback routes only fill patterns
|
|
1914
|
+
* the stored set does not cover. Never XOR — the first stored route must not
|
|
1915
|
+
* drop the implicit per-locale home-page fallback (or vice versa).
|
|
1916
|
+
*
|
|
1917
|
+
* Coverage rules mirror the router's locale matching (EC-018/019): a stored
|
|
1918
|
+
* locale-agnostic route (no `locale`) covers its pattern for every locale; a
|
|
1919
|
+
* locale-specific stored route covers only that locale, so fallbacks for the
|
|
1920
|
+
* other locales still apply.
|
|
1921
|
+
*/
|
|
1922
|
+
declare function mergeRouteRecords(stored: readonly RouteRecord[], fallback: readonly RouteRecord[]): RouteRecord[];
|
|
1923
|
+
/**
|
|
1924
|
+
* Assemble a `ContentSource` from plain prefetched data — the pure half of
|
|
1925
|
+
* {@link createContentSnapshot}, shared with hosts that fetched the data
|
|
1926
|
+
* themselves (e.g. the EC-156 live delivery snapshot). Accessor semantics are
|
|
1927
|
+
* byte-identical across both paths: the same `ContentLookup` feeds the same
|
|
1928
|
+
* pure `@elytracms/content` client.
|
|
1929
|
+
*/
|
|
1930
|
+
declare function createStaticContentSource(data: StaticContentData): ContentSnapshot;
|
|
1931
|
+
/**
|
|
1932
|
+
* Prefetch a project's content from a `PersistenceAdapter` into an in-memory
|
|
1933
|
+
* `ContentSource`. The snapshot is a point-in-time view: load once per
|
|
1934
|
+
* request (or memoize across requests for fixture-backed sites) — accessors
|
|
1935
|
+
* never reach back to the adapter.
|
|
1936
|
+
*/
|
|
1937
|
+
declare function createContentSnapshot(adapter: PersistenceAdapter, projectId: string, options?: ContentSnapshotOptions): Promise<ContentSnapshot>;
|
|
1938
|
+
|
|
1939
|
+
/**
|
|
1940
|
+
* The pure half of the catch-all route helper (EC-144): URL → outcome, with
|
|
1941
|
+
* no Next.js imports — so redirects, 404s, locale handling, payload assembly,
|
|
1942
|
+
* and tag merging are all unit-testable as plain functions. `route.tsx` maps
|
|
1943
|
+
* each outcome onto `redirect()` / `permanentRedirect()` / `notFound()` /
|
|
1944
|
+
* `<CanvasRenderer />`.
|
|
1945
|
+
*/
|
|
1946
|
+
/** What the host's payload factory sees for one request. */
|
|
1947
|
+
interface CanvasPayloadContext {
|
|
1948
|
+
/** Accessor client bound to this request's perspective + locale. */
|
|
1949
|
+
client: ContentClient;
|
|
1950
|
+
result: ResolvePageResult;
|
|
1951
|
+
perspective: Perspective;
|
|
1952
|
+
/** The normalized request path (locale prefix stripped). */
|
|
1953
|
+
path: string;
|
|
1954
|
+
params: Record<string, string>;
|
|
1955
|
+
/**
|
|
1956
|
+
* Register the `CacheTags` of any extra accessor call this factory makes
|
|
1957
|
+
* (e.g. `listDocuments`) so they join the request's merged tag set — the
|
|
1958
|
+
* EC-146 invalidation hook sees everything the request actually read.
|
|
1959
|
+
*/
|
|
1960
|
+
addTags(tags: CacheTags): void;
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Builds the binding payloads for a page render. The defaults
|
|
1964
|
+
* (`document`, `params`) are always present; factory entries win on conflict.
|
|
1965
|
+
*/
|
|
1966
|
+
type CanvasPayloadsFactory = (ctx: CanvasPayloadContext) => BindingPayloads | Promise<BindingPayloads>;
|
|
1967
|
+
type CanvasPageOutcome = {
|
|
1968
|
+
kind: 'redirect';
|
|
1969
|
+
target: string;
|
|
1970
|
+
permanent: boolean;
|
|
1971
|
+
tags: CacheTags;
|
|
1972
|
+
} | {
|
|
1973
|
+
kind: 'not-found';
|
|
1974
|
+
tags: CacheTags;
|
|
1975
|
+
} | {
|
|
1976
|
+
kind: 'render';
|
|
1977
|
+
result: ResolvePageResult;
|
|
1978
|
+
graph: ProjectGraph | null;
|
|
1979
|
+
payloads: BindingPayloads;
|
|
1980
|
+
/**
|
|
1981
|
+
* The delivery-shaped assets the page may reference (EC-195). Baked into
|
|
1982
|
+
* the outcome (serializable) so the renderer resolves `asset`-typed Image
|
|
1983
|
+
* props to a url + intrinsic dimensions, and a cached page keeps its
|
|
1984
|
+
* images during a backend outage.
|
|
1985
|
+
*/
|
|
1986
|
+
assets: readonly ResolvedAsset[];
|
|
1987
|
+
/**
|
|
1988
|
+
* The composition-node `relation` props this page references, populated to
|
|
1989
|
+
* depth-1 delivery shape and keyed `collection:id` (EC-254). Baked into the
|
|
1990
|
+
* outcome (serializable) so `<CanvasRenderer />` rebuilds
|
|
1991
|
+
* `RenderContext.resolveRelation` from it inside the RSC — a function (the
|
|
1992
|
+
* content client) cannot ride the data cache or cross the RSC boundary, so
|
|
1993
|
+
* the data is baked exactly as the page-scoped `assets` list is.
|
|
1994
|
+
*/
|
|
1995
|
+
relations: Record<string, RelationTarget>;
|
|
1996
|
+
/**
|
|
1997
|
+
* The documents each `listing` block on this page resolved to (EC-255),
|
|
1998
|
+
* keyed by serialized query. Baked into the outcome (serializable) so
|
|
1999
|
+
* `<CanvasRenderer />` rebuilds `RenderContext.resolveListing` from it inside
|
|
2000
|
+
* the RSC — like `relations`, because the content client that runs the query
|
|
2001
|
+
* cannot ride the data cache or cross the RSC boundary. A listing depends on
|
|
2002
|
+
* collection MEMBERSHIP (a product joining/leaving the category), so its
|
|
2003
|
+
* invalidation rides the per-query `listDocuments` tags merged into `tags`.
|
|
2004
|
+
*/
|
|
2005
|
+
listings: Record<string, RelationTarget[]>;
|
|
2006
|
+
tags: CacheTags;
|
|
2007
|
+
};
|
|
2008
|
+
/** Build the request path from a Next catch-all `slug` param. */
|
|
2009
|
+
declare function pathFromSlug(slug: readonly string[] | undefined): string;
|
|
2010
|
+
/**
|
|
2011
|
+
* Derive the request locale from a leading URL segment (`/de/about` → locale
|
|
2012
|
+
* `de`, path `/about`) and return the remaining path. No prefix (or the
|
|
2013
|
+
* default locale's own content at root) falls back to the default locale.
|
|
2014
|
+
*/
|
|
2015
|
+
declare function splitLocalePath(url: string, locales: LocaleConfig): {
|
|
2016
|
+
locale: Locale;
|
|
2017
|
+
path: string;
|
|
2018
|
+
};
|
|
2019
|
+
interface ResolveCanvasPageOptions {
|
|
2020
|
+
payloads?: CanvasPayloadsFactory;
|
|
2021
|
+
/**
|
|
2022
|
+
* Component registry (EC-205): when provided, the baked {@link CanvasPageOutcome}
|
|
2023
|
+
* `assets` are PAGE-SCOPED to exactly the asset ids this page's composition +
|
|
2024
|
+
* layout resolve through `resolveAsset` — not the whole project asset list,
|
|
2025
|
+
* which otherwise rides in every page's cache entry + RSC payload. Omitted →
|
|
2026
|
+
* all source assets (back-compat). Scoping mirrors the renderer's asset
|
|
2027
|
+
* resolution exactly, so no referenced image is ever dropped.
|
|
2028
|
+
*/
|
|
2029
|
+
registry?: ComponentRegistry;
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Resolve one request URL against a content source: split the locale, run
|
|
2033
|
+
* `resolvePage` in the requested perspective, assemble binding payloads, and
|
|
2034
|
+
* merge every accessor's `CacheTags` into one deterministic set. Pure — all
|
|
2035
|
+
* data was prefetched by the source.
|
|
2036
|
+
*/
|
|
2037
|
+
declare function resolveCanvasPage(source: ContentSource, url: string, perspective: Perspective, options?: ResolveCanvasPageOptions): Promise<CanvasPageOutcome>;
|
|
2038
|
+
|
|
2039
|
+
/**
|
|
2040
|
+
* Catch-all route helper (EC-144): the factory a host app calls once in
|
|
2041
|
+
* `app/[[...slug]]/page.tsx` to wire `resolvePage` → `<CanvasRenderer />`,
|
|
2042
|
+
* with redirects answered by Next's `redirect()`/`permanentRedirect()` (per
|
|
2043
|
+
* the route's `permanent` flag), unknown URLs answered by `notFound()` (the
|
|
2044
|
+
* host's 404 renders), and the perspective chosen by Next `draftMode()` —
|
|
2045
|
+
* draft when enabled (via the token-gated preview route), published
|
|
2046
|
+
* otherwise.
|
|
2047
|
+
*
|
|
2048
|
+
* With `cache` configured (EC-146), the published-perspective resolve+render
|
|
2049
|
+
* data work runs inside `unstable_cache`, keyed by URL and tagged with the
|
|
2050
|
+
* fetch-time tag set of what the response actually contained (see
|
|
2051
|
+
* `cache.ts`). Publishing in the studio then invalidates exactly the affected
|
|
2052
|
+
* entries via the signed `/api/revalidate` webhook — and while the content
|
|
2053
|
+
* backend is unreachable, cached entries keep serving because a cache hit
|
|
2054
|
+
* never invokes `loadContent`.
|
|
2055
|
+
*/
|
|
2056
|
+
interface CanvasRequestInfo {
|
|
2057
|
+
/** The raw request path (including any locale prefix). */
|
|
2058
|
+
url: string;
|
|
2059
|
+
perspective: Perspective;
|
|
2060
|
+
}
|
|
2061
|
+
interface CanvasCacheOptions {
|
|
2062
|
+
/**
|
|
2063
|
+
* Project scope prefix for every tag (`p:<scope>:…`) — must be the project
|
|
2064
|
+
* id the studio publishes under, so the webhook dispatcher's tags match.
|
|
2065
|
+
* Omit only when the emitter also emits unscoped tags (see `cache.ts`).
|
|
2066
|
+
*/
|
|
2067
|
+
scope?: string;
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* What the route helper tells the content loader about the request it is
|
|
2071
|
+
* loading for (EC-156). Loaders that serve both perspectives from one
|
|
2072
|
+
* prefetched source (the fixture snapshot) can ignore it; a live loader uses
|
|
2073
|
+
* it to fetch the draft-perspective snapshot only for draft-mode requests —
|
|
2074
|
+
* it must NOT call request APIs like `draftMode()` itself, because on the
|
|
2075
|
+
* cached published path the loader runs inside `unstable_cache`, where
|
|
2076
|
+
* dynamic request APIs are unavailable.
|
|
2077
|
+
*/
|
|
2078
|
+
interface CanvasContentRequest {
|
|
2079
|
+
perspective: Perspective;
|
|
2080
|
+
}
|
|
2081
|
+
interface CanvasRouteOptions {
|
|
2082
|
+
/**
|
|
2083
|
+
* Async content loader — the seam EC-145/146 plug Convex into. For the
|
|
2084
|
+
* fixture-backed example this memoizes a `createContentSnapshot` call.
|
|
2085
|
+
* Invoked per request; cache/memoize inside the loader as appropriate.
|
|
2086
|
+
* With `cache` configured it only runs on a cache miss (once per
|
|
2087
|
+
* revalidation cycle) — cache hits never touch the content backend.
|
|
2088
|
+
* Receives the request's perspective (see {@link CanvasContentRequest});
|
|
2089
|
+
* zero-arg loaders remain valid.
|
|
2090
|
+
*/
|
|
2091
|
+
loadContent: (request: CanvasContentRequest) => Promise<ContentSource>;
|
|
2092
|
+
/** Host component surface from `defineHostComponents`. */
|
|
2093
|
+
components: HostComponents;
|
|
2094
|
+
/** Extra binding payloads per request (e.g. `listDocuments` for a list page). */
|
|
2095
|
+
payloads?: CanvasPayloadsFactory;
|
|
2096
|
+
/**
|
|
2097
|
+
* EC-146 instant publish: cache the published-perspective resolution in
|
|
2098
|
+
* Next's data cache (`unstable_cache`), tagged with the response's own
|
|
2099
|
+
* fetch-time tags, invalidated by the `/api/revalidate` webhook. Draft mode
|
|
2100
|
+
* always bypasses this cache entirely. Tag-driven only — no time-based
|
|
2101
|
+
* revalidation (the AD-5 model). Cached values are JSON-serialized, so
|
|
2102
|
+
* payload factories must return plain data on the cached path.
|
|
2103
|
+
*/
|
|
2104
|
+
cache?: CanvasCacheOptions;
|
|
2105
|
+
/**
|
|
2106
|
+
* EC-146 hook: receives the merged `CacheTags` of everything this request
|
|
2107
|
+
* read (the `resolvePage` tags plus any tags the payload factory
|
|
2108
|
+
* registered via `addTags`). On a cache hit these are the stored tags of
|
|
2109
|
+
* the run that wrote the entry.
|
|
2110
|
+
*/
|
|
2111
|
+
onTags?: (tags: CacheTags, info: CanvasRequestInfo) => void;
|
|
2112
|
+
/** Override the generated `<head>` metadata for a resolved page. */
|
|
2113
|
+
metadata?: (result: ResolvePageResult, info: CanvasRequestInfo) => Metadata;
|
|
2114
|
+
}
|
|
2115
|
+
interface CanvasRouteProps {
|
|
2116
|
+
params: Promise<{
|
|
2117
|
+
slug?: string[];
|
|
2118
|
+
}>;
|
|
2119
|
+
}
|
|
2120
|
+
interface CanvasRoute {
|
|
2121
|
+
/** The page component: `export default route.Page`. */
|
|
2122
|
+
Page: (props: CanvasRouteProps) => Promise<ReactNode>;
|
|
2123
|
+
/** Next metadata hook: `export const generateMetadata = route.generateMetadata`. */
|
|
2124
|
+
generateMetadata: (props: CanvasRouteProps) => Promise<Metadata>;
|
|
2125
|
+
}
|
|
2126
|
+
/** Create the `{ Page, generateMetadata }` pair for `app/[[...slug]]/page.tsx`. */
|
|
2127
|
+
declare function createCanvasRoute(options: CanvasRouteOptions): CanvasRoute;
|
|
2128
|
+
|
|
2129
|
+
declare function canvasPageMetadata(result: ResolvePageResult, assets?: readonly ResolvedAsset[]): Metadata;
|
|
2130
|
+
|
|
2131
|
+
/**
|
|
2132
|
+
* `sitemap.xml` from published routes (EC-171): walk the same route records
|
|
2133
|
+
* `resolvePage` resolves against, resolve every candidate URL in the
|
|
2134
|
+
* **published** perspective, and emit `MetadataRoute.Sitemap` entries per
|
|
2135
|
+
* locale — Next's `app/sitemap.ts` convention. Pages marked `noindex` (and
|
|
2136
|
+
* URLs that do not resolve, e.g. a page that was never published) are
|
|
2137
|
+
* omitted.
|
|
2138
|
+
*
|
|
2139
|
+
* Dynamic routes (`/blog/:slug`): a route record does not declare which
|
|
2140
|
+
* collection feeds its params, so enumeration is delegated to the host's
|
|
2141
|
+
* {@link SitemapParamsProvider} — it gets a published-perspective accessor
|
|
2142
|
+
* client and returns one param record per concrete URL (typically from
|
|
2143
|
+
* `listDocuments`). **Honest limit:** parameterized routes without a provider
|
|
2144
|
+
* (or provider entries missing a param) are skipped and reported in
|
|
2145
|
+
* `skipped`, never guessed. `lastModified` is omitted — the delivery shape
|
|
2146
|
+
* carries no per-page timestamps today.
|
|
2147
|
+
*
|
|
2148
|
+
* Hierarchy mounts (`/:path*`, EC-218): a page-tree mount self-describes its
|
|
2149
|
+
* URLs, so it is enumerated automatically from the collection's published
|
|
2150
|
+
* documents (composing each page's nested path from its parent chain) — no
|
|
2151
|
+
* params provider entry needed.
|
|
2152
|
+
*
|
|
2153
|
+
* Host usage (`app/sitemap.ts`):
|
|
2154
|
+
*
|
|
2155
|
+
* ```ts
|
|
2156
|
+
* import { createCanvasSitemap } from '@elytracms/next'
|
|
2157
|
+
* import { loadContent } from '../lib/content'
|
|
2158
|
+
*
|
|
2159
|
+
* export default createCanvasSitemap({
|
|
2160
|
+
* loadContent,
|
|
2161
|
+
* baseUrl: 'https://example.com',
|
|
2162
|
+
* params: ({ route, client }) =>
|
|
2163
|
+
* route.id === 'r-post'
|
|
2164
|
+
* ? (client.listDocuments('post').ok ? … : []) // slug per published post
|
|
2165
|
+
* : null,
|
|
2166
|
+
* })
|
|
2167
|
+
* ```
|
|
2168
|
+
*/
|
|
2169
|
+
interface SitemapRouteContext {
|
|
2170
|
+
route: RouteRecord;
|
|
2171
|
+
locale: Locale;
|
|
2172
|
+
/** Published-perspective accessor client bound to `locale`. */
|
|
2173
|
+
client: ContentClient;
|
|
2174
|
+
}
|
|
2175
|
+
/**
|
|
2176
|
+
* Enumerate the param sets of one dynamic route — one record per concrete
|
|
2177
|
+
* URL (e.g. `[{ slug: 'hello-world' }, …]`). Return `null`/`undefined` to
|
|
2178
|
+
* skip the route (reported in `skipped`).
|
|
2179
|
+
*/
|
|
2180
|
+
type SitemapParamsProvider = (ctx: SitemapRouteContext) => readonly Record<string, string>[] | null | undefined;
|
|
2181
|
+
interface CanvasSitemapOptions {
|
|
2182
|
+
/** Absolute site origin, e.g. `https://example.com` (no trailing slash needed). */
|
|
2183
|
+
baseUrl: string;
|
|
2184
|
+
/** Route records to enumerate; defaults to the source's own route table. */
|
|
2185
|
+
routes?: readonly RouteRecord[];
|
|
2186
|
+
/** Param enumeration for dynamic routes (see {@link SitemapParamsProvider}). */
|
|
2187
|
+
params?: SitemapParamsProvider;
|
|
2188
|
+
}
|
|
2189
|
+
interface SitemapSkippedRoute {
|
|
2190
|
+
routeId: string;
|
|
2191
|
+
pattern: string;
|
|
2192
|
+
reason: string;
|
|
2193
|
+
}
|
|
2194
|
+
interface CanvasSitemapResult {
|
|
2195
|
+
entries: MetadataRoute.Sitemap;
|
|
2196
|
+
/** Routes that could not be enumerated — explicit, never silent. */
|
|
2197
|
+
skipped: SitemapSkippedRoute[];
|
|
2198
|
+
}
|
|
2199
|
+
/**
|
|
2200
|
+
* Enumerate the sitemap entries of one content source (published
|
|
2201
|
+
* perspective). Pure over the prefetched source — synchronous accessors,
|
|
2202
|
+
* deterministic ordering (sorted by URL).
|
|
2203
|
+
*/
|
|
2204
|
+
declare function canvasSitemapEntries(source: ContentSource, options: CanvasSitemapOptions): CanvasSitemapResult;
|
|
2205
|
+
interface CreateCanvasSitemapOptions extends CanvasSitemapOptions {
|
|
2206
|
+
/** The same content loader the catch-all route uses (published perspective). */
|
|
2207
|
+
loadContent: (request: {
|
|
2208
|
+
perspective: 'published';
|
|
2209
|
+
}) => Promise<ContentSource>;
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* The `app/sitemap.ts` one-liner: `export default createCanvasSitemap({ … })`.
|
|
2213
|
+
* Loads the published content source and returns the enumerated entries
|
|
2214
|
+
* (skipped routes are dropped here — use {@link canvasSitemapEntries} to
|
|
2215
|
+
* inspect them).
|
|
2216
|
+
*/
|
|
2217
|
+
declare function createCanvasSitemap(options: CreateCanvasSitemapOptions): () => Promise<MetadataRoute.Sitemap>;
|
|
2218
|
+
|
|
2219
|
+
/**
|
|
2220
|
+
* Cache-tag pipeline (EC-146, vision AD-5) — the pure half.
|
|
2221
|
+
*
|
|
2222
|
+
* This module defines (a) the **canonical Next.js tag string format** mapping
|
|
2223
|
+
* the accessor-level `CacheTags` (EC-143) onto `revalidateTag` keys, and (b)
|
|
2224
|
+
* the **tag-discovery caching logic** the catch-all route helper runs inside
|
|
2225
|
+
* `unstable_cache`. No Next.js imports — everything here is unit-testable;
|
|
2226
|
+
* `route.tsx` injects the real `unstable_cache` via {@link CacheWrapper}.
|
|
2227
|
+
*
|
|
2228
|
+
* ## Tag format
|
|
2229
|
+
*
|
|
2230
|
+
* - `doc:<collection>:<id>` — one per document whose content was actually in
|
|
2231
|
+
* the response (fetch-time tagging: includes depth-1 populated relation
|
|
2232
|
+
* targets, and documents inside filtered/dynamic lists *after* the filter
|
|
2233
|
+
* ran).
|
|
2234
|
+
* - `collection:<name>:<locale>` — the coarse membership tag: every response
|
|
2235
|
+
* that read from a collection carries it, so create/delete/newly-matching
|
|
2236
|
+
* documents sweep cached lists without read-set tracking.
|
|
2237
|
+
* - `route:<pattern>:<locale>` — the resolved URL path plus every hop of a
|
|
2238
|
+
* redirect chain.
|
|
2239
|
+
* - `asset:<id>` — one per asset whose delivery shape the page *baked*
|
|
2240
|
+
* (page-scoped images + `seoOgImage`, EC-227); editing that asset record
|
|
2241
|
+
* (alt/dimensions/re-upload) drops exactly the pages that baked it. Not
|
|
2242
|
+
* locale-scoped — an asset record is locale-agnostic.
|
|
2243
|
+
* - `project` — the whole-project sweep tag attached to **every** cached
|
|
2244
|
+
* entry; graph publish/unpublish emits it (a layout or route change can
|
|
2245
|
+
* affect any page, so the honest v1 fallback is a full project sweep).
|
|
2246
|
+
*
|
|
2247
|
+
* ## Project scope
|
|
2248
|
+
*
|
|
2249
|
+
* Every tag is prefixed `p:<projectId>:` when a scope is configured (e.g.
|
|
2250
|
+
* `p:prj_aurora:doc:post:post-1`). The cost is a few bytes per tag and it
|
|
2251
|
+
* keeps multi-project hosts (one Next app serving several builder projects)
|
|
2252
|
+
* from cross-invalidating, so the prefix is **on whenever the host knows its
|
|
2253
|
+
* project id** — decision: include it. The emitter (the studio's webhook
|
|
2254
|
+
* dispatcher, `apps/builder/src/lib/webhooks`) always scopes with the
|
|
2255
|
+
* operation's `projectId`; a host that omits `cache.scope` therefore will not
|
|
2256
|
+
* match studio-emitted tags. Configure `cache.scope` with the same project id
|
|
2257
|
+
* the studio publishes under.
|
|
2258
|
+
*
|
|
2259
|
+
* The studio-side emitter cannot depend on this package (it would drag the
|
|
2260
|
+
* `next` peer dependency into the builder), so
|
|
2261
|
+
* `apps/builder/src/lib/webhooks/tags.ts` mirrors these formatters; tests on
|
|
2262
|
+
* both sides pin the exact literal strings to keep them in lock-step.
|
|
2263
|
+
*/
|
|
2264
|
+
/** Apply the optional multi-project scope prefix to one tag. */
|
|
2265
|
+
declare function scopeCacheTag(tag: string, scope?: string): string;
|
|
2266
|
+
/**
|
|
2267
|
+
* Tag for one document identity. `docKey` is the EC-143 `documentKey` string
|
|
2268
|
+
* (`<collection>:<id>`), exactly as found in `CacheTags.docs`.
|
|
2269
|
+
*/
|
|
2270
|
+
declare function docCacheTag(docKey: string, scope?: string): string;
|
|
2271
|
+
/** The coarse membership tag of one collection in one locale. */
|
|
2272
|
+
declare function collectionCacheTag(collection: string, locale: string, scope?: string): string;
|
|
2273
|
+
/** Tag for one resolved route path (or redirect-chain hop) in one locale. */
|
|
2274
|
+
declare function routeCacheTag(pattern: string, locale: string, scope?: string): string;
|
|
2275
|
+
/**
|
|
2276
|
+
* The whole-project sweep tag. Attached to every cached page entry; emitted
|
|
2277
|
+
* on graph publish/unpublish (and as the emitter's honest fallback when a
|
|
2278
|
+
* change's precise tag set is unknowable).
|
|
2279
|
+
*/
|
|
2280
|
+
declare function projectSweepTag(scope?: string): string;
|
|
2281
|
+
/**
|
|
2282
|
+
* Map an accessor-level `CacheTags` (what one request actually read) onto the
|
|
2283
|
+
* full, deterministic (sorted, deduplicated) set of Next tag strings for the
|
|
2284
|
+
* cache entry — including the project sweep tag.
|
|
2285
|
+
*/
|
|
2286
|
+
declare function nextCacheTags(tags: CacheTags, scope?: string): string[];
|
|
2287
|
+
/**
|
|
2288
|
+
* The slice of `unstable_cache` the discovery logic needs, injectable so the
|
|
2289
|
+
* logic is testable against a faithful in-memory model (and so the test model
|
|
2290
|
+
* can mirror the real semantics this design depends on — see below).
|
|
2291
|
+
*/
|
|
2292
|
+
interface CacheWrapperOptions {
|
|
2293
|
+
tags: string[];
|
|
2294
|
+
/** `false` = never expire by time (tag-driven invalidation only). */
|
|
2295
|
+
revalidate: number | false;
|
|
2296
|
+
}
|
|
2297
|
+
type CacheWrapper = <T>(cb: () => Promise<T>, keyParts: string[], options: CacheWrapperOptions) => () => Promise<T>;
|
|
2298
|
+
/** What one cached request stores: the outcome plus its own discovered tags. */
|
|
2299
|
+
interface CachedEntry<T> {
|
|
2300
|
+
value: T;
|
|
2301
|
+
/** The Next tag strings discovered at fetch time by the run that wrote this entry. */
|
|
2302
|
+
tags: string[];
|
|
2303
|
+
}
|
|
2304
|
+
/**
|
|
2305
|
+
* Fetch-time tag discovery over `unstable_cache` — the heart of EC-146.
|
|
2306
|
+
*
|
|
2307
|
+
* The problem: `unstable_cache` fixes an entry's tags when the *wrapper* is
|
|
2308
|
+
* created (`options.tags` is validated and **copied** at construction time in
|
|
2309
|
+
* Next 15 — mutating the array during the callback does not propagate), but
|
|
2310
|
+
* the correct tags are only known *after* the loader resolved the page. So a
|
|
2311
|
+
* single wrapper can never both (a) run the loader and (b) store the result
|
|
2312
|
+
* under the tags that run discovered.
|
|
2313
|
+
*
|
|
2314
|
+
* The design ("resolve once, cache with discovered tags, serve cached
|
|
2315
|
+
* thereafter") uses two wrapper constructions per request around **one**
|
|
2316
|
+
* cache entry:
|
|
2317
|
+
*
|
|
2318
|
+
* 1. **Probe pass** — a wrapper keyed by `keyParts` whose callback runs the
|
|
2319
|
+
* loader, captures the result + its discovered tags into the closure, and
|
|
2320
|
+
* then throws an internal sentinel.
|
|
2321
|
+
* - Cache **hit**: the stored entry (written by a previous request with
|
|
2322
|
+
* its full discovered tags) is returned; the callback — and therefore
|
|
2323
|
+
* the loader and any backend access — never runs. This is the outage
|
|
2324
|
+
* story: fully cached routes keep serving when the backend is down.
|
|
2325
|
+
* - Cache **miss**: the loader runs exactly once; the sentinel throw
|
|
2326
|
+
* prevents `unstable_cache` from persisting the entry under the
|
|
2327
|
+
* incomplete baseline tags (thrown callbacks cache nothing).
|
|
2328
|
+
* 2. **Write pass** (miss only) — a second wrapper is constructed *now that
|
|
2329
|
+
* the tags are known*, with `options.tags` = the discovered set, same key.
|
|
2330
|
+
* Its callback just returns the already-loaded entry, so the backend is
|
|
2331
|
+
* not hit again; `unstable_cache` persists the entry under the full
|
|
2332
|
+
* fetch-time tag set. `revalidateTag` on any of those tags drops the
|
|
2333
|
+
* entry and the next request repeats the cycle with fresh data.
|
|
2334
|
+
*
|
|
2335
|
+
* The loader runs **once per revalidation cycle** (per key), never once per
|
|
2336
|
+
* request. Time-based revalidation is intentionally not exposed: a
|
|
2337
|
+
* stale-while-revalidate background refresh would re-run the probe callback
|
|
2338
|
+
* (whose bail aborts the refresh), so this design is tag-driven only —
|
|
2339
|
+
* `revalidate: false` — which is exactly the AD-5 model.
|
|
2340
|
+
*
|
|
2341
|
+
* Concurrency: wrappers and the captured closure are per-request, so there is
|
|
2342
|
+
* no cross-request mutation; two racing first requests both resolve and the
|
|
2343
|
+
* last write wins (deterministic content ⇒ identical entries).
|
|
2344
|
+
*/
|
|
2345
|
+
declare function loadCachedEntry<T>(cache: CacheWrapper, keyParts: string[], baselineTags: readonly string[], load: () => Promise<CachedEntry<T>>): Promise<CachedEntry<T>>;
|
|
2346
|
+
|
|
2347
|
+
/**
|
|
2348
|
+
* Pure signature/payload logic of the revalidation webhook receiver (EC-146),
|
|
2349
|
+
* kept free of Next.js imports so it is unit-testable. `revalidate.ts` wires
|
|
2350
|
+
* it into a route handler with `revalidateTag`.
|
|
2351
|
+
*
|
|
2352
|
+
* Scheme: HMAC-SHA256 over the **raw request body** with a shared secret,
|
|
2353
|
+
* carried as `x-elytra-signature: sha256=<hex>`. The emitter (the studio's
|
|
2354
|
+
* webhook dispatcher) signs the exact JSON string it sends; the receiver
|
|
2355
|
+
* verifies over the raw text *before* parsing, with a timing-safe compare.
|
|
2356
|
+
*/
|
|
2357
|
+
/** Header carrying the HMAC signature. */
|
|
2358
|
+
declare const REVALIDATE_SIGNATURE_HEADER = "x-elytra-signature";
|
|
2359
|
+
/** The webhook body: which tags to revalidate, and why. */
|
|
2360
|
+
declare const revalidatePayloadSchema: z.ZodObject<{
|
|
2361
|
+
projectId: z.ZodString;
|
|
2362
|
+
event: z.ZodString;
|
|
2363
|
+
tags: z.ZodArray<z.ZodString>;
|
|
2364
|
+
timestamp: z.ZodString;
|
|
2365
|
+
}, z.core.$strip>;
|
|
2366
|
+
type RevalidatePayload = z.infer<typeof revalidatePayloadSchema>;
|
|
2367
|
+
/** Compute the signature header value for a raw body (emitter/test side). */
|
|
2368
|
+
declare function signRevalidateBody(rawBody: string, secret: string): string;
|
|
2369
|
+
type RevalidateEvaluation = {
|
|
2370
|
+
ok: true;
|
|
2371
|
+
payload: RevalidatePayload;
|
|
2372
|
+
} | {
|
|
2373
|
+
ok: false;
|
|
2374
|
+
status: 400 | 401;
|
|
2375
|
+
message: string;
|
|
2376
|
+
};
|
|
2377
|
+
/**
|
|
2378
|
+
* Evaluate one webhook request: verify the HMAC first (auth before parsing),
|
|
2379
|
+
* then validate the payload shape.
|
|
2380
|
+
*
|
|
2381
|
+
* - unconfigured (empty) secret → 401 for every request: the endpoint stays
|
|
2382
|
+
* closed until a secret is deliberately configured (mirrors EC-144 preview);
|
|
2383
|
+
* - missing/malformed/mismatched signature → 401 (timing-safe compare);
|
|
2384
|
+
* - unparseable or schema-invalid body → 400 with no tag revalidated.
|
|
2385
|
+
*/
|
|
2386
|
+
declare function evaluateRevalidateRequest(rawBody: string, signature: string | null | undefined, secret: string | undefined): RevalidateEvaluation;
|
|
2387
|
+
|
|
2388
|
+
/**
|
|
2389
|
+
* Revalidation webhook route handler (EC-146): the receiving end of the
|
|
2390
|
+
* publish → live-in-seconds pipeline. The studio's dispatcher POSTs a signed
|
|
2391
|
+
* `{projectId, event, tags, timestamp}` payload; this handler verifies the
|
|
2392
|
+
* HMAC over the raw body and calls `revalidateTag` for each tag, dropping
|
|
2393
|
+
* exactly the cached entries whose fetch-time tag sets contained them.
|
|
2394
|
+
*
|
|
2395
|
+
* Server-only by construction — consumed from an App Router route handler
|
|
2396
|
+
* file, never from client bundles.
|
|
2397
|
+
*/
|
|
2398
|
+
interface RevalidateRouteOptions {
|
|
2399
|
+
/**
|
|
2400
|
+
* Shared HMAC secret. An empty/undefined secret rejects every request with
|
|
2401
|
+
* 401 — the endpoint stays closed until deliberately configured.
|
|
2402
|
+
*/
|
|
2403
|
+
secret: string | undefined;
|
|
2404
|
+
/**
|
|
2405
|
+
* CORS origin allowed to call this endpoint (e.g. the studio's origin).
|
|
2406
|
+
* The v1 dispatcher is a browser `fetch` from the studio, and the custom
|
|
2407
|
+
* signature header always triggers a CORS preflight — so cross-origin
|
|
2408
|
+
* studio → host dispatch only works when this is set. Server-side emitters
|
|
2409
|
+
* (the Convex action variant, CLIs, CI) need no CORS and may leave it
|
|
2410
|
+
* unset. Never use `*` with a real secret-bearing deployment unless you
|
|
2411
|
+
* accept that any origin may *attempt* deliveries (they still need the
|
|
2412
|
+
* secret to have any effect).
|
|
2413
|
+
*/
|
|
2414
|
+
allowOrigin?: string;
|
|
2415
|
+
}
|
|
2416
|
+
interface RevalidateRouteHandlers {
|
|
2417
|
+
POST(request: Request): Promise<Response>;
|
|
2418
|
+
OPTIONS(): Promise<Response>;
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Create the webhook route handlers for `app/api/revalidate/route.ts`:
|
|
2422
|
+
*
|
|
2423
|
+
* ```ts
|
|
2424
|
+
* export const { POST, OPTIONS } = createRevalidateRoute({
|
|
2425
|
+
* secret: process.env.REVALIDATE_SECRET,
|
|
2426
|
+
* })
|
|
2427
|
+
* ```
|
|
2428
|
+
*
|
|
2429
|
+
* Unsigned/invalid requests are rejected with 401 (timing-safe HMAC check,
|
|
2430
|
+
* see `evaluateRevalidateRequest`); valid requests revalidate every carried
|
|
2431
|
+
* tag and answer `{ revalidated: tags }`.
|
|
2432
|
+
*/
|
|
2433
|
+
declare function createRevalidateRoute(options: RevalidateRouteOptions): RevalidateRouteHandlers;
|
|
2434
|
+
|
|
2435
|
+
/**
|
|
2436
|
+
* Image delivery for the embedded runtime (EC-152).
|
|
2437
|
+
*
|
|
2438
|
+
* ## The honest v1 design
|
|
2439
|
+
*
|
|
2440
|
+
* Stored asset bytes are served from plain blob-storage URLs (Convex file
|
|
2441
|
+
* storage serve URLs in production). Those URLs do **not** transform — there
|
|
2442
|
+
* is no `?w=`/`?q=` resizing service behind them, and no image CDN build-out
|
|
2443
|
+
* in v1. The standard, no-CDN path is therefore `next/image` with its
|
|
2444
|
+
* **default loader**: the host app's own Next.js optimizer fetches the remote
|
|
2445
|
+
* asset URL, resizes/re-encodes it, and serves the variants same-origin from
|
|
2446
|
+
* `/_next/image`. That is exactly what {@link ElytraImage} does.
|
|
2447
|
+
*
|
|
2448
|
+
* For that to work the host app must allowlist the asset origin in
|
|
2449
|
+
* `next.config.mjs`:
|
|
2450
|
+
*
|
|
2451
|
+
* ```js
|
|
2452
|
+
* const nextConfig = {
|
|
2453
|
+
* images: {
|
|
2454
|
+
* remotePatterns: [
|
|
2455
|
+
* // Convex file storage serve URLs:
|
|
2456
|
+
* { protocol: 'https', hostname: '*.convex.cloud' },
|
|
2457
|
+
* ],
|
|
2458
|
+
* },
|
|
2459
|
+
* }
|
|
2460
|
+
* ```
|
|
2461
|
+
*
|
|
2462
|
+
* {@link createAssetImageLoader} exists for the day the asset URLs sit behind
|
|
2463
|
+
* a CDN that *does* honor width/quality query params — it appends them and
|
|
2464
|
+
* nothing more. Pointing it at raw Convex serve URLs would silently serve
|
|
2465
|
+
* full-size bytes while pretending to resize, so it is **not** the default.
|
|
2466
|
+
*/
|
|
2467
|
+
/**
|
|
2468
|
+
* The slice of the {@link ResolvedAsset} delivery shape an image needs:
|
|
2469
|
+
* the resolvable URL, intrinsic dimensions for layout stability, and alt
|
|
2470
|
+
* text. Structurally satisfied by every `ResolvedAsset`.
|
|
2471
|
+
*/
|
|
2472
|
+
type ElytraImageAsset = Pick<ResolvedAsset, 'url' | 'width' | 'height' | 'alt' | 'focalPoint'>;
|
|
2473
|
+
interface ElytraImageProps {
|
|
2474
|
+
/** The resolved asset (delivery shape). `null` renders nothing. */
|
|
2475
|
+
asset: ElytraImageAsset | null | undefined;
|
|
2476
|
+
/** `sizes` hint forwarded to `next/image` for responsive variants. */
|
|
2477
|
+
sizes?: string;
|
|
2478
|
+
/** Preload hint forwarded to `next/image` (above-the-fold images). */
|
|
2479
|
+
priority?: boolean;
|
|
2480
|
+
/** Quality (1–100) forwarded to `next/image`; Next defaults to 75. */
|
|
2481
|
+
quality?: number;
|
|
2482
|
+
className?: string;
|
|
2483
|
+
/**
|
|
2484
|
+
* Custom `next/image` loader (e.g. from {@link createAssetImageLoader})
|
|
2485
|
+
* when assets sit behind a transforming CDN. Omit for the v1 default:
|
|
2486
|
+
* Next's own optimizer.
|
|
2487
|
+
*/
|
|
2488
|
+
loader?: ImageLoader;
|
|
2489
|
+
}
|
|
2490
|
+
/**
|
|
2491
|
+
* Render a resolved asset through `next/image` (EC-152).
|
|
2492
|
+
*
|
|
2493
|
+
* - With known intrinsic dimensions the optimizer serves resized variants
|
|
2494
|
+
* and the reserved width/height prevent layout shift.
|
|
2495
|
+
* - Without dimensions `next/image` cannot render (it requires `width` +
|
|
2496
|
+
* `height` or `fill`), so the component degrades to a plain `<img>` —
|
|
2497
|
+
* un-optimized but visible, never a crash. Records written by the EC-151
|
|
2498
|
+
* upload flow always carry detected dimensions.
|
|
2499
|
+
* - A `null`/empty asset renders nothing (missing assets surface as
|
|
2500
|
+
* structured `unknown-asset` validation issues upstream, not here).
|
|
2501
|
+
*/
|
|
2502
|
+
declare function ElytraImage(props: ElytraImageProps): ReactNode;
|
|
2503
|
+
interface AssetImageLoaderOptions {
|
|
2504
|
+
/** Query param name for the requested width. Default `"w"`. */
|
|
2505
|
+
widthParam?: string;
|
|
2506
|
+
/** Query param name for the requested quality. Default `"q"`. */
|
|
2507
|
+
qualityParam?: string;
|
|
2508
|
+
/** Quality used when `next/image` passes none. Default `75`. */
|
|
2509
|
+
defaultQuality?: number;
|
|
2510
|
+
}
|
|
2511
|
+
/**
|
|
2512
|
+
* A `next/image` loader for asset URLs served through a transforming CDN:
|
|
2513
|
+
* it appends width/quality query params to the asset URL and returns it.
|
|
2514
|
+
*
|
|
2515
|
+
* Be honest about what this does: it only *requests* a transform. Plain
|
|
2516
|
+
* Convex file-storage serve URLs ignore these params and return the original
|
|
2517
|
+
* bytes, so this loader is only correct once the asset origin actually
|
|
2518
|
+
* resizes (e.g. an image CDN in front of storage). Until then, use the
|
|
2519
|
+
* default `next/image` loader (see module docs) — that is the v1 path.
|
|
2520
|
+
*/
|
|
2521
|
+
declare function createAssetImageLoader(options?: AssetImageLoaderOptions): ImageLoader;
|
|
2522
|
+
/**
|
|
2523
|
+
* Host registration that swaps the `base.primitives.Image` implementation
|
|
2524
|
+
* for {@link ElytraImage} while keeping the primitive's canonical manifest
|
|
2525
|
+
* (the first registration's manifest wins in `defineHostComponents` — by
|
|
2526
|
+
* design, so prop schemas stay canonical; the duplicate-id registry issue it
|
|
2527
|
+
* reports is the documented override signal, not an error):
|
|
2528
|
+
*
|
|
2529
|
+
* ```ts
|
|
2530
|
+
* export const hostComponents = defineHostComponents([
|
|
2531
|
+
* nextImagePrimitive(),
|
|
2532
|
+
* // ...project components
|
|
2533
|
+
* ])
|
|
2534
|
+
* ```
|
|
2535
|
+
*/
|
|
2536
|
+
declare function nextImagePrimitive(): HostComponent;
|
|
2537
|
+
|
|
2538
|
+
/**
|
|
2539
|
+
* Pure token/target logic of the draft-preview routes (EC-144), kept free of
|
|
2540
|
+
* Next.js imports so it is unit-testable. `preview.ts` wires it into route
|
|
2541
|
+
* handlers with `draftMode()`.
|
|
2542
|
+
*/
|
|
2543
|
+
type PreviewEvaluation = {
|
|
2544
|
+
ok: true;
|
|
2545
|
+
redirectTo: string;
|
|
2546
|
+
} | {
|
|
2547
|
+
ok: false;
|
|
2548
|
+
status: 400 | 401;
|
|
2549
|
+
message: string;
|
|
2550
|
+
};
|
|
2551
|
+
/**
|
|
2552
|
+
* Only same-site path targets are accepted (`/...` but not `//host`), so the
|
|
2553
|
+
* preview endpoint can never be used as an open redirect.
|
|
2554
|
+
*/
|
|
2555
|
+
declare function safeRedirectTarget(value: string | null | undefined, fallback?: string): string | null;
|
|
2556
|
+
/**
|
|
2557
|
+
* Evaluate a preview-enable request. Drafts are token-gated (EC-144
|
|
2558
|
+
* acceptance: without the token, drafts are never reachable):
|
|
2559
|
+
*
|
|
2560
|
+
* - an unconfigured (empty) expected token rejects every request — preview
|
|
2561
|
+
* cannot be accidentally left open;
|
|
2562
|
+
* - a missing or mismatched `token` query param is a 401;
|
|
2563
|
+
* - an off-site `redirect` target is a 400.
|
|
2564
|
+
*/
|
|
2565
|
+
declare function evaluatePreviewRequest(requestUrl: string | URL, expectedToken: string | undefined): PreviewEvaluation;
|
|
2566
|
+
|
|
2567
|
+
/**
|
|
2568
|
+
* Draft preview route handlers (EC-144): Next.js `draftMode()` integration.
|
|
2569
|
+
* Server-only by construction — these factories are consumed from App Router
|
|
2570
|
+
* route handler files, never from client bundles.
|
|
2571
|
+
*/
|
|
2572
|
+
interface PreviewRouteOptions {
|
|
2573
|
+
/**
|
|
2574
|
+
* The shared secret gating draft preview. An empty/undefined token means
|
|
2575
|
+
* every request is rejected with 401 — drafts stay unreachable until a
|
|
2576
|
+
* token is configured.
|
|
2577
|
+
*/
|
|
2578
|
+
token: string | undefined;
|
|
2579
|
+
}
|
|
2580
|
+
/** The shape an `app/api/.../route.ts` file re-exports. */
|
|
2581
|
+
interface PreviewRouteHandlers {
|
|
2582
|
+
GET(request: Request): Promise<Response>;
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* Create the preview-enable route handler for
|
|
2586
|
+
* `app/api/preview/route.ts`:
|
|
2587
|
+
*
|
|
2588
|
+
* ```ts
|
|
2589
|
+
* export const { GET } = createPreviewRoute({ token: process.env.PREVIEW_TOKEN })
|
|
2590
|
+
* ```
|
|
2591
|
+
*
|
|
2592
|
+
* `GET /api/preview?token=...&redirect=/some/path` checks the token, enables
|
|
2593
|
+
* Next draft mode (the catch-all helper then renders the `draft`
|
|
2594
|
+
* perspective), and redirects to the target path. Wrong/missing token → 401.
|
|
2595
|
+
*/
|
|
2596
|
+
declare function createPreviewRoute(options: PreviewRouteOptions): PreviewRouteHandlers;
|
|
2597
|
+
/**
|
|
2598
|
+
* Create the preview-disable route handler (e.g.
|
|
2599
|
+
* `app/api/preview/disable/route.ts`). Disabling needs no token — it only
|
|
2600
|
+
* ever reduces visibility back to the published perspective.
|
|
2601
|
+
*/
|
|
2602
|
+
declare function createPreviewDisableRoute(): PreviewRouteHandlers;
|
|
2603
|
+
|
|
2604
|
+
/**
|
|
2605
|
+
* @elytracms/next — the embedded runtime (EC-144, vision AD-1): render
|
|
2606
|
+
* builder-managed pages inside the user's own Next.js App Router repo with
|
|
2607
|
+
* the user's own components. Drop `createCanvasRoute` into a catch-all
|
|
2608
|
+
* route, register components with `defineHostComponents`, done.
|
|
2609
|
+
*
|
|
2610
|
+
* Nothing here imports builder-only code (no studio, no operations, no
|
|
2611
|
+
* TanStack) — only the runtime packages: content accessors, the component
|
|
2612
|
+
* registry, and the runtime renderer.
|
|
2613
|
+
*/
|
|
2614
|
+
declare const PACKAGE = "@elytracms/next";
|
|
2615
|
+
|
|
2616
|
+
export { type AssetImageLoaderOptions, type AssetRecord, type BindingPayloads, type CacheWrapper, type CacheWrapperOptions, type CachedEntry, type CanvasCacheOptions, type CanvasContentRequest, type CanvasDataSource, type CanvasPageOutcome, type CanvasPayloadContext, type CanvasPayloadsFactory, CanvasRenderer, type CanvasRendererProps, type CanvasRequestInfo, type CanvasRoute, type CanvasRouteOptions, type CanvasRouteProps, type CanvasSitemapOptions, type CanvasSitemapResult, type CmsDocument, type CollectionDef, type ComponentImplementations, type ComponentManifest, type ComponentNode, type ContentSnapshot, type ContentSnapshotOptions, type ContentSource, type CreateCanvasSitemapOptions, type DefineHostComponentsOptions, ElytraImage, type ElytraImageAsset, type ElytraImageProps, type FieldDef, type HostComponent, type HostComponents, type Locale, type LocaleConfig, type MaterializeSourcePayloadsInput, type MaterializedSourcePayloads, PACKAGE, PROJECT_GRAPH_SCHEMA_VERSION, type Perspective, type PreviewEvaluation, type PreviewRouteHandlers, type PreviewRouteOptions, type ProjectGraph, type PropField, REVALIDATE_SIGNATURE_HEADER, type RedirectRecord, type RenderAsset, type ResolveCanvasPageOptions, type RevalidateEvaluation, type RevalidatePayload, type RevalidateRouteHandlers, type RevalidateRouteOptions, type RouteRecord, type SitemapParamsProvider, type SitemapRouteContext, type SitemapSkippedRoute, type SlotSpec, type SourcePayloadError, type StaticContentData, canvasPageMetadata, canvasSitemapEntries, collectBindingSourceIds, collectionCacheTag, createAssetImageLoader, createBindingResolver, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defaultBindingPayloads, defineComponent, defineHostComponents, docCacheTag, documentSchema, evaluatePreviewRequest, evaluateRevalidateRequest, isSourcePayloadError, loadCachedEntry, localeConfigSchema, materializeSourcePayloads, mergeCacheTags, mergeRouteRecords, nextCacheTags, nextImagePrimitive, parseProjectGraph, pathFromSlug, projectSweepTag, redirectRecordSchema, resolveCanvasPage, resolvePayloadToken, revalidatePayloadSchema, routeCacheTag, routeRecordSchema, safeRedirectTarget, scopeCacheTag, signRevalidateBody, sourcePayloadError, splitLocalePath };
|