@shapio/schema 0.5.7 → 0.5.9
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 +144 -9
- package/dist/index.js +461 -157
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -224,6 +224,11 @@ const ModelDefinitionInputSchema = Type.Object({
|
|
|
224
224
|
localized: Type.Optional(Type.Boolean()),
|
|
225
225
|
/** Entries have separate draft and published states. Default true. */
|
|
226
226
|
draftAndPublish: Type.Optional(Type.Boolean()),
|
|
227
|
+
/**
|
|
228
|
+
* Collections only: the stable ID of the collection this one extends (its parent). The model inherits
|
|
229
|
+
* the parent's fields, locked, and adds its own (plan polymorphic-types).
|
|
230
|
+
*/
|
|
231
|
+
extends: Type.Optional(Type.String()),
|
|
227
232
|
display: Type.Optional(ModelDisplayInputSchema)
|
|
228
233
|
}, closed$3);
|
|
229
234
|
const ComponentDefinitionInputSchema = Type.Object({
|
|
@@ -828,159 +833,148 @@ const suggestPlural = (apiKey) => {
|
|
|
828
833
|
return `${apiKey.slice(0, 59)}${PLURAL_FALLBACK_SUFFIX}`;
|
|
829
834
|
};
|
|
830
835
|
//#endregion
|
|
831
|
-
//#region src/
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
]
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
836
|
+
//#region src/inheritance/resolve.ts
|
|
837
|
+
/**
|
|
838
|
+
* Model inheritance (plan polymorphic-types). A collection may extend another (`extends`, the parent's stable
|
|
839
|
+
* ID). Stored definitions hold their own fields only; the effective definition is resolved at read time:
|
|
840
|
+
* every ancestor's fields (root first, each marked `inheritedFrom`), then the model's own, and the display
|
|
841
|
+
* merged down the chain. Nothing is copied into children, so a parent edit changes one definition.
|
|
842
|
+
*/
|
|
843
|
+
/** Most levels a family may have, root included (`Products > Physical > Apparel` is three). */
|
|
844
|
+
const MAX_INHERITANCE_DEPTH = 8;
|
|
845
|
+
/** A definition that may be a parent: a collection. */
|
|
846
|
+
const canBeParent = (definition) => definition !== void 0 && definition.kind === "collection";
|
|
847
|
+
/**
|
|
848
|
+
* The ancestors of a definition, root first, walking `extends` up. Stops at the first problem (missing,
|
|
849
|
+
* invalid or cyclic parent, or more than MAX_INHERITANCE_DEPTH levels) and returns what resolved so far.
|
|
850
|
+
*/
|
|
851
|
+
const ancestorChain = (definition, lookup) => {
|
|
852
|
+
const upward = [];
|
|
853
|
+
const seen = /* @__PURE__ */ new Set([definition.id]);
|
|
854
|
+
let current = definition;
|
|
855
|
+
while (isModelDefinition(current) && current.extends !== void 0) {
|
|
856
|
+
const parentId = current.extends;
|
|
857
|
+
const at = current.id;
|
|
858
|
+
const stop = (problem) => ({
|
|
859
|
+
ids: upward.reverse(),
|
|
860
|
+
problem,
|
|
861
|
+
problemAt: at
|
|
862
|
+
});
|
|
863
|
+
if (parentId === current.id) return stop("invalid");
|
|
864
|
+
if (seen.has(parentId)) return stop("cycle");
|
|
865
|
+
const parent = lookup(parentId);
|
|
866
|
+
if (!parent) return stop("missing");
|
|
867
|
+
if (!canBeParent(parent)) return stop("invalid");
|
|
868
|
+
if (upward.length + 1 >= 8) return stop("tooDeep");
|
|
869
|
+
upward.push(parentId);
|
|
870
|
+
seen.add(parentId);
|
|
871
|
+
current = parent;
|
|
862
872
|
}
|
|
863
|
-
return
|
|
864
|
-
};
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
required: field.required ?? false,
|
|
873
|
-
localized: field.localized ?? false,
|
|
874
|
-
public: field.public ?? true,
|
|
875
|
-
unique: field.unique ?? false,
|
|
876
|
-
filterable: field.filterable ?? false,
|
|
877
|
-
sortable: field.sortable ?? false,
|
|
878
|
-
deprecated: field.deprecated ?? false,
|
|
879
|
-
defaultValue: field.defaultValue,
|
|
880
|
-
settings: {
|
|
881
|
-
...SETTINGS_DEFAULTS[field.type],
|
|
882
|
-
...field.settings
|
|
883
|
-
},
|
|
884
|
-
editor: {
|
|
885
|
-
id: field.editor?.id ?? DEFAULT_EDITORS[field.type],
|
|
886
|
-
options: field.editor?.options ?? {}
|
|
887
|
-
},
|
|
888
|
-
width: field.width === "full" ? void 0 : field.width
|
|
889
|
-
};
|
|
890
|
-
return withoutEmpty(normalized);
|
|
891
|
-
};
|
|
892
|
-
const normalizeModelDisplay = (display) => withoutEmpty({
|
|
893
|
-
...display,
|
|
894
|
-
layout: display?.layout === "document" ? void 0 : display?.layout
|
|
895
|
-
});
|
|
896
|
-
const normalizeComponentDisplay = (display) => withoutEmpty({ ...display });
|
|
873
|
+
return { ids: upward.reverse() };
|
|
874
|
+
};
|
|
875
|
+
/** Fields a model sees from one ancestor: its own fields, marked with the ancestor's ID. */
|
|
876
|
+
const inheritedFieldsOf = (ancestor) => ancestor.fields.map((field) => ({
|
|
877
|
+
...field,
|
|
878
|
+
inheritedFrom: ancestor.id
|
|
879
|
+
}));
|
|
880
|
+
/** IDs of the fields in `fields` that `pick` keeps, in field order. */
|
|
881
|
+
const fieldIdsWhere = (fields, pick) => fields.filter(pick).map((field) => field.id);
|
|
897
882
|
/**
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
*
|
|
902
|
-
* gets the suggested one (`suggestPlural`), so files and definitions from before plural API IDs keep working.
|
|
883
|
+
* A child's display over its parent's (already merged) one. Scalars and `listFieldIds` take the child's value
|
|
884
|
+
* when set, else the parent's. Groups are the parent's, then the child's. Canvas and strip take the child's
|
|
885
|
+
* list when set; else the parent's list (when set) with the child's own fields appended where the automatic
|
|
886
|
+
* rule would put them: canvas-eligible fields to the canvas, the other fields (not the cover) to the strip.
|
|
903
887
|
*/
|
|
904
|
-
const
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
draftAndPublish: input.draftAndPublish ?? true,
|
|
926
|
-
fields,
|
|
927
|
-
display: normalizeModelDisplay(input.display)
|
|
928
|
-
});
|
|
888
|
+
const mergeDisplay = (parent, child, ownFields) => {
|
|
889
|
+
const merged = {};
|
|
890
|
+
const layout = child.layout ?? parent.layout;
|
|
891
|
+
const titleFieldId = child.titleFieldId ?? parent.titleFieldId;
|
|
892
|
+
const listFieldIds = child.listFieldIds ?? parent.listFieldIds;
|
|
893
|
+
const defaultSort = child.defaultSort ?? parent.defaultSort;
|
|
894
|
+
const coverFieldId = child.coverFieldId ?? parent.coverFieldId;
|
|
895
|
+
const groups = [...parent.groups ?? [], ...child.groups ?? []];
|
|
896
|
+
const ownCanvas = fieldIdsWhere(ownFields, isCanvasEligible);
|
|
897
|
+
const canvasFieldIds = child.canvasFieldIds ?? (parent.canvasFieldIds ? [...parent.canvasFieldIds, ...ownCanvas] : void 0);
|
|
898
|
+
const canvas = new Set(canvasFieldIds ?? []);
|
|
899
|
+
const stripFieldIds = child.stripFieldIds ?? (parent.stripFieldIds ? [...parent.stripFieldIds, ...fieldIdsWhere(ownFields, (field) => !isCanvasEligible(field) && !canvas.has(field.id) && field.id !== coverFieldId)] : void 0);
|
|
900
|
+
if (layout !== void 0) merged.layout = layout;
|
|
901
|
+
if (titleFieldId !== void 0) merged.titleFieldId = titleFieldId;
|
|
902
|
+
if (listFieldIds !== void 0) merged.listFieldIds = listFieldIds;
|
|
903
|
+
if (defaultSort !== void 0) merged.defaultSort = defaultSort;
|
|
904
|
+
if (groups.length > 0) merged.groups = groups;
|
|
905
|
+
if (canvasFieldIds !== void 0) merged.canvasFieldIds = canvasFieldIds;
|
|
906
|
+
if (coverFieldId !== void 0) merged.coverFieldId = coverFieldId;
|
|
907
|
+
if (stripFieldIds !== void 0) merged.stripFieldIds = stripFieldIds;
|
|
908
|
+
return merged;
|
|
929
909
|
};
|
|
930
|
-
/** Normalizes a definition that is already normalized-shaped (e.g. read back from the database). */
|
|
931
|
-
const renormalizeDefinition = (definition) => normalizeDefinition(definition);
|
|
932
|
-
//#endregion
|
|
933
|
-
//#region src/validators/defaultValue.ts
|
|
934
910
|
/**
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
911
|
+
* The effective definition of a model: ancestors' fields first (root first, `inheritedFrom` set, otherwise the
|
|
912
|
+
* same field objects' values and IDs), then its own fields; display merged root → model. A broken chain
|
|
913
|
+
* (see `ancestorChain`) resolves over the part that resolved; the validator reports the problem.
|
|
938
914
|
*/
|
|
939
|
-
const
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
case "code":
|
|
948
|
-
case "email":
|
|
949
|
-
case "url": return isString ? null : "must be a string";
|
|
950
|
-
case "slug": return matches(SLUG_PATTERN) ? null : "must be a lower-case slug";
|
|
951
|
-
case "uid": return matches(UID_PATTERN) ? null : "must contain only URL-safe characters";
|
|
952
|
-
case "number": return typeof value === "number" && Number.isFinite(value) ? null : "must be a number";
|
|
953
|
-
case "integer": return Number.isSafeInteger(value) ? null : "must be an integer";
|
|
954
|
-
case "decimal": return matches(DECIMAL_PATTERN) ? null : "must be a decimal string, e.g. \"12.50\"";
|
|
955
|
-
case "biginteger": return matches(BIGINTEGER_PATTERN) ? null : "must be an integer string, e.g. \"9007199254740993\"";
|
|
956
|
-
case "boolean": return typeof value === "boolean" ? null : "must be true or false";
|
|
957
|
-
case "date": return matches(DATE_PATTERN) ? null : "must be a date, YYYY-MM-DD";
|
|
958
|
-
case "datetime": return matches(DATETIME_PATTERN) ? null : "must be a UTC date-time, YYYY-MM-DDTHH:mm:ssZ";
|
|
959
|
-
case "time": return matches(TIME_PATTERN) ? null : "must be a time, HH:mm[:ss]";
|
|
960
|
-
case "enum": {
|
|
961
|
-
const allowed = new Set(field.settings.values.map((entry) => entry.value));
|
|
962
|
-
if (field.settings.multiple) return Array.isArray(value) && value.every((item) => typeof item === "string" && allowed.has(item)) ? null : "must be a list of the enum values";
|
|
963
|
-
return isString && allowed.has(value) ? null : "must be one of the enum values";
|
|
964
|
-
}
|
|
965
|
-
case "json": return null;
|
|
966
|
-
default: return `${field.type} fields cannot have a default value`;
|
|
915
|
+
const resolveModelDefinition = (definition, lookup) => {
|
|
916
|
+
const { ids } = ancestorChain(definition, lookup);
|
|
917
|
+
const ancestors = ids.map((id) => lookup(id));
|
|
918
|
+
let display;
|
|
919
|
+
const fields = [];
|
|
920
|
+
for (const ancestor of ancestors) {
|
|
921
|
+
display = display === void 0 ? ancestor.display : mergeDisplay(display, ancestor.display, ancestor.fields);
|
|
922
|
+
fields.push(...inheritedFieldsOf(ancestor));
|
|
967
923
|
}
|
|
924
|
+
return {
|
|
925
|
+
...definition,
|
|
926
|
+
fields: [...fields, ...definition.fields],
|
|
927
|
+
display: display === void 0 ? definition.display : mergeDisplay(display, definition.display, definition.fields),
|
|
928
|
+
ancestorIds: ids
|
|
929
|
+
};
|
|
968
930
|
};
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
931
|
+
/** The fields a resolved model declares itself (not inherited). */
|
|
932
|
+
const ownFields = (resolved) => resolved.fields.filter((field) => field.inheritedFrom === void 0);
|
|
933
|
+
/** Definitions whose parent is `id`, in the given order. */
|
|
934
|
+
const findChildDefinitions = (definitions, id) => definitions.filter((definition) => isModelDefinition(definition) && definition.extends === id && definition.id !== id);
|
|
935
|
+
const memo = (compute) => {
|
|
936
|
+
const cache = /* @__PURE__ */ new Map();
|
|
937
|
+
return (id) => {
|
|
938
|
+
if (!cache.has(id)) cache.set(id, compute(id));
|
|
939
|
+
return cache.get(id);
|
|
940
|
+
};
|
|
941
|
+
};
|
|
942
|
+
const createDefinitionIndex = (definitions) => {
|
|
943
|
+
const byId = new Map(definitions.map((definition) => [definition.id, definition]));
|
|
944
|
+
const lookup = (id) => byId.get(id);
|
|
945
|
+
const children = /* @__PURE__ */ new Map();
|
|
946
|
+
for (const definition of definitions) if (isModelDefinition(definition) && definition.extends !== void 0 && definition.extends !== definition.id) children.set(definition.extends, [...children.get(definition.extends) ?? [], definition.id]);
|
|
947
|
+
const ancestors = memo((id) => {
|
|
948
|
+
const definition = byId.get(id);
|
|
949
|
+
return definition ? ancestorChain(definition, lookup).ids : [];
|
|
950
|
+
});
|
|
951
|
+
const childrenOf = (id) => children.get(id) ?? [];
|
|
952
|
+
const descendants = memo((id) => {
|
|
953
|
+
const found = [];
|
|
954
|
+
const seen = /* @__PURE__ */ new Set([id]);
|
|
955
|
+
const walk = (current) => {
|
|
956
|
+
for (const child of childrenOf(current)) if (!seen.has(child)) {
|
|
957
|
+
seen.add(child);
|
|
958
|
+
found.push(child);
|
|
959
|
+
walk(child);
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
walk(id);
|
|
963
|
+
return found;
|
|
964
|
+
});
|
|
965
|
+
return {
|
|
966
|
+
get: lookup,
|
|
967
|
+
resolve: memo((id) => {
|
|
968
|
+
const definition = byId.get(id);
|
|
969
|
+
return definition && isModelDefinition(definition) ? resolveModelDefinition(definition, lookup) : void 0;
|
|
970
|
+
}),
|
|
971
|
+
ancestors,
|
|
972
|
+
childrenOf,
|
|
973
|
+
descendants,
|
|
974
|
+
family: memo((id) => [id, ...descendants(id)]),
|
|
975
|
+
rootOf: (id) => ancestors(id)[0] ?? id
|
|
976
|
+
};
|
|
978
977
|
};
|
|
979
|
-
const issue = (path, code, message) => ({
|
|
980
|
-
path,
|
|
981
|
-
code,
|
|
982
|
-
message
|
|
983
|
-
});
|
|
984
978
|
//#endregion
|
|
985
979
|
//#region src/validators/naming.ts
|
|
986
980
|
/**
|
|
@@ -1000,6 +994,11 @@ const MODEL_TYPE_SUFFIXES = [
|
|
|
1000
994
|
"Sort"
|
|
1001
995
|
];
|
|
1002
996
|
const COMPONENT_TYPE_SUFFIXES = ["", "Input"];
|
|
997
|
+
/**
|
|
998
|
+
* Suffix of the GraphQL interface a model with children gets (`ProductInterface`, plan polymorphic-types).
|
|
999
|
+
* Claimed only by models that have children, so existing schemas keep validating.
|
|
1000
|
+
*/
|
|
1001
|
+
const INTERFACE_TYPE_SUFFIX = "Interface";
|
|
1003
1002
|
const MUTATION_PREFIXES = [
|
|
1004
1003
|
"create",
|
|
1005
1004
|
"update",
|
|
@@ -1124,11 +1123,15 @@ const RESERVED_QUERY_NAMES = [
|
|
|
1124
1123
|
"_snapshot",
|
|
1125
1124
|
"_site"
|
|
1126
1125
|
];
|
|
1127
|
-
/**
|
|
1128
|
-
|
|
1126
|
+
/**
|
|
1127
|
+
* Every name a definition contributes to the GraphQL schema. Fields are its own: a model that extends another
|
|
1128
|
+
* reuses the enum and zone types of inherited fields, which belong to the declaring model. `hasChildren` adds
|
|
1129
|
+
* the interface name of a model other models extend.
|
|
1130
|
+
*/
|
|
1131
|
+
const generatedNames = (definition, options = {}) => {
|
|
1129
1132
|
const typeName = toTypeName(definition.apiKey);
|
|
1130
1133
|
const definitionId = definition.id;
|
|
1131
|
-
const names =
|
|
1134
|
+
const names = [...definition.kind === "component" ? COMPONENT_TYPE_SUFFIXES : MODEL_TYPE_SUFFIXES, ...options.hasChildren && definition.kind !== "component" ? [INTERFACE_TYPE_SUFFIX] : []].map((suffix) => ({
|
|
1132
1135
|
namespace: "type",
|
|
1133
1136
|
name: `${typeName}${suffix}`,
|
|
1134
1137
|
definitionId
|
|
@@ -1177,6 +1180,198 @@ const generatedNames = (definition) => {
|
|
|
1177
1180
|
return names;
|
|
1178
1181
|
};
|
|
1179
1182
|
//#endregion
|
|
1183
|
+
//#region src/naming/routePath.ts
|
|
1184
|
+
/**
|
|
1185
|
+
* Nested delivery paths (plan polymorphic-types). A model that extends another is addressed under its
|
|
1186
|
+
* ancestors: `/api/content/products/digital` is the route key `products/digital`, the ancestors' route keys
|
|
1187
|
+
* (`routeKeyOf`) and its own, joined by `/`. `routeKeyOf` alone stays the plural API ID that GraphQL names
|
|
1188
|
+
* and the API ID namespace use.
|
|
1189
|
+
*/
|
|
1190
|
+
const SEGMENT = API_KEY_PATTERN.source.slice(1, -1);
|
|
1191
|
+
/** A route key: one to MAX_INHERITANCE_DEPTH API IDs joined by `/` (`products/digital`). */
|
|
1192
|
+
const ROUTE_KEY_PATTERN = new RegExp(`^${SEGMENT}(?:/${SEGMENT}){0,7}$`);
|
|
1193
|
+
/** Longest route key: every level at the longest API ID, plus the separators. */
|
|
1194
|
+
const MAX_ROUTE_KEY_LENGTH = 520;
|
|
1195
|
+
/** The route key of a model under its ancestors (root first): `products/digital`. A root's is `routeKeyOf`. */
|
|
1196
|
+
const routePathOf = (definition, ancestors) => [...ancestors, definition].map(routeKeyOf).join("/");
|
|
1197
|
+
/**
|
|
1198
|
+
* Splits the path segments after a content prefix (`/api/content/`) into the route key and the rest: the
|
|
1199
|
+
* leading segments that are API IDs (at most MAX_INHERITANCE_DEPTH) form the key; the first one that is not
|
|
1200
|
+
* (an entry ID is a UUID, which always has a hyphen) starts the rest. A single segment that already holds a
|
|
1201
|
+
* whole route key (`products%2Fdigital`, decoded) is taken as it is. Undefined when no segment is a key.
|
|
1202
|
+
* Purely syntactic: shared by the server's URL rewrite and the in-process delivery matcher.
|
|
1203
|
+
*/
|
|
1204
|
+
const splitRoutePath = (segments) => {
|
|
1205
|
+
const [first] = segments;
|
|
1206
|
+
if (first === void 0) return;
|
|
1207
|
+
if (!API_KEY_PATTERN.test(first)) return ROUTE_KEY_PATTERN.test(first) ? {
|
|
1208
|
+
routeKey: first,
|
|
1209
|
+
rest: segments.slice(1)
|
|
1210
|
+
} : void 0;
|
|
1211
|
+
let count = 1;
|
|
1212
|
+
while (count < segments.length && count < 8 && API_KEY_PATTERN.test(segments[count])) count += 1;
|
|
1213
|
+
return {
|
|
1214
|
+
routeKey: segments.slice(0, count).join("/"),
|
|
1215
|
+
rest: segments.slice(count)
|
|
1216
|
+
};
|
|
1217
|
+
};
|
|
1218
|
+
//#endregion
|
|
1219
|
+
//#region src/normalize.ts
|
|
1220
|
+
const SETTINGS_DEFAULTS = {
|
|
1221
|
+
code: { language: "plain" },
|
|
1222
|
+
richtext: { formatVersion: 1 },
|
|
1223
|
+
enum: { multiple: false },
|
|
1224
|
+
media: { multiple: false },
|
|
1225
|
+
component: { repeatable: false }
|
|
1226
|
+
};
|
|
1227
|
+
/** Optional text properties where an empty string means "not set". */
|
|
1228
|
+
const OPTIONAL_TEXT_KEYS = /* @__PURE__ */ new Set([
|
|
1229
|
+
"description",
|
|
1230
|
+
"category",
|
|
1231
|
+
"icon",
|
|
1232
|
+
"titleFieldId",
|
|
1233
|
+
"coverFieldId",
|
|
1234
|
+
"pluralApiKey",
|
|
1235
|
+
"extends"
|
|
1236
|
+
]);
|
|
1237
|
+
/** Optional list properties where an empty list means "not set". `fields` is never optional. */
|
|
1238
|
+
const OPTIONAL_LIST_KEYS = /* @__PURE__ */ new Set([
|
|
1239
|
+
"listFieldIds",
|
|
1240
|
+
"groups",
|
|
1241
|
+
"canvasFieldIds",
|
|
1242
|
+
"stripFieldIds"
|
|
1243
|
+
]);
|
|
1244
|
+
const withoutEmpty = (value) => {
|
|
1245
|
+
const result = {};
|
|
1246
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1247
|
+
const isEmptyText = OPTIONAL_TEXT_KEYS.has(key) && entry === "";
|
|
1248
|
+
const isEmptyList = OPTIONAL_LIST_KEYS.has(key) && Array.isArray(entry) && entry.length === 0;
|
|
1249
|
+
if (entry === void 0 || isEmptyText || isEmptyList) continue;
|
|
1250
|
+
result[key] = entry;
|
|
1251
|
+
}
|
|
1252
|
+
return result;
|
|
1253
|
+
};
|
|
1254
|
+
const normalizeField = (field) => {
|
|
1255
|
+
const normalized = {
|
|
1256
|
+
id: field.id,
|
|
1257
|
+
apiKey: field.apiKey,
|
|
1258
|
+
label: field.label,
|
|
1259
|
+
description: field.description,
|
|
1260
|
+
type: field.type,
|
|
1261
|
+
required: field.required ?? false,
|
|
1262
|
+
localized: field.localized ?? false,
|
|
1263
|
+
public: field.public ?? true,
|
|
1264
|
+
unique: field.unique ?? false,
|
|
1265
|
+
filterable: field.filterable ?? false,
|
|
1266
|
+
sortable: field.sortable ?? false,
|
|
1267
|
+
deprecated: field.deprecated ?? false,
|
|
1268
|
+
defaultValue: field.defaultValue,
|
|
1269
|
+
settings: {
|
|
1270
|
+
...SETTINGS_DEFAULTS[field.type],
|
|
1271
|
+
...field.settings
|
|
1272
|
+
},
|
|
1273
|
+
editor: {
|
|
1274
|
+
id: field.editor?.id ?? DEFAULT_EDITORS[field.type],
|
|
1275
|
+
options: field.editor?.options ?? {}
|
|
1276
|
+
},
|
|
1277
|
+
width: field.width === "full" ? void 0 : field.width
|
|
1278
|
+
};
|
|
1279
|
+
return withoutEmpty(normalized);
|
|
1280
|
+
};
|
|
1281
|
+
const normalizeModelDisplay = (display) => withoutEmpty({
|
|
1282
|
+
...display,
|
|
1283
|
+
layout: display?.layout === "document" ? void 0 : display?.layout
|
|
1284
|
+
});
|
|
1285
|
+
const normalizeComponentDisplay = (display) => withoutEmpty({ ...display });
|
|
1286
|
+
/**
|
|
1287
|
+
* Applies defaults so that two definitions meaning the same thing are byte-identical once serialized
|
|
1288
|
+
* canonically: a hand-written file that omits `public` hashes the same as one that says `public: true`.
|
|
1289
|
+
* Empty optional strings and arrays are dropped, and so are the defaults of keys added after definitions
|
|
1290
|
+
* were first hashed (`width: 'full'`, `display.layout: 'document'`). A collection without a plural API ID
|
|
1291
|
+
* gets the suggested one (`suggestPlural`), so files and definitions from before plural API IDs keep working.
|
|
1292
|
+
*/
|
|
1293
|
+
const normalizeDefinition = (input) => {
|
|
1294
|
+
const fields = input.fields.map(normalizeField);
|
|
1295
|
+
if (input.kind === "component") return withoutEmpty({
|
|
1296
|
+
id: input.id,
|
|
1297
|
+
kind: "component",
|
|
1298
|
+
apiKey: input.apiKey,
|
|
1299
|
+
label: input.label,
|
|
1300
|
+
description: input.description,
|
|
1301
|
+
category: input.category,
|
|
1302
|
+
icon: input.icon,
|
|
1303
|
+
fields,
|
|
1304
|
+
display: normalizeComponentDisplay(input.display)
|
|
1305
|
+
});
|
|
1306
|
+
return withoutEmpty({
|
|
1307
|
+
id: input.id,
|
|
1308
|
+
kind: input.kind,
|
|
1309
|
+
apiKey: input.apiKey,
|
|
1310
|
+
pluralApiKey: input.kind === "collection" ? input.pluralApiKey || suggestPlural(input.apiKey) : void 0,
|
|
1311
|
+
label: input.label,
|
|
1312
|
+
description: input.description,
|
|
1313
|
+
localized: input.localized ?? false,
|
|
1314
|
+
draftAndPublish: input.draftAndPublish ?? true,
|
|
1315
|
+
extends: input.extends,
|
|
1316
|
+
fields,
|
|
1317
|
+
display: normalizeModelDisplay(input.display)
|
|
1318
|
+
});
|
|
1319
|
+
};
|
|
1320
|
+
/** Normalizes a definition that is already normalized-shaped (e.g. read back from the database). */
|
|
1321
|
+
const renormalizeDefinition = (definition) => normalizeDefinition(definition);
|
|
1322
|
+
//#endregion
|
|
1323
|
+
//#region src/validators/defaultValue.ts
|
|
1324
|
+
/**
|
|
1325
|
+
* Checks that a field's `defaultValue` has the right JSON shape for its type. Full content rules
|
|
1326
|
+
* (lengths, ranges) are applied by the content validator when the default is used.
|
|
1327
|
+
* Returns a message, or null when the default is acceptable.
|
|
1328
|
+
*/
|
|
1329
|
+
const checkDefaultValue = (field) => {
|
|
1330
|
+
const value = field.defaultValue;
|
|
1331
|
+
if (value === void 0) return null;
|
|
1332
|
+
const isString = typeof value === "string";
|
|
1333
|
+
const matches = (pattern) => isString && pattern.test(value);
|
|
1334
|
+
switch (field.type) {
|
|
1335
|
+
case "string":
|
|
1336
|
+
case "text":
|
|
1337
|
+
case "code":
|
|
1338
|
+
case "email":
|
|
1339
|
+
case "url": return isString ? null : "must be a string";
|
|
1340
|
+
case "slug": return matches(SLUG_PATTERN) ? null : "must be a lower-case slug";
|
|
1341
|
+
case "uid": return matches(UID_PATTERN) ? null : "must contain only URL-safe characters";
|
|
1342
|
+
case "number": return typeof value === "number" && Number.isFinite(value) ? null : "must be a number";
|
|
1343
|
+
case "integer": return Number.isSafeInteger(value) ? null : "must be an integer";
|
|
1344
|
+
case "decimal": return matches(DECIMAL_PATTERN) ? null : "must be a decimal string, e.g. \"12.50\"";
|
|
1345
|
+
case "biginteger": return matches(BIGINTEGER_PATTERN) ? null : "must be an integer string, e.g. \"9007199254740993\"";
|
|
1346
|
+
case "boolean": return typeof value === "boolean" ? null : "must be true or false";
|
|
1347
|
+
case "date": return matches(DATE_PATTERN) ? null : "must be a date, YYYY-MM-DD";
|
|
1348
|
+
case "datetime": return matches(DATETIME_PATTERN) ? null : "must be a UTC date-time, YYYY-MM-DDTHH:mm:ssZ";
|
|
1349
|
+
case "time": return matches(TIME_PATTERN) ? null : "must be a time, HH:mm[:ss]";
|
|
1350
|
+
case "enum": {
|
|
1351
|
+
const allowed = new Set(field.settings.values.map((entry) => entry.value));
|
|
1352
|
+
if (field.settings.multiple) return Array.isArray(value) && value.every((item) => typeof item === "string" && allowed.has(item)) ? null : "must be a list of the enum values";
|
|
1353
|
+
return isString && allowed.has(value) ? null : "must be one of the enum values";
|
|
1354
|
+
}
|
|
1355
|
+
case "json": return null;
|
|
1356
|
+
default: return `${field.type} fields cannot have a default value`;
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
//#endregion
|
|
1360
|
+
//#region src/validators/issues.ts
|
|
1361
|
+
var SchemaValidationError = class extends Error {
|
|
1362
|
+
issues;
|
|
1363
|
+
constructor(issues) {
|
|
1364
|
+
super(`Schema definition is invalid: ${issues.map((issue) => `${issue.path} ${issue.message}`).join("; ")}`);
|
|
1365
|
+
this.name = "SchemaValidationError";
|
|
1366
|
+
this.issues = issues;
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
const issue = (path, code, message) => ({
|
|
1370
|
+
path,
|
|
1371
|
+
code,
|
|
1372
|
+
message
|
|
1373
|
+
});
|
|
1374
|
+
//#endregion
|
|
1180
1375
|
//#region src/validators/text.ts
|
|
1181
1376
|
/**
|
|
1182
1377
|
* Text no database can store as is: PostgreSQL `text` and `jsonb` reject U+0000, and `jsonb` rejects an
|
|
@@ -1451,6 +1646,10 @@ const checkDocumentLayout = (model, fields) => {
|
|
|
1451
1646
|
}));
|
|
1452
1647
|
return issues;
|
|
1453
1648
|
};
|
|
1649
|
+
/**
|
|
1650
|
+
* The display's references to fields. Also run by the inheritance check on a child's resolved definition,
|
|
1651
|
+
* whose display may name inherited fields.
|
|
1652
|
+
*/
|
|
1454
1653
|
const checkDisplay = (definition) => {
|
|
1455
1654
|
const fields = new Map(definition.fields.map((field) => [field.id, field]));
|
|
1456
1655
|
const fieldIds = new Set(fields.keys());
|
|
@@ -1499,10 +1698,94 @@ const validateDefinition = (definition) => {
|
|
|
1499
1698
|
if (!syntax) issues.push(...checkPluralApiKey(definition));
|
|
1500
1699
|
definition.fields.forEach((field, index) => issues.push(...checkField(field, index, definition)));
|
|
1501
1700
|
issues.push(...checkFieldUniqueness(definition));
|
|
1502
|
-
issues.push(...checkDisplay(definition));
|
|
1701
|
+
if (definition.kind === "component" || definition.extends === void 0) issues.push(...checkDisplay(definition));
|
|
1503
1702
|
return issues;
|
|
1504
1703
|
};
|
|
1505
1704
|
//#endregion
|
|
1705
|
+
//#region src/validators/inheritance.ts
|
|
1706
|
+
/**
|
|
1707
|
+
* Cross-definition checks of model inheritance (plan polymorphic-types): the parent exists and is a
|
|
1708
|
+
* collection, no cycles, at most MAX_INHERITANCE_DEPTH levels, a child's flags equal its parent's, its own
|
|
1709
|
+
* fields' API IDs differ from every inherited one, and its display is valid against its resolved fields.
|
|
1710
|
+
* Issues are reported on the definition that has to change, on its own paths.
|
|
1711
|
+
*/
|
|
1712
|
+
const withDefinition$1 = (definition, found) => ({
|
|
1713
|
+
...found,
|
|
1714
|
+
definitionId: definition.id
|
|
1715
|
+
});
|
|
1716
|
+
/** Whether following `extends` from the definition comes back to it. */
|
|
1717
|
+
const isInCycle = (definition, index) => {
|
|
1718
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1719
|
+
let current = definition;
|
|
1720
|
+
while (current && isModelDefinition(current) && current.extends !== void 0 && !seen.has(current.id)) {
|
|
1721
|
+
seen.add(current.id);
|
|
1722
|
+
if (current.extends === definition.id) return true;
|
|
1723
|
+
current = index.get(current.extends);
|
|
1724
|
+
}
|
|
1725
|
+
return false;
|
|
1726
|
+
};
|
|
1727
|
+
const parentIssues = (definition, index) => {
|
|
1728
|
+
if (definition.extends === void 0) return [];
|
|
1729
|
+
if (definition.kind !== "collection") return [issue("/extends", "INVALID_PARENT", `only a collection can extend another; this is a ${definition.kind}`)];
|
|
1730
|
+
const chain = ancestorChain(definition, index.get);
|
|
1731
|
+
const parent = index.get(definition.extends);
|
|
1732
|
+
switch (chain.problem) {
|
|
1733
|
+
case "missing": return chain.problemAt === definition.id ? [issue("/extends", "UNKNOWN_PARENT", "extends a type that does not exist")] : [];
|
|
1734
|
+
case "invalid":
|
|
1735
|
+
if (chain.problemAt !== definition.id) return [];
|
|
1736
|
+
return [issue("/extends", "INVALID_PARENT", definition.extends === definition.id ? "a type cannot extend itself" : `can only extend a collection; "${parent?.apiKey ?? definition.extends}" is a ${parent?.kind ?? "definition"}`)];
|
|
1737
|
+
case "cycle": return isInCycle(definition, index) ? [issue("/extends", "INHERITANCE_CYCLE", "types extend each other in a cycle")] : [];
|
|
1738
|
+
case "tooDeep": return [issue("/extends", "INHERITANCE_TOO_DEEP", `a family can have at most 8 levels of types`)];
|
|
1739
|
+
default: return [];
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
const FLAGS = ["localized", "draftAndPublish"];
|
|
1743
|
+
const flagIssues = (definition, parent) => FLAGS.filter((flag) => definition[flag] !== parent[flag]).map((flag) => issue(`/${flag}`, "INHERITED_FLAG_MISMATCH", `must equal the parent's: set "${flag}" to ${String(parent[flag])}, as "${parent.apiKey}" has it`));
|
|
1744
|
+
const collisionIssues = (definition, index) => {
|
|
1745
|
+
const resolved = index.resolve(definition.id);
|
|
1746
|
+
const inherited = /* @__PURE__ */ new Map();
|
|
1747
|
+
for (const field of resolved?.fields ?? []) if (field.inheritedFrom !== void 0) inherited.set(foldApiKey(field.apiKey), index.get(field.inheritedFrom)?.apiKey ?? field.inheritedFrom);
|
|
1748
|
+
return definition.fields.flatMap((field, position) => {
|
|
1749
|
+
const owner = inherited.get(foldApiKey(field.apiKey));
|
|
1750
|
+
return owner === void 0 ? [] : [issue(`/fields/${position}/apiKey`, "INHERITED_FIELD_COLLISION", `"${field.apiKey}" is already a field of "${owner}", which this type extends (keys are case-insensitive)`)];
|
|
1751
|
+
});
|
|
1752
|
+
};
|
|
1753
|
+
const GROUP_PATH = /^\/display\/groups\/(\d+)(\/.*)?$/;
|
|
1754
|
+
/**
|
|
1755
|
+
* The display checked against the resolved fields. An issue counts when it is about a value the child sets
|
|
1756
|
+
* itself (the parent's own values were checked on the parent); group paths are moved to the child's groups.
|
|
1757
|
+
*/
|
|
1758
|
+
const displayIssues = (definition, index) => {
|
|
1759
|
+
const resolved = index.resolve(definition.id);
|
|
1760
|
+
if (!resolved) return [];
|
|
1761
|
+
const inheritedGroups = (resolved.display.groups?.length ?? 0) - (definition.display.groups?.length ?? 0);
|
|
1762
|
+
const own = definition.display;
|
|
1763
|
+
return checkDisplay(resolved).flatMap((found) => {
|
|
1764
|
+
const group = GROUP_PATH.exec(found.path);
|
|
1765
|
+
if (group) {
|
|
1766
|
+
const position = Number(group[1]) - inheritedGroups;
|
|
1767
|
+
return position < 0 ? [] : [{
|
|
1768
|
+
...found,
|
|
1769
|
+
path: `/display/groups/${position}${group[2] ?? ""}`
|
|
1770
|
+
}];
|
|
1771
|
+
}
|
|
1772
|
+
const property = found.path.split("/")[2];
|
|
1773
|
+
return property !== void 0 && own[property] !== void 0 ? [found] : [];
|
|
1774
|
+
});
|
|
1775
|
+
};
|
|
1776
|
+
/** Every inheritance problem of a schema (see the module comment). */
|
|
1777
|
+
const checkInheritance = (definitions, index = createDefinitionIndex(definitions)) => definitions.flatMap((definition) => {
|
|
1778
|
+
if (!isModelDefinition(definition) || definition.extends === void 0) return [];
|
|
1779
|
+
const problems = parentIssues(definition, index);
|
|
1780
|
+
const parent = index.get(definition.extends);
|
|
1781
|
+
const checks = problems.length === 0 && parent && isModelDefinition(parent) ? [
|
|
1782
|
+
...flagIssues(definition, parent),
|
|
1783
|
+
...collisionIssues(definition, index),
|
|
1784
|
+
...displayIssues(definition, index)
|
|
1785
|
+
] : [];
|
|
1786
|
+
return [...problems, ...checks].map((found) => withDefinition$1(definition, found));
|
|
1787
|
+
});
|
|
1788
|
+
//#endregion
|
|
1506
1789
|
//#region src/validators/parse.ts
|
|
1507
1790
|
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1508
1791
|
const structuralIssues = (raw) => {
|
|
@@ -1622,7 +1905,8 @@ const checkGeneratedNames = (definitions) => {
|
|
|
1622
1905
|
const byId = new Map(definitions.map((definition) => [definition.id, definition]));
|
|
1623
1906
|
const issues = [];
|
|
1624
1907
|
const seen = /* @__PURE__ */ new Map();
|
|
1625
|
-
|
|
1908
|
+
const parents = new Set(definitions.flatMap((definition) => isModelDefinition(definition) && definition.extends !== void 0 ? [definition.extends] : []));
|
|
1909
|
+
for (const definition of definitions) for (const name of generatedNames(definition, { hasChildren: parents.has(definition.id) })) {
|
|
1626
1910
|
const folded = `${name.namespace}:${foldApiKey(name.name)}`;
|
|
1627
1911
|
const path = name.fieldId ? `/fields/${definition.fields.findIndex((field) => field.id === name.fieldId)}/apiKey` : `/${name.property ?? "apiKey"}`;
|
|
1628
1912
|
if (name.namespace === "type" && RESERVED_TYPES.has(foldApiKey(name.name))) {
|
|
@@ -1712,11 +1996,12 @@ const checkNesting = (definitions) => {
|
|
|
1712
1996
|
};
|
|
1713
1997
|
/**
|
|
1714
1998
|
* Cross-definition checks over a whole schema (every active definition with the proposed change applied):
|
|
1715
|
-
* unique IDs and API keys, GraphQL name collisions, relation and component targets,
|
|
1716
|
-
* nesting depth. Each definition must already pass `validateDefinition`.
|
|
1999
|
+
* unique IDs and API keys, model inheritance, GraphQL name collisions, relation and component targets,
|
|
2000
|
+
* component cycles and nesting depth. Each definition must already pass `validateDefinition`.
|
|
1717
2001
|
*/
|
|
1718
2002
|
const validateSchema = (definitions) => [
|
|
1719
2003
|
...checkIdentity(definitions),
|
|
2004
|
+
...checkInheritance(definitions),
|
|
1720
2005
|
...checkGeneratedNames(definitions),
|
|
1721
2006
|
...checkReferences(definitions),
|
|
1722
2007
|
...checkNesting(definitions)
|
|
@@ -1724,12 +2009,14 @@ const validateSchema = (definitions) => [
|
|
|
1724
2009
|
/** Definitions that reference `id` (relations, components, dynamic zones). Used to block deletions. */
|
|
1725
2010
|
const findReferencingDefinitions = (definitions, id) => definitions.filter((definition) => definition.id !== id && definition.fields.some((field) => field.type === "relation" && field.settings.target === id || field.type === "component" && field.settings.component === id || field.type === "dynamiczone" && field.settings.components.includes(id)));
|
|
1726
2011
|
/**
|
|
1727
|
-
* Models that embed a component directly or through
|
|
1728
|
-
* when the component does (brief §5: "editing a shared component must account for
|
|
2012
|
+
* Models that embed a component directly, through other components or through an inherited field, i.e.
|
|
2013
|
+
* whose entries change shape when the component does (brief §5: "editing a shared component must account for
|
|
2014
|
+
* all dependent models").
|
|
1729
2015
|
*/
|
|
1730
2016
|
const findDependentModels = (definitions, componentId) => {
|
|
1731
2017
|
const embeddedBy = /* @__PURE__ */ new Map();
|
|
1732
2018
|
for (const definition of definitions) for (const child of embeddedComponentIds(definition)) embeddedBy.set(child, [...embeddedBy.get(child) ?? [], definition]);
|
|
2019
|
+
const index = createDefinitionIndex(definitions);
|
|
1733
2020
|
const models = /* @__PURE__ */ new Map();
|
|
1734
2021
|
const visited = /* @__PURE__ */ new Set();
|
|
1735
2022
|
const queue = [componentId];
|
|
@@ -1738,7 +2025,10 @@ const findDependentModels = (definitions, componentId) => {
|
|
|
1738
2025
|
if (visited.has(current)) continue;
|
|
1739
2026
|
visited.add(current);
|
|
1740
2027
|
for (const parent of embeddedBy.get(current) ?? []) if (parent.kind === "component") queue.push(parent.id);
|
|
1741
|
-
else
|
|
2028
|
+
else for (const id of index.family(parent.id)) {
|
|
2029
|
+
const model = index.get(id);
|
|
2030
|
+
if (model) models.set(id, model);
|
|
2031
|
+
}
|
|
1742
2032
|
}
|
|
1743
2033
|
return [...models.values()];
|
|
1744
2034
|
};
|
|
@@ -1753,6 +2043,7 @@ const CHANGE_KINDS = [
|
|
|
1753
2043
|
"model.kind",
|
|
1754
2044
|
"model.localized",
|
|
1755
2045
|
"model.draftAndPublish",
|
|
2046
|
+
"model.extends",
|
|
1756
2047
|
"field.added",
|
|
1757
2048
|
"field.removed",
|
|
1758
2049
|
"field.order",
|
|
@@ -1922,6 +2213,12 @@ const diffDefinitions = (before, after) => {
|
|
|
1922
2213
|
});
|
|
1923
2214
|
}
|
|
1924
2215
|
if (before.kind !== "component" && after.kind !== "component") {
|
|
2216
|
+
if ((before.extends ?? null) !== (after.extends ?? null)) changes.push({
|
|
2217
|
+
kind: "model.extends",
|
|
2218
|
+
definitionId,
|
|
2219
|
+
from: before.extends ?? null,
|
|
2220
|
+
to: after.extends ?? null
|
|
2221
|
+
});
|
|
1925
2222
|
if (before.localized !== after.localized) changes.push({
|
|
1926
2223
|
kind: "model.localized",
|
|
1927
2224
|
definitionId,
|
|
@@ -2060,8 +2357,12 @@ const convertsAcrossFamilies = (from, to) => {
|
|
|
2060
2357
|
* that flag rather than deciding again from the type names.
|
|
2061
2358
|
*/
|
|
2062
2359
|
const typeChangeDropsValues = (from, to) => storageFamilyOf(from) !== storageFamilyOf(to) && !convertsAcrossFamilies(from, to);
|
|
2063
|
-
/**
|
|
2064
|
-
|
|
2360
|
+
/**
|
|
2361
|
+
* Whether a (classified) change drops every stored value of its field; false for plans without the flag. A
|
|
2362
|
+
* type change Shapio cannot convert, or an inherited field a model loses when it moves to another parent
|
|
2363
|
+
* (`field.removed` marked by the planner, plan polymorphic-types decision 6).
|
|
2364
|
+
*/
|
|
2365
|
+
const changeDropsValues = (change) => (change.kind === "field.type" || change.kind === "field.removed") && change.dropsValues === true;
|
|
2065
2366
|
/** A required field whose values are dropped is backfilled from its default, else checked (no silent nulls). */
|
|
2066
2367
|
const requiredStep = (field) => {
|
|
2067
2368
|
if (!field?.required) return [];
|
|
@@ -2243,6 +2544,9 @@ const classifyChange = (change, context) => {
|
|
|
2243
2544
|
});
|
|
2244
2545
|
break;
|
|
2245
2546
|
}
|
|
2547
|
+
case "model.extends":
|
|
2548
|
+
classification = result("contract", { breaking: true });
|
|
2549
|
+
break;
|
|
2246
2550
|
case "model.draftAndPublish":
|
|
2247
2551
|
classification = change.to === true ? result("additive") : result("conversion", {
|
|
2248
2552
|
destructive: true,
|
|
@@ -2885,6 +3189,6 @@ const SeoDefaultsSchema = Type.Object({
|
|
|
2885
3189
|
twitterHandle: Type.Union([Type.String({ pattern: SEO_TWITTER_HANDLE_PATTERN.source }), Type.Null()])
|
|
2886
3190
|
}, closed);
|
|
2887
3191
|
//#endregion
|
|
2888
|
-
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, INVALID_TEXT_MESSAGE, 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, changeDropsValues, checkApiKeySyntax, checkDefaultValue, checkThemeContrast, classifyChange, classifyChanges, classifyLocaleChange, collectionQueryName, contrastRatio, createStableId, decideSync, diffDefinitions, effectiveFormLayout, effectiveLayout, effectiveTitleField, embeddedComponentIds, entryLayoutOf, escapeHtml, findDependentModels, findInvalidText, findReferencingDefinitions, foldApiKey, generatedNames, hashDefinition, isApiKeySyntaxValid, isCanvasEligible, isComponentDefinition, isCoverEligible, isCustomEditorId, isDataType, isDocumentPlaceable, isEmptyRichText, isModelDefinition, isSeoField, isStableId, isValidText, 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, storageFamilyOf, stripFieldsOf, suggestPlural, summarizeChanges, toTypeName, typeChangeDropsValues, uniqueFieldsChangingLocalization, upgradeLockFile, validateDefinition, validateRichText, validateSchema, withDerivedPlural, withSiteCovered };
|
|
3192
|
+
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, INTERFACE_TYPE_SUFFIX, INVALID_TEXT_MESSAGE, 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_INHERITANCE_DEPTH, MAX_LABEL_LENGTH, MAX_RICHTEXT_DEPTH, MAX_ROUTE_KEY_LENGTH, 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, ROUTE_KEY_PATTERN, 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, ancestorChain, applyTitleTemplate, assignIds, canonicalJson, changeDropsValues, checkApiKeySyntax, checkDefaultValue, checkDisplay, checkInheritance, checkThemeContrast, classifyChange, classifyChanges, classifyLocaleChange, collectionQueryName, contrastRatio, createDefinitionIndex, createStableId, decideSync, diffDefinitions, effectiveFormLayout, effectiveLayout, effectiveTitleField, embeddedComponentIds, entryLayoutOf, escapeHtml, findChildDefinitions, findDependentModels, findInvalidText, findReferencingDefinitions, foldApiKey, generatedNames, hashDefinition, isApiKeySyntaxValid, isCanvasEligible, isComponentDefinition, isCoverEligible, isCustomEditorId, isDataType, isDocumentPlaceable, isEmptyRichText, isModelDefinition, isSeoField, isStableId, isValidText, isValidTitleTemplate, issue, listCompatibleEditors, lockCoversSite, lockEntriesForSite, mergeDisplay, mergePulledLock, normalizeDefinition, ownFields, parseDefinition, parseLockFile, plainTextToRichText, renderRichTextHtml, renormalizeDefinition, resolveModelDefinition, resolveSeo, richTextBodyOf, richTextMediaIds, richTextPlainText, richTextToPlainText, routeKeyOf, routePathOf, schemaFilePath, scopeDirectory, scopeOfSchemaFilePath, seoComponentDefinition, seoDefaultsForLocale, serializeDefinition, serializeLockFile, sha256, splitRoutePath, storageFamilyOf, stripFieldsOf, suggestPlural, summarizeChanges, toTypeName, typeChangeDropsValues, uniqueFieldsChangingLocalization, upgradeLockFile, validateDefinition, validateRichText, validateSchema, withDerivedPlural, withSiteCovered };
|
|
2889
3193
|
|
|
2890
3194
|
//# sourceMappingURL=index.js.map
|