@relyio/sdk 0.12.0 → 0.14.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/dist/{chunk-3DRDHXLY.js → chunk-5UMY6CS4.js} +435 -23
  3. package/dist/{chunk-FRJEUAAF.js → chunk-DBBZVZOS.js} +968 -2670
  4. package/dist/{chunk-DXCVO6M5.cjs → chunk-GUPV2M3X.cjs} +969 -2680
  5. package/dist/{chunk-POEEDYFM.js → chunk-PCFTJLEP.js} +543 -260
  6. package/dist/{chunk-FI5VIF4M.cjs → chunk-RSHMA4KT.cjs} +442 -22
  7. package/dist/{chunk-3MSFWXQL.cjs → chunk-X4KSS7PP.cjs} +542 -260
  8. package/dist/client/index.cjs +3 -3
  9. package/dist/client/index.d.cts +4 -5
  10. package/dist/client/index.d.ts +4 -5
  11. package/dist/client/index.js +1 -1
  12. package/dist/{include-paths-CL4gHv6I.d.cts → define-content-type-BUZeWtpk.d.cts} +4458 -1312
  13. package/dist/{include-paths-CrJNTsMl.d.ts → define-content-type-DOrZb_a_.d.ts} +4458 -1312
  14. package/dist/define-schema-5aHmsA8j.d.ts +699 -0
  15. package/dist/define-schema-nCfZeboB.d.cts +699 -0
  16. package/dist/index-B97ZDj44.d.ts +507 -0
  17. package/dist/index-C-pcllqY.d.cts +507 -0
  18. package/dist/index-DOhFQbv7.d.ts +3065 -0
  19. package/dist/index-DgVV5Nj7.d.cts +3065 -0
  20. package/dist/index.cjs +114 -147
  21. package/dist/index.d.cts +49 -87
  22. package/dist/index.d.ts +49 -87
  23. package/dist/index.js +5 -29
  24. package/dist/markdown/index.cjs +13750 -0
  25. package/dist/markdown/index.d.cts +111 -0
  26. package/dist/markdown/index.d.ts +111 -0
  27. package/dist/markdown/index.js +13742 -0
  28. package/dist/rich-text/index.cjs +26 -6
  29. package/dist/rich-text/index.d.cts +3 -148
  30. package/dist/rich-text/index.d.ts +3 -148
  31. package/dist/rich-text/index.js +1 -1
  32. package/dist/rich-text.d-BWc-5gX1.d.cts +326 -0
  33. package/dist/rich-text.d-BWc-5gX1.d.ts +326 -0
  34. package/dist/schema/index.cjs +33 -37
  35. package/dist/schema/index.d.cts +37 -191
  36. package/dist/schema/index.d.ts +37 -191
  37. package/dist/schema/index.js +1 -1
  38. package/dist/types/index.d.cts +2 -3
  39. package/dist/types/index.d.ts +2 -3
  40. package/package.json +14 -3
  41. package/dist/define-schema-DgctHlI2.d.cts +0 -1034
  42. package/dist/define-schema-mrEZbY1h.d.ts +0 -1034
  43. package/dist/index-B9RfZVF1.d.ts +0 -861
  44. package/dist/index-BrvEg6-A.d.cts +0 -3729
  45. package/dist/index-Pw0onkJe.d.ts +0 -3729
  46. package/dist/index-b-AkfTUv.d.cts +0 -861
  47. package/dist/rich-text.d-rKmpgdGd.d.cts +0 -200
  48. package/dist/rich-text.d-rKmpgdGd.d.ts +0 -200
@@ -2,11 +2,362 @@ import { __name, __export } from './chunk-XQ4QHWZE.js';
2
2
 
3
3
  /* RelyIO SDK - Built with ❤️ by the RelyIO Team */
4
4
 
5
+ // src/schema/fields/keyed.ts
6
+ var KEYED_FIELD_OPTIONS = [
7
+ "displayName",
8
+ "description",
9
+ "ui",
10
+ "ai",
11
+ "rules",
12
+ "localized",
13
+ "fallback",
14
+ "deprecated"
15
+ ];
16
+ function keyedField(type, options) {
17
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
18
+ throw new Error(
19
+ `fields.${type} takes one options object: the id-first form, fields.${type}('id', { config }), was removed in 0.14. Write the Field under its key, as id: fields.${type}({ \u2026 })`
20
+ );
21
+ }
22
+ return { _tag: "keyedField", type, options: { ...options } };
23
+ }
24
+ __name(keyedField, "keyedField");
25
+ function labelFromKey(key) {
26
+ const words = key.replace(/[_\-\s]+/g, " ").replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").trim().split(/\s+/).filter((word) => word.length > 0);
27
+ return words.map((word, index) => {
28
+ const acronym = word.length > 1 && word === word.toUpperCase() && /[A-Z]/.test(word);
29
+ const cased = acronym ? word : word.toLowerCase();
30
+ return index === 0 ? cased.charAt(0).toUpperCase() + cased.slice(1) : cased;
31
+ }).join(" ");
32
+ }
33
+ __name(labelFromKey, "labelFromKey");
34
+ function isKeyedField(value) {
35
+ return typeof value === "object" && value !== null && value._tag === "keyedField";
36
+ }
37
+ __name(isKeyedField, "isKeyedField");
38
+ function localizationOf(localized, id) {
39
+ if (localized === true) return true;
40
+ if (typeof localized === "object" && localized !== null && !Array.isArray(localized)) {
41
+ const { requiredLocales, fallback } = localized;
42
+ return {
43
+ enabled: true,
44
+ ...requiredLocales !== void 0 && { requiredLocales },
45
+ ...fallback !== void 0 && { fallback }
46
+ };
47
+ }
48
+ throw new Error(
49
+ `Field '${id}' has localized: ${JSON.stringify(localized)}. It takes true, or { requiredLocales, fallback } \u2014 leave it out for a Field every locale shares`
50
+ );
51
+ }
52
+ __name(localizationOf, "localizationOf");
53
+ function compileKeyedField(id, field) {
54
+ const config = {};
55
+ const shared = {};
56
+ const sharedNames = new Set(KEYED_FIELD_OPTIONS);
57
+ for (const [key, value] of Object.entries(field.options)) {
58
+ if (value === void 0) continue;
59
+ if (sharedNames.has(key)) shared[key] = value;
60
+ else config[key] = value;
61
+ }
62
+ return {
63
+ id,
64
+ type: field.type,
65
+ ...Object.keys(config).length > 0 && { config },
66
+ ui: {
67
+ displayName: typeof shared.displayName === "string" ? shared.displayName : labelFromKey(id),
68
+ ...shared.description !== void 0 && { description: shared.description },
69
+ ...shared.ui !== void 0 && { options: shared.ui }
70
+ },
71
+ ...shared.ai !== void 0 && { ai: shared.ai },
72
+ ...shared.rules !== void 0 && { rules: shared.rules },
73
+ ...shared.localized !== void 0 && { localization: localizationOf(shared.localized, id) },
74
+ ...shared.fallback !== void 0 && { fallback: shared.fallback },
75
+ ...shared.deprecated !== void 0 && { deprecated: shared.deprecated }
76
+ };
77
+ }
78
+ __name(compileKeyedField, "compileKeyedField");
79
+ function isSection(value) {
80
+ return typeof value === "object" && value !== null && !("type" in value) && "fields" in value && Array.isArray(value.fields);
81
+ }
82
+ __name(isSection, "isSection");
83
+ function notKeyed(id) {
84
+ return new Error(
85
+ `Field '${id}' is not written in the keyed form: under a key, write it without an id, as fields.shortText({ \u2026 })`
86
+ );
87
+ }
88
+ __name(notKeyed, "notKeyed");
89
+ function compileKeyedFields(fields2) {
90
+ return Object.entries(fields2).map(([id, field]) => {
91
+ if (isSection(field)) {
92
+ throw new Error(
93
+ `'${id}' is a fieldset, and a settings group holds no Sections: write its Fields beside the others`
94
+ );
95
+ }
96
+ if (!isKeyedField(field)) throw notKeyed(id);
97
+ return compileKeyedField(id, field);
98
+ });
99
+ }
100
+ __name(compileKeyedFields, "compileKeyedFields");
101
+ function compileKeyedMembers(members) {
102
+ const compiled = Object.entries(members).map(([id, member]) => {
103
+ if (isKeyedField(member)) return compileKeyedField(id, member);
104
+ if (!isSection(member)) throw notKeyed(id);
105
+ if (member.id !== id) {
106
+ throw new Error(
107
+ `Section '${id}' holds fieldset '${member.id}': a Section's key is its fieldset's id, so write it as ${member.id}: \u2026`
108
+ );
109
+ }
110
+ return member;
111
+ });
112
+ refuseAFieldIdTwice(compiled);
113
+ return compiled;
114
+ }
115
+ __name(compileKeyedMembers, "compileKeyedMembers");
116
+ function fieldPaths(members, prefix) {
117
+ return members.flatMap((member) => {
118
+ if (isSection(member)) return fieldPaths(member.fields, `${prefix}${member.id}.`);
119
+ const { id } = member;
120
+ return [[id, `${prefix}${id}`]];
121
+ });
122
+ }
123
+ __name(fieldPaths, "fieldPaths");
124
+ function refuseAFieldIdTwice(members) {
125
+ const firstPath = /* @__PURE__ */ new Map();
126
+ for (const [id, path] of fieldPaths(members, "")) {
127
+ const first = firstPath.get(id);
128
+ if (first !== void 0) {
129
+ throw new Error(
130
+ `Field '${id}' is declared twice, at '${first}' and at '${path}': a Section's Fields join the record it sits in, so a Field id is used once across its Sections`
131
+ );
132
+ }
133
+ firstPath.set(id, path);
134
+ }
135
+ }
136
+ __name(refuseAFieldIdTwice, "refuseAFieldIdTwice");
137
+
138
+ // src/schema/define-content-type.ts
139
+ function fieldset(id, config) {
140
+ if (Array.isArray(config.fields)) {
141
+ throw new Error(
142
+ `fieldset('${id}') takes its fields as a record, each Field under its id: the list form was removed in 0.14`
143
+ );
144
+ }
145
+ return {
146
+ id,
147
+ displayName: config.displayName ?? labelFromKey(id),
148
+ ...config.description !== void 0 && { description: config.description },
149
+ ...config.collapsed !== void 0 && { collapsedByDefault: config.collapsed },
150
+ ...config.ai !== void 0 && { ai: config.ai },
151
+ fields: compileKeyedMembers(config.fields)
152
+ };
153
+ }
154
+ __name(fieldset, "fieldset");
155
+ function keyedSettings(config) {
156
+ return {
157
+ ...config.displayName !== void 0 && { displayName: config.displayName },
158
+ ...config.displayNamePlural !== void 0 && { displayNamePlural: config.displayNamePlural },
159
+ ...config.description !== void 0 && { description: config.description },
160
+ ...config.ai !== void 0 && { ai: config.ai },
161
+ ...config.displayField !== void 0 && { displayField: config.displayField },
162
+ ...config.accessTags !== void 0 && { accessTags: config.accessTags },
163
+ ...config.workflow !== void 0 && { workflow: config.workflow }
164
+ };
165
+ }
166
+ __name(keyedSettings, "keyedSettings");
167
+ function idFormRemoved(builder, name) {
168
+ return new Error(
169
+ `${builder}('${name}', \u2026) is the id-first form, removed in 0.14: write ${builder}({ \u2026 }) under its key in defineSchema`
170
+ );
171
+ }
172
+ __name(idFormRemoved, "idFormRemoved");
173
+ function defineContentType(config) {
174
+ if (typeof config === "string") throw idFormRemoved("defineContentType", config);
175
+ return {
176
+ _tag: "contentType",
177
+ ...keyedSettings(config),
178
+ // Written only when given, so a TypeScript type and its YAML twin hash alike (REL-1458)
179
+ ...config.hierarchical !== void 0 && { hierarchical: config.hierarchical },
180
+ fields: compileKeyedMembers(config.fields)
181
+ };
182
+ }
183
+ __name(defineContentType, "defineContentType");
184
+
185
+ // src/schema/keyed-declarations.ts
186
+ function isList(value) {
187
+ return Array.isArray(value);
188
+ }
189
+ __name(isList, "isList");
190
+ function isKeyed(value, tag) {
191
+ return typeof value === "object" && value !== null && value._tag === tag;
192
+ }
193
+ __name(isKeyed, "isKeyed");
194
+
195
+ // src/schema/define-role.ts
196
+ var BUILT_IN_ROLES = ["admin", "editor", "viewer"];
197
+ var ROLE_ACTIONS = [
198
+ "create",
199
+ "read",
200
+ "update",
201
+ "delete",
202
+ "publish",
203
+ "unpublish",
204
+ "duplicate",
205
+ "comment",
206
+ "approveElevated"
207
+ ];
208
+ var BUILT_IN_ROLE_ACTIONS = {
209
+ admin: ["create", "read", "update", "delete", "publish", "unpublish", "duplicate", "comment"],
210
+ editor: ["create", "read", "update", "delete", "publish", "unpublish", "duplicate", "comment"],
211
+ viewer: ["read", "comment"]
212
+ };
213
+ function defineRole(config) {
214
+ if (typeof config === "string") throw idFormRemoved("defineRole", config);
215
+ return { ...config, _tag: "keyedRole" };
216
+ }
217
+ __name(defineRole, "defineRole");
218
+ function compileKeyedRoles(roles) {
219
+ return Object.entries(roles).map(([id, role]) => {
220
+ if (!isKeyed(role, "keyedRole")) {
221
+ throw new Error(
222
+ `Role '${id}' is not written in the keyed form: under a key, write it without an id, as defineRole({ \u2026 })`
223
+ );
224
+ }
225
+ const { _tag: _keyed, displayName, ...config } = role;
226
+ return roleDefinition(id, { ...config, displayName: displayName ?? labelFromKey(id) });
227
+ });
228
+ }
229
+ __name(compileKeyedRoles, "compileKeyedRoles");
230
+ function roleDefinition(id, config) {
231
+ if (!id || id.length === 0) {
232
+ throw new Error("Role ID is required");
233
+ }
234
+ const roleIdPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$|^[a-z][a-zA-Z0-9]*$/;
235
+ if (!roleIdPattern.test(id)) {
236
+ throw new Error(
237
+ `Role ID '${id}' must be kebab-case (e.g., blog-editor) or camelCase (e.g., blogEditor)`
238
+ );
239
+ }
240
+ if (BUILT_IN_ROLES.includes(id)) {
241
+ throw new Error(`Role ID '${id}' cannot use a built-in role name (admin, editor, viewer)`);
242
+ }
243
+ if (!config.displayName || config.displayName.length === 0) {
244
+ throw new Error("Role displayName is required");
245
+ }
246
+ const hasBaseRole = config.baseRole !== void 0;
247
+ const hasIncludes = Array.isArray(config.includes) && config.includes.length > 0;
248
+ const hasPermissions = Array.isArray(config.permissions) && config.permissions.length > 0;
249
+ const hasHiddenFields = config.fields !== void 0 && Array.isArray(config.fields.hidden) && config.fields.hidden.length > 0;
250
+ const hasReadonlyFields = config.fields !== void 0 && Array.isArray(config.fields.readonly) && config.fields.readonly.length > 0;
251
+ const hasFields = hasHiddenFields || hasReadonlyFields;
252
+ if (!hasBaseRole && !hasIncludes && !hasPermissions && !hasFields) {
253
+ throw new Error(
254
+ `Role '${id}' must have at least one of: 'baseRole', 'includes', or permissions (with tags/actions or fields)`
255
+ );
256
+ }
257
+ if (Array.isArray(config.permissions) && config.permissions.length > 0) {
258
+ for (const perm of config.permissions) {
259
+ if (perm.tags.length === 0) {
260
+ throw new Error(`Permission in role '${id}' must have at least one tag`);
261
+ }
262
+ if (perm.actions.length === 0) {
263
+ throw new Error(`Permission in role '${id}' must have at least one action`);
264
+ }
265
+ for (const action of perm.actions) {
266
+ if (!ROLE_ACTIONS.includes(action)) {
267
+ throw new Error(
268
+ `Invalid action '${action}' in role '${id}'. Valid actions: ${ROLE_ACTIONS.join(", ")}`
269
+ );
270
+ }
271
+ }
272
+ }
273
+ }
274
+ return {
275
+ _tag: "role",
276
+ id,
277
+ displayName: config.displayName,
278
+ ...config.description !== void 0 && { description: config.description },
279
+ ...config.baseRole !== void 0 && { baseRole: config.baseRole },
280
+ ...config.includes !== void 0 && { includes: config.includes },
281
+ ...config.permissions !== void 0 && { permissions: config.permissions },
282
+ ...config.fields !== void 0 && { fields: config.fields }
283
+ };
284
+ }
285
+ __name(roleDefinition, "roleDefinition");
286
+ function isRoleDefinition(value) {
287
+ return typeof value === "object" && value !== null && "_tag" in value && value._tag === "role";
288
+ }
289
+ __name(isRoleDefinition, "isRoleDefinition");
290
+ function isBuiltInRole(roleId) {
291
+ return BUILT_IN_ROLES.includes(roleId);
292
+ }
293
+ __name(isBuiltInRole, "isBuiltInRole");
294
+ function getBuiltInRoleActions(roleId) {
295
+ return BUILT_IN_ROLE_ACTIONS[roleId];
296
+ }
297
+ __name(getBuiltInRoleActions, "getBuiltInRoleActions");
298
+
299
+ // src/schema/define-view.ts
300
+ function defineView(config) {
301
+ if (typeof config === "string") throw idFormRemoved("defineView", config);
302
+ return { ...config, _tag: "keyedView" };
303
+ }
304
+ __name(defineView, "defineView");
305
+ function compileKeyedViews(views) {
306
+ return Object.entries(views).map(([id, view]) => {
307
+ if (!isKeyed(view, "keyedView")) {
308
+ throw new Error(
309
+ `View '${id}' is not written in the keyed form: under a key, write it without an id, as defineView({ \u2026 })`
310
+ );
311
+ }
312
+ const { _tag: _keyed, name, ...config } = view;
313
+ return viewDefinition(id, { ...config, name: name ?? labelFromKey(id) });
314
+ });
315
+ }
316
+ __name(compileKeyedViews, "compileKeyedViews");
317
+ function viewDefinition(id, config) {
318
+ if (!id.match(/^[a-zA-Z][a-zA-Z0-9_-]*$/)) {
319
+ throw new Error(
320
+ `View ID '${id}' must start with a letter and contain only letters, numbers, underscores, and hyphens`
321
+ );
322
+ }
323
+ if (!config.name || config.name.length === 0) {
324
+ throw new Error(`View '${id}' must have a name`);
325
+ }
326
+ if (config.metadataFields && config.metadataFields.length > 3) {
327
+ throw new Error(`View '${id}' can have at most 3 metadata fields`);
328
+ }
329
+ if (!config.scope?.type) {
330
+ throw new Error(`View '${id}' must have a valid scope`);
331
+ }
332
+ const grouped = (config.filters ?? []).findIndex(
333
+ (filter) => typeof filter === "object" && filter !== null && "conditions" in filter
334
+ );
335
+ if (grouped !== -1) {
336
+ throw new Error(
337
+ `View '${id}' filter ${grouped} is a filter group: a view's filters are a flat list, joined by AND. List each of its conditions as a filter of its own, or use 'isAnyOf' for an OR within one field.`
338
+ );
339
+ }
340
+ if (config.slug !== void 0 && !/^[a-z][a-z0-9-]*$/.test(config.slug)) {
341
+ throw new Error(
342
+ `View '${id}' slug '${config.slug}' must be lowercase, start with a letter, and contain only letters, numbers, and hyphens`
343
+ );
344
+ }
345
+ return {
346
+ _tag: "view",
347
+ id,
348
+ ...config
349
+ };
350
+ }
351
+ __name(viewDefinition, "viewDefinition");
352
+
5
353
  // src/schema/define-schema.ts
6
354
  function defineSchema(config) {
7
355
  if (config.contentTypes == null) {
8
356
  throw new Error("Schema must declare contentTypes (an empty object declares none)");
9
357
  }
358
+ refuseOneDefinitionUnderTwoKeys(config);
359
+ const roles = config.roles === void 0 ? void 0 : listed("roles", config.roles, compileKeyedRoles);
360
+ const views = config.views === void 0 ? void 0 : listed("views", config.views, compileKeyedViews);
10
361
  if (config.locales && config.locales.length > 0) {
11
362
  const defaultLocales = config.locales.filter((l) => l.default === true);
12
363
  if (defaultLocales.length === 0) {
@@ -34,9 +385,9 @@ function defineSchema(config) {
34
385
  }
35
386
  }
36
387
  }
37
- if (config.roles && config.roles.length > 0) {
388
+ if (roles && roles.length > 0) {
38
389
  const roleIds = /* @__PURE__ */ new Set();
39
- for (const role of config.roles) {
390
+ for (const role of roles) {
40
391
  if (roleIds.has(role.id)) {
41
392
  throw new Error(`Duplicate role ID '${role.id}'`);
42
393
  }
@@ -45,7 +396,7 @@ function defineSchema(config) {
45
396
  }
46
397
  if (config.workflow) {
47
398
  const builtInRoles = /* @__PURE__ */ new Set(["admin", "editor", "viewer"]);
48
- const customRoleIds = new Set(config.roles?.map((r) => r.id) ?? []);
399
+ const customRoleIds = new Set(roles?.map((r) => r.id) ?? []);
49
400
  const allRoleIds = /* @__PURE__ */ new Set([...builtInRoles, ...customRoleIds]);
50
401
  for (const stage of config.workflow.stages ?? []) {
51
402
  if (stage.allowedRoles) {
@@ -82,9 +433,9 @@ function defineSchema(config) {
82
433
  }
83
434
  }
84
435
  }
85
- if (config.views && config.views.length > 0) {
436
+ if (views && views.length > 0) {
86
437
  const viewIds = /* @__PURE__ */ new Set();
87
- for (const view of config.views) {
438
+ for (const view of views) {
88
439
  if (viewIds.has(view.id)) {
89
440
  throw new Error(`Duplicate view ID '${view.id}'`);
90
441
  }
@@ -94,17 +445,76 @@ function defineSchema(config) {
94
445
  return {
95
446
  _tag: "schema",
96
447
  ...config.locales !== void 0 && { locales: config.locales },
97
- contentTypes: config.contentTypes,
98
- singletonContentTypes: config.singletonContentTypes ?? {},
99
- queries: config.queries ?? {},
100
- settingsGroups: config.settingsGroups,
101
- ...config.assetTypes && { assetTypes: config.assetTypes },
102
- ...config.roles !== void 0 && { roles: config.roles },
448
+ contentTypes: withKeys(config.contentTypes, "labelled"),
449
+ singletonContentTypes: withKeys(
450
+ config.singletonContentTypes ?? {},
451
+ "labelled"
452
+ ),
453
+ queries: withKeys(config.queries ?? {}, "unlabelled"),
454
+ settingsGroups: config.settingsGroups && withKeys(config.settingsGroups, "labelled"),
455
+ ...config.assetTypes && { assetTypes: withKeys(config.assetTypes, "labelled") },
456
+ // A keyed record's list is the one it compiled to, each item under its key as its id — what
457
+ // `RolesAsList` and `ViewsAsList` say, which is all the casts say
458
+ ...roles !== void 0 && { roles },
103
459
  ...config.workflow && { workflow: config.workflow },
104
- ...config.views !== void 0 && { views: config.views }
460
+ ...views !== void 0 && { views }
105
461
  };
106
462
  }
107
463
  __name(defineSchema, "defineSchema");
464
+ function listed(section, keyed, compile) {
465
+ if (Array.isArray(keyed)) {
466
+ throw new Error(
467
+ `defineSchema takes ${section} as a record, each under its id: the list form was removed in 0.14`
468
+ );
469
+ }
470
+ return compile(keyed);
471
+ }
472
+ __name(listed, "listed");
473
+ function withKeys(items, labels) {
474
+ return Object.fromEntries(
475
+ Object.entries(items).map(([key, definition]) => {
476
+ const written = definition;
477
+ if (written.name !== void 0) return [key, definition];
478
+ return [
479
+ key,
480
+ {
481
+ ...definition,
482
+ name: key,
483
+ ...labels === "labelled" && written.displayName === void 0 && { displayName: labelFromKey(key) }
484
+ }
485
+ ];
486
+ })
487
+ );
488
+ }
489
+ __name(withKeys, "withKeys");
490
+ var KEYED_SECTIONS = [
491
+ "contentTypes",
492
+ "singletonContentTypes",
493
+ "assetTypes",
494
+ "queries",
495
+ "settingsGroups",
496
+ "roles",
497
+ "views"
498
+ ];
499
+ function refuseOneDefinitionUnderTwoKeys(config) {
500
+ const seen = /* @__PURE__ */ new Map();
501
+ for (const section of KEYED_SECTIONS) {
502
+ const items = config[section];
503
+ if (items === null || typeof items !== "object" || Array.isArray(items)) continue;
504
+ for (const [key, definition] of Object.entries(items)) {
505
+ if (definition === null || typeof definition !== "object") continue;
506
+ const path = `${section}.${key}`;
507
+ const first = seen.get(definition);
508
+ if (first !== void 0) {
509
+ throw new Error(
510
+ `One definition is registered under two keys, '${first}' and '${path}': each key deploys a declaration of its own, so give each key its own definition`
511
+ );
512
+ }
513
+ seen.set(definition, path);
514
+ }
515
+ }
516
+ }
517
+ __name(refuseOneDefinitionUnderTwoKeys, "refuseOneDefinitionUnderTwoKeys");
108
518
 
109
519
  // src/schema/define-locale.ts
110
520
  var BCP47_PATTERN = /^[a-z]{2}(-[A-Z]{2})?$/;
@@ -178,193 +588,87 @@ function buildFallbackChain(localeCode, locales, maxDepth = 10) {
178
588
  }
179
589
  __name(buildFallbackChain, "buildFallbackChain");
180
590
 
181
- // src/schema/define-content-type.ts
182
- function fieldset(id, config) {
183
- return {
184
- id,
185
- displayName: config.displayName,
186
- description: config.description,
187
- ...config.collapsedByDefault !== void 0 && {
188
- collapsedByDefault: config.collapsedByDefault
189
- },
190
- ai: config.ai,
191
- fields: config.fields
192
- };
193
- }
194
- __name(fieldset, "fieldset");
195
- function defineContentType(name, config) {
196
- return {
197
- _tag: "contentType",
198
- name,
199
- displayName: config.displayName,
200
- displayNamePlural: config.displayNamePlural,
201
- description: config.description,
202
- ai: config.ai,
203
- displayField: config.displayField,
204
- // Written only when given: it was `false` whenever not, so the same content type was one
205
- // definition from YAML and another from TypeScript, with a different schemaHash (REL-1458)
206
- ...config.hierarchical !== void 0 && { hierarchical: config.hierarchical },
207
- ...config.accessTags !== void 0 && { accessTags: config.accessTags },
208
- ...config.workflow !== void 0 && { workflow: config.workflow },
209
- fields: config.fields
210
- };
211
- }
212
- __name(defineContentType, "defineContentType");
213
-
214
591
  // src/schema/define-singleton.ts
215
- function defineSingletonContentType(name, config) {
592
+ function defineSingleton(config) {
593
+ if (typeof config === "string") throw idFormRemoved("defineSingleton", config);
216
594
  return {
217
595
  _tag: "singletonContentType",
218
- name,
219
- displayName: config.displayName,
220
- displayNamePlural: config.displayNamePlural,
221
- description: config.description,
222
- ai: config.ai,
223
- displayField: config.displayField,
596
+ ...keyedSettings(config),
224
597
  // Written only when given, as a content type's is (REL-1458)
225
598
  ...config.hierarchical !== void 0 && { hierarchical: config.hierarchical },
226
- ...config.accessTags !== void 0 && { accessTags: config.accessTags },
227
- // Its config type took `workflow` and this dropped it, so a singleton's workflow never deployed
228
- ...config.workflow !== void 0 && { workflow: config.workflow },
229
- fields: config.fields
599
+ fields: compileKeyedMembers(config.fields)
230
600
  };
231
601
  }
232
- __name(defineSingletonContentType, "defineSingletonContentType");
233
- var defineSingleton = defineSingletonContentType;
602
+ __name(defineSingleton, "defineSingleton");
234
603
 
235
604
  // src/schema/define-settings-group.ts
236
- function defineSettingsGroup(name, config) {
605
+ function defineSettingsGroup(config) {
606
+ if (typeof config === "string") throw idFormRemoved("defineSettingsGroup", config);
237
607
  return {
238
608
  _tag: "settingsGroup",
239
- name,
240
- displayName: config.displayName,
241
- description: config.description,
242
- ai: config.ai,
609
+ ...config.displayName !== void 0 && { displayName: config.displayName },
610
+ ...config.description !== void 0 && { description: config.description },
611
+ ...config.ai !== void 0 && { ai: config.ai },
243
612
  ...config.accessTags !== void 0 && { accessTags: config.accessTags },
244
- fields: config.fields
613
+ fields: compileKeyedFields(config.fields)
245
614
  };
246
615
  }
247
616
  __name(defineSettingsGroup, "defineSettingsGroup");
248
617
 
249
618
  // src/schema/define-asset-type.ts
250
- function defineAssetType(name, config) {
619
+ function refuseKindKeys(config, what) {
251
620
  const kindKeys = ["image", "video", "file"].filter((k) => config[k] !== void 0);
252
621
  if (kindKeys.length === 0) {
253
- throw new Error(`Asset type '${name}' must declare exactly one kind key: image, video, or file`);
622
+ throw new Error(`${what} must declare exactly one kind key: image, video, or file`);
254
623
  }
255
624
  if (kindKeys.length > 1) {
256
625
  throw new Error(
257
- `Asset type '${name}' declares multiple kind keys (${kindKeys.join(", ")}); exactly one is allowed`
626
+ `${what} declares multiple kind keys (${kindKeys.join(", ")}); exactly one is allowed`
258
627
  );
259
628
  }
629
+ }
630
+ __name(refuseKindKeys, "refuseKindKeys");
631
+ function defineAssetType(config) {
632
+ if (typeof config === "string") throw idFormRemoved("defineAssetType", config);
633
+ refuseKindKeys(config, "An asset type");
260
634
  return {
261
635
  _tag: "assetType",
262
- name,
263
- displayName: config.displayName,
264
- displayNamePlural: config.displayNamePlural,
265
- description: config.description,
266
- ai: config.ai,
267
- displayField: config.displayField,
268
- // An empty list too, as a content type's and a settings group's are kept (REL-1457, REL-1458)
269
- ...config.accessTags !== void 0 && { accessTags: config.accessTags },
270
- ...config.workflow !== void 0 && { workflow: config.workflow },
636
+ ...keyedSettings(config),
271
637
  ...config.image !== void 0 && { image: config.image },
272
638
  ...config.video !== void 0 && { video: config.video },
273
639
  ...config.file !== void 0 && { file: config.file },
274
- ...config.fields !== void 0 && { fields: config.fields }
640
+ ...config.fields !== void 0 && { fields: compileKeyedMembers(config.fields) }
275
641
  };
276
642
  }
277
643
  __name(defineAssetType, "defineAssetType");
278
644
 
279
- // src/schema/define-role.ts
280
- var BUILT_IN_ROLES = ["admin", "editor", "viewer"];
281
- var ROLE_ACTIONS = [
282
- "create",
283
- "read",
284
- "update",
285
- "delete",
286
- "publish",
287
- "unpublish",
288
- "duplicate",
289
- "comment",
290
- "approveElevated"
291
- ];
292
- var BUILT_IN_ROLE_ACTIONS = {
293
- admin: ["create", "read", "update", "delete", "publish", "unpublish", "duplicate", "comment"],
294
- editor: ["create", "read", "update", "delete", "publish", "unpublish", "duplicate", "comment"],
295
- viewer: ["read", "comment"]
296
- };
297
- function defineRole(id, config) {
298
- if (!id || id.length === 0) {
299
- throw new Error("Role ID is required");
300
- }
301
- const roleIdPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$|^[a-z][a-zA-Z0-9]*$/;
302
- if (!roleIdPattern.test(id)) {
303
- throw new Error(
304
- `Role ID '${id}' must be kebab-case (e.g., blog-editor) or camelCase (e.g., blogEditor)`
305
- );
306
- }
307
- if (BUILT_IN_ROLES.includes(id)) {
308
- throw new Error(`Role ID '${id}' cannot use a built-in role name (admin, editor, viewer)`);
309
- }
310
- if (!config.displayName || config.displayName.length === 0) {
311
- throw new Error("Role displayName is required");
312
- }
313
- const hasBaseRole = config.baseRole !== void 0;
314
- const hasIncludes = Array.isArray(config.includes) && config.includes.length > 0;
315
- const hasPermissions = Array.isArray(config.permissions) && config.permissions.length > 0;
316
- const hasHiddenFields = config.fields !== void 0 && Array.isArray(config.fields.hidden) && config.fields.hidden.length > 0;
317
- const hasReadonlyFields = config.fields !== void 0 && Array.isArray(config.fields.readonly) && config.fields.readonly.length > 0;
318
- const hasFields = hasHiddenFields || hasReadonlyFields;
319
- if (!hasBaseRole && !hasIncludes && !hasPermissions && !hasFields) {
320
- throw new Error(
321
- `Role '${id}' must have at least one of: 'baseRole', 'includes', or permissions (with tags/actions or fields)`
322
- );
323
- }
324
- if (Array.isArray(config.permissions) && config.permissions.length > 0) {
325
- for (const perm of config.permissions) {
326
- if (perm.tags.length === 0) {
327
- throw new Error(`Permission in role '${id}' must have at least one tag`);
328
- }
329
- if (perm.actions.length === 0) {
330
- throw new Error(`Permission in role '${id}' must have at least one action`);
331
- }
332
- for (const action of perm.actions) {
333
- if (!ROLE_ACTIONS.includes(action)) {
334
- throw new Error(
335
- `Invalid action '${action}' in role '${id}'. Valid actions: ${ROLE_ACTIONS.join(", ")}`
336
- );
337
- }
338
- }
339
- }
340
- }
341
- return {
342
- _tag: "role",
343
- id,
344
- displayName: config.displayName,
345
- ...config.description !== void 0 && { description: config.description },
346
- ...config.baseRole !== void 0 && { baseRole: config.baseRole },
347
- ...config.includes !== void 0 && { includes: config.includes },
348
- ...config.permissions !== void 0 && { permissions: config.permissions },
349
- ...config.fields !== void 0 && { fields: config.fields }
350
- };
351
- }
352
- __name(defineRole, "defineRole");
353
- function isRoleDefinition(value) {
354
- return typeof value === "object" && value !== null && "_tag" in value && value._tag === "role";
355
- }
356
- __name(isRoleDefinition, "isRoleDefinition");
357
- function isBuiltInRole(roleId) {
358
- return BUILT_IN_ROLES.includes(roleId);
645
+ // src/schema/define-workflow.ts
646
+ function defineWorkflowStage(config) {
647
+ if (typeof config === "string") throw idFormRemoved("defineWorkflowStage", config);
648
+ return { ...config, _tag: "keyedWorkflowStage" };
359
649
  }
360
- __name(isBuiltInRole, "isBuiltInRole");
361
- function getBuiltInRoleActions(roleId) {
362
- return BUILT_IN_ROLE_ACTIONS[roleId];
650
+ __name(defineWorkflowStage, "defineWorkflowStage");
651
+ function compileKeyedStages(stages) {
652
+ const seen = /* @__PURE__ */ new Map();
653
+ return Object.entries(stages).map(([id, stage]) => {
654
+ if (!isKeyed(stage, "keyedWorkflowStage")) {
655
+ throw new Error(
656
+ `Workflow stage '${id}' is not written in the keyed form: under a key, write it without an id, as defineWorkflowStage({ \u2026 })`
657
+ );
658
+ }
659
+ const first = seen.get(stage);
660
+ if (first !== void 0) {
661
+ throw new Error(
662
+ `One stage is registered under two keys, '${first}' and '${id}': each key is a stage of its own, so give each key its own defineWorkflowStage({ \u2026 })`
663
+ );
664
+ }
665
+ seen.set(stage, id);
666
+ const { _tag: _keyed, displayName, ...config } = stage;
667
+ return stageDefinition(id, { ...config, displayName: displayName ?? labelFromKey(id) });
668
+ });
363
669
  }
364
- __name(getBuiltInRoleActions, "getBuiltInRoleActions");
365
-
366
- // src/schema/define-workflow.ts
367
- function defineWorkflowStage(id, config) {
670
+ __name(compileKeyedStages, "compileKeyedStages");
671
+ function stageDefinition(id, config) {
368
672
  if (!id || id.length === 0) {
369
673
  throw new Error("Workflow stage ID is required");
370
674
  }
@@ -411,10 +715,16 @@ function defineWorkflowStage(id, config) {
411
715
  ...config.escalation !== void 0 && { escalation: config.escalation }
412
716
  };
413
717
  }
414
- __name(defineWorkflowStage, "defineWorkflowStage");
718
+ __name(stageDefinition, "stageDefinition");
415
719
  function defineWorkflow(config) {
720
+ if (config.stages !== void 0 && isList(config.stages)) {
721
+ throw new Error(
722
+ "defineWorkflow takes its stages as a record keyed by stage id, in the order content moves through them: the list form was removed in 0.14 \u2014 write stages: { review: defineWorkflowStage({ \u2026 }) }"
723
+ );
724
+ }
725
+ const stages = config.stages === void 0 ? void 0 : compileKeyedStages(config.stages);
416
726
  const stageIds = /* @__PURE__ */ new Set();
417
- for (const stage of config.stages ?? []) {
727
+ for (const stage of stages ?? []) {
418
728
  if (stageIds.has(stage.id)) {
419
729
  throw new Error(`Duplicate workflow stage ID '${stage.id}'`);
420
730
  }
@@ -423,7 +733,7 @@ function defineWorkflow(config) {
423
733
  return {
424
734
  _tag: "workflow",
425
735
  ...config.enabled !== void 0 && { enabled: config.enabled },
426
- ...config.stages !== void 0 && { stages: config.stages }
736
+ ...stages !== void 0 && { stages }
427
737
  };
428
738
  }
429
739
  __name(defineWorkflow, "defineWorkflow");
@@ -482,45 +792,9 @@ function isFinalWorkflowStage(workflow, stageId) {
482
792
  }
483
793
  __name(isFinalWorkflowStage, "isFinalWorkflowStage");
484
794
 
485
- // src/schema/define-view.ts
486
- function defineView(id, config) {
487
- if (!id.match(/^[a-zA-Z][a-zA-Z0-9_-]*$/)) {
488
- throw new Error(
489
- `View ID '${id}' must start with a letter and contain only letters, numbers, underscores, and hyphens`
490
- );
491
- }
492
- if (!config.name || config.name.length === 0) {
493
- throw new Error(`View '${id}' must have a name`);
494
- }
495
- if (config.metadataFields && config.metadataFields.length > 3) {
496
- throw new Error(`View '${id}' can have at most 3 metadata fields`);
497
- }
498
- if (!config.scope?.type) {
499
- throw new Error(`View '${id}' must have a valid scope`);
500
- }
501
- const grouped = (config.filters ?? []).findIndex(
502
- (filter) => typeof filter === "object" && filter !== null && "conditions" in filter
503
- );
504
- if (grouped !== -1) {
505
- throw new Error(
506
- `View '${id}' filter ${grouped} is a filter group: a view's filters are a flat list, joined by AND. List each of its conditions as a filter of its own, or use 'isAnyOf' for an OR within one field.`
507
- );
508
- }
509
- if (config.slug !== void 0 && !/^[a-z][a-z0-9-]*$/.test(config.slug)) {
510
- throw new Error(
511
- `View '${id}' slug '${config.slug}' must be lowercase, start with a letter, and contain only letters, numbers, and hyphens`
512
- );
513
- }
514
- return {
515
- _tag: "view",
516
- id,
517
- ...config
518
- };
519
- }
520
- __name(defineView, "defineView");
521
-
522
795
  // src/schema/define-query.ts
523
- function defineQuery(name, config) {
796
+ function defineQuery(config) {
797
+ if (typeof config === "string") throw idFormRemoved("defineQuery", config);
524
798
  if (!config.fetch && !config.aggregate && !config.search) {
525
799
  throw new Error("Query must have at least one of: fetch, aggregate or search");
526
800
  }
@@ -529,7 +803,6 @@ function defineQuery(name, config) {
529
803
  }
530
804
  return {
531
805
  _tag: "query",
532
- name,
533
806
  type: config.type,
534
807
  endpoint: config.endpoint,
535
808
  description: config.description,
@@ -558,6 +831,7 @@ __export(fields_exports, {
558
831
  email: () => email,
559
832
  enum: () => enumField,
560
833
  integer: () => integer,
834
+ json: () => json,
561
835
  list: () => list,
562
836
  longText: () => longText,
563
837
  object: () => object,
@@ -571,97 +845,106 @@ __export(fields_exports, {
571
845
  });
572
846
 
573
847
  // src/schema/fields/builders.ts
574
- function createField(id, type, options = {}) {
575
- return {
576
- id,
577
- type,
578
- ...options.config !== void 0 && { config: options.config },
579
- ...options.ui !== void 0 && { ui: options.ui },
580
- ...options.ai !== void 0 && { ai: options.ai },
581
- // An empty list too: dropping it made a YAML field and a TypeScript one two definitions (REL-1458)
582
- ...options.rules !== void 0 && { rules: options.rules },
583
- ...options.localization !== void 0 && { localization: options.localization },
584
- ...options.fallback !== void 0 && { fallback: options.fallback },
585
- ...options.deprecated !== void 0 && { deprecated: options.deprecated }
586
- };
587
- }
588
- __name(createField, "createField");
589
- function shortText(id, options = {}) {
590
- return createField(id, "shortText", options);
848
+ function shortText(options = {}) {
849
+ return keyedField("shortText", options);
591
850
  }
592
851
  __name(shortText, "shortText");
593
- function longText(id, options = {}) {
594
- return createField(id, "longText", options);
852
+ function longText(options = {}) {
853
+ return keyedField("longText", options);
595
854
  }
596
855
  __name(longText, "longText");
597
- function richText(id, options = {}) {
598
- return createField(id, "richText", options);
856
+ function richText(options = {}) {
857
+ return keyedField("richText", options);
599
858
  }
600
859
  __name(richText, "richText");
601
- function table(id, options = {}) {
602
- return createField(id, "table", options);
860
+ function table(options = {}) {
861
+ return keyedField("table", options);
603
862
  }
604
863
  __name(table, "table");
605
- function code(id, options = {}) {
606
- return createField(id, "code", options);
864
+ function code(options = {}) {
865
+ return keyedField("code", options);
607
866
  }
608
867
  __name(code, "code");
609
- function slug(id, options = {}) {
610
- return createField(id, "slug", options);
868
+ function slug(options = {}) {
869
+ return keyedField("slug", options);
611
870
  }
612
871
  __name(slug, "slug");
613
- function url(id, options = {}) {
614
- return createField(id, "url", options);
872
+ function url(options = {}) {
873
+ return keyedField("url", options);
615
874
  }
616
875
  __name(url, "url");
617
- function email(id, options = {}) {
618
- return createField(id, "email", options);
876
+ function email(options = {}) {
877
+ return keyedField("email", options);
619
878
  }
620
879
  __name(email, "email");
621
- function integer(id, options = {}) {
622
- return createField(id, "integer", options);
880
+ function integer(options = {}) {
881
+ return keyedField("integer", options);
623
882
  }
624
883
  __name(integer, "integer");
625
- function decimal(id, options = {}) {
626
- return createField(id, "decimal", options);
884
+ function decimal(options = {}) {
885
+ return keyedField("decimal", options);
627
886
  }
628
887
  __name(decimal, "decimal");
629
- function boolean(id, options = {}) {
630
- return createField(id, "boolean", options);
888
+ function boolean(options = {}) {
889
+ return keyedField("boolean", options);
631
890
  }
632
891
  __name(boolean, "boolean");
633
- function enumField(id, options = {}) {
634
- return createField(id, "enum", options);
892
+ function enumOptionsList(options) {
893
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
894
+ throw new Error(
895
+ "An enum's options in the keyed form are a value-to-label map, { draft: 'Draft', live: 'Live' }, in the order the editor shows them"
896
+ );
897
+ }
898
+ return Object.entries(options).map(([value, label]) => {
899
+ if (/^(0|[1-9]\d*)$/.test(value)) {
900
+ throw new Error(
901
+ `An enum option's value '${value}' is a whole number, which a value-to-label map cannot keep in the order it was written: JavaScript orders such keys first`
902
+ );
903
+ }
904
+ if (typeof label !== "string") {
905
+ throw new Error(`An enum option's label must be a string: '${value}' has ${String(label)}`);
906
+ }
907
+ return { value, label };
908
+ });
909
+ }
910
+ __name(enumOptionsList, "enumOptionsList");
911
+ function enumField(options) {
912
+ const written = options;
913
+ return keyedField("enum", { ...written, options: enumOptionsList(written.options) });
635
914
  }
636
915
  __name(enumField, "enumField");
637
- function date(id, options = {}) {
638
- return createField(id, "date", options);
916
+ function date(options = {}) {
917
+ return keyedField("date", options);
639
918
  }
640
919
  __name(date, "date");
641
- function dateTime(id, options = {}) {
642
- return createField(id, "dateTime", options);
920
+ function dateTime(options = {}) {
921
+ return keyedField("dateTime", options);
643
922
  }
644
923
  __name(dateTime, "dateTime");
645
- function reference(id, options = {}) {
646
- return createField(id, "reference", options);
924
+ function reference(options) {
925
+ return keyedField("reference", options);
647
926
  }
648
927
  __name(reference, "reference");
649
- function tags(id, options = {}) {
650
- return createField(id, "tags", options);
928
+ function tags(options = {}) {
929
+ return keyedField("tags", options);
651
930
  }
652
931
  __name(tags, "tags");
653
- function list(id, options = {}) {
654
- return createField(id, "list", options);
932
+ function list(options = {}) {
933
+ return keyedField("list", options);
655
934
  }
656
935
  __name(list, "list");
657
- function object(id, options) {
658
- return createField(id, "object", options);
936
+ function object(options) {
937
+ return keyedField("object", options);
659
938
  }
660
939
  __name(object, "object");
940
+ function json(options = {}) {
941
+ return keyedField("json", options);
942
+ }
943
+ __name(json, "json");
661
944
 
662
945
  // src/schema/index.ts
663
946
  var fields = fields_exports;
664
947
 
665
- export { BUILT_IN_ROLES, BUILT_IN_ROLE_ACTIONS, ROLE_ACTIONS, buildFallbackChain, defineAssetType, defineContentType, defineContentTypeWorkflow, defineLocale, defineQuery, defineRole, defineSchema, defineSettingsGroup, defineSingleton, defineSingletonContentType, defineView, defineWorkflow, defineWorkflowStage, fields, fieldset, findDefaultLocale, findWorkflowStage, getActiveLocales, getBuiltInRoleActions, getFirstWorkflowStage, getLocaleCodes, getNextWorkflowStage, getWorkflowStageIds, isBuiltInRole, isFinalWorkflowStage, isLocaleDefinition, isRoleDefinition, isWorkflowDefinition, isWorkflowStageDefinition };
666
- //# sourceMappingURL=chunk-POEEDYFM.js.map
667
- //# sourceMappingURL=chunk-POEEDYFM.js.map
948
+ export { BUILT_IN_ROLES, BUILT_IN_ROLE_ACTIONS, ROLE_ACTIONS, buildFallbackChain, defineAssetType, defineContentType, defineContentTypeWorkflow, defineLocale, defineQuery, defineRole, defineSchema, defineSettingsGroup, defineSingleton, defineView, defineWorkflow, defineWorkflowStage, fields, fieldset, findDefaultLocale, findWorkflowStage, getActiveLocales, getBuiltInRoleActions, getFirstWorkflowStage, getLocaleCodes, getNextWorkflowStage, getWorkflowStageIds, isBuiltInRole, isFinalWorkflowStage, isLocaleDefinition, isRoleDefinition, isWorkflowDefinition, isWorkflowStageDefinition };
949
+ //# sourceMappingURL=chunk-PCFTJLEP.js.map
950
+ //# sourceMappingURL=chunk-PCFTJLEP.js.map