riverbankcms 0.19.0 → 0.21.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.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as classifySdkEventSchedule, i as sdkEventUpsertOptionalFieldSchemas, n as sdkCourseBookingSettingsSchema, o as classifySdkEventBookingOffer } from "./sdk-event-upsert-BdvfnCyV.mjs";
3
- import { u as EVENT_PRESET_VALUES } from "./src-yL0DBH7S.mjs";
2
+ import { _ as normalizeLinkValue, a as classifySdkEventSchedule, g as linkSchema, i as sdkEventUpsertOptionalFieldSchemas, n as sdkCourseBookingSettingsSchema, o as classifySdkEventBookingOffer } from "./sdk-event-upsert-CrOuJrxo.mjs";
3
+ import { u as EVENT_PRESET_VALUES } from "./src-BENi4Q5k.mjs";
4
4
  import { z } from "zod";
5
5
 
6
6
  //#region ../blocks/src/lib/typeGuards.ts
@@ -449,6 +449,294 @@ function normalizeSlugSegment(segment) {
449
449
  return normalized;
450
450
  }
451
451
 
452
+ //#endregion
453
+ //#region ../content-model/src/content-types/sentinels.ts
454
+ /** Leaf field types that receive a placeholder sentinel (containers recurse). */
455
+ const SENTINEL_LEAF_TYPES = [
456
+ "text",
457
+ "textarea",
458
+ "richText",
459
+ "number",
460
+ "date",
461
+ "datetime",
462
+ "url",
463
+ "link",
464
+ "media",
465
+ "file",
466
+ "boolean",
467
+ "select",
468
+ "reference"
469
+ ];
470
+ /** Matches the closed sentinel shape `"<REQUIRED: …>"`. */
471
+ const SENTINEL_PATTERN = /^<REQUIRED:.*>$/;
472
+ /**
473
+ * True when a value is an unfilled placeholder sentinel. Shared by the emitter
474
+ * (self-check) and the validator (rejection) so recognition can't drift.
475
+ */
476
+ function isPlaceholderSentinel(value) {
477
+ return typeof value === "string" && SENTINEL_PATTERN.test(value);
478
+ }
479
+ function effectiveReferenceKind(field) {
480
+ const kind = field.referenceKind;
481
+ const legacy = field.reference?.type;
482
+ return kind ?? legacy;
483
+ }
484
+ function selectOptionValues$1(field) {
485
+ const options = field.options;
486
+ return options ? options.map((o) => o.value) : [];
487
+ }
488
+ /**
489
+ * The placeholder sentinel for a leaf field. Container types (group/button/
490
+ * repeater) do not have a leaf sentinel — they expand structurally — and calling
491
+ * this for them returns a generic marker only for completeness.
492
+ */
493
+ function buildSentinel(field) {
494
+ switch (field.type) {
495
+ case "text":
496
+ case "textarea": return "<REQUIRED: text>";
497
+ case "richText": return "<REQUIRED: rich-text>";
498
+ case "number": return "<REQUIRED: number>";
499
+ case "date": return "<REQUIRED: date (YYYY-MM-DD)>";
500
+ case "datetime": return "<REQUIRED: datetime (ISO 8601)>";
501
+ case "url": return "<REQUIRED: url>";
502
+ case "link": return "<REQUIRED: link>";
503
+ case "media": return "<REQUIRED: media-identifier>";
504
+ case "file": return "<REQUIRED: file-identifier>";
505
+ case "boolean": return "<REQUIRED: boolean>";
506
+ case "select": {
507
+ const values = selectOptionValues$1(field);
508
+ return values.length > 0 ? `<REQUIRED: select (${values.join("|")})>` : "<REQUIRED: select>";
509
+ }
510
+ case "reference": {
511
+ const kind = effectiveReferenceKind(field);
512
+ return kind ? `<REQUIRED: reference-identifier (${kind})>` : "<REQUIRED: reference-identifier>";
513
+ }
514
+ case "group": return "<REQUIRED: group>";
515
+ case "button": return "<REQUIRED: button>";
516
+ case "repeater": return "<REQUIRED: repeater>";
517
+ }
518
+ }
519
+ /**
520
+ * Deep-scan arbitrary entry data for any unfilled placeholder sentinels. Needs no
521
+ * schema, so it powers the CLI's pre-HTTP upsert pre-flight.
522
+ */
523
+ function findUnfilledSentinels(data, pathPrefix = "") {
524
+ const found = [];
525
+ const walk = (value, path) => {
526
+ if (isPlaceholderSentinel(value)) {
527
+ found.push({
528
+ path: path || "(root)",
529
+ value
530
+ });
531
+ return;
532
+ }
533
+ if (Array.isArray(value)) {
534
+ value.forEach((item, index) => walk(item, `${path}[${index}]`));
535
+ return;
536
+ }
537
+ if (value !== null && typeof value === "object") {
538
+ for (const [key, child] of Object.entries(value)) {
539
+ walk(child, path ? `${path}.${key}` : key);
540
+ }
541
+ }
542
+ };
543
+ walk(data, pathPrefix);
544
+ return found;
545
+ }
546
+
547
+ //#endregion
548
+ //#region ../content-model/src/content-types/entryValidation.ts
549
+ /**
550
+ * Entry-instance validator, compiled from the content-type field DSL.
551
+ *
552
+ * `ContentTypeSchemaZ` validates schema DOCUMENTS; this validates entry DATA
553
+ * against a schema: required-field presence, rejection of unfilled placeholder
554
+ * sentinels (shared vocabulary in `sentinels.ts`), and basic per-type shape
555
+ * checks. It is intentionally lightweight — enough to power scaffold diagnostics
556
+ * and a CLI pre-flight — not a replacement for server-side write validation.
557
+ */
558
+ function subfieldsOf$1(field) {
559
+ const direct = field.fields;
560
+ if (direct && direct.length > 0) return direct;
561
+ const nested = field.schema?.fields;
562
+ return nested ?? [];
563
+ }
564
+ function hasRepeaterVariants(field) {
565
+ return field.itemTypes !== undefined;
566
+ }
567
+ function resolveRepeaterVariantFields(field, item) {
568
+ if (!hasRepeaterVariants(field)) return subfieldsOf$1(field);
569
+ const itemTypes = field.itemTypes;
570
+ const typeId = item._type;
571
+ if (typeof typeId !== "string") return null;
572
+ return itemTypes[typeId]?.fields ?? null;
573
+ }
574
+ function selectOptionValues(field) {
575
+ const options = field.options;
576
+ return options ? options.map((o) => o.value) : [];
577
+ }
578
+ function isPlainObject(value) {
579
+ return value !== null && typeof value === "object" && !Array.isArray(value);
580
+ }
581
+ function isAllowedIdentifierBearingMedia(field, value) {
582
+ if (!isPlainObject(value) || typeof value.identifier !== "string" || value.identifier.trim().length === 0) {
583
+ return false;
584
+ }
585
+ if (value.type === undefined) return true;
586
+ const allowedKinds = field.type === "media" ? field.mediaKinds : field.fileKinds;
587
+ if (typeof value.type !== "string") return false;
588
+ const effectiveAllowedKinds = allowedKinds ?? (field.type === "media" ? ["image"] : ["document"]);
589
+ return effectiveAllowedKinds.some((kind) => kind === value.type);
590
+ }
591
+ function assertNever$2(type) {
592
+ throw new Error(`Unsupported entry field type: ${String(type)}`);
593
+ }
594
+ function scalarRule(type) {
595
+ switch (type) {
596
+ case "text":
597
+ case "textarea":
598
+ case "url":
599
+ case "reference":
600
+ case "date":
601
+ case "datetime":
602
+ case "select": return {
603
+ accepts: (value) => typeof value === "string",
604
+ expected: "a string"
605
+ };
606
+ case "link": return {
607
+ accepts: (value) => linkSchema.safeParse(normalizeLinkValue(value)).success,
608
+ expected: "a valid link"
609
+ };
610
+ case "number": return {
611
+ accepts: (value) => typeof value === "number" && !Number.isNaN(value),
612
+ expected: "a number"
613
+ };
614
+ case "boolean": return {
615
+ accepts: (value) => typeof value === "boolean",
616
+ expected: "a boolean"
617
+ };
618
+ case "richText": return {
619
+ accepts: (value) => typeof value === "string" || isPlainObject(value),
620
+ expected: "rich-text (string or document object)"
621
+ };
622
+ case "group":
623
+ case "button":
624
+ case "media":
625
+ case "file": return {
626
+ accepts: isPlainObject,
627
+ expected: "an object"
628
+ };
629
+ case "repeater": return {
630
+ accepts: Array.isArray,
631
+ expected: "an array"
632
+ };
633
+ default: return assertNever$2(type);
634
+ }
635
+ }
636
+ function leafTypeErrors(field, value, path) {
637
+ if (field.type === "media" || field.type === "file") return mediaLeafErrors(field, value, path);
638
+ if (field.type === "select") return selectLeafErrors(field, value, path);
639
+ const rule = scalarRule(field.type);
640
+ return rule.accepts(value) ? [] : [{
641
+ path,
642
+ fieldType: field.type,
643
+ code: "wrong_type",
644
+ message: `"${path}" must be ${rule.expected}.`
645
+ }];
646
+ }
647
+ function mediaLeafErrors(field, value, path) {
648
+ if (isPlainObject(value) && isPlaceholderSentinel(value.identifier)) {
649
+ return [{
650
+ path,
651
+ fieldType: field.type,
652
+ code: "unfilled_placeholder",
653
+ message: `"${path}" still contains an unfilled placeholder (${value.identifier}).`
654
+ }];
655
+ }
656
+ return isAllowedIdentifierBearingMedia(field, value) ? [] : [{
657
+ path,
658
+ fieldType: field.type,
659
+ code: "wrong_type",
660
+ message: `"${path}" must be a media object with an identifier.`
661
+ }];
662
+ }
663
+ function selectLeafErrors(field, value, path) {
664
+ if (typeof value !== "string") return [{
665
+ path,
666
+ fieldType: field.type,
667
+ code: "wrong_type",
668
+ message: `"${path}" must be a string.`
669
+ }];
670
+ const options = selectOptionValues(field);
671
+ return options.length > 0 && !options.includes(value) ? [{
672
+ path,
673
+ fieldType: field.type,
674
+ code: "invalid_option",
675
+ message: `"${path}" must be one of: ${options.join(", ")}.`
676
+ }] : [];
677
+ }
678
+ function validateField(field, value, path, allowIncomplete) {
679
+ const missing = value === undefined || value === null || value === "";
680
+ if (missing) return field.required === true && !allowIncomplete ? [{
681
+ path,
682
+ fieldType: field.type,
683
+ code: "missing_required",
684
+ message: `Required field "${path}" is missing.`
685
+ }] : [];
686
+ if (isPlaceholderSentinel(value)) return [{
687
+ path,
688
+ fieldType: field.type,
689
+ code: "unfilled_placeholder",
690
+ message: `"${path}" still contains an unfilled placeholder (${value}).`
691
+ }];
692
+ return validatePresentField(field, value, path, allowIncomplete);
693
+ }
694
+ function validatePresentField(field, value, path, allowIncomplete) {
695
+ if (field.type === "group" || field.type === "button") {
696
+ return isPlainObject(value) ? validateFields(subfieldsOf$1(field), value, path, allowIncomplete) : [{
697
+ path,
698
+ fieldType: field.type,
699
+ code: "wrong_type",
700
+ message: `"${path}" must be an object.`
701
+ }];
702
+ }
703
+ if (field.type === "repeater") {
704
+ return Array.isArray(value) ? value.flatMap((item, index) => validateRepeaterItem(field, item, `${path}[${index}]`, allowIncomplete)) : [{
705
+ path,
706
+ fieldType: field.type,
707
+ code: "wrong_type",
708
+ message: `"${path}" must be an array.`
709
+ }];
710
+ }
711
+ return leafTypeErrors(field, value, path);
712
+ }
713
+ function validateRepeaterItem(field, item, path, allowIncomplete) {
714
+ if (!isPlainObject(item)) return [{
715
+ path,
716
+ fieldType: "repeater",
717
+ code: "wrong_type",
718
+ message: `"${path}" must be an object.`
719
+ }];
720
+ const fields = resolveRepeaterVariantFields(field, item);
721
+ return fields ? validateFields(fields, item, path, allowIncomplete) : [{
722
+ path,
723
+ fieldType: "repeater",
724
+ code: "invalid_option",
725
+ message: `"${path}" has an unknown or missing repeater item type.`
726
+ }];
727
+ }
728
+ function validateFields(fields, data, pathPrefix, allowIncomplete) {
729
+ return fields.flatMap((field) => validateField(field, data[field.id], pathPrefix ? `${pathPrefix}.${field.id}` : field.id, allowIncomplete));
730
+ }
731
+ /** Validate all entry fields, returning every error so callers can fix them in one pass. */
732
+ function validateEntryInstance(input) {
733
+ const errors = validateFields(input.schema.fields, input.data, "", input.allowIncomplete ?? false);
734
+ return {
735
+ valid: errors.length === 0,
736
+ errors
737
+ };
738
+ }
739
+
452
740
  //#endregion
453
741
  //#region ../content-model/src/referenceFields.ts
454
742
  const BUILT_IN_REFERENCE_KINDS = [
@@ -549,6 +837,9 @@ const DISPLAY_MEDIA_KINDS$1 = [
549
837
  "video",
550
838
  "audio"
551
839
  ];
840
+ function mapContentFields(fields, context) {
841
+ return fields.map((field) => toFieldDefinition(field, context)).filter((field) => field !== null);
842
+ }
552
843
  function toFieldDefinition(field, context = {}) {
553
844
  const label = field.label ?? startCase(field.id);
554
845
  const base = {
@@ -672,18 +963,26 @@ function toFieldDefinition(field, context = {}) {
672
963
  };
673
964
  }
674
965
  case "repeater": {
675
- const nestedFields$1 = (field.fields ?? field.schema?.fields ?? []).map((f) => toFieldDefinition(f, context)).filter((f) => f !== null);
676
- if (!nestedFields$1.length) return null;
677
- return {
678
- id: field.id,
679
- label,
680
- description: field.description ?? undefined,
681
- required: field.required ?? false,
966
+ const repeater = {
967
+ ...base,
682
968
  type: "repeater",
683
- ui: normalizeFieldUi(field.ui),
684
- itemLabel: field.itemLabel ?? "Item",
685
- schema: { fields: nestedFields$1 }
969
+ itemLabel: field.itemLabel ?? "Item"
686
970
  };
971
+ if (hasRepeaterVariants(field)) {
972
+ return {
973
+ ...repeater,
974
+ polymorphic: true,
975
+ itemTypes: Object.fromEntries(Object.entries(field.itemTypes).map(([typeId, variant]) => [typeId, {
976
+ label: typeof variant.label === "string" ? variant.label : startCase(typeId),
977
+ fields: mapContentFields(variant.fields ?? [], context)
978
+ }]))
979
+ };
980
+ }
981
+ const fields = mapContentFields(field.fields ?? field.schema?.fields ?? [], context);
982
+ return fields.length ? {
983
+ ...repeater,
984
+ schema: { fields }
985
+ } : null;
687
986
  }
688
987
  default: return null;
689
988
  }
@@ -706,14 +1005,7 @@ function startCase(value) {
706
1005
  * @returns Array of field definitions for form rendering
707
1006
  */
708
1007
  function mapContentSchemaToFormFields(schema, context = {}) {
709
- const fields = [];
710
- for (const field of schema.fields ?? []) {
711
- const definition = toFieldDefinition(field, context);
712
- if (definition) {
713
- fields.push(definition);
714
- }
715
- }
716
- return fields;
1008
+ return mapContentFields(schema.fields ?? [], context);
717
1009
  }
718
1010
  function expandButtonFieldGroup(fieldId, fieldLabel, context = {}) {
719
1011
  const variants = context.variants ?? [...DEFAULT_BUTTON_VARIANTS];
@@ -1233,26 +1525,7 @@ function getPublishedMetadataFromSnapshot(snapshot, routing) {
1233
1525
  };
1234
1526
  }
1235
1527
  function buildToolbarState(snapshot, context, options) {
1236
- const formatPublishMessage = ({ status, publishedAt, scheduledAt }) => {
1237
- if (status === "published") {
1238
- if (!publishedAt) return "Published";
1239
- const date = new Date(publishedAt);
1240
- if (Number.isNaN(date.getTime())) return "Published";
1241
- return `Published ${date.toLocaleString()}`;
1242
- }
1243
- if (status === "scheduled" && scheduledAt) {
1244
- const date = new Date(scheduledAt);
1245
- if (!Number.isNaN(date.getTime())) {
1246
- return `Scheduled for ${date.toLocaleString()}`;
1247
- }
1248
- }
1249
- return "Not yet published";
1250
- };
1251
- const publishedMessage = formatPublishMessage({
1252
- status: snapshot.status,
1253
- publishedAt: snapshot.published.publishAt,
1254
- scheduledAt: snapshot.draft.publishAt
1255
- });
1528
+ const publishedMessage = formatPublishMessage(snapshot, options.formatPublicationDate);
1256
1529
  const publishLabel = routeLifecycleContext(snapshot)?.draftStatus === "scheduled" ? "Publish now" : `Publish ${options.entityLabel.toLowerCase()}`;
1257
1530
  return {
1258
1531
  currentStatus: snapshot.status,
@@ -1263,6 +1536,15 @@ function buildToolbarState(snapshot, context, options) {
1263
1536
  values: context.values
1264
1537
  };
1265
1538
  }
1539
+ /** Sentence adapter only; date admission and typography belong to the shared formatter. */
1540
+ function formatPublishMessage(snapshot, formatDate) {
1541
+ if (snapshot.status === "published") {
1542
+ const date = snapshot.published.publishAt ? formatDate(snapshot.published.publishAt) : null;
1543
+ return date ? `Published ${date}` : "Published";
1544
+ }
1545
+ const scheduled = snapshot.status === "scheduled" && snapshot.draft.publishAt ? formatDate(snapshot.draft.publishAt) : null;
1546
+ return scheduled ? `Scheduled for ${scheduled}` : "Not yet published";
1547
+ }
1266
1548
 
1267
1549
  //#endregion
1268
1550
  //#region ../content-model/src/content-types/schema.ts
@@ -1403,6 +1685,10 @@ const BaseContentFieldSchema = z.object({
1403
1685
  }
1404
1686
  }).passthrough();
1405
1687
  const ContentFieldSchema = BaseContentFieldSchema.extend({
1688
+ itemTypes: z.lazy(() => z.record(z.string(), z.object({
1689
+ label: z.string().optional(),
1690
+ fields: z.array(ContentFieldSchema).optional()
1691
+ }).passthrough())).optional(),
1406
1692
  fields: z.lazy(() => z.array(ContentFieldSchema)).optional(),
1407
1693
  schema: z.lazy(() => z.object({ fields: z.array(ContentFieldSchema).optional() }).passthrough()).optional()
1408
1694
  });
@@ -1516,284 +1802,6 @@ function isFileFieldKind(value) {
1516
1802
  return FILE_FIELD_KINDS.includes(value);
1517
1803
  }
1518
1804
 
1519
- //#endregion
1520
- //#region ../content-model/src/content-types/sentinels.ts
1521
- /** Leaf field types that receive a placeholder sentinel (containers recurse). */
1522
- const SENTINEL_LEAF_TYPES = [
1523
- "text",
1524
- "textarea",
1525
- "richText",
1526
- "number",
1527
- "date",
1528
- "datetime",
1529
- "url",
1530
- "link",
1531
- "media",
1532
- "file",
1533
- "boolean",
1534
- "select",
1535
- "reference"
1536
- ];
1537
- /** Matches the closed sentinel shape `"<REQUIRED: …>"`. */
1538
- const SENTINEL_PATTERN = /^<REQUIRED:.*>$/;
1539
- /**
1540
- * True when a value is an unfilled placeholder sentinel. Shared by the emitter
1541
- * (self-check) and the validator (rejection) so recognition can't drift.
1542
- */
1543
- function isPlaceholderSentinel(value) {
1544
- return typeof value === "string" && SENTINEL_PATTERN.test(value);
1545
- }
1546
- function effectiveReferenceKind(field) {
1547
- const kind = field.referenceKind;
1548
- const legacy = field.reference?.type;
1549
- return kind ?? legacy;
1550
- }
1551
- function selectOptionValues$1(field) {
1552
- const options = field.options;
1553
- return options ? options.map((o) => o.value) : [];
1554
- }
1555
- /**
1556
- * The placeholder sentinel for a leaf field. Container types (group/button/
1557
- * repeater) do not have a leaf sentinel — they expand structurally — and calling
1558
- * this for them returns a generic marker only for completeness.
1559
- */
1560
- function buildSentinel(field) {
1561
- switch (field.type) {
1562
- case "text":
1563
- case "textarea": return "<REQUIRED: text>";
1564
- case "richText": return "<REQUIRED: rich-text>";
1565
- case "number": return "<REQUIRED: number>";
1566
- case "date": return "<REQUIRED: date (YYYY-MM-DD)>";
1567
- case "datetime": return "<REQUIRED: datetime (ISO 8601)>";
1568
- case "url": return "<REQUIRED: url>";
1569
- case "link": return "<REQUIRED: link>";
1570
- case "media": return "<REQUIRED: media-identifier>";
1571
- case "file": return "<REQUIRED: file-identifier>";
1572
- case "boolean": return "<REQUIRED: boolean>";
1573
- case "select": {
1574
- const values = selectOptionValues$1(field);
1575
- return values.length > 0 ? `<REQUIRED: select (${values.join("|")})>` : "<REQUIRED: select>";
1576
- }
1577
- case "reference": {
1578
- const kind = effectiveReferenceKind(field);
1579
- return kind ? `<REQUIRED: reference-identifier (${kind})>` : "<REQUIRED: reference-identifier>";
1580
- }
1581
- case "group": return "<REQUIRED: group>";
1582
- case "button": return "<REQUIRED: button>";
1583
- case "repeater": return "<REQUIRED: repeater>";
1584
- }
1585
- }
1586
- /**
1587
- * Deep-scan arbitrary entry data for any unfilled placeholder sentinels. Needs no
1588
- * schema, so it powers the CLI's pre-HTTP upsert pre-flight.
1589
- */
1590
- function findUnfilledSentinels(data, pathPrefix = "") {
1591
- const found = [];
1592
- const walk = (value, path) => {
1593
- if (isPlaceholderSentinel(value)) {
1594
- found.push({
1595
- path: path || "(root)",
1596
- value
1597
- });
1598
- return;
1599
- }
1600
- if (Array.isArray(value)) {
1601
- value.forEach((item, index) => walk(item, `${path}[${index}]`));
1602
- return;
1603
- }
1604
- if (value !== null && typeof value === "object") {
1605
- for (const [key, child] of Object.entries(value)) {
1606
- walk(child, path ? `${path}.${key}` : key);
1607
- }
1608
- }
1609
- };
1610
- walk(data, pathPrefix);
1611
- return found;
1612
- }
1613
-
1614
- //#endregion
1615
- //#region ../content-model/src/content-types/entryValidation.ts
1616
- function subfieldsOf$1(field) {
1617
- const direct = field.fields;
1618
- if (direct && direct.length > 0) return direct;
1619
- const nested = field.schema?.fields;
1620
- return nested ?? [];
1621
- }
1622
- function repeaterVariantFields(field, item) {
1623
- const itemTypes = field.itemTypes;
1624
- if (!itemTypes) return subfieldsOf$1(field);
1625
- const typeId = item._type;
1626
- if (typeof typeId !== "string") return null;
1627
- return itemTypes[typeId]?.fields ?? null;
1628
- }
1629
- function selectOptionValues(field) {
1630
- const options = field.options;
1631
- return options ? options.map((o) => o.value) : [];
1632
- }
1633
- function isPlainObject(value) {
1634
- return value !== null && typeof value === "object" && !Array.isArray(value);
1635
- }
1636
- const STRING_TYPES = new Set([
1637
- "text",
1638
- "textarea",
1639
- "url",
1640
- "link",
1641
- "reference",
1642
- "date",
1643
- "datetime",
1644
- "select"
1645
- ]);
1646
- function isAllowedIdentifierBearingMedia(field, value) {
1647
- if (!isPlainObject(value) || typeof value.identifier !== "string" || value.identifier.trim().length === 0) {
1648
- return false;
1649
- }
1650
- if (value.type === undefined) return true;
1651
- const allowedKinds = field.type === "media" ? field.mediaKinds : field.fileKinds;
1652
- if (typeof value.type !== "string") return false;
1653
- const effectiveAllowedKinds = allowedKinds ?? (field.type === "media" ? ["image"] : ["document"]);
1654
- return effectiveAllowedKinds.some((kind) => kind === value.type);
1655
- }
1656
- function checkLeafType(field, value, path, errors) {
1657
- const push = (code, message) => {
1658
- errors.push({
1659
- path,
1660
- fieldType: field.type,
1661
- code,
1662
- message
1663
- });
1664
- };
1665
- if (field.type === "number") {
1666
- if (typeof value !== "number" || Number.isNaN(value)) {
1667
- push("wrong_type", `"${path}" must be a number.`);
1668
- }
1669
- return;
1670
- }
1671
- if (field.type === "boolean") {
1672
- if (typeof value !== "boolean") push("wrong_type", `"${path}" must be a boolean.`);
1673
- return;
1674
- }
1675
- if (field.type === "richText") {
1676
- if (typeof value !== "string" && !isPlainObject(value)) {
1677
- push("wrong_type", `"${path}" must be rich-text (string or document object).`);
1678
- }
1679
- return;
1680
- }
1681
- if (field.type === "media" || field.type === "file") {
1682
- if (isPlainObject(value) && isPlaceholderSentinel(value.identifier)) {
1683
- push("unfilled_placeholder", `"${path}" still contains an unfilled placeholder (${value.identifier}).`);
1684
- return;
1685
- }
1686
- if (!isAllowedIdentifierBearingMedia(field, value)) {
1687
- push("wrong_type", `"${path}" must be a media object with an identifier.`);
1688
- }
1689
- return;
1690
- }
1691
- if (field.type === "select") {
1692
- if (typeof value !== "string") {
1693
- push("wrong_type", `"${path}" must be a string.`);
1694
- return;
1695
- }
1696
- const options = selectOptionValues(field);
1697
- if (options.length > 0 && !options.includes(value)) {
1698
- push("invalid_option", `"${path}" must be one of: ${options.join(", ")}.`);
1699
- }
1700
- return;
1701
- }
1702
- if (STRING_TYPES.has(field.type)) {
1703
- if (typeof value !== "string") push("wrong_type", `"${path}" must be a string.`);
1704
- }
1705
- }
1706
- function validateField(field, value, path, errors, allowIncomplete) {
1707
- const missing = value === undefined || value === null || value === "";
1708
- if (missing) {
1709
- if (field.required === true && !allowIncomplete) {
1710
- errors.push({
1711
- path,
1712
- fieldType: field.type,
1713
- code: "missing_required",
1714
- message: `Required field "${path}" is missing.`
1715
- });
1716
- }
1717
- return;
1718
- }
1719
- if (isPlaceholderSentinel(value)) {
1720
- errors.push({
1721
- path,
1722
- fieldType: field.type,
1723
- code: "unfilled_placeholder",
1724
- message: `"${path}" still contains an unfilled placeholder (${value}).`
1725
- });
1726
- return;
1727
- }
1728
- if (field.type === "group" || field.type === "button") {
1729
- if (!isPlainObject(value)) {
1730
- errors.push({
1731
- path,
1732
- fieldType: field.type,
1733
- code: "wrong_type",
1734
- message: `"${path}" must be an object.`
1735
- });
1736
- return;
1737
- }
1738
- validateFields(subfieldsOf$1(field), value, path, errors, allowIncomplete);
1739
- return;
1740
- }
1741
- if (field.type === "repeater") {
1742
- if (!Array.isArray(value)) {
1743
- errors.push({
1744
- path,
1745
- fieldType: field.type,
1746
- code: "wrong_type",
1747
- message: `"${path}" must be an array.`
1748
- });
1749
- return;
1750
- }
1751
- value.forEach((item, index) => {
1752
- const itemPath = `${path}[${index}]`;
1753
- if (!isPlainObject(item)) {
1754
- errors.push({
1755
- path: itemPath,
1756
- fieldType: "repeater",
1757
- code: "wrong_type",
1758
- message: `"${itemPath}" must be an object.`
1759
- });
1760
- return;
1761
- }
1762
- const itemFields = repeaterVariantFields(field, item);
1763
- if (!itemFields) {
1764
- errors.push({
1765
- path: itemPath,
1766
- fieldType: "repeater",
1767
- code: "invalid_option",
1768
- message: `"${itemPath}" has an unknown or missing repeater item type.`
1769
- });
1770
- return;
1771
- }
1772
- validateFields(itemFields, item, itemPath, errors, allowIncomplete);
1773
- });
1774
- return;
1775
- }
1776
- checkLeafType(field, value, path, errors);
1777
- }
1778
- function validateFields(fields, data, pathPrefix, errors, allowIncomplete) {
1779
- for (const field of fields) {
1780
- const path = pathPrefix ? `${pathPrefix}.${field.id}` : field.id;
1781
- validateField(field, data[field.id], path, errors, allowIncomplete);
1782
- }
1783
- }
1784
- /**
1785
- * Validate entry data against a content-type schema. Returns all errors so
1786
- * callers can list everything an agent must fix in one pass.
1787
- */
1788
- function validateEntryInstance(input) {
1789
- const errors = [];
1790
- validateFields(input.schema.fields, input.data, "", errors, input.allowIncomplete ?? false);
1791
- return {
1792
- valid: errors.length === 0,
1793
- errors
1794
- };
1795
- }
1796
-
1797
1805
  //#endregion
1798
1806
  //#region ../content-model/src/content-types/scaffold.ts
1799
1807
  /** Nested subfields for group/repeater/button-style fields (both DSL shapes). */
@@ -4324,13 +4332,14 @@ const FIELD_TYPE_CLASSIFICATION = {
4324
4332
  repeater: "whole",
4325
4333
  presetOrCustom: "opaque",
4326
4334
  contentTypeSelect: "opaque",
4327
- entryPicker: "opaque",
4335
+ entryPicker: "leaf",
4328
4336
  time: "opaque"
4329
4337
  };
4330
4338
  function classify(type) {
4331
4339
  return FIELD_TYPE_CLASSIFICATION[type] ?? "unknown";
4332
4340
  }
4333
4341
  function leafToBlockFieldType(type) {
4342
+ if (type === "entryPicker") return "reference";
4334
4343
  return classify(type) === "leaf" ? type : null;
4335
4344
  }
4336
4345
  /**
@@ -4342,10 +4351,11 @@ function collectLeaves(fields, prefix, out) {
4342
4351
  for (const field of fields) {
4343
4352
  const id = prefix ? `${prefix}.${field.id}` : field.id;
4344
4353
  const cls = classify(field.type);
4345
- if (cls === "leaf") {
4354
+ const leafType = leafToBlockFieldType(field.type);
4355
+ if (leafType) {
4346
4356
  out.push({
4347
4357
  id,
4348
- type: field.type,
4358
+ type: leafType,
4349
4359
  required: Boolean(field.required)
4350
4360
  });
4351
4361
  continue;
@@ -4366,7 +4376,6 @@ function collectLeaves(fields, prefix, out) {
4366
4376
  if (tab.fields) collectLeaves(tab.fields, id, out);
4367
4377
  }
4368
4378
  }
4369
- void leafToBlockFieldType;
4370
4379
  }
4371
4380
  }
4372
4381
  function buildManifestRepeaterShape(field) {
@@ -4754,15 +4763,14 @@ const eventBookingOfferInputSchema = classifiedSchema((input) => {
4754
4763
  }]
4755
4764
  } : result;
4756
4765
  });
4757
- const eventRunInputSchema = z.object({
4766
+ const eventRunCommonInputSchema = z.object({
4758
4767
  identifier: z.string().min(1),
4759
4768
  eventRef: z.string().min(1),
4760
- name: z.string().nullable().optional(),
4761
4769
  status: sdkEventUpsertOptionalFieldSchemas.status,
4762
4770
  visibility: sdkEventUpsertOptionalFieldSchemas.visibility,
4763
- bookingScope: z.enum(["single_occurrence", "whole_series"]),
4764
4771
  occurrences: sdkEventUpsertOptionalFieldSchemas.occurrences.unwrap(),
4765
4772
  schedule: eventScheduleInputSchema.optional(),
4773
+ attendeeMigration: sdkEventUpsertOptionalFieldSchemas.attendeeMigration,
4766
4774
  bookingOffer: eventBookingOfferInputSchema.optional(),
4767
4775
  capacity: sdkEventUpsertOptionalFieldSchemas.capacity,
4768
4776
  venueRef: z.string().nullable().optional(),
@@ -4770,6 +4778,10 @@ const eventRunInputSchema = z.object({
4770
4778
  settings: z.record(z.string(), z.unknown()).optional(),
4771
4779
  tags: z.array(z.string()).optional()
4772
4780
  }).strict();
4781
+ const eventRunInputSchema = z.discriminatedUnion("bookingScope", [eventRunCommonInputSchema.extend({ bookingScope: z.literal("single_occurrence") }).strict(), eventRunCommonInputSchema.extend({
4782
+ bookingScope: z.literal("whole_series"),
4783
+ name: z.string().nullable().optional()
4784
+ }).strict()]);
4773
4785
  const eventUpsertInputSchema = z.object({
4774
4786
  identifier: z.string().min(1).optional(),
4775
4787
  courseBooking: sdkCourseBookingSettingsSchema.optional(),