riverbankcms 0.2.0 → 0.2.2

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,4 @@
1
1
  #!/usr/bin/env node
2
- import { z } from "zod";
3
-
4
2
  //#region ../core/src/result.ts
5
3
  function ok(value) {
6
4
  return {
@@ -46,6 +44,7 @@ const unsafeAsEntryId = (id) => id;
46
44
  const unsafeAsPreviewEntryId = (id) => id;
47
45
  const unsafeAsEventInstanceId = (id) => id;
48
46
  const unsafeAsSiteId = (id) => id;
47
+ const unsafeAsApiKeyId = (id) => id;
49
48
  const unsafeAsSiteRouteId = (id) => id;
50
49
  const unsafeAsPageId = (id) => id;
51
50
  const unsafeAsPageSurfaceId = (id) => id;
@@ -155,6 +154,7 @@ const unsafeAsEncounterRecordId = (id) => id;
155
154
  const unsafeAsRecordEntryId = (id) => id;
156
155
  const unsafeAsProtectedPayloadKeyVersion = (id) => id;
157
156
  const parseSiteId = (value) => parseUuidBrand(value, unsafeAsSiteId);
157
+ const parseApiKeyId = (value) => parseUuidBrand(value, unsafeAsApiKeyId);
158
158
  const parseSiteRouteId = (value) => parseUuidBrand(value, unsafeAsSiteRouteId);
159
159
  const parsePageId = (value) => parseUuidBrand(value, unsafeAsPageId);
160
160
  const parsePageSurfaceId = (value) => parseUuidBrand(value, unsafeAsPageSurfaceId);
@@ -639,18 +639,36 @@ function formatEventPricing(pricing, options = {}) {
639
639
 
640
640
  //#endregion
641
641
  //#region ../core/src/event-page-surface-subject.ts
642
+ function createEventSeriesPageSurfaceSubject(eventSeriesId, bookingScope) {
643
+ return bookingScope === "whole_series" ? {
644
+ kind: "eventSeries",
645
+ eventSeriesId,
646
+ bookingScope,
647
+ isWholeSeriesBooking: true
648
+ } : {
649
+ kind: "eventSeries",
650
+ eventSeriesId,
651
+ bookingScope
652
+ };
653
+ }
642
654
  function parseEventPageSurfaceSubject(value) {
643
- if (!isRecord$8(value)) return err({ kind: "notObject" });
655
+ if (!isRecord$6(value)) return err({ kind: "notObject" });
644
656
  switch (value.kind) {
645
657
  case "eventSeries": {
646
658
  const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
647
- return eventSeriesId.ok ? ok({
648
- kind: "eventSeries",
649
- eventSeriesId: eventSeriesId.value
650
- }) : err({
651
- kind: "invalidEventSeriesId",
652
- error: eventSeriesId.error
653
- });
659
+ if (!eventSeriesId.ok) {
660
+ return err({
661
+ kind: "invalidEventSeriesId",
662
+ error: eventSeriesId.error
663
+ });
664
+ }
665
+ if (value.bookingScope !== "single_occurrence" && value.bookingScope !== "whole_series") {
666
+ return err({
667
+ kind: "invalidEventSeriesBookingScope",
668
+ bookingScope: value.bookingScope
669
+ });
670
+ }
671
+ return ok(createEventSeriesPageSurfaceSubject(eventSeriesId.value, value.bookingScope));
654
672
  }
655
673
  case "eventOffering": {
656
674
  const eventOfferingId = parseEventOfferingId(value.eventOfferingId);
@@ -692,7 +710,7 @@ function parseEventPageSurfaceSubject(value) {
692
710
  function toEventPageSurfaceSubjectWire(subject) {
693
711
  return subject;
694
712
  }
695
- function isRecord$8(value) {
713
+ function isRecord$6(value) {
696
714
  return typeof value === "object" && value !== null && !Array.isArray(value);
697
715
  }
698
716
 
@@ -2233,7 +2251,7 @@ function reconcileParticipantRoster(input) {
2233
2251
  }
2234
2252
  function planParticipantSubjectResolution(input) {
2235
2253
  const existingParticipantIdByEmail = new Map(input.existingParticipantsByEmail.map((participant) => [participant.email, participant.participantId]));
2236
- const duplicateSlotKeysByEmail = new Map(input.projection.issues.filter((issue$3) => issue$3.kind === "duplicate_email_in_booking_context").map((issue$3) => [issue$3.email, issue$3.slotKeys]));
2254
+ const duplicateSlotKeysByEmail = new Map(input.projection.issues.filter((issue) => issue.kind === "duplicate_email_in_booking_context").map((issue) => [issue.email, issue.slotKeys]));
2237
2255
  return input.projection.slots.map((slot) => {
2238
2256
  if (slot.email === null) {
2239
2257
  return {
@@ -2699,7 +2717,7 @@ const knownSocialPlatforms = [
2699
2717
  "x",
2700
2718
  "youtube"
2701
2719
  ];
2702
- function isRecord$7(value) {
2720
+ function isRecord$5(value) {
2703
2721
  return typeof value === "object" && value !== null && !Array.isArray(value);
2704
2722
  }
2705
2723
  function trimmedOptionalString(value) {
@@ -2709,7 +2727,7 @@ function trimmedOptionalString(value) {
2709
2727
  return trimmed.length > 0 ? trimmed : null;
2710
2728
  }
2711
2729
  function parsePublicContactDetails(value) {
2712
- if (!isRecord$7(value)) {
2730
+ if (!isRecord$5(value)) {
2713
2731
  return err({
2714
2732
  code: "invalid_shape",
2715
2733
  path: "contact",
@@ -2805,7 +2823,7 @@ function minutesSinceMidnight(value) {
2805
2823
  }
2806
2824
  function parseOpeningHoursDay(value, expectedWeekday, index) {
2807
2825
  const path = `openingHours.days.${index}`;
2808
- if (!isRecord$7(value) || value.weekday !== expectedWeekday) {
2826
+ if (!isRecord$5(value) || value.weekday !== expectedWeekday) {
2809
2827
  return err({
2810
2828
  code: "invalid_weekdays",
2811
2829
  path,
@@ -2826,7 +2844,7 @@ function parseOpeningHoursDay(value, expectedWeekday, index) {
2826
2844
  const intervals = [];
2827
2845
  for (const [intervalIndex, rawInterval] of value.intervals.entries()) {
2828
2846
  const intervalPath = `${path}.intervals.${intervalIndex}`;
2829
- if (!isRecord$7(rawInterval)) {
2847
+ if (!isRecord$5(rawInterval)) {
2830
2848
  return err({
2831
2849
  code: "invalid_interval",
2832
2850
  path: intervalPath,
@@ -2876,7 +2894,7 @@ function parseOpeningHoursDay(value, expectedWeekday, index) {
2876
2894
  });
2877
2895
  }
2878
2896
  function parseWeeklyOpeningHours(value) {
2879
- if (!isRecord$7(value) || !Array.isArray(value.days) || value.days.length !== publicProfileWeekdays.length) {
2897
+ if (!isRecord$5(value) || !Array.isArray(value.days) || value.days.length !== publicProfileWeekdays.length) {
2880
2898
  return err({
2881
2899
  code: "invalid_weekdays",
2882
2900
  path: "openingHours.days",
@@ -2956,7 +2974,7 @@ function parsePublicSocialLinks(value) {
2956
2974
  const ids = new Set();
2957
2975
  for (const [index, rawLink] of value.entries()) {
2958
2976
  const path = `socialLinks.${index}`;
2959
- if (!isRecord$7(rawLink)) {
2977
+ if (!isRecord$5(rawLink)) {
2960
2978
  return err({
2961
2979
  code: "invalid_social_link",
2962
2980
  path,
@@ -3003,7 +3021,7 @@ function parsePublicSocialLinks(value) {
3003
3021
  return ok(links);
3004
3022
  }
3005
3023
  function parsePublicProfile(value) {
3006
- if (!isRecord$7(value)) {
3024
+ if (!isRecord$5(value)) {
3007
3025
  return err({
3008
3026
  code: "invalid_shape",
3009
3027
  path: "",
@@ -3032,7 +3050,7 @@ function emptyPublicProfile() {
3032
3050
 
3033
3051
  //#endregion
3034
3052
  //#region ../core/src/rich-text.ts
3035
- function isRecord$6(value) {
3053
+ function isRecord$4(value) {
3036
3054
  return typeof value === "object" && value !== null && !Array.isArray(value);
3037
3055
  }
3038
3056
  function hasOnlyKeys(value, allowed) {
@@ -3054,10 +3072,10 @@ function isDenseArrayOf(value, predicate) {
3054
3072
  return true;
3055
3073
  }
3056
3074
  function isTipTapMark(value) {
3057
- return isRecord$6(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === undefined || isRecord$6(value.attrs));
3075
+ return isRecord$4(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === undefined || isRecord$4(value.attrs));
3058
3076
  }
3059
3077
  function isTipTapNode(value) {
3060
- if (!isRecord$6(value) || !hasOnlyKeys(value, nodeKeys) || typeof value.type !== "string" || value.type.length === 0 || value.type === "doc" || value.attrs !== undefined && !isRecord$6(value.attrs) || value.marks !== undefined && (!Array.isArray(value.marks) || !isDenseArrayOf(value.marks, isTipTapMark)) || value.content !== undefined && (!Array.isArray(value.content) || !isDenseArrayOf(value.content, isTipTapNode))) {
3078
+ if (!isRecord$4(value) || !hasOnlyKeys(value, nodeKeys) || typeof value.type !== "string" || value.type.length === 0 || value.type === "doc" || value.attrs !== undefined && !isRecord$4(value.attrs) || value.marks !== undefined && (!Array.isArray(value.marks) || !isDenseArrayOf(value.marks, isTipTapMark)) || value.content !== undefined && (!Array.isArray(value.content) || !isDenseArrayOf(value.content, isTipTapNode))) {
3061
3079
  return false;
3062
3080
  }
3063
3081
  if (value.type === "text") {
@@ -3066,7 +3084,7 @@ function isTipTapNode(value) {
3066
3084
  return value.text === undefined;
3067
3085
  }
3068
3086
  function isCanonicalRichTextDoc(value) {
3069
- return isRecord$6(value) && hasOnlyKeys(value, rootKeys) && value.type === "doc" && Array.isArray(value.content) && isDenseArrayOf(value.content, isTipTapNode);
3087
+ return isRecord$4(value) && hasOnlyKeys(value, rootKeys) && value.type === "doc" && Array.isArray(value.content) && isDenseArrayOf(value.content, isTipTapNode);
3070
3088
  }
3071
3089
 
3072
3090
  //#endregion
@@ -3074,7 +3092,7 @@ function isCanonicalRichTextDoc(value) {
3074
3092
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
3075
3093
  const MAILCHIMP_AUDIENCE_ID_PATTERN = /^[A-Za-z0-9_-]{1,100}$/u;
3076
3094
  const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
3077
- function isRecord$5(value) {
3095
+ function isRecord$3(value) {
3078
3096
  return typeof value === "object" && value !== null && !Array.isArray(value);
3079
3097
  }
3080
3098
  function parseTrimmedString(value) {
@@ -3102,7 +3120,7 @@ function parseNewsletterSignupDestinationProvider(value) {
3102
3120
  return value === "riverbank" || value === "mailchimp" ? value : null;
3103
3121
  }
3104
3122
  function parseNewsletterSignupAuthoringDestinationValue(value) {
3105
- if (!isRecord$5(value) || typeof value.tag !== "string") {
3123
+ if (!isRecord$3(value) || typeof value.tag !== "string") {
3106
3124
  return err({ kind: "invalid-destination-shape" });
3107
3125
  }
3108
3126
  switch (value.tag) {
@@ -3148,7 +3166,7 @@ function parseNewsletterSignupAuthoringDestinationValue(value) {
3148
3166
 
3149
3167
  //#endregion
3150
3168
  //#region ../core/src/footer-engagement.ts
3151
- function isRecord$4(value) {
3169
+ function isRecord$2(value) {
3152
3170
  return typeof value === "object" && value !== null && !Array.isArray(value);
3153
3171
  }
3154
3172
  function invalid(path, message) {
@@ -3182,7 +3200,7 @@ function parseEngagementBase(value, base, path) {
3182
3200
  }
3183
3201
  const treatment = parseTreatment(value.treatment, `${path}.treatment`);
3184
3202
  if (!treatment.ok) return treatment;
3185
- if (!isRecord$4(value.source)) {
3203
+ if (!isRecord$2(value.source)) {
3186
3204
  return err(invalid(`${path}.source`, "Footer engagement source is invalid."));
3187
3205
  }
3188
3206
  const intro = parseBoundedText(value.source.intro, `${path}.source.intro`, {
@@ -3248,7 +3266,7 @@ function parseFooterEngagementSectionValue(value, sectionBase, path) {
3248
3266
  const base = parseEngagementBase(value, sectionBase, path);
3249
3267
  if (!base.ok) return base;
3250
3268
  const source = value.source;
3251
- if (!isRecord$4(source)) {
3269
+ if (!isRecord$2(source)) {
3252
3270
  return err(invalid(`${path}.source`, "Footer engagement source is invalid."));
3253
3271
  }
3254
3272
  switch (source.kind) {
@@ -3363,7 +3381,7 @@ function unsafeAsFooterModuleId(value) {
3363
3381
  function parseFooterModuleId(value) {
3364
3382
  return parseSectionId(value, "sectionId");
3365
3383
  }
3366
- function isRecord$3(value) {
3384
+ function isRecord$1(value) {
3367
3385
  return typeof value === "object" && value !== null && !Array.isArray(value);
3368
3386
  }
3369
3387
  function isRichText(value) {
@@ -3393,7 +3411,7 @@ function parseHeading(value, path) {
3393
3411
  }
3394
3412
  function parseFooterSection(value, index) {
3395
3413
  const path = `sections.${index}`;
3396
- if (!isRecord$3(value)) {
3414
+ if (!isRecord$1(value)) {
3397
3415
  return err({
3398
3416
  code: "invalid_section",
3399
3417
  path,
@@ -3428,7 +3446,7 @@ function parseFooterSection(value, index) {
3428
3446
  }
3429
3447
  break;
3430
3448
  case "navigation":
3431
- if (isRecord$3(value.source) && value.source.kind === "menu-role" && value.source.role === "main") {
3449
+ if (isRecord$1(value.source) && value.source.kind === "menu-role" && value.source.role === "main") {
3432
3450
  return ok({
3433
3451
  ...base,
3434
3452
  kind: "navigation",
@@ -3441,7 +3459,7 @@ function parseFooterSection(value, index) {
3441
3459
  }
3442
3460
  break;
3443
3461
  case "legal-navigation":
3444
- if (isRecord$3(value.source) && value.source.kind === "menu-role" && value.source.role === "legal-policy") {
3462
+ if (isRecord$1(value.source) && value.source.kind === "menu-role" && value.source.role === "legal-policy") {
3445
3463
  return ok({
3446
3464
  ...base,
3447
3465
  kind: "legal-navigation",
@@ -3454,7 +3472,7 @@ function parseFooterSection(value, index) {
3454
3472
  }
3455
3473
  break;
3456
3474
  case "business-address":
3457
- if (isRecord$3(value.source) && value.source.kind === "business-address") {
3475
+ if (isRecord$1(value.source) && value.source.kind === "business-address") {
3458
3476
  return ok({
3459
3477
  ...base,
3460
3478
  kind: "business-address",
@@ -3466,7 +3484,7 @@ function parseFooterSection(value, index) {
3466
3484
  case "contact":
3467
3485
  case "opening-hours":
3468
3486
  case "social-links":
3469
- if (isRecord$3(value.source) && value.source.kind === "public-profile") {
3487
+ if (isRecord$1(value.source) && value.source.kind === "public-profile") {
3470
3488
  return ok({
3471
3489
  ...base,
3472
3490
  kind: value.kind,
@@ -3518,7 +3536,7 @@ function validateFooterComposition(composition) {
3518
3536
  return ok(composition);
3519
3537
  }
3520
3538
  function parseFooterCompositionV1(value) {
3521
- if (!isRecord$3(value)) {
3539
+ if (!isRecord$1(value)) {
3522
3540
  return err({
3523
3541
  code: "invalid_shape",
3524
3542
  path: "",
@@ -3551,7 +3569,7 @@ function parseFooterCompositionV1(value) {
3551
3569
  });
3552
3570
  }
3553
3571
  function parseLegacyPlacementSection(value, index, selectedIds) {
3554
- if (!isRecord$3(value)) {
3572
+ if (!isRecord$1(value)) {
3555
3573
  return err({
3556
3574
  code: "invalid_section",
3557
3575
  path: `modules.${index}`,
@@ -3559,7 +3577,7 @@ function parseLegacyPlacementSection(value, index, selectedIds) {
3559
3577
  });
3560
3578
  }
3561
3579
  const selected = typeof value.id === "string" && selectedIds.has(value.id);
3562
- const source = isRecord$3(value.source) ? value.source : null;
3580
+ const source = isRecord$1(value.source) ? value.source : null;
3563
3581
  const mapped = value.kind === "navigation" && source?.kind === "menu-role" && source.role === "footer" ? {
3564
3582
  ...value,
3565
3583
  source: {
@@ -3578,7 +3596,7 @@ function parseLegacyPlacementSection(value, index, selectedIds) {
3578
3596
  * It is intentionally not called by the runtime parser.
3579
3597
  */
3580
3598
  function upgradePlacementFooterCompositionV1(value) {
3581
- if (!isRecord$3(value) || value.schemaVersion !== 1 || !Array.isArray(value.modules) || !Array.isArray(value.placements)) {
3599
+ if (!isRecord$1(value) || value.schemaVersion !== 1 || !Array.isArray(value.modules) || !Array.isArray(value.placements)) {
3582
3600
  return err({
3583
3601
  code: "invalid_shape",
3584
3602
  path: "",
@@ -3587,7 +3605,7 @@ function upgradePlacementFooterCompositionV1(value) {
3587
3605
  }
3588
3606
  const selectedIds = new Set();
3589
3607
  for (const [index, placement] of value.placements.entries()) {
3590
- if (!isRecord$3(placement) || typeof placement.moduleId !== "string") {
3608
+ if (!isRecord$1(placement) || typeof placement.moduleId !== "string") {
3591
3609
  return err({
3592
3610
  code: "invalid_shape",
3593
3611
  path: `placements.${index}`,
@@ -3802,7 +3820,7 @@ const defaultSections = (legacy, options) => {
3802
3820
  ];
3803
3821
  };
3804
3822
  function normalizeLegacyFooterContentToV1(value, options = {}) {
3805
- if (!isRecord$3(value)) {
3823
+ if (!isRecord$1(value)) {
3806
3824
  return err({
3807
3825
  code: "invalid_shape",
3808
3826
  path: "",
@@ -3887,11 +3905,11 @@ const PRIORITY_IDS = new Set([
3887
3905
  "emailaddress"
3888
3906
  ]);
3889
3907
  const MAX_COLUMNS = 5;
3890
- function isRecord$2(value) {
3908
+ function isRecord(value) {
3891
3909
  return typeof value === "object" && value !== null && !Array.isArray(value);
3892
3910
  }
3893
3911
  function isSchemaField(value) {
3894
- return isRecord$2(value) && typeof value.id === "string";
3912
+ return isRecord(value) && typeof value.id === "string";
3895
3913
  }
3896
3914
  function selectSubmissionPreviewFields(schemaFields, submissions) {
3897
3915
  const fields = Array.isArray(schemaFields) ? schemaFields.filter(isSchemaField) : [];
@@ -3984,7 +4002,7 @@ function valueIsMeaningful(value) {
3984
4002
  if (value === null || value === undefined) return false;
3985
4003
  if (typeof value === "string") return value.trim().length > 0;
3986
4004
  if (Array.isArray(value)) return value.length > 0;
3987
- if (isRecord$2(value)) return Object.keys(value).length > 0;
4005
+ if (isRecord(value)) return Object.keys(value).length > 0;
3988
4006
  return true;
3989
4007
  }
3990
4008
  function orderSubmissionFields(schemaFields, data) {
@@ -4473,6 +4491,82 @@ function sortStableJsonValue(value) {
4473
4491
  throw new TypeError(`Stable JSON does not support ${typeof value}`);
4474
4492
  }
4475
4493
 
4494
+ //#endregion
4495
+ //#region ../core/src/secret.ts
4496
+ /**
4497
+ * Credential values that must remain inert when they cross diagnostics,
4498
+ * logging, or serialization boundaries.
4499
+ *
4500
+ * The value is deliberately held behind a non-enumerable capability rather
4501
+ * than exposed as a property. The capability uses a global symbol so a Secret
4502
+ * created by another bundled copy of core remains consumable at an approved
4503
+ * `exposeSecret` boundary.
4504
+ */
4505
+ const SECRET_KINDS = [
4506
+ "management-key",
4507
+ "content-key",
4508
+ "preview-key",
4509
+ "webhook-secret",
4510
+ "oauth-access-token",
4511
+ "oauth-refresh-token",
4512
+ "preview-session-refresh-token",
4513
+ "oauth-authorization-code",
4514
+ "oauth-pkce-verifier",
4515
+ "registrar-epp-code"
4516
+ ];
4517
+ const secretValueSymbol = Symbol.for("riverbank.secret.value");
4518
+ const SECRET_INSPECT_SYMBOL = Symbol.for("nodejs.util.inspect.custom");
4519
+ const MANAGEMENT_KEY_PREFIX = "bld_mgmt_sk_";
4520
+ function placeholderFor(kind) {
4521
+ return `[riverbank:redacted:${kind}]`;
4522
+ }
4523
+ /** Create an opaque secret after a caller has parsed/validated its boundary. */
4524
+ function createSecret(kind, value) {
4525
+ return createSecretInternal(kind, value);
4526
+ }
4527
+ /** Construct a Secret for an already validated kind/value pair. */
4528
+ function createSecretInternal(kind, value) {
4529
+ const placeholder = placeholderFor(kind);
4530
+ const secret = {
4531
+ kind: "secret",
4532
+ secretType: kind,
4533
+ toString: () => placeholder,
4534
+ toJSON: () => placeholder,
4535
+ [SECRET_INSPECT_SYMBOL]: () => placeholder
4536
+ };
4537
+ Object.defineProperty(secret, secretValueSymbol, {
4538
+ configurable: false,
4539
+ enumerable: false,
4540
+ value: () => value,
4541
+ writable: false
4542
+ });
4543
+ return Object.freeze(secret);
4544
+ }
4545
+ /**
4546
+ * Parse an untrusted management API key before constructing its Secret value.
4547
+ *
4548
+ * The Secret type protects an already validated value from accidental
4549
+ * disclosure; this boundary is what validates the value's wire format.
4550
+ */
4551
+ function parseManagementKey(raw) {
4552
+ if (typeof raw !== "string" || !raw.startsWith(MANAGEMENT_KEY_PREFIX) || raw.length === MANAGEMENT_KEY_PREFIX.length) {
4553
+ return err({ kind: "invalid-management-key" });
4554
+ }
4555
+ return ok(createSecretInternal("management-key", raw));
4556
+ }
4557
+ /**
4558
+ * The only generic plaintext exposure operation. Keep calls at named I/O
4559
+ * boundaries; do not pass exposed strings through domain/application layers.
4560
+ */
4561
+ function exposeSecret(secret) {
4562
+ const getValue = secret[secretValueSymbol];
4563
+ if (typeof getValue !== "function") return failInvalidSecret();
4564
+ return getValue();
4565
+ }
4566
+ function failInvalidSecret() {
4567
+ throw new TypeError("Invalid Riverbank secret");
4568
+ }
4569
+
4476
4570
  //#endregion
4477
4571
  //#region ../core/src/site-route-target.ts
4478
4572
  const SITE_ROUTE_NODE_TYPES = [
@@ -4600,885 +4694,4 @@ function disabledSelection() {
4600
4694
  }
4601
4695
 
4602
4696
  //#endregion
4603
- //#region ../api/src/sdk-event-payment-terms.ts
4604
- const supportedKinds = new Set([
4605
- "upfront",
4606
- "deferred_manual",
4607
- "flexible_balance"
4608
- ]);
4609
- function issue$2(path, message) {
4610
- return {
4611
- path,
4612
- message
4613
- };
4614
- }
4615
- function isRecord$1(value) {
4616
- return typeof value === "object" && value !== null && !Array.isArray(value);
4617
- }
4618
- function isWholeNumber$1(value) {
4619
- return typeof value === "number" && Number.isInteger(value);
4620
- }
4621
- function isPositiveWholeNumber(value) {
4622
- return isWholeNumber$1(value) && value > 0;
4623
- }
4624
- function isDepositRefundability(value) {
4625
- return value === "refundable" || value === "non_refundable";
4626
- }
4627
- function isSupportedOptionKind(value) {
4628
- return typeof value === "string" && supportedKinds.has(value);
4629
- }
4630
- function validateFlexibleBalanceDueDateRule(raw, path) {
4631
- if (!isRecord$1(raw)) {
4632
- return [issue$2(path, "Flexible balance dueDateRule must be an object")];
4633
- }
4634
- if (raw.kind === "relative_to_start_date") {
4635
- if (!isPositiveWholeNumber(raw.daysBeforeStart)) {
4636
- return [issue$2([...path, "daysBeforeStart"], "Flexible balance daysBeforeStart must be a positive integer")];
4637
- }
4638
- return {
4639
- kind: "relative_to_start_date",
4640
- daysBeforeStart: raw.daysBeforeStart
4641
- };
4642
- }
4643
- if (raw.kind === "fixed_calendar_date") {
4644
- if (typeof raw.dueAt !== "string" || !Number.isFinite(Date.parse(raw.dueAt))) {
4645
- return [issue$2([...path, "dueAt"], "Flexible balance fixed dueAt must be an ISO datetime")];
4646
- }
4647
- return {
4648
- kind: "fixed_calendar_date",
4649
- dueAt: raw.dueAt
4650
- };
4651
- }
4652
- return [issue$2([...path, "kind"], "Flexible balance dueDateRule kind must be relative_to_start_date or fixed_calendar_date")];
4653
- }
4654
- function validateFlexibleBalanceConfig(raw, path) {
4655
- if (!isRecord$1(raw)) {
4656
- return [issue$2(path, "Flexible balance config must be an object")];
4657
- }
4658
- const issues = [];
4659
- const upfrontAmountCents = raw.upfrontAmountCents;
4660
- const depositRefundability = raw.depositRefundability;
4661
- let validatedUpfrontAmountCents = null;
4662
- let validatedDepositRefundability = null;
4663
- if (!isWholeNumber$1(upfrontAmountCents) || upfrontAmountCents < 0) {
4664
- issues.push(issue$2([...path, "upfrontAmountCents"], "Flexible balance upfrontAmountCents must be a non-negative integer"));
4665
- } else {
4666
- validatedUpfrontAmountCents = upfrontAmountCents;
4667
- }
4668
- if (!isDepositRefundability(depositRefundability)) {
4669
- issues.push(issue$2([...path, "depositRefundability"], "Flexible balance depositRefundability must be refundable or non_refundable"));
4670
- } else {
4671
- validatedDepositRefundability = depositRefundability;
4672
- }
4673
- const dueDateRule = validateFlexibleBalanceDueDateRule(raw.dueDateRule, [...path, "dueDateRule"]);
4674
- if (Array.isArray(dueDateRule)) {
4675
- issues.push(...dueDateRule);
4676
- }
4677
- if (issues.length > 0 || Array.isArray(dueDateRule) || validatedUpfrontAmountCents === null || validatedDepositRefundability === null) {
4678
- return issues;
4679
- }
4680
- return {
4681
- upfrontAmountCents: validatedUpfrontAmountCents,
4682
- depositRefundability: validatedDepositRefundability,
4683
- dueDateRule
4684
- };
4685
- }
4686
- function validateCollectionOption(raw, index) {
4687
- const path = ["allowedOptions", String(index)];
4688
- if (!isRecord$1(raw)) {
4689
- return [issue$2(path, "Payment option must be an object")];
4690
- }
4691
- if (raw.kind === "instalment_plan") {
4692
- return [issue$2([...path, "kind"], "Event instalment plans are not supported yet. Use upfront, deferred_manual, or flexible_balance.")];
4693
- }
4694
- if (!isSupportedOptionKind(raw.kind)) {
4695
- return [issue$2([...path, "kind"], "Payment option kind must be upfront, deferred_manual, or flexible_balance")];
4696
- }
4697
- switch (raw.kind) {
4698
- case "upfront":
4699
- case "deferred_manual": return { kind: raw.kind };
4700
- case "flexible_balance": {
4701
- const config = validateFlexibleBalanceConfig(raw.config, [...path, "config"]);
4702
- if (Array.isArray(config)) {
4703
- return config;
4704
- }
4705
- return {
4706
- kind: "flexible_balance",
4707
- config
4708
- };
4709
- }
4710
- }
4711
- }
4712
- function optionCombinationIssues(options) {
4713
- const issues = [];
4714
- const seenKinds = new Set();
4715
- options.forEach((option, index) => {
4716
- if (seenKinds.has(option.kind)) {
4717
- issues.push(issue$2([
4718
- "allowedOptions",
4719
- String(index),
4720
- "kind"
4721
- ], `Duplicate payment option kind "${option.kind}"`));
4722
- }
4723
- seenKinds.add(option.kind);
4724
- });
4725
- if (options.length > 2) {
4726
- issues.push(issue$2(["allowedOptions"], "Event payment terms can offer one option, or a choice between upfront and one alternative option"));
4727
- }
4728
- if (options.length === 2 && (!seenKinds.has("upfront") || !seenKinds.has("deferred_manual") && !seenKinds.has("flexible_balance"))) {
4729
- issues.push(issue$2(["allowedOptions"], "Event payment terms with two options must combine upfront with deferred_manual or flexible_balance"));
4730
- }
4731
- return issues;
4732
- }
4733
- function classifySdkEventPaymentTerms(rawInput) {
4734
- if (rawInput === undefined || rawInput === null) {
4735
- return {
4736
- ok: true,
4737
- paymentTerms: undefined
4738
- };
4739
- }
4740
- if (!isRecord$1(rawInput)) {
4741
- return {
4742
- ok: false,
4743
- issues: [issue$2([], "Event paymentTerms must be an object")]
4744
- };
4745
- }
4746
- if (!Array.isArray(rawInput.allowedOptions)) {
4747
- return {
4748
- ok: false,
4749
- issues: [issue$2(["allowedOptions"], "Event paymentTerms.allowedOptions is required")]
4750
- };
4751
- }
4752
- if (rawInput.allowedOptions.length === 0) {
4753
- return {
4754
- ok: false,
4755
- issues: [issue$2(["allowedOptions"], "Event paymentTerms.allowedOptions must include at least one option")]
4756
- };
4757
- }
4758
- const options = [];
4759
- const issues = [];
4760
- rawInput.allowedOptions.forEach((option, index) => {
4761
- const validated = validateCollectionOption(option, index);
4762
- if (Array.isArray(validated)) {
4763
- issues.push(...validated);
4764
- return;
4765
- }
4766
- options.push(validated);
4767
- });
4768
- issues.push(...optionCombinationIssues(options));
4769
- if (issues.length > 0) {
4770
- return {
4771
- ok: false,
4772
- issues
4773
- };
4774
- }
4775
- return {
4776
- ok: true,
4777
- paymentTerms: { allowedOptions: options }
4778
- };
4779
- }
4780
-
4781
- //#endregion
4782
- //#region ../api/src/sdk-event-pricing.ts
4783
- const identifierRegex = /^[a-z0-9][a-z0-9-]*$/;
4784
- const currencyRegex = /^[A-Z]{3}$/;
4785
- const isoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T.+/;
4786
- function issue$1(path, message) {
4787
- return {
4788
- path,
4789
- message
4790
- };
4791
- }
4792
- function isRecord(value) {
4793
- return typeof value === "object" && value !== null && !Array.isArray(value);
4794
- }
4795
- function isCurrency(value) {
4796
- return typeof value === "string" && currencyRegex.test(value);
4797
- }
4798
- function isWholeNumber(value) {
4799
- return typeof value === "number" && Number.isInteger(value);
4800
- }
4801
- function isWholeMinuteIsoDateTime(value) {
4802
- if (!isoDateTimeRegex.test(value)) return false;
4803
- const epochMs = Date.parse(value);
4804
- return Number.isFinite(epochMs) && epochMs % 6e4 === 0;
4805
- }
4806
- function validateTicketType(raw, index, currency) {
4807
- const path = ["ticketTypes", String(index)];
4808
- if (!isRecord(raw)) return [issue$1(path, "Ticket type must be an object")];
4809
- const issues = [];
4810
- const identifier = raw.identifier;
4811
- if (typeof identifier !== "string" || !identifierRegex.test(identifier)) {
4812
- issues.push(issue$1([...path, "identifier"], "Ticket identifier must use lowercase letters, numbers, and hyphens"));
4813
- }
4814
- const name = raw.name;
4815
- if (typeof name !== "string" || name.trim().length === 0) {
4816
- issues.push(issue$1([...path, "name"], "Ticket name is required"));
4817
- }
4818
- const priceAmount = raw.priceAmount;
4819
- if (!isWholeNumber(priceAmount) || priceAmount < 0) {
4820
- issues.push(issue$1([...path, "priceAmount"], "Ticket priceAmount must be a non-negative integer"));
4821
- }
4822
- const priceCurrency = raw.priceCurrency;
4823
- if (priceCurrency !== undefined && priceCurrency !== null && priceCurrency !== currency) {
4824
- issues.push(issue$1([...path, "priceCurrency"], "Ticket priceCurrency must match pricing.priceCurrency"));
4825
- }
4826
- const sortOrder = raw.sortOrder;
4827
- if (sortOrder !== undefined && (!isWholeNumber(sortOrder) || sortOrder < 0)) {
4828
- issues.push(issue$1([...path, "sortOrder"], "Ticket sortOrder must be a non-negative integer"));
4829
- }
4830
- const capacityLimit = raw.capacityLimit;
4831
- if (capacityLimit !== undefined && capacityLimit !== null && (!isWholeNumber(capacityLimit) || capacityLimit < 1)) {
4832
- issues.push(issue$1([...path, "capacityLimit"], "Ticket capacityLimit must be a positive integer"));
4833
- }
4834
- const attendanceScope = raw.attendanceScope;
4835
- if (attendanceScope !== undefined && attendanceScope !== "in_person" && attendanceScope !== "online" && attendanceScope !== "both") {
4836
- issues.push(issue$1([...path, "attendanceScope"], "Ticket attendanceScope must be in_person, online, or both"));
4837
- }
4838
- const saleStartsAt = raw.saleStartsAt;
4839
- if (saleStartsAt !== undefined && saleStartsAt !== null && (typeof saleStartsAt !== "string" || !isWholeMinuteIsoDateTime(saleStartsAt))) {
4840
- issues.push(issue$1([...path, "saleStartsAt"], "Ticket saleStartsAt must be an ISO datetime aligned to a whole minute"));
4841
- }
4842
- const saleEndsAt = raw.saleEndsAt;
4843
- if (saleEndsAt !== undefined && saleEndsAt !== null && (typeof saleEndsAt !== "string" || !isWholeMinuteIsoDateTime(saleEndsAt))) {
4844
- issues.push(issue$1([...path, "saleEndsAt"], "Ticket saleEndsAt must be an ISO datetime aligned to a whole minute"));
4845
- }
4846
- if (typeof saleStartsAt === "string" && typeof saleEndsAt === "string" && Date.parse(saleEndsAt) <= Date.parse(saleStartsAt)) {
4847
- issues.push(issue$1([...path, "saleEndsAt"], "Ticket saleEndsAt must be after saleStartsAt"));
4848
- }
4849
- const cmsTicketTypeId = raw.cmsTicketTypeId;
4850
- if (cmsTicketTypeId !== undefined && cmsTicketTypeId !== null && (typeof cmsTicketTypeId !== "string" || cmsTicketTypeId.length === 0)) {
4851
- issues.push(issue$1([...path, "cmsTicketTypeId"], "Ticket cmsTicketTypeId must be a non-empty string"));
4852
- }
4853
- if (issues.length > 0) return issues;
4854
- return {
4855
- identifier,
4856
- ...cmsTicketTypeId ? { cmsTicketTypeId } : {},
4857
- name,
4858
- description: typeof raw.description === "string" ? raw.description : null,
4859
- priceAmount,
4860
- isActive: typeof raw.isActive === "boolean" ? raw.isActive : true,
4861
- sortOrder: isWholeNumber(sortOrder) ? sortOrder : index,
4862
- capacityLimit: isWholeNumber(capacityLimit) ? capacityLimit : null,
4863
- attendanceScope: attendanceScope === "in_person" || attendanceScope === "online" || attendanceScope === "both" ? attendanceScope : "both",
4864
- saleStartsAt: typeof saleStartsAt === "string" ? saleStartsAt : null,
4865
- saleEndsAt: typeof saleEndsAt === "string" ? saleEndsAt : null
4866
- };
4867
- }
4868
- function validateTicketTypes(rawTicketTypes, currency) {
4869
- if (!Array.isArray(rawTicketTypes) || rawTicketTypes.length === 0) {
4870
- return [issue$1(["ticketTypes"], "Ticket-type pricing requires at least one ticket type")];
4871
- }
4872
- const issues = [];
4873
- const ticketTypes = [];
4874
- const seenIdentifiers = new Set();
4875
- rawTicketTypes.forEach((rawTicketType, index) => {
4876
- const rawIdentifier = isRecord(rawTicketType) ? rawTicketType.identifier : undefined;
4877
- const hasValidIdentifier = typeof rawIdentifier === "string" && identifierRegex.test(rawIdentifier);
4878
- const isDuplicateIdentifier = hasValidIdentifier && seenIdentifiers.has(rawIdentifier);
4879
- if (isDuplicateIdentifier) {
4880
- issues.push(issue$1([
4881
- "ticketTypes",
4882
- String(index),
4883
- "identifier"
4884
- ], `Duplicate ticket identifier "${rawIdentifier}"`));
4885
- } else if (hasValidIdentifier) {
4886
- seenIdentifiers.add(rawIdentifier);
4887
- }
4888
- const parsed = validateTicketType(rawTicketType, index, currency);
4889
- if (Array.isArray(parsed)) {
4890
- issues.push(...parsed);
4891
- return;
4892
- }
4893
- if (isDuplicateIdentifier) {
4894
- return;
4895
- }
4896
- ticketTypes.push(parsed);
4897
- });
4898
- if (issues.length > 0) return issues;
4899
- return sortSdkEventTicketTypes(ticketTypes);
4900
- }
4901
- function isPricingIssueArray(value) {
4902
- const first = value[0];
4903
- return first !== undefined && "message" in first;
4904
- }
4905
- function sortSdkEventTicketTypes(ticketTypes) {
4906
- return [...ticketTypes].sort((left, right) => {
4907
- const leftOrder = left.sortOrder ?? Number.MAX_SAFE_INTEGER;
4908
- const rightOrder = right.sortOrder ?? Number.MAX_SAFE_INTEGER;
4909
- if (leftOrder !== rightOrder) return leftOrder - rightOrder;
4910
- const identifierCompare = left.identifier.localeCompare(right.identifier);
4911
- if (identifierCompare !== 0) return identifierCompare;
4912
- return left.name.localeCompare(right.name);
4913
- });
4914
- }
4915
- function suggestSdkEventTicketIdentifier(name, usedIdentifiers) {
4916
- const base = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "ticket";
4917
- let candidate = base;
4918
- let suffix = 2;
4919
- while (usedIdentifiers.has(candidate)) {
4920
- candidate = `${base}-${suffix}`;
4921
- suffix += 1;
4922
- }
4923
- return candidate;
4924
- }
4925
- function classifySdkEventPricing(rawInput) {
4926
- if (rawInput === undefined || rawInput === null) {
4927
- return {
4928
- ok: true,
4929
- pricing: { kind: "free" }
4930
- };
4931
- }
4932
- if (!isRecord(rawInput)) {
4933
- return {
4934
- ok: false,
4935
- issues: [issue$1([], "Pricing must be an object")]
4936
- };
4937
- }
4938
- const record = rawInput;
4939
- const kind = typeof record.kind === "string" ? record.kind : record.pricingMode === "ticket_types" ? "ticket_types" : record.pricingMode === "fixed_price" ? "fixed_price" : record.pricingMode === "free" ? "free" : record.requiresPayment || typeof record.priceAmount === "number" && record.priceAmount > 0 ? "fixed_price" : "free";
4940
- const acceptsPasses = typeof record.acceptsPasses === "boolean" ? record.acceptsPasses : undefined;
4941
- const acceptsMemberships = typeof record.acceptsMemberships === "boolean" ? record.acceptsMemberships : undefined;
4942
- const priceDisplay = typeof record.priceDisplay === "string" ? record.priceDisplay : null;
4943
- switch (kind) {
4944
- case "free": return {
4945
- ok: true,
4946
- pricing: {
4947
- kind: "free",
4948
- priceDisplay,
4949
- ...acceptsPasses !== undefined ? { acceptsPasses } : {},
4950
- ...acceptsMemberships !== undefined ? { acceptsMemberships } : {}
4951
- }
4952
- };
4953
- case "fixed_price": {
4954
- const issues = [];
4955
- const priceAmount = record.priceAmount;
4956
- if (!isWholeNumber(priceAmount) || priceAmount < 0) {
4957
- issues.push(issue$1(["priceAmount"], "Fixed-price events require a non-negative integer priceAmount"));
4958
- }
4959
- const rawPriceCurrency = record.priceCurrency;
4960
- const priceCurrency = rawPriceCurrency === undefined || rawPriceCurrency === null ? "USD" : isCurrency(rawPriceCurrency) ? rawPriceCurrency : null;
4961
- if (rawPriceCurrency !== undefined && rawPriceCurrency !== null && !isCurrency(rawPriceCurrency)) {
4962
- issues.push(issue$1(["priceCurrency"], "priceCurrency must be a 3-letter uppercase currency code"));
4963
- }
4964
- if (issues.length > 0) return {
4965
- ok: false,
4966
- issues
4967
- };
4968
- if (!isWholeNumber(priceAmount) || priceCurrency === null) {
4969
- return {
4970
- ok: false,
4971
- issues: [issue$1(["priceAmount"], "Invalid fixed-price event pricing")]
4972
- };
4973
- }
4974
- return {
4975
- ok: true,
4976
- pricing: {
4977
- kind: "fixed_price",
4978
- priceAmount,
4979
- priceCurrency,
4980
- priceDisplay,
4981
- ...acceptsPasses !== undefined ? { acceptsPasses } : {},
4982
- ...acceptsMemberships !== undefined ? { acceptsMemberships } : {}
4983
- }
4984
- };
4985
- }
4986
- case "ticket_types": {
4987
- const priceCurrency = record.priceCurrency;
4988
- if (!isCurrency(priceCurrency)) {
4989
- return {
4990
- ok: false,
4991
- issues: [issue$1(["priceCurrency"], "Ticket-type pricing requires a 3-letter uppercase priceCurrency")]
4992
- };
4993
- }
4994
- const ticketTypes = record.ticketTypes === undefined || record.ticketTypes === null ? [] : validateTicketTypes(record.ticketTypes, priceCurrency);
4995
- if (isPricingIssueArray(ticketTypes)) {
4996
- return {
4997
- ok: false,
4998
- issues: ticketTypes
4999
- };
5000
- }
5001
- return {
5002
- ok: true,
5003
- pricing: {
5004
- kind: "ticket_types",
5005
- priceCurrency,
5006
- priceDisplay,
5007
- ...acceptsPasses !== undefined ? { acceptsPasses } : {},
5008
- ...acceptsMemberships !== undefined ? { acceptsMemberships } : {},
5009
- ticketTypes
5010
- }
5011
- };
5012
- }
5013
- default: return {
5014
- ok: false,
5015
- issues: [issue$1(["kind"], "Pricing kind must be free, fixed_price, or ticket_types")]
5016
- };
5017
- }
5018
- }
5019
-
5020
- //#endregion
5021
- //#region ../api/src/sdk-event-schedule.ts
5022
- const timeRegex = /^([01]\d|2[0-3]):([0-5]\d)(:([0-5]\d))?$/;
5023
- const explicitOffsetDateTimeRegex = /^(\d{4})-(\d{2})-(\d{2})T([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?(\.\d{1,3})?(Z|[+-]([01]\d|2[0-3]):[0-5]\d)$/;
5024
- const localDateTimeRegex = /^(\d{4})-(\d{2})-(\d{2})T([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?(\.\d{1,3})?$/;
5025
- function issue(path, message) {
5026
- return {
5027
- path,
5028
- message
5029
- };
5030
- }
5031
- function isValidDateParts(year, month, day) {
5032
- const date = new Date(Date.UTC(year, month - 1, day));
5033
- return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
5034
- }
5035
- function isZeroFraction(fraction) {
5036
- return fraction === undefined || /^\.0+$/.test(fraction);
5037
- }
5038
- function datePart(year, month, day) {
5039
- return `${year}-${month}-${day}`;
5040
- }
5041
- function timePart(hours, minutes, seconds = "00") {
5042
- return `${hours}:${minutes}:${seconds}`;
5043
- }
5044
- function parseSdkEventDateTime(value) {
5045
- const explicitMatch = explicitOffsetDateTimeRegex.exec(value);
5046
- if (explicitMatch) {
5047
- const [, yearRaw$1, monthRaw$1, dayRaw$1, hours$1, minutes$1, seconds$1 = "00", fraction$1] = explicitMatch;
5048
- if (!yearRaw$1 || !monthRaw$1 || !dayRaw$1 || !hours$1 || !minutes$1) return null;
5049
- const year$1 = Number(yearRaw$1);
5050
- const month$1 = Number(monthRaw$1);
5051
- const day$1 = Number(dayRaw$1);
5052
- if (!isValidDateParts(year$1, month$1, day$1) || !isZeroFraction(fraction$1)) return null;
5053
- const epochMs$1 = Date.parse(value);
5054
- if (!Number.isFinite(epochMs$1)) return null;
5055
- return {
5056
- epochMs: epochMs$1,
5057
- date: datePart(yearRaw$1, monthRaw$1, dayRaw$1),
5058
- time: timePart(hours$1, minutes$1, seconds$1),
5059
- iso: new Date(epochMs$1).toISOString()
5060
- };
5061
- }
5062
- const localMatch = localDateTimeRegex.exec(value);
5063
- if (!localMatch) return null;
5064
- const [, yearRaw, monthRaw, dayRaw, hours, minutes, seconds = "00", fraction] = localMatch;
5065
- if (!yearRaw || !monthRaw || !dayRaw || !hours || !minutes) return null;
5066
- const year = Number(yearRaw);
5067
- const month = Number(monthRaw);
5068
- const day = Number(dayRaw);
5069
- if (!isValidDateParts(year, month, day) || !isZeroFraction(fraction)) return null;
5070
- const epochMs = Date.UTC(year, month - 1, day, Number(hours), Number(minutes), Number(seconds));
5071
- return {
5072
- epochMs,
5073
- date: datePart(yearRaw, monthRaw, dayRaw),
5074
- time: timePart(hours, minutes, seconds),
5075
- iso: new Date(epochMs).toISOString()
5076
- };
5077
- }
5078
- function durationMinutesBetween(startsAt, endsAt) {
5079
- if (endsAt.epochMs <= startsAt.epochMs) return null;
5080
- const durationMs = endsAt.epochMs - startsAt.epochMs;
5081
- if (durationMs % 6e4 !== 0) return null;
5082
- return durationMs / 6e4;
5083
- }
5084
- function exceedsMaxDuration(durationMinutes, options) {
5085
- return options.maxDurationMinutes !== undefined && durationMinutes > options.maxDurationMinutes;
5086
- }
5087
- function maxDurationMessage(options) {
5088
- return options.maxDurationDays === undefined ? "Duration is above the allowed maximum" : `Duration must be ${options.maxDurationDays} days or less`;
5089
- }
5090
- function classifyCanonicalSchedule(input, options) {
5091
- const issues = [];
5092
- if (options.requireCanonicalFields && input.recurrenceRule === undefined) {
5093
- issues.push(issue(["recurrenceRule"], "recurrenceRule is required"));
5094
- }
5095
- if (input.defaultStartTime !== undefined && input.defaultStartTime !== null && !timeRegex.test(input.defaultStartTime)) {
5096
- issues.push(issue(["defaultStartTime"], "Invalid time format (HH:MM or HH:MM:SS)"));
5097
- }
5098
- if (options.requireCanonicalFields && !input.defaultStartTime) {
5099
- issues.push(issue(["defaultStartTime"], "defaultStartTime is required"));
5100
- }
5101
- if (options.requireCanonicalFields && input.durationMinutes === undefined) {
5102
- issues.push(issue(["durationMinutes"], "durationMinutes is required"));
5103
- }
5104
- if (input.durationMinutes !== undefined && input.durationMinutes !== null) {
5105
- if (input.durationMinutes < 1) {
5106
- issues.push(issue(["durationMinutes"], "durationMinutes must be at least 1"));
5107
- } else if (exceedsMaxDuration(input.durationMinutes, options)) {
5108
- issues.push(issue(["durationMinutes"], maxDurationMessage(options)));
5109
- }
5110
- }
5111
- if (issues.length > 0) return {
5112
- ok: false,
5113
- issues
5114
- };
5115
- return {
5116
- ok: true,
5117
- kind: "canonical",
5118
- canonical: {
5119
- recurrenceRule: input.recurrenceRule ?? null,
5120
- startDate: input.startDate,
5121
- timezone: input.timezone,
5122
- defaultStartTime: input.defaultStartTime ?? "09:00:00",
5123
- durationMinutes: input.durationMinutes ?? 60
5124
- }
5125
- };
5126
- }
5127
- function classifySdkEventSchedule(input, options = {}) {
5128
- const issues = [];
5129
- const hasStartsAt = input.startsAt !== undefined && input.startsAt !== null;
5130
- const hasEndsAt = input.endsAt !== undefined && input.endsAt !== null;
5131
- const hasDateTimeDuration = hasStartsAt && input.durationMinutes !== undefined && input.durationMinutes !== null;
5132
- const hasCanonicalStart = input.startDate !== undefined || input.defaultStartTime !== undefined;
5133
- if (!hasStartsAt && !hasEndsAt) {
5134
- return classifyCanonicalSchedule(input, options);
5135
- }
5136
- if (hasStartsAt && hasCanonicalStart) {
5137
- issues.push(issue(["startsAt"], "Use either startsAt or startDate/defaultStartTime, not both"));
5138
- }
5139
- if (input.recurrenceRule) {
5140
- issues.push(issue(["recurrenceRule"], "startsAt schedules are only supported for one-off events"));
5141
- }
5142
- if (!hasStartsAt) {
5143
- issues.push(issue(["startsAt"], "startsAt is required when endsAt is provided"));
5144
- }
5145
- if (!hasEndsAt && !hasDateTimeDuration) {
5146
- issues.push(issue(["endsAt"], "Provide either endsAt or durationMinutes with startsAt"));
5147
- }
5148
- if (hasEndsAt && hasDateTimeDuration) {
5149
- issues.push(issue(["endsAt"], "Use either endsAt or durationMinutes with startsAt, not both"));
5150
- }
5151
- const startsAt = typeof input.startsAt === "string" ? parseSdkEventDateTime(input.startsAt) : null;
5152
- const endsAt = typeof input.endsAt === "string" ? parseSdkEventDateTime(input.endsAt) : null;
5153
- if (hasStartsAt && !startsAt) {
5154
- issues.push(issue(["startsAt"], "Invalid datetime format. Use ISO datetime format, e.g. 2026-06-01T10:00:00Z"));
5155
- }
5156
- if (hasEndsAt && !endsAt) {
5157
- issues.push(issue(["endsAt"], "Invalid datetime format. Use ISO datetime format, e.g. 2026-06-07T10:00:00Z"));
5158
- }
5159
- if (startsAt && endsAt) {
5160
- const durationMinutes = durationMinutesBetween(startsAt, endsAt);
5161
- if (durationMinutes === null) {
5162
- issues.push(issue(["endsAt"], "endsAt must be after startsAt and align to a whole minute"));
5163
- } else if (exceedsMaxDuration(durationMinutes, options)) {
5164
- issues.push(issue(["endsAt"], maxDurationMessage(options)));
5165
- }
5166
- if (issues.length === 0 && durationMinutes !== null) {
5167
- return {
5168
- ok: true,
5169
- kind: "start-end",
5170
- canonical: {
5171
- recurrenceRule: null,
5172
- startDate: startsAt.date,
5173
- timezone: input.timezone,
5174
- defaultStartTime: startsAt.time,
5175
- durationMinutes
5176
- },
5177
- startsAtIso: startsAt.iso,
5178
- endsAtIso: endsAt.iso
5179
- };
5180
- }
5181
- }
5182
- if (startsAt && hasDateTimeDuration && input.durationMinutes !== undefined && input.durationMinutes !== null) {
5183
- if (input.durationMinutes < 1) {
5184
- issues.push(issue(["durationMinutes"], "durationMinutes must be at least 1"));
5185
- } else if (exceedsMaxDuration(input.durationMinutes, options)) {
5186
- issues.push(issue(["durationMinutes"], maxDurationMessage(options)));
5187
- }
5188
- if (issues.length === 0) {
5189
- const endsAtMs = startsAt.epochMs + input.durationMinutes * 6e4;
5190
- return {
5191
- ok: true,
5192
- kind: "start-duration",
5193
- canonical: {
5194
- recurrenceRule: null,
5195
- startDate: startsAt.date,
5196
- timezone: input.timezone,
5197
- defaultStartTime: startsAt.time,
5198
- durationMinutes: input.durationMinutes
5199
- },
5200
- startsAtIso: startsAt.iso,
5201
- endsAtIso: new Date(endsAtMs).toISOString()
5202
- };
5203
- }
5204
- }
5205
- return {
5206
- ok: false,
5207
- issues
5208
- };
5209
- }
5210
-
5211
- //#endregion
5212
- //#region ../api/src/sdk-event-upsert.ts
5213
- const sdkEventOccurrenceWireSchema = z.object({
5214
- startsAt: z.string(),
5215
- endsAt: z.string(),
5216
- timeZone: z.string()
5217
- });
5218
- const sdkEventOccurrenceInputSchema = z.object({
5219
- startsAt: z.string().datetime({ offset: true }),
5220
- endsAt: z.string().datetime({ offset: true }),
5221
- timeZone: z.string().min(1).refine((value) => parseBookingTimeZone(value).ok, "Invalid IANA timezone")
5222
- }).refine(({ startsAt, endsAt }) => Date.parse(endsAt) > Date.parse(startsAt), {
5223
- path: ["endsAt"],
5224
- message: "endsAt must be after startsAt"
5225
- });
5226
- const sdkEventUpsertOptionalFieldSchemas = {
5227
- path: z.string().startsWith("/", "path must start with /").nullable().optional(),
5228
- status: z.enum([
5229
- "draft",
5230
- "active",
5231
- "archived",
5232
- "cancelled",
5233
- "completed"
5234
- ]).optional(),
5235
- visibility: z.enum([
5236
- "private",
5237
- "public",
5238
- "unlisted"
5239
- ]).optional(),
5240
- preset: z.enum([
5241
- "event",
5242
- "class",
5243
- "course",
5244
- "workshop",
5245
- "retreat",
5246
- "performance"
5247
- ]).optional(),
5248
- bookingScope: z.enum(["single_occurrence", "whole_series"]).optional(),
5249
- occurrences: z.array(sdkEventOccurrenceWireSchema).optional(),
5250
- capacity: z.number().int().positive().nullable().optional()
5251
- };
5252
- function timingIssue(path, message) {
5253
- return {
5254
- path,
5255
- message
5256
- };
5257
- }
5258
- function parseExplicitOccurrence(occurrence, index) {
5259
- const startsAt = parseUtcIsoDateTimeString(occurrence.startsAt);
5260
- const endsAt = parseUtcIsoDateTimeString(occurrence.endsAt);
5261
- const timeZone = parseBookingTimeZone(occurrence.timeZone);
5262
- const path = ["occurrences", String(index)];
5263
- const issues = [];
5264
- if (!startsAt.ok) issues.push(timingIssue([...path, "startsAt"], "startsAt must be a valid offset-aware ISO datetime"));
5265
- if (!endsAt.ok) issues.push(timingIssue([...path, "endsAt"], "endsAt must be a valid offset-aware ISO datetime"));
5266
- if (!timeZone.ok) issues.push(timingIssue([...path, "timeZone"], "Invalid IANA timezone"));
5267
- if (!startsAt.ok || !endsAt.ok || !timeZone.ok) return {
5268
- ok: false,
5269
- issues
5270
- };
5271
- if (Date.parse(endsAt.value) <= Date.parse(startsAt.value)) {
5272
- return {
5273
- ok: false,
5274
- issues: [timingIssue([...path, "endsAt"], "endsAt must be after startsAt")]
5275
- };
5276
- }
5277
- return {
5278
- ok: true,
5279
- value: {
5280
- startsAt: startsAt.value,
5281
- endsAt: endsAt.value,
5282
- timeZone: timeZone.value
5283
- }
5284
- };
5285
- }
5286
- function classifySdkEventTiming(input, options = {}) {
5287
- if (input.occurrences && input.occurrences.length > 0) {
5288
- if (input.bookingScope === undefined) {
5289
- return {
5290
- ok: false,
5291
- issues: [timingIssue(["bookingScope"], "bookingScope is required when occurrences are provided")]
5292
- };
5293
- }
5294
- const occurrences = [];
5295
- const starts = new Set();
5296
- let timeZone;
5297
- const issues = [];
5298
- input.occurrences.forEach((occurrence, index) => {
5299
- const parsed = parseExplicitOccurrence(occurrence, index);
5300
- if (!parsed.ok) {
5301
- issues.push(...parsed.issues);
5302
- return;
5303
- }
5304
- if (starts.has(parsed.value.startsAt)) {
5305
- issues.push(timingIssue([
5306
- "occurrences",
5307
- String(index),
5308
- "startsAt"
5309
- ], "Occurrence start times must be unique within a series"));
5310
- return;
5311
- }
5312
- if (timeZone && timeZone !== parsed.value.timeZone) {
5313
- issues.push(timingIssue([
5314
- "occurrences",
5315
- String(index),
5316
- "timeZone"
5317
- ], "All event occurrences must use the same timezone"));
5318
- return;
5319
- }
5320
- starts.add(parsed.value.startsAt);
5321
- timeZone ??= parsed.value.timeZone;
5322
- occurrences.push(parsed.value);
5323
- });
5324
- const [first, ...rest] = occurrences;
5325
- if (issues.length > 0 || !first || !timeZone) return {
5326
- ok: false,
5327
- issues
5328
- };
5329
- return {
5330
- ok: true,
5331
- timingAuthority: {
5332
- kind: "explicit_occurrences",
5333
- bookingScope: input.bookingScope,
5334
- occurrences: [first, ...rest],
5335
- timeZone
5336
- }
5337
- };
5338
- }
5339
- if (input.schedule === undefined || typeof input.schedule !== "object" || input.schedule === null || Array.isArray(input.schedule)) {
5340
- return {
5341
- ok: false,
5342
- issues: [timingIssue(["schedule"], "Either schedule or a non-empty occurrences list is required")]
5343
- };
5344
- }
5345
- const schedule = classifySdkEventSchedule(input.schedule, options);
5346
- return schedule.ok ? {
5347
- ok: true,
5348
- timingAuthority: {
5349
- kind: "authored_schedule",
5350
- schedule: schedule.canonical,
5351
- bookingScope: input.bookingScope
5352
- }
5353
- } : {
5354
- ok: false,
5355
- issues: schedule.issues.map((issue$3) => timingIssue(["schedule", ...issue$3.path], issue$3.message))
5356
- };
5357
- }
5358
- function parseSdkEventUpsertInput(input, options = {}) {
5359
- const runRefs = new Set(input.runRefs);
5360
- const runsByIdentifier = new Map(input.runs.map((run) => [run.identifier, run]));
5361
- if (input.runRefs.length !== runRefs.size || runsByIdentifier.size !== input.runs.length || input.runRefs.length !== input.runs.length || input.runRefs.some((ref) => !runsByIdentifier.has(ref)) || input.runs.some((run) => run.eventRef !== input.identifier)) {
5362
- return {
5363
- ok: false,
5364
- issues: [{
5365
- source: "graph",
5366
- path: ["runRefs"],
5367
- message: "Event runRefs and runs must form one complete parent graph",
5368
- issue: { kind: "incoherentParentRunGraph" }
5369
- }]
5370
- };
5371
- }
5372
- const runForRef = (ref) => {
5373
- const run = runsByIdentifier.get(ref);
5374
- if (!run) throw new Error("Unreachable missing Event run after graph validation");
5375
- return run;
5376
- };
5377
- const firstRun = runForRef(input.runRefs[0]);
5378
- const remainingRuns = input.runRefs.slice(1).map(runForRef);
5379
- if (firstRun.bookingScope === "single_occurrence" && remainingRuns.length > 0 || remainingRuns.some((run) => run.bookingScope !== firstRun.bookingScope)) {
5380
- return {
5381
- ok: false,
5382
- issues: [{
5383
- source: "graph",
5384
- path: ["runs"],
5385
- message: "An Event must contain one ordinary run or one or more whole-series runs",
5386
- issue: { kind: "incoherentParentRunGraph" }
5387
- }]
5388
- };
5389
- }
5390
- const firstResult = parseSdkEventRunUpsertInput(firstRun, 0, options);
5391
- const remainingResults = remainingRuns.map((run, index) => parseSdkEventRunUpsertInput(run, index + 1, options));
5392
- const issues = [firstResult, ...remainingResults].flatMap((result) => result.ok ? [] : result.issues);
5393
- if (issues.length > 0) return {
5394
- ok: false,
5395
- issues
5396
- };
5397
- if (!firstResult.ok) return {
5398
- ok: false,
5399
- issues: firstResult.issues
5400
- };
5401
- const parsedRemaining = remainingResults.flatMap((result) => result.ok ? [result.value] : []);
5402
- return {
5403
- ok: true,
5404
- value: {
5405
- ...input,
5406
- runs: [firstResult.value, ...parsedRemaining]
5407
- }
5408
- };
5409
- }
5410
- function toSdkEventRunUpsertBody(input) {
5411
- const { timingAuthority, ...common } = input;
5412
- switch (timingAuthority.kind) {
5413
- case "authored_schedule": return {
5414
- ...common,
5415
- ...timingAuthority.bookingScope === undefined ? {} : { bookingScope: timingAuthority.bookingScope },
5416
- schedule: timingAuthority.schedule
5417
- };
5418
- case "explicit_occurrences": return {
5419
- ...common,
5420
- bookingScope: timingAuthority.bookingScope,
5421
- occurrences: timingAuthority.occurrences
5422
- };
5423
- default: return timingAuthority;
5424
- }
5425
- }
5426
- function toSdkEventUpsertBody(input) {
5427
- const [first, ...rest] = input.runs;
5428
- return {
5429
- ...input,
5430
- runs: [toSdkEventRunUpsertBody(first), ...rest.map(toSdkEventRunUpsertBody)]
5431
- };
5432
- }
5433
- function parseSdkEventRunUpsertInput(input, runIndex, options) {
5434
- const timingResult = classifySdkEventTiming(input, options.schedule);
5435
- const pricingResult = classifySdkEventPricing(input.pricing);
5436
- const paymentTermsResult = classifySdkEventPaymentTerms(input.paymentTerms);
5437
- const issues = [];
5438
- if (timingResult.ok && pricingResult.ok && paymentTermsResult.ok) {
5439
- const { schedule: _schedule, occurrences: _occurrences, bookingScope: _bookingScope, pricing: _pricing, paymentTerms: _paymentTerms, ...common } = input;
5440
- return {
5441
- ok: true,
5442
- value: {
5443
- ...common,
5444
- timingAuthority: timingResult.timingAuthority,
5445
- pricing: pricingResult.pricing,
5446
- paymentTerms: paymentTermsResult.paymentTerms
5447
- }
5448
- };
5449
- }
5450
- if (!timingResult.ok) {
5451
- issues.push(...timingResult.issues.map((timing) => ({
5452
- runIndex,
5453
- source: "timing",
5454
- path: timing.path,
5455
- message: timing.message,
5456
- issue: timing
5457
- })));
5458
- }
5459
- if (!pricingResult.ok) {
5460
- issues.push(...pricingResult.issues.map((pricingIssue) => ({
5461
- runIndex,
5462
- source: "pricing",
5463
- path: pricingIssue.path,
5464
- message: pricingIssue.message,
5465
- issue: pricingIssue
5466
- })));
5467
- }
5468
- if (!paymentTermsResult.ok) {
5469
- issues.push(...paymentTermsResult.issues.map((paymentTermsIssue) => ({
5470
- runIndex,
5471
- source: "paymentTerms",
5472
- path: paymentTermsIssue.path,
5473
- message: paymentTermsIssue.message,
5474
- issue: paymentTermsIssue
5475
- })));
5476
- }
5477
- return {
5478
- ok: false,
5479
- issues
5480
- };
5481
- }
5482
-
5483
- //#endregion
5484
- export { unsafeAsDetectedMimeType as $, getBookingLocalDateKey as A, publicEventPricingCurrency as B, normalizeLocationAddressText as C, formatBookingTime as D, formatBookingDateParts as E, toScheduledBookingDateRange as F, parseMediaAssetId as G, formatCurrencyAmount as H, toScheduledBookingDateTime as I, unsafeAsAppointmentResourceId as J, parsePageId as K, parseEventPageSurfaceSubject as L, getLocalScheduleTime as M, parseUtcIsoDateTimeString as N, formatLocalScheduleDate as O, toBookingTimeZone as P, unsafeAsDemoCapability as Q, formatBookingPriceDisplay as R, isPresent as S, formatBookingDate as T, assertNever as U, publicEventPricingType as V, parseEventSeriesId as W, unsafeAsBookingSessionId as X, unsafeAsBookingRequirementId as Y, unsafeAsDeclaredMimeType as Z, isCanonicalRichTextDoc as _, unsafeAsUploadSessionId as _t, classifySdkEventPricing as a, unsafeAsOccurrenceId as at, mapNonEmptyArray as b, ok as bt, stableJsonStringify as c, unsafeAsPositiveSeconds as ct, parseFooterModuleId as d, unsafeAsSiteFontId as dt, unsafeAsEntitlementId as et, parseMailchimpAudienceId as f, unsafeAsSiteId as ft, parseNewsletterSignupDestinationProvider as g, unsafeAsStoragePath as gt, parseNewsletterSignupAuthoringDestinationValue as h, unsafeAsStorageBucketName as ht, classifySdkEventSchedule as i, unsafeAsMediaAssetId as it, getLocalScheduleDate as j, formatLocalScheduleTime as k, footerNavigationLayouts as l, unsafeAsProductId as lt, parseNewsletterListId as m, unsafeAsStaffMemberId as mt, sdkEventOccurrenceInputSchema as n, unsafeAsEventSeriesId as nt, classifySdkEventPaymentTerms as o, unsafeAsOriginalFilename as ot, parseMailchimpTagName as p, unsafeAsSiteScopedStoragePath as pt, parseSiteId as q, sdkEventUpsertOptionalFieldSchemas as r, unsafeAsFileChecksumSha256 as rt, resolvePlanningDateInput as s, unsafeAsPositiveByteCount as st, classifySdkEventTiming as t, unsafeAsEventOfferingId as tt, parseFooterCompositionV1 as u, unsafeAsSafeDownloadFilename as ut, assertAllowedPublicUrl as v, unsafeAsUserId as vt, toLocationCoordinates as w, hasText as x, fromArray as y, err as yt, formatEventPricing as z };
4697
+ export { unsafeAsDetectedMimeType as $, parseUtcIsoDateTimeString as A, formatCurrencyAmount as B, formatBookingDateParts as C, getBookingLocalDateKey as D, formatLocalScheduleTime as E, parseEventPageSurfaceSubject as F, parsePageId as G, parseApiKeyId as H, formatBookingPriceDisplay as I, unsafeAsAppointmentResourceId as J, parseSiteId as K, formatEventPricing as L, toScheduledBookingDateRange as M, toScheduledBookingDateTime as N, getLocalScheduleDate as O, parseBookingTimeZone as P, unsafeAsDemoCapability as Q, publicEventPricingCurrency as R, formatBookingDate as S, formatLocalScheduleDate as T, parseEventSeriesId as U, assertNever as V, parseMediaAssetId as W, unsafeAsBookingSessionId as X, unsafeAsBookingRequirementId as Y, unsafeAsDeclaredMimeType as Z, mapNonEmptyArray as _, unsafeAsUploadSessionId as _t, stableJsonStringify as a, unsafeAsOccurrenceId as at, normalizeLocationAddressText as b, ok as bt, parseFooterModuleId as c, unsafeAsPositiveSeconds as ct, parseNewsletterListId as d, unsafeAsSiteFontId as dt, unsafeAsEntitlementId as et, parseNewsletterSignupAuthoringDestinationValue as f, unsafeAsSiteId as ft, fromArray as g, unsafeAsStoragePath as gt, assertAllowedPublicUrl as h, unsafeAsStorageBucketName as ht, parseManagementKey as i, unsafeAsMediaAssetId as it, toBookingTimeZone as j, getLocalScheduleTime as k, parseMailchimpAudienceId as l, unsafeAsProductId as lt, isCanonicalRichTextDoc as m, unsafeAsStaffMemberId as mt, createSecret as n, unsafeAsEventSeriesId as nt, footerNavigationLayouts as o, unsafeAsOriginalFilename as ot, parseNewsletterSignupDestinationProvider as p, unsafeAsSiteScopedStoragePath as pt, parseUserId as q, exposeSecret as r, unsafeAsFileChecksumSha256 as rt, parseFooterCompositionV1 as s, unsafeAsPositiveByteCount as st, resolvePlanningDateInput as t, unsafeAsEventOfferingId as tt, parseMailchimpTagName as u, unsafeAsSafeDownloadFilename as ut, hasText as v, unsafeAsUserId as vt, formatBookingTime as w, toLocationCoordinates as x, isPresent as y, err as yt, publicEventPricingType as z };