@shapio/schema 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/html.d.ts +26 -0
- package/dist/html.js +424 -0
- package/dist/html.js.map +1 -0
- package/dist/index.d.ts +1082 -0
- package/dist/index.js +2268 -0
- package/dist/index.js.map +1 -0
- package/dist/validate-BQGutaBo.js +381 -0
- package/dist/validate-BQGutaBo.js.map +1 -0
- package/dist/validate-Oyo0GJ9j.d.ts +41 -0
- package/package.json +52 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
import { a as isEmptyRichText, i as RichTextResult, n as RichTextNode, o as richTextMediaIds, r as RichTextProblem, s as validateRichText, t as RichTextDocument } from "./validate-Oyo0GJ9j.js";
|
|
2
|
+
import { Static, TSchema, Type } from "typebox";
|
|
3
|
+
//#region src/ids.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Stable IDs for models, components and fields: random UUIDs (v4).
|
|
6
|
+
*
|
|
7
|
+
* Why UUID rather than ULID: PostgreSQL stores and indexes `uuid` natively (16 bytes) for the system
|
|
8
|
+
* tables that key on model IDs; `crypto.randomUUID()` exists in Node and every browser, so the admin can
|
|
9
|
+
* create field IDs client-side with no dependency; and nothing in Shapio needs IDs to sort by creation time
|
|
10
|
+
* (revisions carry their own version numbers and timestamps). IDs are compared case-insensitively nowhere:
|
|
11
|
+
* they are always lower-case as generated.
|
|
12
|
+
*/
|
|
13
|
+
export declare const STABLE_ID_PATTERN: RegExp;
|
|
14
|
+
export declare const isStableId: (value: unknown) => value is string;
|
|
15
|
+
export declare const createStableId: () => string;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/types/dataTypes.d.ts
|
|
18
|
+
/** Every stored data type. A field's data type is separate from the editor that renders it (brief §2.5). */
|
|
19
|
+
export declare const DATA_TYPES: readonly ["string", "text", "richtext", "number", "integer", "decimal", "biginteger", "boolean", "date", "datetime", "time", "enum", "json", "slug", "email", "url", "uid", "media", "relation", "component", "dynamiczone"];
|
|
20
|
+
export type DataType = (typeof DATA_TYPES)[number];
|
|
21
|
+
export declare const isDataType: (value: unknown) => value is DataType;
|
|
22
|
+
/**
|
|
23
|
+
* Scalar types stored as one JSON value per field (string, number or boolean). Only these can be unique,
|
|
24
|
+
* filterable or sortable: their index expression is a single cast of `data ->> fieldId` (ADR 0001).
|
|
25
|
+
*/
|
|
26
|
+
export declare const SCALAR_DATA_TYPES: ReadonlySet<DataType>;
|
|
27
|
+
/** Types that may carry `unique: true` (boolean and long text make no sense as unique keys). */
|
|
28
|
+
export declare const UNIQUE_CAPABLE_DATA_TYPES: ReadonlySet<DataType>;
|
|
29
|
+
/** Types whose values point at other definitions; the reference checks look at these. */
|
|
30
|
+
export declare const REFERENCE_DATA_TYPES: ReadonlySet<DataType>;
|
|
31
|
+
/**
|
|
32
|
+
* How a value is physically represented in the content JSON. Changing a field's type within a storage
|
|
33
|
+
* family never needs a data conversion, only a validity check; across families it is a conversion.
|
|
34
|
+
*/
|
|
35
|
+
export type StorageFamily = 'jsonString' | 'jsonNumber' | 'numericString' | 'boolean' | 'json' | 'richtext' | 'media' | 'relation' | 'component' | 'dynamiczone';
|
|
36
|
+
export declare const STORAGE_FAMILY: Readonly<Record<DataType, StorageFamily>>;
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/types/json.d.ts
|
|
39
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
40
|
+
export type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
41
|
+
[key: string]: JsonValue;
|
|
42
|
+
};
|
|
43
|
+
export type JsonObject = {
|
|
44
|
+
[key: string]: JsonValue;
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/types/settings.d.ts
|
|
48
|
+
export declare const MEDIA_KINDS: readonly ["image", "video", "audio", "document", "other"];
|
|
49
|
+
export declare const RELATION_CARDINALITIES: readonly ["one", "many"];
|
|
50
|
+
export declare const EnumValueSchema: Type.TObject<{
|
|
51
|
+
value: Type.TString;
|
|
52
|
+
label: Type.TString;
|
|
53
|
+
}>;
|
|
54
|
+
export declare const SETTINGS_SCHEMAS: {
|
|
55
|
+
string: Type.TObject<{
|
|
56
|
+
pattern: Type.TOptional<Type.TString>;
|
|
57
|
+
minLength: Type.TOptional<Type.TInteger>;
|
|
58
|
+
maxLength: Type.TOptional<Type.TInteger>;
|
|
59
|
+
}>;
|
|
60
|
+
text: Type.TObject<{
|
|
61
|
+
minLength: Type.TOptional<Type.TInteger>;
|
|
62
|
+
maxLength: Type.TOptional<Type.TInteger>;
|
|
63
|
+
}>;
|
|
64
|
+
richtext: Type.TObject<{
|
|
65
|
+
formatVersion: Type.TOptional<Type.TInteger>;
|
|
66
|
+
maxLength: Type.TOptional<Type.TInteger>;
|
|
67
|
+
}>;
|
|
68
|
+
number: Type.TObject<{
|
|
69
|
+
min: Type.TOptional<Type.TNumber>;
|
|
70
|
+
max: Type.TOptional<Type.TNumber>;
|
|
71
|
+
}>;
|
|
72
|
+
integer: Type.TObject<{
|
|
73
|
+
min: Type.TOptional<Type.TInteger>;
|
|
74
|
+
max: Type.TOptional<Type.TInteger>;
|
|
75
|
+
}>;
|
|
76
|
+
decimal: Type.TObject<{
|
|
77
|
+
precision: Type.TOptional<Type.TInteger>;
|
|
78
|
+
scale: Type.TOptional<Type.TInteger>;
|
|
79
|
+
min: Type.TOptional<Type.TString>;
|
|
80
|
+
max: Type.TOptional<Type.TString>;
|
|
81
|
+
}>;
|
|
82
|
+
biginteger: Type.TObject<{
|
|
83
|
+
min: Type.TOptional<Type.TString>;
|
|
84
|
+
max: Type.TOptional<Type.TString>;
|
|
85
|
+
}>;
|
|
86
|
+
boolean: Type.TObject<{}>;
|
|
87
|
+
date: Type.TObject<{
|
|
88
|
+
min: Type.TOptional<Type.TString>;
|
|
89
|
+
max: Type.TOptional<Type.TString>;
|
|
90
|
+
}>;
|
|
91
|
+
datetime: Type.TObject<{
|
|
92
|
+
min: Type.TOptional<Type.TString>;
|
|
93
|
+
max: Type.TOptional<Type.TString>;
|
|
94
|
+
}>;
|
|
95
|
+
time: Type.TObject<{
|
|
96
|
+
min: Type.TOptional<Type.TString>;
|
|
97
|
+
max: Type.TOptional<Type.TString>;
|
|
98
|
+
}>;
|
|
99
|
+
enum: Type.TObject<{
|
|
100
|
+
values: Type.TArray<Type.TObject<{
|
|
101
|
+
value: Type.TString;
|
|
102
|
+
label: Type.TString;
|
|
103
|
+
}>>;
|
|
104
|
+
multiple: Type.TOptional<Type.TBoolean>;
|
|
105
|
+
}>;
|
|
106
|
+
json: Type.TObject<{}>;
|
|
107
|
+
slug: Type.TObject<{
|
|
108
|
+
sourceFieldId: Type.TOptional<Type.TString>;
|
|
109
|
+
minLength: Type.TOptional<Type.TInteger>;
|
|
110
|
+
maxLength: Type.TOptional<Type.TInteger>;
|
|
111
|
+
}>;
|
|
112
|
+
email: Type.TObject<{}>;
|
|
113
|
+
url: Type.TObject<{
|
|
114
|
+
protocols: Type.TOptional<Type.TArray<Type.TEnum<["http", "https", "mailto", "tel"]>>>;
|
|
115
|
+
}>;
|
|
116
|
+
uid: Type.TObject<{
|
|
117
|
+
minLength: Type.TOptional<Type.TInteger>;
|
|
118
|
+
maxLength: Type.TOptional<Type.TInteger>;
|
|
119
|
+
}>;
|
|
120
|
+
media: Type.TObject<{
|
|
121
|
+
min: Type.TOptional<Type.TInteger>;
|
|
122
|
+
max: Type.TOptional<Type.TInteger>;
|
|
123
|
+
multiple: Type.TOptional<Type.TBoolean>;
|
|
124
|
+
allowedKinds: Type.TOptional<Type.TArray<Type.TEnum<["image", "video", "audio", "document", "other"]>>>;
|
|
125
|
+
}>;
|
|
126
|
+
relation: Type.TObject<{
|
|
127
|
+
min: Type.TOptional<Type.TInteger>;
|
|
128
|
+
max: Type.TOptional<Type.TInteger>;
|
|
129
|
+
target: Type.TString;
|
|
130
|
+
cardinality: Type.TEnum<["one", "many"]>;
|
|
131
|
+
}>;
|
|
132
|
+
component: Type.TObject<{
|
|
133
|
+
min: Type.TOptional<Type.TInteger>;
|
|
134
|
+
max: Type.TOptional<Type.TInteger>;
|
|
135
|
+
component: Type.TString;
|
|
136
|
+
repeatable: Type.TOptional<Type.TBoolean>;
|
|
137
|
+
}>;
|
|
138
|
+
dynamiczone: Type.TObject<{
|
|
139
|
+
min: Type.TOptional<Type.TInteger>;
|
|
140
|
+
max: Type.TOptional<Type.TInteger>;
|
|
141
|
+
components: Type.TArray<Type.TString>;
|
|
142
|
+
}>;
|
|
143
|
+
};
|
|
144
|
+
type SettingsSchemas = typeof SETTINGS_SCHEMAS;
|
|
145
|
+
/** Settings as authored (defaults may be missing). */
|
|
146
|
+
export type DataTypeSettingsInput = { [T in DataType]: Static<SettingsSchemas[T]>; };
|
|
147
|
+
/** Settings after normalization: the defaulted keys are always present. */
|
|
148
|
+
export type DataTypeSettings = Omit<DataTypeSettingsInput, 'richtext' | 'enum' | 'media' | 'component'> & {
|
|
149
|
+
richtext: DataTypeSettingsInput['richtext'] & {
|
|
150
|
+
formatVersion: number;
|
|
151
|
+
};
|
|
152
|
+
enum: DataTypeSettingsInput['enum'] & {
|
|
153
|
+
multiple: boolean;
|
|
154
|
+
};
|
|
155
|
+
media: DataTypeSettingsInput['media'] & {
|
|
156
|
+
multiple: boolean;
|
|
157
|
+
};
|
|
158
|
+
component: DataTypeSettingsInput['component'] & {
|
|
159
|
+
repeatable: boolean;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/types/definitions.d.ts
|
|
164
|
+
export declare const MODEL_KINDS: readonly ["collection", "singleton"];
|
|
165
|
+
export type ModelKind = (typeof MODEL_KINDS)[number];
|
|
166
|
+
export declare const DEFINITION_KINDS: readonly ["collection", "singleton", "component"];
|
|
167
|
+
export type DefinitionKind = (typeof DEFINITION_KINDS)[number];
|
|
168
|
+
export declare const SORT_DIRECTIONS: readonly ["asc", "desc"];
|
|
169
|
+
export type SortDirection = (typeof SORT_DIRECTIONS)[number];
|
|
170
|
+
export declare const MAX_FIELDS_PER_DEFINITION = 500;
|
|
171
|
+
export declare const MAX_LABEL_LENGTH = 200;
|
|
172
|
+
export declare const MAX_DESCRIPTION_LENGTH = 2000;
|
|
173
|
+
export declare const EditorChoiceInputSchema: Type.TObject<{
|
|
174
|
+
id: Type.TString;
|
|
175
|
+
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
176
|
+
}>;
|
|
177
|
+
export declare const FieldInputSchema: Type.TObject<{
|
|
178
|
+
id: Type.TOptional<Type.TString>;
|
|
179
|
+
apiKey: Type.TString;
|
|
180
|
+
label: Type.TString;
|
|
181
|
+
/** Help text shown under the control. */
|
|
182
|
+
description: Type.TOptional<Type.TString>;
|
|
183
|
+
type: Type.TEnum<["string", "text", "richtext", "number", "integer", "decimal", "biginteger", "boolean", "date", "datetime", "time", "enum", "json", "slug", "email", "url", "uid", "media", "relation", "component", "dynamiczone"]>;
|
|
184
|
+
required: Type.TOptional<Type.TBoolean>;
|
|
185
|
+
/** Per-locale value. Ignored (must be false) when the model is not localized. */
|
|
186
|
+
localized: Type.TOptional<Type.TBoolean>;
|
|
187
|
+
/** Readable by delivery principals wherever the model is readable (ADR 0005). Default true. */
|
|
188
|
+
public: Type.TOptional<Type.TBoolean>;
|
|
189
|
+
unique: Type.TOptional<Type.TBoolean>;
|
|
190
|
+
filterable: Type.TOptional<Type.TBoolean>;
|
|
191
|
+
sortable: Type.TOptional<Type.TBoolean>;
|
|
192
|
+
/** Hidden from editors and the API; stored values are preserved until an explicit cleanup. */
|
|
193
|
+
deprecated: Type.TOptional<Type.TBoolean>;
|
|
194
|
+
/** Used for new entries and to backfill existing ones when the field becomes required. */
|
|
195
|
+
defaultValue: Type.TOptional<Type.TUnknown>;
|
|
196
|
+
/** Type-specific settings, validated against SETTINGS_SCHEMAS[type]. */
|
|
197
|
+
settings: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
198
|
+
editor: Type.TOptional<Type.TObject<{
|
|
199
|
+
id: Type.TString;
|
|
200
|
+
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
201
|
+
}>>;
|
|
202
|
+
}>;
|
|
203
|
+
export declare const ModelDisplayInputSchema: Type.TObject<{
|
|
204
|
+
/** Field whose value labels an entry in lists, pickers and relation summaries. */
|
|
205
|
+
titleFieldId: Type.TOptional<Type.TString>;
|
|
206
|
+
/** Columns of the content list, in order. */
|
|
207
|
+
listFieldIds: Type.TOptional<Type.TArray<Type.TString>>;
|
|
208
|
+
defaultSort: Type.TOptional<Type.TObject<{
|
|
209
|
+
fieldId: Type.TString;
|
|
210
|
+
direction: Type.TEnum<["asc", "desc"]>;
|
|
211
|
+
}>>;
|
|
212
|
+
/** Editor layout: named sections of the entry form. */
|
|
213
|
+
groups: Type.TOptional<Type.TArray<Type.TObject<{
|
|
214
|
+
id: Type.TString;
|
|
215
|
+
label: Type.TString;
|
|
216
|
+
fieldIds: Type.TArray<Type.TString>;
|
|
217
|
+
}>>>;
|
|
218
|
+
/** Fields rendered as blocks in the entry document's canvas, in order. Default: see `effectiveLayout`. */
|
|
219
|
+
canvasFieldIds: Type.TOptional<Type.TArray<Type.TString>>;
|
|
220
|
+
/** A single image `media` field shown as the cover above the title. Default: see `effectiveLayout`. */
|
|
221
|
+
coverFieldId: Type.TOptional<Type.TString>;
|
|
222
|
+
/** Properties shown as chips in the document's strip. Default: the first non-empty ones. */
|
|
223
|
+
stripFieldIds: Type.TOptional<Type.TArray<Type.TString>>;
|
|
224
|
+
}>;
|
|
225
|
+
export declare const ComponentDisplayInputSchema: Type.TObject<{
|
|
226
|
+
titleFieldId: Type.TOptional<Type.TString>;
|
|
227
|
+
groups: Type.TOptional<Type.TArray<Type.TObject<{
|
|
228
|
+
id: Type.TString;
|
|
229
|
+
label: Type.TString;
|
|
230
|
+
fieldIds: Type.TArray<Type.TString>;
|
|
231
|
+
}>>>;
|
|
232
|
+
}>;
|
|
233
|
+
export declare const ModelDefinitionInputSchema: Type.TObject<{
|
|
234
|
+
kind: Type.TEnum<["collection", "singleton"]>;
|
|
235
|
+
/**
|
|
236
|
+
* Collections only: names the list route and query (`/api/content/articles`, `articles(…)`). Optional
|
|
237
|
+
* here so files written before plural API IDs still parse; normalizing fills it on a collection.
|
|
238
|
+
*/
|
|
239
|
+
pluralApiKey: Type.TOptional<Type.TString>;
|
|
240
|
+
localized: Type.TOptional<Type.TBoolean>;
|
|
241
|
+
/** Entries have separate draft and published states. Default true. */
|
|
242
|
+
draftAndPublish: Type.TOptional<Type.TBoolean>;
|
|
243
|
+
display: Type.TOptional<Type.TObject<{
|
|
244
|
+
/** Field whose value labels an entry in lists, pickers and relation summaries. */
|
|
245
|
+
titleFieldId: Type.TOptional<Type.TString>;
|
|
246
|
+
/** Columns of the content list, in order. */
|
|
247
|
+
listFieldIds: Type.TOptional<Type.TArray<Type.TString>>;
|
|
248
|
+
defaultSort: Type.TOptional<Type.TObject<{
|
|
249
|
+
fieldId: Type.TString;
|
|
250
|
+
direction: Type.TEnum<["asc", "desc"]>;
|
|
251
|
+
}>>;
|
|
252
|
+
/** Editor layout: named sections of the entry form. */
|
|
253
|
+
groups: Type.TOptional<Type.TArray<Type.TObject<{
|
|
254
|
+
id: Type.TString;
|
|
255
|
+
label: Type.TString;
|
|
256
|
+
fieldIds: Type.TArray<Type.TString>;
|
|
257
|
+
}>>>;
|
|
258
|
+
/** Fields rendered as blocks in the entry document's canvas, in order. Default: see `effectiveLayout`. */
|
|
259
|
+
canvasFieldIds: Type.TOptional<Type.TArray<Type.TString>>;
|
|
260
|
+
/** A single image `media` field shown as the cover above the title. Default: see `effectiveLayout`. */
|
|
261
|
+
coverFieldId: Type.TOptional<Type.TString>;
|
|
262
|
+
/** Properties shown as chips in the document's strip. Default: the first non-empty ones. */
|
|
263
|
+
stripFieldIds: Type.TOptional<Type.TArray<Type.TString>>;
|
|
264
|
+
}>>;
|
|
265
|
+
id: Type.TOptional<Type.TString>;
|
|
266
|
+
apiKey: Type.TString;
|
|
267
|
+
label: Type.TString;
|
|
268
|
+
description: Type.TOptional<Type.TString>;
|
|
269
|
+
fields: Type.TArray<Type.TObject<{
|
|
270
|
+
id: Type.TOptional<Type.TString>;
|
|
271
|
+
apiKey: Type.TString;
|
|
272
|
+
label: Type.TString;
|
|
273
|
+
/** Help text shown under the control. */
|
|
274
|
+
description: Type.TOptional<Type.TString>;
|
|
275
|
+
type: Type.TEnum<["string", "text", "richtext", "number", "integer", "decimal", "biginteger", "boolean", "date", "datetime", "time", "enum", "json", "slug", "email", "url", "uid", "media", "relation", "component", "dynamiczone"]>;
|
|
276
|
+
required: Type.TOptional<Type.TBoolean>;
|
|
277
|
+
/** Per-locale value. Ignored (must be false) when the model is not localized. */
|
|
278
|
+
localized: Type.TOptional<Type.TBoolean>;
|
|
279
|
+
/** Readable by delivery principals wherever the model is readable (ADR 0005). Default true. */
|
|
280
|
+
public: Type.TOptional<Type.TBoolean>;
|
|
281
|
+
unique: Type.TOptional<Type.TBoolean>;
|
|
282
|
+
filterable: Type.TOptional<Type.TBoolean>;
|
|
283
|
+
sortable: Type.TOptional<Type.TBoolean>;
|
|
284
|
+
/** Hidden from editors and the API; stored values are preserved until an explicit cleanup. */
|
|
285
|
+
deprecated: Type.TOptional<Type.TBoolean>;
|
|
286
|
+
/** Used for new entries and to backfill existing ones when the field becomes required. */
|
|
287
|
+
defaultValue: Type.TOptional<Type.TUnknown>;
|
|
288
|
+
/** Type-specific settings, validated against SETTINGS_SCHEMAS[type]. */
|
|
289
|
+
settings: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
290
|
+
editor: Type.TOptional<Type.TObject<{
|
|
291
|
+
id: Type.TString;
|
|
292
|
+
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
293
|
+
}>>;
|
|
294
|
+
}>>;
|
|
295
|
+
}>;
|
|
296
|
+
export declare const ComponentDefinitionInputSchema: Type.TObject<{
|
|
297
|
+
kind: Type.TLiteral<"component">;
|
|
298
|
+
category: Type.TOptional<Type.TString>;
|
|
299
|
+
icon: Type.TOptional<Type.TString>;
|
|
300
|
+
display: Type.TOptional<Type.TObject<{
|
|
301
|
+
titleFieldId: Type.TOptional<Type.TString>;
|
|
302
|
+
groups: Type.TOptional<Type.TArray<Type.TObject<{
|
|
303
|
+
id: Type.TString;
|
|
304
|
+
label: Type.TString;
|
|
305
|
+
fieldIds: Type.TArray<Type.TString>;
|
|
306
|
+
}>>>;
|
|
307
|
+
}>>;
|
|
308
|
+
id: Type.TOptional<Type.TString>;
|
|
309
|
+
apiKey: Type.TString;
|
|
310
|
+
label: Type.TString;
|
|
311
|
+
description: Type.TOptional<Type.TString>;
|
|
312
|
+
fields: Type.TArray<Type.TObject<{
|
|
313
|
+
id: Type.TOptional<Type.TString>;
|
|
314
|
+
apiKey: Type.TString;
|
|
315
|
+
label: Type.TString;
|
|
316
|
+
/** Help text shown under the control. */
|
|
317
|
+
description: Type.TOptional<Type.TString>;
|
|
318
|
+
type: Type.TEnum<["string", "text", "richtext", "number", "integer", "decimal", "biginteger", "boolean", "date", "datetime", "time", "enum", "json", "slug", "email", "url", "uid", "media", "relation", "component", "dynamiczone"]>;
|
|
319
|
+
required: Type.TOptional<Type.TBoolean>;
|
|
320
|
+
/** Per-locale value. Ignored (must be false) when the model is not localized. */
|
|
321
|
+
localized: Type.TOptional<Type.TBoolean>;
|
|
322
|
+
/** Readable by delivery principals wherever the model is readable (ADR 0005). Default true. */
|
|
323
|
+
public: Type.TOptional<Type.TBoolean>;
|
|
324
|
+
unique: Type.TOptional<Type.TBoolean>;
|
|
325
|
+
filterable: Type.TOptional<Type.TBoolean>;
|
|
326
|
+
sortable: Type.TOptional<Type.TBoolean>;
|
|
327
|
+
/** Hidden from editors and the API; stored values are preserved until an explicit cleanup. */
|
|
328
|
+
deprecated: Type.TOptional<Type.TBoolean>;
|
|
329
|
+
/** Used for new entries and to backfill existing ones when the field becomes required. */
|
|
330
|
+
defaultValue: Type.TOptional<Type.TUnknown>;
|
|
331
|
+
/** Type-specific settings, validated against SETTINGS_SCHEMAS[type]. */
|
|
332
|
+
settings: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
333
|
+
editor: Type.TOptional<Type.TObject<{
|
|
334
|
+
id: Type.TString;
|
|
335
|
+
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
336
|
+
}>>;
|
|
337
|
+
}>>;
|
|
338
|
+
}>;
|
|
339
|
+
export declare const DefinitionInputSchema: Type.TUnion<[Type.TObject<{
|
|
340
|
+
kind: Type.TEnum<["collection", "singleton"]>;
|
|
341
|
+
/**
|
|
342
|
+
* Collections only: names the list route and query (`/api/content/articles`, `articles(…)`). Optional
|
|
343
|
+
* here so files written before plural API IDs still parse; normalizing fills it on a collection.
|
|
344
|
+
*/
|
|
345
|
+
pluralApiKey: Type.TOptional<Type.TString>;
|
|
346
|
+
localized: Type.TOptional<Type.TBoolean>;
|
|
347
|
+
/** Entries have separate draft and published states. Default true. */
|
|
348
|
+
draftAndPublish: Type.TOptional<Type.TBoolean>;
|
|
349
|
+
display: Type.TOptional<Type.TObject<{
|
|
350
|
+
/** Field whose value labels an entry in lists, pickers and relation summaries. */
|
|
351
|
+
titleFieldId: Type.TOptional<Type.TString>;
|
|
352
|
+
/** Columns of the content list, in order. */
|
|
353
|
+
listFieldIds: Type.TOptional<Type.TArray<Type.TString>>;
|
|
354
|
+
defaultSort: Type.TOptional<Type.TObject<{
|
|
355
|
+
fieldId: Type.TString;
|
|
356
|
+
direction: Type.TEnum<["asc", "desc"]>;
|
|
357
|
+
}>>;
|
|
358
|
+
/** Editor layout: named sections of the entry form. */
|
|
359
|
+
groups: Type.TOptional<Type.TArray<Type.TObject<{
|
|
360
|
+
id: Type.TString;
|
|
361
|
+
label: Type.TString;
|
|
362
|
+
fieldIds: Type.TArray<Type.TString>;
|
|
363
|
+
}>>>;
|
|
364
|
+
/** Fields rendered as blocks in the entry document's canvas, in order. Default: see `effectiveLayout`. */
|
|
365
|
+
canvasFieldIds: Type.TOptional<Type.TArray<Type.TString>>;
|
|
366
|
+
/** A single image `media` field shown as the cover above the title. Default: see `effectiveLayout`. */
|
|
367
|
+
coverFieldId: Type.TOptional<Type.TString>;
|
|
368
|
+
/** Properties shown as chips in the document's strip. Default: the first non-empty ones. */
|
|
369
|
+
stripFieldIds: Type.TOptional<Type.TArray<Type.TString>>;
|
|
370
|
+
}>>;
|
|
371
|
+
id: Type.TOptional<Type.TString>;
|
|
372
|
+
apiKey: Type.TString;
|
|
373
|
+
label: Type.TString;
|
|
374
|
+
description: Type.TOptional<Type.TString>;
|
|
375
|
+
fields: Type.TArray<Type.TObject<{
|
|
376
|
+
id: Type.TOptional<Type.TString>;
|
|
377
|
+
apiKey: Type.TString;
|
|
378
|
+
label: Type.TString;
|
|
379
|
+
/** Help text shown under the control. */
|
|
380
|
+
description: Type.TOptional<Type.TString>;
|
|
381
|
+
type: Type.TEnum<["string", "text", "richtext", "number", "integer", "decimal", "biginteger", "boolean", "date", "datetime", "time", "enum", "json", "slug", "email", "url", "uid", "media", "relation", "component", "dynamiczone"]>;
|
|
382
|
+
required: Type.TOptional<Type.TBoolean>;
|
|
383
|
+
/** Per-locale value. Ignored (must be false) when the model is not localized. */
|
|
384
|
+
localized: Type.TOptional<Type.TBoolean>;
|
|
385
|
+
/** Readable by delivery principals wherever the model is readable (ADR 0005). Default true. */
|
|
386
|
+
public: Type.TOptional<Type.TBoolean>;
|
|
387
|
+
unique: Type.TOptional<Type.TBoolean>;
|
|
388
|
+
filterable: Type.TOptional<Type.TBoolean>;
|
|
389
|
+
sortable: Type.TOptional<Type.TBoolean>;
|
|
390
|
+
/** Hidden from editors and the API; stored values are preserved until an explicit cleanup. */
|
|
391
|
+
deprecated: Type.TOptional<Type.TBoolean>;
|
|
392
|
+
/** Used for new entries and to backfill existing ones when the field becomes required. */
|
|
393
|
+
defaultValue: Type.TOptional<Type.TUnknown>;
|
|
394
|
+
/** Type-specific settings, validated against SETTINGS_SCHEMAS[type]. */
|
|
395
|
+
settings: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
396
|
+
editor: Type.TOptional<Type.TObject<{
|
|
397
|
+
id: Type.TString;
|
|
398
|
+
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
399
|
+
}>>;
|
|
400
|
+
}>>;
|
|
401
|
+
}>, Type.TObject<{
|
|
402
|
+
kind: Type.TLiteral<"component">;
|
|
403
|
+
category: Type.TOptional<Type.TString>;
|
|
404
|
+
icon: Type.TOptional<Type.TString>;
|
|
405
|
+
display: Type.TOptional<Type.TObject<{
|
|
406
|
+
titleFieldId: Type.TOptional<Type.TString>;
|
|
407
|
+
groups: Type.TOptional<Type.TArray<Type.TObject<{
|
|
408
|
+
id: Type.TString;
|
|
409
|
+
label: Type.TString;
|
|
410
|
+
fieldIds: Type.TArray<Type.TString>;
|
|
411
|
+
}>>>;
|
|
412
|
+
}>>;
|
|
413
|
+
id: Type.TOptional<Type.TString>;
|
|
414
|
+
apiKey: Type.TString;
|
|
415
|
+
label: Type.TString;
|
|
416
|
+
description: Type.TOptional<Type.TString>;
|
|
417
|
+
fields: Type.TArray<Type.TObject<{
|
|
418
|
+
id: Type.TOptional<Type.TString>;
|
|
419
|
+
apiKey: Type.TString;
|
|
420
|
+
label: Type.TString;
|
|
421
|
+
/** Help text shown under the control. */
|
|
422
|
+
description: Type.TOptional<Type.TString>;
|
|
423
|
+
type: Type.TEnum<["string", "text", "richtext", "number", "integer", "decimal", "biginteger", "boolean", "date", "datetime", "time", "enum", "json", "slug", "email", "url", "uid", "media", "relation", "component", "dynamiczone"]>;
|
|
424
|
+
required: Type.TOptional<Type.TBoolean>;
|
|
425
|
+
/** Per-locale value. Ignored (must be false) when the model is not localized. */
|
|
426
|
+
localized: Type.TOptional<Type.TBoolean>;
|
|
427
|
+
/** Readable by delivery principals wherever the model is readable (ADR 0005). Default true. */
|
|
428
|
+
public: Type.TOptional<Type.TBoolean>;
|
|
429
|
+
unique: Type.TOptional<Type.TBoolean>;
|
|
430
|
+
filterable: Type.TOptional<Type.TBoolean>;
|
|
431
|
+
sortable: Type.TOptional<Type.TBoolean>;
|
|
432
|
+
/** Hidden from editors and the API; stored values are preserved until an explicit cleanup. */
|
|
433
|
+
deprecated: Type.TOptional<Type.TBoolean>;
|
|
434
|
+
/** Used for new entries and to backfill existing ones when the field becomes required. */
|
|
435
|
+
defaultValue: Type.TOptional<Type.TUnknown>;
|
|
436
|
+
/** Type-specific settings, validated against SETTINGS_SCHEMAS[type]. */
|
|
437
|
+
settings: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
438
|
+
editor: Type.TOptional<Type.TObject<{
|
|
439
|
+
id: Type.TString;
|
|
440
|
+
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
441
|
+
}>>;
|
|
442
|
+
}>>;
|
|
443
|
+
}>]>;
|
|
444
|
+
export type FieldInput = Static<typeof FieldInputSchema>;
|
|
445
|
+
export type ModelDefinitionInput = Static<typeof ModelDefinitionInputSchema>;
|
|
446
|
+
export type ComponentDefinitionInput = Static<typeof ComponentDefinitionInputSchema>;
|
|
447
|
+
export type DefinitionInput = ModelDefinitionInput | ComponentDefinitionInput;
|
|
448
|
+
export type EditorChoice = {
|
|
449
|
+
id: string;
|
|
450
|
+
options: Record<string, JsonValue>;
|
|
451
|
+
};
|
|
452
|
+
type FieldBase = {
|
|
453
|
+
id: string;
|
|
454
|
+
apiKey: string;
|
|
455
|
+
label: string;
|
|
456
|
+
description?: string;
|
|
457
|
+
required: boolean;
|
|
458
|
+
localized: boolean;
|
|
459
|
+
public: boolean;
|
|
460
|
+
unique: boolean;
|
|
461
|
+
filterable: boolean;
|
|
462
|
+
sortable: boolean;
|
|
463
|
+
deprecated: boolean;
|
|
464
|
+
defaultValue?: JsonValue;
|
|
465
|
+
editor: EditorChoice;
|
|
466
|
+
};
|
|
467
|
+
/** A normalized field. Narrow on `type` to get typed `settings`. */
|
|
468
|
+
export type FieldDefinition<T extends DataType = DataType> = { [K in T]: FieldBase & {
|
|
469
|
+
type: K;
|
|
470
|
+
settings: DataTypeSettings[K];
|
|
471
|
+
}; }[T];
|
|
472
|
+
export type FieldGroup = {
|
|
473
|
+
id: string;
|
|
474
|
+
label: string;
|
|
475
|
+
fieldIds: string[];
|
|
476
|
+
};
|
|
477
|
+
export type ModelDisplay = {
|
|
478
|
+
titleFieldId?: string;
|
|
479
|
+
listFieldIds?: string[];
|
|
480
|
+
defaultSort?: {
|
|
481
|
+
fieldId: string;
|
|
482
|
+
direction: SortDirection;
|
|
483
|
+
};
|
|
484
|
+
groups?: FieldGroup[];
|
|
485
|
+
canvasFieldIds?: string[];
|
|
486
|
+
coverFieldId?: string;
|
|
487
|
+
stripFieldIds?: string[];
|
|
488
|
+
};
|
|
489
|
+
export type ComponentDisplay = {
|
|
490
|
+
titleFieldId?: string;
|
|
491
|
+
groups?: FieldGroup[];
|
|
492
|
+
};
|
|
493
|
+
export type ModelDefinition = {
|
|
494
|
+
id: string;
|
|
495
|
+
kind: ModelKind;
|
|
496
|
+
apiKey: string;
|
|
497
|
+
/**
|
|
498
|
+
* Present on collections (filled by normalizing), absent on singletons. Read it through `routeKeyOf`,
|
|
499
|
+
* which also covers a stored definition that predates plural API IDs.
|
|
500
|
+
*/
|
|
501
|
+
pluralApiKey?: string;
|
|
502
|
+
label: string;
|
|
503
|
+
description?: string;
|
|
504
|
+
localized: boolean;
|
|
505
|
+
draftAndPublish: boolean;
|
|
506
|
+
fields: FieldDefinition[];
|
|
507
|
+
display: ModelDisplay;
|
|
508
|
+
};
|
|
509
|
+
export type ComponentDefinition = {
|
|
510
|
+
id: string;
|
|
511
|
+
kind: 'component';
|
|
512
|
+
apiKey: string;
|
|
513
|
+
label: string;
|
|
514
|
+
description?: string;
|
|
515
|
+
category?: string;
|
|
516
|
+
icon?: string;
|
|
517
|
+
fields: FieldDefinition[];
|
|
518
|
+
display: ComponentDisplay;
|
|
519
|
+
};
|
|
520
|
+
/** Either kind. Most schema machinery (diff, file format, registry) works on both. */
|
|
521
|
+
export type SchemaDefinition = ModelDefinition | ComponentDefinition;
|
|
522
|
+
export declare const isComponentDefinition: (definition: SchemaDefinition) => definition is ComponentDefinition;
|
|
523
|
+
export declare const isModelDefinition: (definition: SchemaDefinition) => definition is ModelDefinition;
|
|
524
|
+
//#endregion
|
|
525
|
+
//#region src/types/locales.d.ts
|
|
526
|
+
/** BCP 47 subset: language (2–3 letters) plus optional subtags, e.g. `en`, `fr-CA`, `zh-Hant-TW`. */
|
|
527
|
+
export declare const LOCALE_CODE_PATTERN: RegExp;
|
|
528
|
+
export declare const MAX_FALLBACK_CHAIN = 10;
|
|
529
|
+
export declare const LocaleInputSchema: Type.TObject<{
|
|
530
|
+
code: Type.TString;
|
|
531
|
+
label: Type.TString;
|
|
532
|
+
/** Locales tried in order when content is missing in this one; the default locale is always last. */
|
|
533
|
+
fallbacks: Type.TOptional<Type.TArray<Type.TString>>;
|
|
534
|
+
}>;
|
|
535
|
+
export type LocaleInput = Static<typeof LocaleInputSchema>;
|
|
536
|
+
export type LocaleDefinition = {
|
|
537
|
+
code: string;
|
|
538
|
+
label: string;
|
|
539
|
+
isDefault: boolean;
|
|
540
|
+
fallbacks: string[];
|
|
541
|
+
};
|
|
542
|
+
//#endregion
|
|
543
|
+
//#region src/types/valueFormats.d.ts
|
|
544
|
+
/**
|
|
545
|
+
* Canonical text formats for values stored as JSON strings (ADR 0001: dates and times are canonical UTC
|
|
546
|
+
* ISO-8601 text so they index and sort as text; decimal and biginteger are strings so no precision is lost).
|
|
547
|
+
* The content validator (package E) and the settings validators here share these patterns.
|
|
548
|
+
*/
|
|
549
|
+
export declare const DATE_PATTERN: RegExp;
|
|
550
|
+
export declare const DATETIME_PATTERN: RegExp;
|
|
551
|
+
export declare const TIME_PATTERN: RegExp;
|
|
552
|
+
export declare const DECIMAL_PATTERN: RegExp;
|
|
553
|
+
export declare const BIGINTEGER_PATTERN: RegExp;
|
|
554
|
+
/** Lower-case words joined by single hyphens, e.g. `summer-sale-2026`. */
|
|
555
|
+
export declare const SLUG_PATTERN: RegExp;
|
|
556
|
+
/** URL-safe identifier characters for `uid` fields. */
|
|
557
|
+
export declare const UID_PATTERN: RegExp;
|
|
558
|
+
/** The rich-text document envelope version this release reads and writes (ADR 0003). */
|
|
559
|
+
export declare const RICHTEXT_FORMAT = "shapio-richtext";
|
|
560
|
+
export declare const RICHTEXT_FORMAT_VERSION = 1;
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/editors/catalogue.d.ts
|
|
563
|
+
/**
|
|
564
|
+
* Built-in editors (ADR 0009): which data types each can edit and the schema of its options. The server
|
|
565
|
+
* validates every field's editor choice against this catalogue, so selecting and configuring an editor is
|
|
566
|
+
* runtime metadata. Custom editors (loaded at runtime from the project) use namespaced IDs containing a dot,
|
|
567
|
+
* e.g. `acme.colorWheel`; their options are free-form JSON here because their schema lives in the project.
|
|
568
|
+
*/
|
|
569
|
+
export type EditorCatalogueEntry = {
|
|
570
|
+
id: string;
|
|
571
|
+
dataTypes: readonly DataType[];
|
|
572
|
+
optionsSchema: TSchema;
|
|
573
|
+
/** Extra compatibility rule beyond the data type (e.g. radio buttons only for single-value enums). */
|
|
574
|
+
supports?: (field: FieldDefinition) => boolean;
|
|
575
|
+
};
|
|
576
|
+
export declare const EDITOR_CATALOGUE: ReadonlyMap<string, EditorCatalogueEntry>;
|
|
577
|
+
/** The editor a field gets when its definition names none. */
|
|
578
|
+
export declare const DEFAULT_EDITORS: Readonly<Record<DataType, string>>;
|
|
579
|
+
/** `vendor.name` (letters, digits, `_`, `-`), e.g. `acme.colorWheel`. Built-in IDs never contain a dot. */
|
|
580
|
+
export declare const CUSTOM_EDITOR_ID_PATTERN: RegExp;
|
|
581
|
+
export declare const isCustomEditorId: (id: string) => boolean;
|
|
582
|
+
/** Built-in editors that can edit this field (for the admin's editor picker). */
|
|
583
|
+
export declare const listCompatibleEditors: (field: FieldDefinition) => EditorCatalogueEntry[];
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region src/display/layout.d.ts
|
|
586
|
+
/**
|
|
587
|
+
* Data types that can be blocks in the entry document's canvas. Components must also be repeatable and
|
|
588
|
+
* media must hold several files (`isCanvasEligible`); a single component or file is a property.
|
|
589
|
+
*/
|
|
590
|
+
export declare const CANVAS_ELIGIBLE: ReadonlySet<DataType>;
|
|
591
|
+
/** Title types that the document edits inline as its H1; any other title type stays a property. */
|
|
592
|
+
export declare const INLINE_TITLE_TYPES: ReadonlySet<DataType>;
|
|
593
|
+
/** How many properties the strip shows when `display.stripFieldIds` is not set. */
|
|
594
|
+
export declare const STRIP_DEFAULT_COUNT = 5;
|
|
595
|
+
export declare const isCanvasEligible: (field: FieldDefinition) => boolean;
|
|
596
|
+
/** A single `media` field that may hold an image. */
|
|
597
|
+
export declare const isCoverEligible: (field: FieldDefinition) => boolean;
|
|
598
|
+
/** Properties under one of the model's `display.groups` (`label` undefined for ungrouped fields). */
|
|
599
|
+
export type PropertyGroup = {
|
|
600
|
+
id: string;
|
|
601
|
+
label: string | undefined;
|
|
602
|
+
fields: FieldDefinition[];
|
|
603
|
+
};
|
|
604
|
+
export type DocumentLayout = {
|
|
605
|
+
/** The field that labels the entry (`effectiveTitleField`), if any. */
|
|
606
|
+
title: FieldDefinition | undefined;
|
|
607
|
+
/** The title is edited inline as the H1 (string/text); otherwise it is shown read-only and is a property. */
|
|
608
|
+
titleInline: boolean;
|
|
609
|
+
cover: FieldDefinition | undefined;
|
|
610
|
+
/** Canvas blocks, in order. */
|
|
611
|
+
canvas: FieldDefinition[];
|
|
612
|
+
/** Every other live field, ordered by `display.groups`, then field order. */
|
|
613
|
+
properties: FieldDefinition[];
|
|
614
|
+
/** `properties` split by group, for the settings drawer and the property grid. */
|
|
615
|
+
propertyGroups: PropertyGroup[];
|
|
616
|
+
/**
|
|
617
|
+
* The strip's configured properties, or null when it is not configured: then it shows the first
|
|
618
|
+
* STRIP_DEFAULT_COUNT non-empty properties (`stripFieldsOf`), which depends on the entry's values.
|
|
619
|
+
*/
|
|
620
|
+
strip: FieldDefinition[] | null;
|
|
621
|
+
};
|
|
622
|
+
/**
|
|
623
|
+
* The entry document's layout: title, cover, canvas blocks and properties. Configured `display` ids win
|
|
624
|
+
* when they point at live, eligible fields; anything else falls back to the defaults (canvas: every
|
|
625
|
+
* eligible field in field order; cover: the first single image field). Deprecated and ineligible ids are
|
|
626
|
+
* skipped, so a stale definition never breaks the editor. Pure: no values, no I/O.
|
|
627
|
+
*/
|
|
628
|
+
export declare const effectiveLayout: (model: ModelDefinition) => DocumentLayout;
|
|
629
|
+
/** The strip's properties: the configured ones, or the first `limit` that `hasValue` says are non-empty. */
|
|
630
|
+
export declare const stripFieldsOf: (layout: DocumentLayout, hasValue: (field: FieldDefinition) => boolean, limit?: number) => FieldDefinition[];
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region src/display/titleField.d.ts
|
|
633
|
+
/** Field types that may label entries: the only types `display.titleFieldId` accepts, and the fallback's. */
|
|
634
|
+
export declare const TITLE_FIELD_TYPES: ReadonlySet<string>;
|
|
635
|
+
/**
|
|
636
|
+
* Field types the list API's `q` search runs on, checked on top of the title field: an enum title is not
|
|
637
|
+
* searchable. `url` is listed for search only; it is never a title.
|
|
638
|
+
*/
|
|
639
|
+
export declare const TEXT_TITLE_FIELD_TYPES: ReadonlySet<string>;
|
|
640
|
+
/**
|
|
641
|
+
* The field that labels a definition's entries in lists, pickers, relation summaries and search: the
|
|
642
|
+
* configured `display.titleFieldId` when it points at a live (non-deprecated) field, otherwise the first
|
|
643
|
+
* live field of a title type (TITLE_FIELD_TYPES) in field order, otherwise undefined. `titleFieldId` is
|
|
644
|
+
* optional, so a model built without one still gets a readable title.
|
|
645
|
+
*/
|
|
646
|
+
export declare const effectiveTitleField: (definition: SchemaDefinition) => FieldDefinition | undefined;
|
|
647
|
+
//#endregion
|
|
648
|
+
//#region src/naming/plural.d.ts
|
|
649
|
+
/** Appended when English has no distinct plural (`news` → `newsItems`, `series` → `seriesItems`). */
|
|
650
|
+
export declare const PLURAL_FALLBACK_SUFFIX = "Items";
|
|
651
|
+
/**
|
|
652
|
+
* Suggests the plural API ID of a collection from its singular one (`article` → `articles`,
|
|
653
|
+
* `category` → `categories`). Only a suggestion for the admin and the value filled into definitions that
|
|
654
|
+
* predate plural API IDs: the plural is stored on the definition and nothing pluralises at request time.
|
|
655
|
+
* Deterministic for a given `pluralize` version, which is pinned exactly for that reason.
|
|
656
|
+
*/
|
|
657
|
+
export declare const suggestPlural: (apiKey: string) => string;
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region src/normalize.d.ts
|
|
660
|
+
/** Input whose IDs have been assigned (see `assignIds`). */
|
|
661
|
+
type WithIds<T extends {
|
|
662
|
+
id?: string;
|
|
663
|
+
}> = T & {
|
|
664
|
+
id: string;
|
|
665
|
+
};
|
|
666
|
+
export type FieldInputWithId = WithIds<FieldInput>;
|
|
667
|
+
export type DefinitionInputWithIds = (WithIds<ModelDefinitionInput> & {
|
|
668
|
+
fields: FieldInputWithId[];
|
|
669
|
+
}) | (WithIds<ComponentDefinitionInput> & {
|
|
670
|
+
fields: FieldInputWithId[];
|
|
671
|
+
});
|
|
672
|
+
/**
|
|
673
|
+
* Applies defaults so that two definitions meaning the same thing are byte-identical once serialized
|
|
674
|
+
* canonically: a hand-written file that omits `public` hashes the same as one that says `public: true`.
|
|
675
|
+
* Empty optional strings and arrays are dropped. A collection without a plural API ID gets the suggested
|
|
676
|
+
* one (`suggestPlural`), so files and definitions from before plural API IDs keep working.
|
|
677
|
+
*/
|
|
678
|
+
export declare const normalizeDefinition: (input: DefinitionInputWithIds) => SchemaDefinition;
|
|
679
|
+
/** Normalizes a definition that is already normalized-shaped (e.g. read back from the database). */
|
|
680
|
+
export declare const renormalizeDefinition: (definition: SchemaDefinition) => SchemaDefinition;
|
|
681
|
+
//#endregion
|
|
682
|
+
//#region src/validators/issues.d.ts
|
|
683
|
+
/**
|
|
684
|
+
* A problem found in a definition. `path` is a JSON pointer into the definition (`/fields/2/apiKey`), so
|
|
685
|
+
* the admin can put the message next to the control and the CLI can point at the file location.
|
|
686
|
+
*/
|
|
687
|
+
export type ValidationIssue = {
|
|
688
|
+
path: string;
|
|
689
|
+
code: IssueCode;
|
|
690
|
+
message: string;
|
|
691
|
+
/** Set by cross-definition checks so a multi-definition apply can say which file is wrong. */
|
|
692
|
+
definitionId?: string;
|
|
693
|
+
};
|
|
694
|
+
export type IssueCode = 'INVALID_STRUCTURE' | 'INVALID_ID' | 'DUPLICATE_ID' | 'API_KEY_INVALID' | 'API_KEY_TOO_LONG' | 'API_KEY_RESERVED_PREFIX' | 'API_KEY_RESERVED' | 'API_KEY_COLLISION' | 'PLURAL_API_KEY_SAME_AS_SINGULAR' | 'GENERATED_NAME_COLLISION' | 'INVALID_SETTINGS' | 'INVALID_RANGE' | 'INVALID_PATTERN' | 'DUPLICATE_ENUM_VALUE' | 'INVALID_ENUM_VALUE' | 'UNSUPPORTED_FLAG' | 'INVALID_DEFAULT_VALUE' | 'UNKNOWN_EDITOR' | 'INCOMPATIBLE_EDITOR' | 'INVALID_EDITOR_OPTIONS' | 'UNKNOWN_FIELD_REFERENCE' | 'INVALID_FIELD_REFERENCE' | 'DUPLICATE_GROUP' | 'UNKNOWN_REFERENCE' | 'INVALID_REFERENCE_TARGET' | 'COMPONENT_CYCLE' | 'COMPONENT_TOO_DEEP' | 'REFERENCED_DEFINITION';
|
|
695
|
+
export declare class SchemaValidationError extends Error {
|
|
696
|
+
readonly issues: readonly ValidationIssue[];
|
|
697
|
+
constructor(issues: readonly ValidationIssue[]);
|
|
698
|
+
}
|
|
699
|
+
export declare const issue: (path: string, code: IssueCode, message: string) => ValidationIssue;
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/validators/apiKey.d.ts
|
|
702
|
+
/**
|
|
703
|
+
* API keys (models, components, fields) must be valid GraphQL names from day one (CONTRIBUTING.md rule 4),
|
|
704
|
+
* so GraphQL can expose them without renames.
|
|
705
|
+
*/
|
|
706
|
+
export declare const API_KEY_PATTERN: RegExp;
|
|
707
|
+
export declare const MAX_API_KEY_LENGTH = 64;
|
|
708
|
+
export declare const isApiKeySyntaxValid: (key: string) => boolean;
|
|
709
|
+
/**
|
|
710
|
+
* Syntax checks shared by every kind of API key (`noun` names it in messages, e.g. "plural API ID").
|
|
711
|
+
* Returns the first problem, or null.
|
|
712
|
+
*/
|
|
713
|
+
export declare const checkApiKeySyntax: (key: string, noun?: string) => {
|
|
714
|
+
code: IssueCode;
|
|
715
|
+
message: string;
|
|
716
|
+
} | null;
|
|
717
|
+
/** Case-folded form used for every collision check: `Title` and `title` collide. */
|
|
718
|
+
export declare const foldApiKey: (key: string) => string;
|
|
719
|
+
//#endregion
|
|
720
|
+
//#region src/validators/defaultValue.d.ts
|
|
721
|
+
/**
|
|
722
|
+
* Checks that a field's `defaultValue` has the right JSON shape for its type. Full content rules
|
|
723
|
+
* (lengths, ranges) are applied by the content validator when the default is used.
|
|
724
|
+
* Returns a message, or null when the default is acceptable.
|
|
725
|
+
*/
|
|
726
|
+
export declare const checkDefaultValue: (field: FieldDefinition) => string | null;
|
|
727
|
+
//#endregion
|
|
728
|
+
//#region src/validators/definition.d.ts
|
|
729
|
+
/** All problems with one definition, in document order. Empty means valid on its own. */
|
|
730
|
+
export declare const validateDefinition: (definition: SchemaDefinition) => ValidationIssue[];
|
|
731
|
+
//#endregion
|
|
732
|
+
//#region src/validators/naming.d.ts
|
|
733
|
+
/**
|
|
734
|
+
* The names GraphQL (package J) and other generated contracts derive from API keys. Defined here, with the
|
|
735
|
+
* validator, so a name that would collide in GraphQL is rejected when the model is saved, never later.
|
|
736
|
+
* Package J must generate exactly these names.
|
|
737
|
+
*/
|
|
738
|
+
export declare const toTypeName: (apiKey: string) => string;
|
|
739
|
+
/** Suffixes of the types generated for each model (`Page`, `PageFilter`, `PageInput`...). */
|
|
740
|
+
export declare const MODEL_TYPE_SUFFIXES: readonly ["", "Filter", "Input", "Connection", "Edge", "Localizations", "Sort"];
|
|
741
|
+
export declare const COMPONENT_TYPE_SUFFIXES: readonly ["", "Input"];
|
|
742
|
+
export declare const MUTATION_PREFIXES: readonly ["create", "update", "delete", "publish", "unpublish"];
|
|
743
|
+
/**
|
|
744
|
+
* The key a model is addressed by in the delivery API (`/api/content/:routeKey`, preview routes): the
|
|
745
|
+
* plural API ID of a collection, the API ID of a singleton. A collection stored before plural API IDs
|
|
746
|
+
* existed gets the same derived value normalizing would fill, so this never returns undefined.
|
|
747
|
+
*/
|
|
748
|
+
export declare const routeKeyOf: (definition: ModelDefinition) => string;
|
|
749
|
+
/** GraphQL list query of a collection (`articles(…)`): its plural API ID. */
|
|
750
|
+
export declare const collectionQueryName: (definition: ModelDefinition) => string;
|
|
751
|
+
/**
|
|
752
|
+
* A stored definition with the plural API ID a collection gets when it predates them (the value
|
|
753
|
+
* normalizing fills), and whether anything was filled. Callers that hash definitions re-hash when `filled`.
|
|
754
|
+
*/
|
|
755
|
+
export declare const withDerivedPlural: <T extends SchemaDefinition>(definition: T) => {
|
|
756
|
+
definition: T;
|
|
757
|
+
filled: boolean;
|
|
758
|
+
};
|
|
759
|
+
/**
|
|
760
|
+
* GraphQL built-ins and the fixed types Shapio's schema always defines. Compared case-folded, so a model
|
|
761
|
+
* called `string` (type `String`) or `pageInfo` is rejected.
|
|
762
|
+
*/
|
|
763
|
+
export declare const RESERVED_TYPE_NAMES: readonly string[];
|
|
764
|
+
/**
|
|
765
|
+
* Model and component API keys the admin uses as static URL segments (`/content/new`,
|
|
766
|
+
* `/develop/components`), so a type with one of these keys could not be opened. Compared case-folded.
|
|
767
|
+
*/
|
|
768
|
+
export declare const RESERVED_DEFINITION_API_KEYS: readonly string[];
|
|
769
|
+
/**
|
|
770
|
+
* Field API keys that collide with the system fields every entry exposes in REST and GraphQL.
|
|
771
|
+
* Compared case-folded.
|
|
772
|
+
*/
|
|
773
|
+
export declare const RESERVED_FIELD_API_KEYS: readonly string[];
|
|
774
|
+
/**
|
|
775
|
+
* Root query fields every GraphQL schema has, whatever models exist. Compared case-folded. The first is the
|
|
776
|
+
* schema version field (schemaBuilder.ts reads it by position).
|
|
777
|
+
*/
|
|
778
|
+
export declare const RESERVED_QUERY_NAMES: readonly string[];
|
|
779
|
+
export type NameNamespace = 'type' | 'query' | 'mutation';
|
|
780
|
+
export type GeneratedName = {
|
|
781
|
+
namespace: NameNamespace;
|
|
782
|
+
name: string;
|
|
783
|
+
/** Which definition (and field, for per-field types) produced the name, for error messages. */
|
|
784
|
+
definitionId: string;
|
|
785
|
+
fieldId?: string;
|
|
786
|
+
/** The definition property the name comes from, when not `apiKey` (for the issue path). */
|
|
787
|
+
property?: 'pluralApiKey';
|
|
788
|
+
};
|
|
789
|
+
/** Every name a definition contributes to the GraphQL schema. */
|
|
790
|
+
export declare const generatedNames: (definition: SchemaDefinition) => GeneratedName[];
|
|
791
|
+
//#endregion
|
|
792
|
+
//#region src/validators/parse.d.ts
|
|
793
|
+
export type ParseDefinitionResult = {
|
|
794
|
+
ok: true;
|
|
795
|
+
definition: SchemaDefinition;
|
|
796
|
+
} | {
|
|
797
|
+
ok: false;
|
|
798
|
+
issues: ValidationIssue[];
|
|
799
|
+
};
|
|
800
|
+
export type ParseDefinitionOptions = {
|
|
801
|
+
/**
|
|
802
|
+
* The current definition, if this is an edit. Fields sent without an `id` are matched to the current
|
|
803
|
+
* field with the same API key (case-insensitive) and keep its ID; unmatched fields get a new ID.
|
|
804
|
+
*/
|
|
805
|
+
previous?: SchemaDefinition;
|
|
806
|
+
createId?: () => string;
|
|
807
|
+
};
|
|
808
|
+
/** Fills in missing definition and field IDs (see ParseDefinitionOptions.previous). */
|
|
809
|
+
export declare const assignIds: (input: DefinitionInput, { previous, createId }?: ParseDefinitionOptions) => DefinitionInputWithIds;
|
|
810
|
+
/**
|
|
811
|
+
* The single entry point from untrusted input (request bodies, schema files) to a normalized definition:
|
|
812
|
+
* structure, settings per data type, ID assignment, defaults, then the per-definition semantic checks.
|
|
813
|
+
* Cross-definition rules (references, cycles, GraphQL name collisions) are `validateSchema`'s job.
|
|
814
|
+
*/
|
|
815
|
+
export declare const parseDefinition: (raw: unknown, options?: ParseDefinitionOptions) => ParseDefinitionResult;
|
|
816
|
+
//#endregion
|
|
817
|
+
//#region src/validators/schema.d.ts
|
|
818
|
+
/** Deepest allowed nesting of components (a component inside a component inside ... ) below an entry. */
|
|
819
|
+
export declare const MAX_COMPONENT_DEPTH = 5;
|
|
820
|
+
/** Component IDs a definition embeds directly (component and dynamic-zone fields). */
|
|
821
|
+
export declare const embeddedComponentIds: (definition: SchemaDefinition) => string[];
|
|
822
|
+
/**
|
|
823
|
+
* Cross-definition checks over a whole schema (every active definition with the proposed change applied):
|
|
824
|
+
* unique IDs and API keys, GraphQL name collisions, relation and component targets, component cycles and
|
|
825
|
+
* nesting depth. Each definition must already pass `validateDefinition`.
|
|
826
|
+
*/
|
|
827
|
+
export declare const validateSchema: (definitions: readonly SchemaDefinition[]) => ValidationIssue[];
|
|
828
|
+
/** Definitions that reference `id` (relations, components, dynamic zones). Used to block deletions. */
|
|
829
|
+
export declare const findReferencingDefinitions: (definitions: readonly SchemaDefinition[], id: string) => SchemaDefinition[];
|
|
830
|
+
/**
|
|
831
|
+
* Models that embed a component directly or through other components, i.e. whose entries change shape
|
|
832
|
+
* when the component does (brief §5: "editing a shared component must account for all dependent models").
|
|
833
|
+
*/
|
|
834
|
+
export declare const findDependentModels: (definitions: readonly SchemaDefinition[], componentId: string) => SchemaDefinition[];
|
|
835
|
+
//#endregion
|
|
836
|
+
//#region src/diff/types.d.ts
|
|
837
|
+
export declare const CHANGE_KINDS: readonly ["definition.added", "definition.removed", "definition.apiKey", "definition.pluralApiKey", "definition.metadata", "model.kind", "model.localized", "model.draftAndPublish", "field.added", "field.removed", "field.order", "field.apiKey", "field.metadata", "field.editor", "field.type", "field.required", "field.localized", "field.public", "field.deprecated", "field.unique", "field.filterable", "field.sortable", "field.settings"];
|
|
838
|
+
export type ChangeKind = (typeof CHANGE_KINDS)[number];
|
|
839
|
+
/**
|
|
840
|
+
* One difference between two versions of a definition. `property` names the changed property for
|
|
841
|
+
* `*.metadata` (`label`, `description`, ...) and `field.settings` (the settings key); `from`/`to` hold the
|
|
842
|
+
* old and new values (absent when the side does not exist).
|
|
843
|
+
*/
|
|
844
|
+
export type SchemaChange = {
|
|
845
|
+
kind: ChangeKind;
|
|
846
|
+
definitionId: string;
|
|
847
|
+
fieldId?: string;
|
|
848
|
+
property?: string;
|
|
849
|
+
from?: JsonValue;
|
|
850
|
+
to?: JsonValue;
|
|
851
|
+
};
|
|
852
|
+
//#endregion
|
|
853
|
+
//#region src/diff/diffDefinitions.d.ts
|
|
854
|
+
/**
|
|
855
|
+
* Every difference between two versions of a definition, matched by stable IDs (so a renamed field is a
|
|
856
|
+
* rename, not a removal plus an addition). Pass `null` for a side that does not exist. The order is
|
|
857
|
+
* deterministic: definition-level changes, then fields in the new order, then removed fields.
|
|
858
|
+
*/
|
|
859
|
+
export declare const diffDefinitions: (before: SchemaDefinition | null, after: SchemaDefinition | null) => SchemaChange[];
|
|
860
|
+
//#endregion
|
|
861
|
+
//#region src/diff/classify.d.ts
|
|
862
|
+
/**
|
|
863
|
+
* Change classification (brief §5, ADR 0002). Each change gets a category, whether it needs prerequisite
|
|
864
|
+
* work before the new revision can be activated, and flags for breaking API contracts and destroying data.
|
|
865
|
+
*
|
|
866
|
+
* | Brief §5 row | category | activation |
|
|
867
|
+
* | ------------------------------------ | -------------------------------- | ----------------------------- |
|
|
868
|
+
* | Label, help text, editor layout | metadata | immediate |
|
|
869
|
+
* | Compatible editor swap | editorSwap | immediate |
|
|
870
|
+
* | Optional field or new model | additive | immediate (live) |
|
|
871
|
+
* | Required field | requiredField | validateRequired / backfill |
|
|
872
|
+
* | Type or rich-text format conversion | conversion | convert (+ validate) |
|
|
873
|
+
* | API key rename | contract (breaking) | immediate, needs ack |
|
|
874
|
+
* | Field removal | removal (breaking, data kept) | immediate, needs ack |
|
|
875
|
+
* | Unique/index constraint | constraint / index | checkUnique / buildIndex |
|
|
876
|
+
* | (plus) type change of a unique field | as its type change | + checkUnique |
|
|
877
|
+
* | (plus) tightened validation rules | validation | validateValues |
|
|
878
|
+
* | (plus) localized true→false | conversion (destructive) | convert |
|
|
879
|
+
*/
|
|
880
|
+
export type ChangeCategory = 'metadata' | 'editorSwap' | 'additive' | 'requiredField' | 'validation' | 'conversion' | 'contract' | 'removal' | 'constraint' | 'index';
|
|
881
|
+
/** Work that must succeed before activation. Content-touching kinds run through the content ports. */
|
|
882
|
+
export type PrerequisiteKind = 'validateRequired' | 'backfill' | 'validateValues' | 'checkUnique' | 'buildIndex' | 'convert';
|
|
883
|
+
/** Work done after activation, once nothing reads the old structure. */
|
|
884
|
+
export type CleanupKind = 'dropIndex' | 'releaseUnique';
|
|
885
|
+
export type ClassifiedChange = SchemaChange & {
|
|
886
|
+
category: ChangeCategory;
|
|
887
|
+
/** Changes the public API contract (REST/GraphQL shape or visibility); needs explicit acknowledgement. */
|
|
888
|
+
breaking: boolean;
|
|
889
|
+
/** Loses stored information that a schema rollback cannot restore. */
|
|
890
|
+
destructive: boolean;
|
|
891
|
+
/** False for conversions Shapio cannot perform; the planner refuses them. */
|
|
892
|
+
supported: boolean;
|
|
893
|
+
prerequisites: PrerequisiteKind[];
|
|
894
|
+
cleanup: CleanupKind[];
|
|
895
|
+
};
|
|
896
|
+
export type ClassifyContext = {
|
|
897
|
+
before: SchemaDefinition | null;
|
|
898
|
+
after: SchemaDefinition | null;
|
|
899
|
+
};
|
|
900
|
+
/** Classifies one change in the context of the definitions it was diffed from. */
|
|
901
|
+
export declare const classifyChange: (change: SchemaChange, context: ClassifyContext) => ClassifiedChange;
|
|
902
|
+
export declare const classifyChanges: (changes: readonly SchemaChange[], context: ClassifyContext) => ClassifiedChange[];
|
|
903
|
+
export type ChangeSummary = {
|
|
904
|
+
breaking: boolean;
|
|
905
|
+
destructive: boolean;
|
|
906
|
+
supported: boolean;
|
|
907
|
+
prerequisites: PrerequisiteKind[];
|
|
908
|
+
cleanup: CleanupKind[];
|
|
909
|
+
/** True when nothing but metadata/editor changes are involved. */
|
|
910
|
+
metadataOnly: boolean;
|
|
911
|
+
};
|
|
912
|
+
export declare const summarizeChanges: (changes: readonly ClassifiedChange[]) => ChangeSummary;
|
|
913
|
+
/** Locale configuration changes (ADR 0002/0004). Locales are global, so they are planned on their own. */
|
|
914
|
+
export type LocaleChangeKind = 'locale.added' | 'locale.removed' | 'locale.defaultChanged' | 'locale.metadata';
|
|
915
|
+
export type ClassifiedLocaleChange = {
|
|
916
|
+
kind: LocaleChangeKind;
|
|
917
|
+
code: string;
|
|
918
|
+
category: ChangeCategory;
|
|
919
|
+
breaking: boolean;
|
|
920
|
+
destructive: boolean;
|
|
921
|
+
};
|
|
922
|
+
/**
|
|
923
|
+
* - added: live (entries gain the locale as editors fill it in);
|
|
924
|
+
* - removed: destroys that locale's content (purged by a follow-up job), so it needs explicit consent;
|
|
925
|
+
* - default changed: delivery falls back to a different locale, a contract change for API consumers;
|
|
926
|
+
* - label or fallback chain: metadata.
|
|
927
|
+
*/
|
|
928
|
+
export declare const classifyLocaleChange: (kind: LocaleChangeKind, code: string) => ClassifiedLocaleChange;
|
|
929
|
+
//#endregion
|
|
930
|
+
//#region src/fileFormat/canonical.d.ts
|
|
931
|
+
export declare const canonicalJson: (value: unknown) => string;
|
|
932
|
+
/**
|
|
933
|
+
* The bytes Shapio hashes and writes for a definition. The definition must be normalized
|
|
934
|
+
* (`normalizeDefinition`), so defaults are explicit and the same meaning has one spelling.
|
|
935
|
+
*/
|
|
936
|
+
export declare const serializeDefinition: (definition: SchemaDefinition) => string;
|
|
937
|
+
//#endregion
|
|
938
|
+
//#region src/fileFormat/hash.d.ts
|
|
939
|
+
/** `sha256:<hex>` of a UTF-8 string, via Web Crypto (Node and browsers alike). */
|
|
940
|
+
export declare const sha256: (text: string) => Promise<string>;
|
|
941
|
+
/** Hash of a normalized definition's canonical serialization; recorded per model in the lock file. */
|
|
942
|
+
export declare const hashDefinition: (definition: SchemaDefinition) => Promise<string>;
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region src/fileFormat/lockFile.d.ts
|
|
945
|
+
/** Where `shapio schema pull` keeps its record of what it pulled, relative to the project root. */
|
|
946
|
+
export declare const LOCK_FILE_PATH = ".shapio/schema-lock.json";
|
|
947
|
+
export declare const LOCK_FILE_FORMAT_VERSION = 1;
|
|
948
|
+
export declare const LockEntrySchema: Type.TObject<{
|
|
949
|
+
kind: Type.TEnum<["collection", "singleton", "component"]>;
|
|
950
|
+
apiKey: Type.TString;
|
|
951
|
+
/** The definition's per-model version on the instance when it was pulled. */
|
|
952
|
+
version: Type.TInteger;
|
|
953
|
+
/** `hashDefinition` of the pulled definition; a local file with another hash has been edited. */
|
|
954
|
+
hash: Type.TString;
|
|
955
|
+
}>;
|
|
956
|
+
export declare const LockFileSchema: Type.TObject<{
|
|
957
|
+
formatVersion: Type.TLiteral<1>;
|
|
958
|
+
/** The instance's global schema version at pull time (informational; the guard is per model). */
|
|
959
|
+
schemaVersion: Type.TInteger;
|
|
960
|
+
/** Keyed by definition ID. */
|
|
961
|
+
definitions: Type.TRecord<"^.*$", Type.TObject<{
|
|
962
|
+
kind: Type.TEnum<["collection", "singleton", "component"]>;
|
|
963
|
+
apiKey: Type.TString;
|
|
964
|
+
/** The definition's per-model version on the instance when it was pulled. */
|
|
965
|
+
version: Type.TInteger;
|
|
966
|
+
/** `hashDefinition` of the pulled definition; a local file with another hash has been edited. */
|
|
967
|
+
hash: Type.TString;
|
|
968
|
+
}>>;
|
|
969
|
+
}>;
|
|
970
|
+
export type LockEntry = Static<typeof LockEntrySchema>;
|
|
971
|
+
export type LockFile = Static<typeof LockFileSchema>;
|
|
972
|
+
export declare class LockFileError extends Error {
|
|
973
|
+
constructor(message: string);
|
|
974
|
+
}
|
|
975
|
+
export declare const parseLockFile: (text: string) => LockFile;
|
|
976
|
+
export declare const serializeLockFile: (lock: LockFile) => string;
|
|
977
|
+
//#endregion
|
|
978
|
+
//#region src/sync/threeWay.d.ts
|
|
979
|
+
/**
|
|
980
|
+
* Per-definition three-way decision for `shapio schema apply` (ADR 0002). The base is what the lock file
|
|
981
|
+
* recorded at pull time; local is the file in the repository; remote is the target instance now.
|
|
982
|
+
*
|
|
983
|
+
* - unchanged locally → skip, even if the target changed it since (no silent revert of production edits);
|
|
984
|
+
* - changed locally and unchanged on the target → apply, guarded by the target's current version;
|
|
985
|
+
* - changed on both → refuse and show the diff, like a rejected non-fast-forward push.
|
|
986
|
+
*
|
|
987
|
+
* Deletions need `prune`. Identical content on both sides is never a conflict.
|
|
988
|
+
*/
|
|
989
|
+
export type SyncSide = {
|
|
990
|
+
version: number;
|
|
991
|
+
hash: string;
|
|
992
|
+
};
|
|
993
|
+
export type SyncLocal = {
|
|
994
|
+
definition: SchemaDefinition;
|
|
995
|
+
hash: string;
|
|
996
|
+
};
|
|
997
|
+
export type SyncRemote = SyncSide & {
|
|
998
|
+
definition: SchemaDefinition;
|
|
999
|
+
};
|
|
1000
|
+
export type SyncInput = {
|
|
1001
|
+
local?: SyncLocal;
|
|
1002
|
+
base?: SyncSide;
|
|
1003
|
+
remote?: SyncRemote;
|
|
1004
|
+
prune: boolean;
|
|
1005
|
+
};
|
|
1006
|
+
export type SyncSkipReason = 'unchangedLocally' | 'alreadyApplied' | 'remoteOnly' | 'deletedLocallyWithoutPrune' | 'alreadyDeleted';
|
|
1007
|
+
export type SyncConflictReason = 'changedOnBoth' | 'deletedOnTarget' | 'existsOnTarget' | 'changedOnTargetBeforeDelete';
|
|
1008
|
+
export type SyncDecision = {
|
|
1009
|
+
action: 'skip';
|
|
1010
|
+
reason: SyncSkipReason;
|
|
1011
|
+
} | {
|
|
1012
|
+
action: 'create';
|
|
1013
|
+
} | {
|
|
1014
|
+
action: 'update';
|
|
1015
|
+
expectedVersion: number;
|
|
1016
|
+
} | {
|
|
1017
|
+
action: 'delete';
|
|
1018
|
+
expectedVersion: number;
|
|
1019
|
+
} | {
|
|
1020
|
+
action: 'conflict';
|
|
1021
|
+
reason: SyncConflictReason;
|
|
1022
|
+
};
|
|
1023
|
+
export declare const decideSync: ({ local, base, remote, prune }: SyncInput) => SyncDecision;
|
|
1024
|
+
//#endregion
|
|
1025
|
+
//#region src/richtext/spec.d.ts
|
|
1026
|
+
/**
|
|
1027
|
+
* The rich-text document schema (ADR 0003, build plan §3.9): the baseline Tiptap/ProseMirror node and mark
|
|
1028
|
+
* set, as data. The server validates stored documents against it and renders HTML from it; the admin's
|
|
1029
|
+
* Tiptap editor (package F) must produce exactly these nodes, marks and attributes. Anything else is
|
|
1030
|
+
* rejected, never stripped silently.
|
|
1031
|
+
*
|
|
1032
|
+
* Why a hand-written structural check instead of `@tiptap/core` headless: the baseline is small and fixed,
|
|
1033
|
+
* the check needs no DOM shim, no extra runtime dependency on the server and no schema instantiation per
|
|
1034
|
+
* request, and it reports precise paths. Package F keeps the editor in step with this module (it is the
|
|
1035
|
+
* contract), and the shared JSON fixtures in this module's tests are what both sides agree on.
|
|
1036
|
+
*/
|
|
1037
|
+
export type AttrRule = {
|
|
1038
|
+
/** Returns true when the value is acceptable. `null` is checked like any other value. */
|
|
1039
|
+
check: (value: unknown) => boolean;
|
|
1040
|
+
/** Used when the attribute is absent. */
|
|
1041
|
+
default: unknown;
|
|
1042
|
+
};
|
|
1043
|
+
export type NodeRule = {
|
|
1044
|
+
/** `block` nodes sit in block containers; `inline` nodes in textblocks. */
|
|
1045
|
+
group: 'block' | 'inline' | 'listItem' | 'tableRow' | 'tableCell' | 'doc';
|
|
1046
|
+
/** Allowed child groups or node types, and the minimum number of children. */
|
|
1047
|
+
content?: {
|
|
1048
|
+
allow: readonly string[];
|
|
1049
|
+
min: number;
|
|
1050
|
+
};
|
|
1051
|
+
attrs?: Readonly<Record<string, AttrRule>>;
|
|
1052
|
+
/** Inline children may carry marks (text nodes in a textblock); false for code blocks. */
|
|
1053
|
+
marks?: boolean;
|
|
1054
|
+
};
|
|
1055
|
+
/** `href` values a link may carry: web, mail and phone links, and same-document/relative paths. */
|
|
1056
|
+
export declare const SAFE_LINK_PATTERN: RegExp;
|
|
1057
|
+
export declare const NODE_RULES: Readonly<Record<string, NodeRule>>;
|
|
1058
|
+
export type MarkRule = {
|
|
1059
|
+
attrs?: Readonly<Record<string, AttrRule>>;
|
|
1060
|
+
};
|
|
1061
|
+
export declare const MARK_RULES: Readonly<Record<string, MarkRule>>;
|
|
1062
|
+
/** Nesting deeper than this is rejected (keeps validation and rendering bounded). */
|
|
1063
|
+
export declare const MAX_RICHTEXT_DEPTH = 40;
|
|
1064
|
+
//#endregion
|
|
1065
|
+
//#region src/richtext/render.d.ts
|
|
1066
|
+
/**
|
|
1067
|
+
* HTML rendering of stored rich text for delivery (ADR 0003). Rendered from JSON only, never from stored
|
|
1068
|
+
* HTML: every tag comes from this allow-list and every text and attribute value is escaped, so the output
|
|
1069
|
+
* is safe whatever the document held. Deterministic, so a site built at a pinned snapshot gets the same HTML.
|
|
1070
|
+
*/
|
|
1071
|
+
/** Resolves an image's media ID to a deliverable URL; undefined when the asset is missing or not visible. */
|
|
1072
|
+
export type MediaUrlResolver = (mediaId: string) => string | undefined;
|
|
1073
|
+
export declare const escapeHtml: (value: string) => string;
|
|
1074
|
+
/** Renders a validated document to sanitized HTML. */
|
|
1075
|
+
export declare const renderRichTextHtml: (document: RichTextDocument, resolveMedia?: MediaUrlResolver) => string;
|
|
1076
|
+
/** The document's text, blocks separated by blank lines (rich text → plain text conversions, search). */
|
|
1077
|
+
export declare const richTextToPlainText: (document: RichTextDocument) => string;
|
|
1078
|
+
/** Plain text → a document of paragraphs (blank lines split paragraphs; single newlines are hard breaks). */
|
|
1079
|
+
export declare const plainTextToRichText: (text: string) => RichTextDocument;
|
|
1080
|
+
//#endregion
|
|
1081
|
+
export { RichTextDocument, RichTextNode, RichTextProblem, RichTextResult, isEmptyRichText, richTextMediaIds, validateRichText };
|
|
1082
|
+
//# sourceMappingURL=index.d.ts.map
|