@shapio/schema 0.5.4 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +96 -3
- package/dist/index.js +147 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -181,6 +181,19 @@ export declare const DEFINITION_KINDS: readonly ["collection", "singleton", "com
|
|
|
181
181
|
export type DefinitionKind = (typeof DEFINITION_KINDS)[number];
|
|
182
182
|
export declare const SORT_DIRECTIONS: readonly ["asc", "desc"];
|
|
183
183
|
export type SortDirection = (typeof SORT_DIRECTIONS)[number];
|
|
184
|
+
/**
|
|
185
|
+
* How an entry of a model opens: `document` (title typed into the page, blocks in a canvas, properties in
|
|
186
|
+
* the strip and the settings drawer) or `form` (every field inline at its `width`, sections from
|
|
187
|
+
* `display.groups`). Unset means `document`; normalizing drops an explicit `document`.
|
|
188
|
+
*/
|
|
189
|
+
export declare const ENTRY_LAYOUTS: readonly ["document", "form"];
|
|
190
|
+
export type EntryLayout = (typeof ENTRY_LAYOUTS)[number];
|
|
191
|
+
/**
|
|
192
|
+
* A field's width in a form-layout entry, as a share of the row: rows fill in field order and wrap. Unset
|
|
193
|
+
* means `full`; normalizing drops an explicit `full`.
|
|
194
|
+
*/
|
|
195
|
+
export declare const FIELD_WIDTHS: readonly ["full", "two-thirds", "half", "third"];
|
|
196
|
+
export type FieldWidth = (typeof FIELD_WIDTHS)[number];
|
|
184
197
|
export declare const MAX_FIELDS_PER_DEFINITION = 500;
|
|
185
198
|
export declare const MAX_LABEL_LENGTH = 200;
|
|
186
199
|
export declare const MAX_DESCRIPTION_LENGTH = 2000;
|
|
@@ -213,8 +226,15 @@ export declare const FieldInputSchema: Type.TObject<{
|
|
|
213
226
|
id: Type.TString;
|
|
214
227
|
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
215
228
|
}>>;
|
|
229
|
+
/**
|
|
230
|
+
* Width in a form-layout entry (`display.layout: 'form'`). Default `full`. Model fields only: the
|
|
231
|
+
* validator refuses it on a component's field.
|
|
232
|
+
*/
|
|
233
|
+
width: Type.TOptional<Type.TEnum<["full", "two-thirds", "half", "third"]>>;
|
|
216
234
|
}>;
|
|
217
235
|
export declare const ModelDisplayInputSchema: Type.TObject<{
|
|
236
|
+
/** How entries open: `document` (default) or `form`. See `ENTRY_LAYOUTS`. */
|
|
237
|
+
layout: Type.TOptional<Type.TEnum<["document", "form"]>>;
|
|
218
238
|
/** Field whose value labels an entry in lists, pickers and relation summaries. */
|
|
219
239
|
titleFieldId: Type.TOptional<Type.TString>;
|
|
220
240
|
/** Columns of the content list, in order. */
|
|
@@ -255,6 +275,8 @@ export declare const ModelDefinitionInputSchema: Type.TObject<{
|
|
|
255
275
|
/** Entries have separate draft and published states. Default true. */
|
|
256
276
|
draftAndPublish: Type.TOptional<Type.TBoolean>;
|
|
257
277
|
display: Type.TOptional<Type.TObject<{
|
|
278
|
+
/** How entries open: `document` (default) or `form`. See `ENTRY_LAYOUTS`. */
|
|
279
|
+
layout: Type.TOptional<Type.TEnum<["document", "form"]>>;
|
|
258
280
|
/** Field whose value labels an entry in lists, pickers and relation summaries. */
|
|
259
281
|
titleFieldId: Type.TOptional<Type.TString>;
|
|
260
282
|
/** Columns of the content list, in order. */
|
|
@@ -305,6 +327,11 @@ export declare const ModelDefinitionInputSchema: Type.TObject<{
|
|
|
305
327
|
id: Type.TString;
|
|
306
328
|
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
307
329
|
}>>;
|
|
330
|
+
/**
|
|
331
|
+
* Width in a form-layout entry (`display.layout: 'form'`). Default `full`. Model fields only: the
|
|
332
|
+
* validator refuses it on a component's field.
|
|
333
|
+
*/
|
|
334
|
+
width: Type.TOptional<Type.TEnum<["full", "two-thirds", "half", "third"]>>;
|
|
308
335
|
}>>;
|
|
309
336
|
}>;
|
|
310
337
|
export declare const ComponentDefinitionInputSchema: Type.TObject<{
|
|
@@ -348,6 +375,11 @@ export declare const ComponentDefinitionInputSchema: Type.TObject<{
|
|
|
348
375
|
id: Type.TString;
|
|
349
376
|
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
350
377
|
}>>;
|
|
378
|
+
/**
|
|
379
|
+
* Width in a form-layout entry (`display.layout: 'form'`). Default `full`. Model fields only: the
|
|
380
|
+
* validator refuses it on a component's field.
|
|
381
|
+
*/
|
|
382
|
+
width: Type.TOptional<Type.TEnum<["full", "two-thirds", "half", "third"]>>;
|
|
351
383
|
}>>;
|
|
352
384
|
}>;
|
|
353
385
|
export declare const DefinitionInputSchema: Type.TUnion<[Type.TObject<{
|
|
@@ -361,6 +393,8 @@ export declare const DefinitionInputSchema: Type.TUnion<[Type.TObject<{
|
|
|
361
393
|
/** Entries have separate draft and published states. Default true. */
|
|
362
394
|
draftAndPublish: Type.TOptional<Type.TBoolean>;
|
|
363
395
|
display: Type.TOptional<Type.TObject<{
|
|
396
|
+
/** How entries open: `document` (default) or `form`. See `ENTRY_LAYOUTS`. */
|
|
397
|
+
layout: Type.TOptional<Type.TEnum<["document", "form"]>>;
|
|
364
398
|
/** Field whose value labels an entry in lists, pickers and relation summaries. */
|
|
365
399
|
titleFieldId: Type.TOptional<Type.TString>;
|
|
366
400
|
/** Columns of the content list, in order. */
|
|
@@ -411,6 +445,11 @@ export declare const DefinitionInputSchema: Type.TUnion<[Type.TObject<{
|
|
|
411
445
|
id: Type.TString;
|
|
412
446
|
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
413
447
|
}>>;
|
|
448
|
+
/**
|
|
449
|
+
* Width in a form-layout entry (`display.layout: 'form'`). Default `full`. Model fields only: the
|
|
450
|
+
* validator refuses it on a component's field.
|
|
451
|
+
*/
|
|
452
|
+
width: Type.TOptional<Type.TEnum<["full", "two-thirds", "half", "third"]>>;
|
|
414
453
|
}>>;
|
|
415
454
|
}>, Type.TObject<{
|
|
416
455
|
kind: Type.TLiteral<"component">;
|
|
@@ -453,6 +492,11 @@ export declare const DefinitionInputSchema: Type.TUnion<[Type.TObject<{
|
|
|
453
492
|
id: Type.TString;
|
|
454
493
|
options: Type.TOptional<Type.TRecord<"^.*$", Type.TUnknown>>;
|
|
455
494
|
}>>;
|
|
495
|
+
/**
|
|
496
|
+
* Width in a form-layout entry (`display.layout: 'form'`). Default `full`. Model fields only: the
|
|
497
|
+
* validator refuses it on a component's field.
|
|
498
|
+
*/
|
|
499
|
+
width: Type.TOptional<Type.TEnum<["full", "two-thirds", "half", "third"]>>;
|
|
456
500
|
}>>;
|
|
457
501
|
}>]>;
|
|
458
502
|
export type FieldInput = Static<typeof FieldInputSchema>;
|
|
@@ -477,6 +521,8 @@ type FieldBase = {
|
|
|
477
521
|
deprecated: boolean;
|
|
478
522
|
defaultValue?: JsonValue;
|
|
479
523
|
editor: EditorChoice;
|
|
524
|
+
/** Width in a form-layout entry; absent means `full`. */
|
|
525
|
+
width?: FieldWidth;
|
|
480
526
|
};
|
|
481
527
|
/** A normalized field. Narrow on `type` to get typed `settings`. */
|
|
482
528
|
export type FieldDefinition<T extends DataType = DataType> = { [K in T]: FieldBase & {
|
|
@@ -489,6 +535,8 @@ export type FieldGroup = {
|
|
|
489
535
|
fieldIds: string[];
|
|
490
536
|
};
|
|
491
537
|
export type ModelDisplay = {
|
|
538
|
+
/** Absent means `document`. */
|
|
539
|
+
layout?: EntryLayout;
|
|
492
540
|
titleFieldId?: string;
|
|
493
541
|
listFieldIds?: string[];
|
|
494
542
|
defaultSort?: {
|
|
@@ -657,6 +705,34 @@ export type DocumentLayout = {
|
|
|
657
705
|
export declare const effectiveLayout: (model: ModelDefinition) => DocumentLayout;
|
|
658
706
|
/** The strip's properties: the configured ones, or the first `limit` that `hasValue` says are non-empty. */
|
|
659
707
|
export declare const stripFieldsOf: (layout: DocumentLayout, hasValue: (field: FieldDefinition) => boolean, limit?: number) => FieldDefinition[];
|
|
708
|
+
/** How the model's entries open (`display.layout`): `document` unless set to `form`. */
|
|
709
|
+
export declare const entryLayoutOf: (model: ModelDefinition) => EntryLayout;
|
|
710
|
+
/**
|
|
711
|
+
* One section of a form-layout entry: a `display.groups` group (its `id` and `label`), or a run of
|
|
712
|
+
* consecutive ungrouped fields (`label` undefined). A run's `id` is `ungrouped-<ID of its first field>`:
|
|
713
|
+
* stable while that field stays first, and unique because a field starts one run at most. Should a group
|
|
714
|
+
* be given that very ID, the run's ID gets `~` prefixed until no group has it.
|
|
715
|
+
*/
|
|
716
|
+
export type FieldSection = PropertyGroup;
|
|
717
|
+
export type FormLayout = {
|
|
718
|
+
/** The field that labels the entry (`effectiveTitleField`): the page heading. It is also in `sections`. */
|
|
719
|
+
title: FieldDefinition | undefined;
|
|
720
|
+
/** Every live field, in sections. */
|
|
721
|
+
sections: FieldSection[];
|
|
722
|
+
};
|
|
723
|
+
/**
|
|
724
|
+
* The entry form's layout (`display.layout: 'form'`): every live field, the title included, in sections.
|
|
725
|
+
* Field order decides everything: a group's section sits where its first field appears, with its fields in
|
|
726
|
+
* field order, and every run of consecutive ungrouped fields is an unlabelled section of its own at its
|
|
727
|
+
* place. So name, price, sku, description with a Pricing group (price, sku) reads [name], Pricing [price,
|
|
728
|
+
* sku], [description]. Unknown and deprecated ids are skipped. Pure: no values, no I/O.
|
|
729
|
+
*/
|
|
730
|
+
export declare const effectiveFormLayout: (model: ModelDefinition) => FormLayout;
|
|
731
|
+
/**
|
|
732
|
+
* The entry's rich-text body, which "Summarize from body" reads: the document's rich-text canvas fields, or
|
|
733
|
+
* every live rich-text field of a form (a form has no canvas; its fields are all on the page).
|
|
734
|
+
*/
|
|
735
|
+
export declare const richTextBodyOf: (model: ModelDefinition) => FieldDefinition[];
|
|
660
736
|
//#endregion
|
|
661
737
|
//#region src/display/titleField.d.ts
|
|
662
738
|
/** Field types that may label entries: the only types `display.titleFieldId` accepts, and the fallback's. */
|
|
@@ -701,8 +777,9 @@ export type DefinitionInputWithIds = (WithIds<ModelDefinitionInput> & {
|
|
|
701
777
|
/**
|
|
702
778
|
* Applies defaults so that two definitions meaning the same thing are byte-identical once serialized
|
|
703
779
|
* canonically: a hand-written file that omits `public` hashes the same as one that says `public: true`.
|
|
704
|
-
* Empty optional strings and arrays are dropped
|
|
705
|
-
*
|
|
780
|
+
* Empty optional strings and arrays are dropped, and so are the defaults of keys added after definitions
|
|
781
|
+
* were first hashed (`width: 'full'`, `display.layout: 'document'`). A collection without a plural API ID
|
|
782
|
+
* gets the suggested one (`suggestPlural`), so files and definitions from before plural API IDs keep working.
|
|
706
783
|
*/
|
|
707
784
|
export declare const normalizeDefinition: (input: DefinitionInputWithIds) => SchemaDefinition;
|
|
708
785
|
/** Normalizes a definition that is already normalized-shaped (e.g. read back from the database). */
|
|
@@ -905,6 +982,7 @@ export declare const diffDefinitions: (before: SchemaDefinition | null, after: S
|
|
|
905
982
|
* | (plus) type change of a unique field | as its type change | + checkUnique |
|
|
906
983
|
* | (plus) tightened validation rules | validation | validateValues |
|
|
907
984
|
* | (plus) localized true→false | conversion (destructive) | convert |
|
|
985
|
+
* | (plus) type change Shapio can't convert | conversion (destructive, drops) | convert (+ validate/required) |
|
|
908
986
|
*/
|
|
909
987
|
export type ChangeCategory = 'metadata' | 'editorSwap' | 'additive' | 'requiredField' | 'validation' | 'conversion' | 'contract' | 'removal' | 'constraint' | 'index';
|
|
910
988
|
/** Work that must succeed before activation. Content-touching kinds run through the content ports. */
|
|
@@ -917,8 +995,10 @@ export type ClassifiedChange = SchemaChange & {
|
|
|
917
995
|
breaking: boolean;
|
|
918
996
|
/** Loses stored information that a schema rollback cannot restore. */
|
|
919
997
|
destructive: boolean;
|
|
920
|
-
/** False for
|
|
998
|
+
/** False for changes Shapio cannot perform; the planner refuses them. None today; kept for future refusals. */
|
|
921
999
|
supported: boolean;
|
|
1000
|
+
/** A type change Shapio cannot convert: every stored value of the field is dropped (destructive). */
|
|
1001
|
+
dropsValues: boolean;
|
|
922
1002
|
prerequisites: PrerequisiteKind[];
|
|
923
1003
|
cleanup: CleanupKind[];
|
|
924
1004
|
};
|
|
@@ -926,6 +1006,11 @@ export type ClassifyContext = {
|
|
|
926
1006
|
before: SchemaDefinition | null;
|
|
927
1007
|
after: SchemaDefinition | null;
|
|
928
1008
|
};
|
|
1009
|
+
/**
|
|
1010
|
+
* A type change Shapio cannot convert: the field keeps its stable ID and API ID, and every stored value is
|
|
1011
|
+
* dropped. The planner (`classifyChange`) and the content conversion share this one rule.
|
|
1012
|
+
*/
|
|
1013
|
+
export declare const typeChangeDropsValues: (from: DataType, to: DataType) => boolean;
|
|
929
1014
|
/** Classifies one change in the context of the definitions it was diffed from. */
|
|
930
1015
|
export declare const classifyChange: (change: SchemaChange, context: ClassifyContext) => ClassifiedChange;
|
|
931
1016
|
export declare const classifyChanges: (changes: readonly SchemaChange[], context: ClassifyContext) => ClassifiedChange[];
|
|
@@ -1180,6 +1265,14 @@ export declare const richTextToPlainText: (document: RichTextDocument) => string
|
|
|
1180
1265
|
/** Plain text → a document of paragraphs (blank lines split paragraphs; single newlines are hard breaks). */
|
|
1181
1266
|
export declare const plainTextToRichText: (text: string) => RichTextDocument;
|
|
1182
1267
|
//#endregion
|
|
1268
|
+
//#region src/richtext/plainText.d.ts
|
|
1269
|
+
/**
|
|
1270
|
+
* A stored rich text value (`{ format, version, doc }`) as one line of plain text: blocks separated by a
|
|
1271
|
+
* space, whitespace collapsed. For summaries (list cells, collapsed items, review rows), never for delivery.
|
|
1272
|
+
* Anything that is not a valid document (an unconverted value of a field that became rich text) reads as ''.
|
|
1273
|
+
*/
|
|
1274
|
+
export declare const richTextPlainText: (value: unknown) => string;
|
|
1275
|
+
//#endregion
|
|
1183
1276
|
//#region src/themes/tokens.d.ts
|
|
1184
1277
|
/**
|
|
1185
1278
|
* Admin themes (docs/plans/themes.md): a theme is a named, complete set of these tokens for a light variant, a
|
package/dist/index.js
CHANGED
|
@@ -110,6 +110,22 @@ const closed$3 = { additionalProperties: false };
|
|
|
110
110
|
const MODEL_KINDS = ["collection", "singleton"];
|
|
111
111
|
const DEFINITION_KINDS = [...MODEL_KINDS, "component"];
|
|
112
112
|
const SORT_DIRECTIONS = ["asc", "desc"];
|
|
113
|
+
/**
|
|
114
|
+
* How an entry of a model opens: `document` (title typed into the page, blocks in a canvas, properties in
|
|
115
|
+
* the strip and the settings drawer) or `form` (every field inline at its `width`, sections from
|
|
116
|
+
* `display.groups`). Unset means `document`; normalizing drops an explicit `document`.
|
|
117
|
+
*/
|
|
118
|
+
const ENTRY_LAYOUTS = ["document", "form"];
|
|
119
|
+
/**
|
|
120
|
+
* A field's width in a form-layout entry, as a share of the row: rows fill in field order and wrap. Unset
|
|
121
|
+
* means `full`; normalizing drops an explicit `full`.
|
|
122
|
+
*/
|
|
123
|
+
const FIELD_WIDTHS = [
|
|
124
|
+
"full",
|
|
125
|
+
"two-thirds",
|
|
126
|
+
"half",
|
|
127
|
+
"third"
|
|
128
|
+
];
|
|
113
129
|
const MAX_FIELDS_PER_DEFINITION = 500;
|
|
114
130
|
const MAX_LABEL_LENGTH = 200;
|
|
115
131
|
const MAX_DESCRIPTION_LENGTH = 2e3;
|
|
@@ -146,7 +162,12 @@ const FieldInputSchema = Type.Object({
|
|
|
146
162
|
defaultValue: Type.Optional(Type.Unknown()),
|
|
147
163
|
/** Type-specific settings, validated against SETTINGS_SCHEMAS[type]. */
|
|
148
164
|
settings: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
149
|
-
editor: Type.Optional(EditorChoiceInputSchema)
|
|
165
|
+
editor: Type.Optional(EditorChoiceInputSchema),
|
|
166
|
+
/**
|
|
167
|
+
* Width in a form-layout entry (`display.layout: 'form'`). Default `full`. Model fields only: the
|
|
168
|
+
* validator refuses it on a component's field.
|
|
169
|
+
*/
|
|
170
|
+
width: Type.Optional(Type.Enum(FIELD_WIDTHS))
|
|
150
171
|
}, closed$3);
|
|
151
172
|
const FieldGroupSchema = Type.Object({
|
|
152
173
|
id: Type.String({
|
|
@@ -157,6 +178,8 @@ const FieldGroupSchema = Type.Object({
|
|
|
157
178
|
fieldIds: Type.Array(Type.String())
|
|
158
179
|
}, closed$3);
|
|
159
180
|
const ModelDisplayInputSchema = Type.Object({
|
|
181
|
+
/** How entries open: `document` (default) or `form`. See `ENTRY_LAYOUTS`. */
|
|
182
|
+
layout: Type.Optional(Type.Enum(ENTRY_LAYOUTS)),
|
|
160
183
|
/** Field whose value labels an entry in lists, pickers and relation summaries. */
|
|
161
184
|
titleFieldId: Type.Optional(Type.String()),
|
|
162
185
|
/** Columns of the content list, in order. */
|
|
@@ -693,6 +716,66 @@ const effectiveLayout = (model) => {
|
|
|
693
716
|
};
|
|
694
717
|
/** The strip's properties: the configured ones, or the first `limit` that `hasValue` says are non-empty. */
|
|
695
718
|
const stripFieldsOf = (layout, hasValue, limit = 5) => layout.strip ?? layout.properties.filter(hasValue).slice(0, limit);
|
|
719
|
+
/** How the model's entries open (`display.layout`): `document` unless set to `form`. */
|
|
720
|
+
const entryLayoutOf = (model) => model.display.layout ?? "document";
|
|
721
|
+
const ungroupedSectionId = (first, groupIds) => {
|
|
722
|
+
let candidate = `ungrouped-${first.id}`;
|
|
723
|
+
while (groupIds.has(candidate)) candidate = `~${candidate}`;
|
|
724
|
+
return candidate;
|
|
725
|
+
};
|
|
726
|
+
/**
|
|
727
|
+
* The entry form's layout (`display.layout: 'form'`): every live field, the title included, in sections.
|
|
728
|
+
* Field order decides everything: a group's section sits where its first field appears, with its fields in
|
|
729
|
+
* field order, and every run of consecutive ungrouped fields is an unlabelled section of its own at its
|
|
730
|
+
* place. So name, price, sku, description with a Pricing group (price, sku) reads [name], Pricing [price,
|
|
731
|
+
* sku], [description]. Unknown and deprecated ids are skipped. Pure: no values, no I/O.
|
|
732
|
+
*/
|
|
733
|
+
const effectiveFormLayout = (model) => {
|
|
734
|
+
const groups = model.display.groups ?? [];
|
|
735
|
+
const groupIds = new Set(groups.map((group) => group.id));
|
|
736
|
+
const groupOf = /* @__PURE__ */ new Map();
|
|
737
|
+
for (const group of groups) for (const fieldId of group.fieldIds) if (!groupOf.has(fieldId)) groupOf.set(fieldId, group);
|
|
738
|
+
const sections = [];
|
|
739
|
+
const groupSections = /* @__PURE__ */ new Map();
|
|
740
|
+
let run;
|
|
741
|
+
for (const field of model.fields) {
|
|
742
|
+
if (field.deprecated) continue;
|
|
743
|
+
const group = groupOf.get(field.id);
|
|
744
|
+
if (!group) {
|
|
745
|
+
if (!run) {
|
|
746
|
+
run = {
|
|
747
|
+
id: ungroupedSectionId(field, groupIds),
|
|
748
|
+
label: void 0,
|
|
749
|
+
fields: []
|
|
750
|
+
};
|
|
751
|
+
sections.push(run);
|
|
752
|
+
}
|
|
753
|
+
run.fields.push(field);
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
run = void 0;
|
|
757
|
+
let section = groupSections.get(group.id);
|
|
758
|
+
if (!section) {
|
|
759
|
+
section = {
|
|
760
|
+
id: group.id,
|
|
761
|
+
label: group.label,
|
|
762
|
+
fields: []
|
|
763
|
+
};
|
|
764
|
+
groupSections.set(group.id, section);
|
|
765
|
+
sections.push(section);
|
|
766
|
+
}
|
|
767
|
+
section.fields.push(field);
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
title: effectiveTitleField(model),
|
|
771
|
+
sections
|
|
772
|
+
};
|
|
773
|
+
};
|
|
774
|
+
/**
|
|
775
|
+
* The entry's rich-text body, which "Summarize from body" reads: the document's rich-text canvas fields, or
|
|
776
|
+
* every live rich-text field of a form (a form has no canvas; its fields are all on the page).
|
|
777
|
+
*/
|
|
778
|
+
const richTextBodyOf = (model) => entryLayoutOf(model) === "form" ? model.fields.filter((field) => field.type === "richtext" && !field.deprecated) : effectiveLayout(model).canvas.filter((field) => field.type === "richtext");
|
|
696
779
|
//#endregion
|
|
697
780
|
//#region src/validators/apiKey.ts
|
|
698
781
|
/**
|
|
@@ -796,17 +879,22 @@ const normalizeField = (field) => {
|
|
|
796
879
|
editor: {
|
|
797
880
|
id: field.editor?.id ?? DEFAULT_EDITORS[field.type],
|
|
798
881
|
options: field.editor?.options ?? {}
|
|
799
|
-
}
|
|
882
|
+
},
|
|
883
|
+
width: field.width === "full" ? void 0 : field.width
|
|
800
884
|
};
|
|
801
885
|
return withoutEmpty(normalized);
|
|
802
886
|
};
|
|
803
|
-
const normalizeModelDisplay = (display) => withoutEmpty({
|
|
887
|
+
const normalizeModelDisplay = (display) => withoutEmpty({
|
|
888
|
+
...display,
|
|
889
|
+
layout: display?.layout === "document" ? void 0 : display?.layout
|
|
890
|
+
});
|
|
804
891
|
const normalizeComponentDisplay = (display) => withoutEmpty({ ...display });
|
|
805
892
|
/**
|
|
806
893
|
* Applies defaults so that two definitions meaning the same thing are byte-identical once serialized
|
|
807
894
|
* canonically: a hand-written file that omits `public` hashes the same as one that says `public: true`.
|
|
808
|
-
* Empty optional strings and arrays are dropped
|
|
809
|
-
*
|
|
895
|
+
* Empty optional strings and arrays are dropped, and so are the defaults of keys added after definitions
|
|
896
|
+
* were first hashed (`width: 'full'`, `display.layout: 'document'`). A collection without a plural API ID
|
|
897
|
+
* gets the suggested one (`suggestPlural`), so files and definitions from before plural API IDs keep working.
|
|
810
898
|
*/
|
|
811
899
|
const normalizeDefinition = (input) => {
|
|
812
900
|
const fields = input.fields.map(normalizeField);
|
|
@@ -1219,6 +1307,7 @@ const checkFlags = (field, path, isComponent) => {
|
|
|
1219
1307
|
"filterable",
|
|
1220
1308
|
"sortable"
|
|
1221
1309
|
]) if (field[flag]) reject(flag, `fields of a component cannot be ${flag}`);
|
|
1310
|
+
if (field.width !== void 0) reject("width", "fields of a component have no width");
|
|
1222
1311
|
}
|
|
1223
1312
|
if (field.deprecated && field.required) reject("required", "a deprecated field cannot be required");
|
|
1224
1313
|
return issues;
|
|
@@ -1672,7 +1761,8 @@ const DEFINITION_METADATA = [
|
|
|
1672
1761
|
const FIELD_METADATA = [
|
|
1673
1762
|
"label",
|
|
1674
1763
|
"description",
|
|
1675
|
-
"defaultValue"
|
|
1764
|
+
"defaultValue",
|
|
1765
|
+
"width"
|
|
1676
1766
|
];
|
|
1677
1767
|
const FIELD_FLAGS = [
|
|
1678
1768
|
["required", "field.required"],
|
|
@@ -1833,6 +1923,7 @@ const result = (category, extra = {}) => ({
|
|
|
1833
1923
|
breaking: false,
|
|
1834
1924
|
destructive: false,
|
|
1835
1925
|
supported: true,
|
|
1926
|
+
dropsValues: false,
|
|
1836
1927
|
prerequisites: [],
|
|
1837
1928
|
cleanup: [],
|
|
1838
1929
|
...extra
|
|
@@ -1882,27 +1973,54 @@ const hasTextConstraints = (field) => {
|
|
|
1882
1973
|
return settings.minLength !== void 0 || settings.maxLength !== void 0 || settings.pattern !== void 0 || settings.validate === true;
|
|
1883
1974
|
};
|
|
1884
1975
|
/**
|
|
1976
|
+
* Whether Shapio converts a cross-family type change value by value (the pairs it knows); within one storage
|
|
1977
|
+
* family the stored JSON stays as it is.
|
|
1978
|
+
*/
|
|
1979
|
+
const convertsAcrossFamilies = (from, to) => {
|
|
1980
|
+
const fromFamily = STORAGE_FAMILY[from];
|
|
1981
|
+
const toFamily = STORAGE_FAMILY[to];
|
|
1982
|
+
const numericPair = fromFamily === "jsonNumber" && toFamily === "numericString" || fromFamily === "numericString" && toFamily === "jsonNumber";
|
|
1983
|
+
const toText = SCALAR_FAMILIES.has(fromFamily) && PLAIN_TEXT_TYPES.has(to);
|
|
1984
|
+
const textToRichtext = fromFamily === "jsonString" && from !== "code" && to === "richtext";
|
|
1985
|
+
const richtextToText = from === "richtext" && PLAIN_TEXT_TYPES.has(to);
|
|
1986
|
+
return numericPair || toText || textToRichtext || richtextToText;
|
|
1987
|
+
};
|
|
1988
|
+
/**
|
|
1989
|
+
* A type change Shapio cannot convert: the field keeps its stable ID and API ID, and every stored value is
|
|
1990
|
+
* dropped. The planner (`classifyChange`) and the content conversion share this one rule.
|
|
1991
|
+
*/
|
|
1992
|
+
const typeChangeDropsValues = (from, to) => STORAGE_FAMILY[from] !== STORAGE_FAMILY[to] && !convertsAcrossFamilies(from, to);
|
|
1993
|
+
/** A required field whose values are dropped is backfilled from its default, else checked (no silent nulls). */
|
|
1994
|
+
const requiredStep = (field) => {
|
|
1995
|
+
if (!field?.required) return [];
|
|
1996
|
+
return [field.defaultValue !== void 0 ? "backfill" : "validateRequired"];
|
|
1997
|
+
};
|
|
1998
|
+
/**
|
|
1885
1999
|
* A type change. Within one storage family the stored JSON stays as it is: widening is live, anything else
|
|
1886
|
-
* re-validates existing values. Across families Shapio converts values with a job, for the pairs it knows
|
|
2000
|
+
* re-validates existing values. Across families Shapio converts values with a job, for the pairs it knows;
|
|
2001
|
+
* any other pair drops the values (destructive, acknowledged in the review).
|
|
1887
2002
|
*/
|
|
1888
2003
|
const classifyTypeChange = (from, to, after) => {
|
|
1889
2004
|
const breaking = API_SCALAR[from] !== API_SCALAR[to] || to === "code" && from !== "code";
|
|
1890
2005
|
const fromFamily = STORAGE_FAMILY[from];
|
|
1891
|
-
|
|
1892
|
-
if (fromFamily === toFamily) return fromFamily === "jsonString" && TEXT_TARGETS.has(to) && !hasTextConstraints(after) || from === "integer" && to === "number" || from === "biginteger" && to === "decimal" && after?.type === "decimal" && after.settings.precision === void 0 ? result("additive", { breaking }) : result("validation", {
|
|
2006
|
+
if (fromFamily === STORAGE_FAMILY[to]) return fromFamily === "jsonString" && TEXT_TARGETS.has(to) && !hasTextConstraints(after) || from === "integer" && to === "number" || from === "biginteger" && to === "decimal" && after?.type === "decimal" && after.settings.precision === void 0 ? result("additive", { breaking }) : result("validation", {
|
|
1893
2007
|
breaking,
|
|
1894
2008
|
prerequisites: ["validateValues"]
|
|
1895
2009
|
});
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
2010
|
+
if (typeChangeDropsValues(from, to)) return result("conversion", {
|
|
2011
|
+
breaking,
|
|
2012
|
+
destructive: true,
|
|
2013
|
+
dropsValues: true,
|
|
2014
|
+
prerequisites: [
|
|
2015
|
+
"convert",
|
|
2016
|
+
"validateValues",
|
|
2017
|
+
...requiredStep(after)
|
|
2018
|
+
]
|
|
2019
|
+
});
|
|
1901
2020
|
return result("conversion", {
|
|
1902
2021
|
breaking,
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
prerequisites: supported ? ["convert", "validateValues"] : []
|
|
2022
|
+
destructive: from === "richtext" && PLAIN_TEXT_TYPES.has(to),
|
|
2023
|
+
prerequisites: ["convert", "validateValues"]
|
|
1906
2024
|
});
|
|
1907
2025
|
};
|
|
1908
2026
|
const compareValues = (a, b) => {
|
|
@@ -2406,6 +2524,17 @@ const plainTextToRichText = (text) => {
|
|
|
2406
2524
|
};
|
|
2407
2525
|
};
|
|
2408
2526
|
//#endregion
|
|
2527
|
+
//#region src/richtext/plainText.ts
|
|
2528
|
+
/**
|
|
2529
|
+
* A stored rich text value (`{ format, version, doc }`) as one line of plain text: blocks separated by a
|
|
2530
|
+
* space, whitespace collapsed. For summaries (list cells, collapsed items, review rows), never for delivery.
|
|
2531
|
+
* Anything that is not a valid document (an unconverted value of a field that became rich text) reads as ''.
|
|
2532
|
+
*/
|
|
2533
|
+
const richTextPlainText = (value) => {
|
|
2534
|
+
const result = validateRichText(value);
|
|
2535
|
+
return result.ok ? richTextToPlainText(result.document).replace(/\s+/g, " ").trim() : "";
|
|
2536
|
+
};
|
|
2537
|
+
//#endregion
|
|
2409
2538
|
//#region src/themes/tokens.ts
|
|
2410
2539
|
/**
|
|
2411
2540
|
* Admin themes (docs/plans/themes.md): a theme is a named, complete set of these tokens for a light variant, a
|
|
@@ -2672,6 +2801,6 @@ const SeoDefaultsSchema = Type.Object({
|
|
|
2672
2801
|
twitterHandle: Type.Union([Type.String({ pattern: SEO_TWITTER_HANDLE_PATTERN.source }), Type.Null()])
|
|
2673
2802
|
}, closed);
|
|
2674
2803
|
//#endregion
|
|
2675
|
-
export { API_KEY_PATTERN, BIGINTEGER_PATTERN, BUILT_IN_THEME_KEYS, CANVAS_ELIGIBLE, CHANGE_KINDS, CODE_LANGUAGES, COMPONENT_TYPE_SUFFIXES, CUSTOM_EDITOR_ID_PATTERN, ComponentDefinitionInputSchema, ComponentDisplayInputSchema, DATA_TYPES, DATETIME_PATTERN, DATE_PATTERN, DECIMAL_PATTERN, DEFAULT_EDITORS, DEFAULT_SEO_KEYS, DEFINITION_KINDS, DefinitionInputSchema, EDITOR_CATALOGUE, EMPTY_SEO_DEFAULTS, EditorChoiceInputSchema, EnumValueSchema, FieldInputSchema, INLINE_TITLE_TYPES, LOCALE_CODE_PATTERN, LOCK_FILE_FORMAT_VERSION, LOCK_FILE_PATH, LocaleInputSchema, LockEntrySchema, LockFileError, LockFileSchema, MARK_RULES, MAX_API_KEY_LENGTH, MAX_COMPONENT_DEPTH, MAX_DESCRIPTION_LENGTH, MAX_FALLBACK_CHAIN, MAX_FIELDS_PER_DEFINITION, MAX_LABEL_LENGTH, MAX_RICHTEXT_DEPTH, MEDIA_KINDS, MODEL_KINDS, MODEL_TYPE_SUFFIXES, MUTATION_PREFIXES, ModelDefinitionInputSchema, ModelDisplayInputSchema, NODE_RULES, PLURAL_FALLBACK_SUFFIX, REFERENCE_DATA_TYPES, RELATION_CARDINALITIES, RESERVED_DEFINITION_API_KEYS, RESERVED_FIELD_API_KEYS, RESERVED_QUERY_NAMES, RESERVED_TYPE_NAMES, RICHTEXT_FORMAT, RICHTEXT_FORMAT_VERSION, SAFE_LINK_PATTERN, SCALAR_DATA_TYPES, SCHEMA_KIND_DIRECTORIES, SEO_COMPONENT_API_KEY, SEO_COMPONENT_ID, SEO_DESCRIPTION_SOFT_MAX, SEO_EDITOR_ID, SEO_FIELD_IDS, SEO_TITLE_PLACEHOLDER, SEO_TITLE_SOFT_MAX, SEO_TITLE_TEMPLATE_PATTERN, SEO_TWITTER_HANDLE_PATTERN, SETTINGS_SCHEMAS, SITES_DIRECTORY, SLUG_PATTERN, SORT_DIRECTIONS, STABLE_ID_PATTERN, STORAGE_FAMILY, STRIP_DEFAULT_COUNT, SchemaValidationError, SeoDefaultsSchema, SeoLocaleDefaultsSchema, TEXT_TITLE_FIELD_TYPES, THEME_BRAND_TOKENS, THEME_COLOUR_PATTERN, THEME_DARK_UI_PAIRS, THEME_KEY_PATTERN, THEME_SEMANTIC_TOKENS, THEME_TEXT_PAIRS, THEME_TOKENS, THEME_UI_PAIRS, THEME_VARIANTS, TIME_PATTERN, TITLE_FIELD_TYPES, UID_PATTERN, UNIQUE_CAPABLE_DATA_TYPES, applyTitleTemplate, assignIds, canonicalJson, checkApiKeySyntax, checkDefaultValue, checkThemeContrast, classifyChange, classifyChanges, classifyLocaleChange, collectionQueryName, contrastRatio, createStableId, decideSync, diffDefinitions, effectiveLayout, effectiveTitleField, embeddedComponentIds, escapeHtml, findDependentModels, findReferencingDefinitions, foldApiKey, generatedNames, hashDefinition, isApiKeySyntaxValid, isCanvasEligible, isComponentDefinition, isCoverEligible, isCustomEditorId, isDataType, isDocumentPlaceable, isEmptyRichText, isModelDefinition, isSeoField, isStableId, isValidTitleTemplate, issue, listCompatibleEditors, lockCoversSite, lockEntriesForSite, mergePulledLock, normalizeDefinition, parseDefinition, parseLockFile, plainTextToRichText, renderRichTextHtml, renormalizeDefinition, resolveSeo, richTextMediaIds, richTextToPlainText, routeKeyOf, schemaFilePath, scopeDirectory, scopeOfSchemaFilePath, seoComponentDefinition, seoDefaultsForLocale, serializeDefinition, serializeLockFile, sha256, stripFieldsOf, suggestPlural, summarizeChanges, toTypeName, upgradeLockFile, validateDefinition, validateRichText, validateSchema, withDerivedPlural, withSiteCovered };
|
|
2804
|
+
export { API_KEY_PATTERN, BIGINTEGER_PATTERN, BUILT_IN_THEME_KEYS, CANVAS_ELIGIBLE, CHANGE_KINDS, CODE_LANGUAGES, COMPONENT_TYPE_SUFFIXES, CUSTOM_EDITOR_ID_PATTERN, ComponentDefinitionInputSchema, ComponentDisplayInputSchema, DATA_TYPES, DATETIME_PATTERN, DATE_PATTERN, DECIMAL_PATTERN, DEFAULT_EDITORS, DEFAULT_SEO_KEYS, DEFINITION_KINDS, DefinitionInputSchema, EDITOR_CATALOGUE, EMPTY_SEO_DEFAULTS, ENTRY_LAYOUTS, EditorChoiceInputSchema, EnumValueSchema, FIELD_WIDTHS, FieldInputSchema, INLINE_TITLE_TYPES, LOCALE_CODE_PATTERN, LOCK_FILE_FORMAT_VERSION, LOCK_FILE_PATH, LocaleInputSchema, LockEntrySchema, LockFileError, LockFileSchema, MARK_RULES, MAX_API_KEY_LENGTH, MAX_COMPONENT_DEPTH, MAX_DESCRIPTION_LENGTH, MAX_FALLBACK_CHAIN, MAX_FIELDS_PER_DEFINITION, MAX_LABEL_LENGTH, MAX_RICHTEXT_DEPTH, MEDIA_KINDS, MODEL_KINDS, MODEL_TYPE_SUFFIXES, MUTATION_PREFIXES, ModelDefinitionInputSchema, ModelDisplayInputSchema, NODE_RULES, PLURAL_FALLBACK_SUFFIX, REFERENCE_DATA_TYPES, RELATION_CARDINALITIES, RESERVED_DEFINITION_API_KEYS, RESERVED_FIELD_API_KEYS, RESERVED_QUERY_NAMES, RESERVED_TYPE_NAMES, RICHTEXT_FORMAT, RICHTEXT_FORMAT_VERSION, SAFE_LINK_PATTERN, SCALAR_DATA_TYPES, SCHEMA_KIND_DIRECTORIES, SEO_COMPONENT_API_KEY, SEO_COMPONENT_ID, SEO_DESCRIPTION_SOFT_MAX, SEO_EDITOR_ID, SEO_FIELD_IDS, SEO_TITLE_PLACEHOLDER, SEO_TITLE_SOFT_MAX, SEO_TITLE_TEMPLATE_PATTERN, SEO_TWITTER_HANDLE_PATTERN, SETTINGS_SCHEMAS, SITES_DIRECTORY, SLUG_PATTERN, SORT_DIRECTIONS, STABLE_ID_PATTERN, STORAGE_FAMILY, STRIP_DEFAULT_COUNT, SchemaValidationError, SeoDefaultsSchema, SeoLocaleDefaultsSchema, TEXT_TITLE_FIELD_TYPES, THEME_BRAND_TOKENS, THEME_COLOUR_PATTERN, THEME_DARK_UI_PAIRS, THEME_KEY_PATTERN, THEME_SEMANTIC_TOKENS, THEME_TEXT_PAIRS, THEME_TOKENS, THEME_UI_PAIRS, THEME_VARIANTS, TIME_PATTERN, TITLE_FIELD_TYPES, UID_PATTERN, UNIQUE_CAPABLE_DATA_TYPES, applyTitleTemplate, assignIds, canonicalJson, checkApiKeySyntax, checkDefaultValue, checkThemeContrast, classifyChange, classifyChanges, classifyLocaleChange, collectionQueryName, contrastRatio, createStableId, decideSync, diffDefinitions, effectiveFormLayout, effectiveLayout, effectiveTitleField, embeddedComponentIds, entryLayoutOf, escapeHtml, findDependentModels, findReferencingDefinitions, foldApiKey, generatedNames, hashDefinition, isApiKeySyntaxValid, isCanvasEligible, isComponentDefinition, isCoverEligible, isCustomEditorId, isDataType, isDocumentPlaceable, isEmptyRichText, isModelDefinition, isSeoField, isStableId, isValidTitleTemplate, issue, listCompatibleEditors, lockCoversSite, lockEntriesForSite, mergePulledLock, normalizeDefinition, parseDefinition, parseLockFile, plainTextToRichText, renderRichTextHtml, renormalizeDefinition, resolveSeo, richTextBodyOf, richTextMediaIds, richTextPlainText, richTextToPlainText, routeKeyOf, schemaFilePath, scopeDirectory, scopeOfSchemaFilePath, seoComponentDefinition, seoDefaultsForLocale, serializeDefinition, serializeLockFile, sha256, stripFieldsOf, suggestPlural, summarizeChanges, toTypeName, typeChangeDropsValues, upgradeLockFile, validateDefinition, validateRichText, validateSchema, withDerivedPlural, withSiteCovered };
|
|
2676
2805
|
|
|
2677
2806
|
//# sourceMappingURL=index.js.map
|