riverbankcms 0.4.3 → 0.6.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,962 +0,0 @@
1
- #!/usr/bin/env node
2
- import { B as parseUtcIsoDateTimeString, W as parseBookingTimeZone } from "./src-DKm0bLEA.mjs";
3
- import { z } from "zod";
4
-
5
- //#region ../api/src/bookingPaymentTermSchemas.ts
6
- const bookingDepositRefundPolicySchema = z.enum(["refundable", "non_refundable"]);
7
- const bookingInstalmentPlanSchema = z.discriminatedUnion("kind", [
8
- z.object({
9
- kind: z.literal("deposit_plus_one_remainder"),
10
- depositAmountCents: z.number().int().nonnegative(),
11
- depositRefundability: bookingDepositRefundPolicySchema,
12
- remainderDueCount: z.number().int().positive(),
13
- intervalUnit: z.enum([
14
- "day",
15
- "week",
16
- "month"
17
- ])
18
- }),
19
- z.object({
20
- kind: z.literal("deposit_plus_scheduled_instalments"),
21
- depositAmountCents: z.number().int().nonnegative(),
22
- depositRefundability: bookingDepositRefundPolicySchema,
23
- instalmentCount: z.number().int().positive(),
24
- intervalCount: z.number().int().positive(),
25
- intervalUnit: z.enum([
26
- "day",
27
- "week",
28
- "month"
29
- ])
30
- }),
31
- z.object({
32
- kind: z.literal("equal_scheduled_instalments"),
33
- instalmentCount: z.number().int().positive(),
34
- intervalCount: z.number().int().positive(),
35
- intervalUnit: z.enum([
36
- "day",
37
- "week",
38
- "month"
39
- ])
40
- })
41
- ]);
42
- const bookingFlexibleBalanceDueDateRuleSchema = z.discriminatedUnion("kind", [z.object({
43
- kind: z.literal("relative_to_start_date"),
44
- daysBeforeStart: z.number().int().positive()
45
- }), z.object({
46
- kind: z.literal("fixed_calendar_date"),
47
- dueAt: z.string().datetime()
48
- })]);
49
- const bookingFlexibleBalanceConfigSchema = z.object({
50
- upfrontAmountCents: z.number().int().nonnegative(),
51
- dueDateRule: bookingFlexibleBalanceDueDateRuleSchema,
52
- depositRefundability: bookingDepositRefundPolicySchema
53
- });
54
- const bookingPaymentCollectionOptionSchema = z.discriminatedUnion("kind", [
55
- z.object({ kind: z.literal("upfront") }),
56
- z.object({ kind: z.literal("deferred_manual") }),
57
- z.object({
58
- kind: z.literal("instalment_plan"),
59
- plan: bookingInstalmentPlanSchema
60
- }),
61
- z.object({
62
- kind: z.literal("flexible_balance"),
63
- config: bookingFlexibleBalanceConfigSchema
64
- })
65
- ]);
66
-
67
- //#endregion
68
- //#region ../api/src/sdk-event-payment-terms.ts
69
- const supportedKinds = new Set([
70
- "upfront",
71
- "deferred_manual",
72
- "instalment_plan",
73
- "flexible_balance"
74
- ]);
75
- function issue$2(path, message) {
76
- return {
77
- path,
78
- message
79
- };
80
- }
81
- function isRecord$1(value) {
82
- return typeof value === "object" && value !== null && !Array.isArray(value);
83
- }
84
- function isWholeNumber$1(value) {
85
- return typeof value === "number" && Number.isInteger(value);
86
- }
87
- function isPositiveWholeNumber(value) {
88
- return isWholeNumber$1(value) && value > 0;
89
- }
90
- function isDepositRefundability(value) {
91
- return value === "refundable" || value === "non_refundable";
92
- }
93
- function validateInstalmentPlan(raw, path) {
94
- if (!isRecord$1(raw)) {
95
- return [issue$2(path, "Instalment plan must be an object")];
96
- }
97
- const parsed = bookingInstalmentPlanSchema.safeParse(raw);
98
- return parsed.success ? parsed.data : parsed.error.issues.map((problem) => issue$2([...path, ...problem.path.map(String)], `Invalid instalment plan: ${problem.message}`));
99
- }
100
- function isSupportedOptionKind(value) {
101
- return typeof value === "string" && supportedKinds.has(value);
102
- }
103
- function validateFlexibleBalanceDueDateRule(raw, path) {
104
- if (!isRecord$1(raw)) {
105
- return [issue$2(path, "Flexible balance dueDateRule must be an object")];
106
- }
107
- if (raw.kind === "relative_to_start_date") {
108
- if (!isPositiveWholeNumber(raw.daysBeforeStart)) {
109
- return [issue$2([...path, "daysBeforeStart"], "Flexible balance daysBeforeStart must be a positive integer")];
110
- }
111
- return {
112
- kind: "relative_to_start_date",
113
- daysBeforeStart: raw.daysBeforeStart
114
- };
115
- }
116
- if (raw.kind === "fixed_calendar_date") {
117
- if (typeof raw.dueAt !== "string" || !Number.isFinite(Date.parse(raw.dueAt))) {
118
- return [issue$2([...path, "dueAt"], "Flexible balance fixed dueAt must be an ISO datetime")];
119
- }
120
- return {
121
- kind: "fixed_calendar_date",
122
- dueAt: raw.dueAt
123
- };
124
- }
125
- return [issue$2([...path, "kind"], "Flexible balance dueDateRule kind must be relative_to_start_date or fixed_calendar_date")];
126
- }
127
- function validateFlexibleBalanceConfig(raw, path) {
128
- if (!isRecord$1(raw)) {
129
- return [issue$2(path, "Flexible balance config must be an object")];
130
- }
131
- const issues = [];
132
- const upfrontAmountCents = raw.upfrontAmountCents;
133
- const depositRefundability = raw.depositRefundability;
134
- let validatedUpfrontAmountCents = null;
135
- let validatedDepositRefundability = null;
136
- if (!isWholeNumber$1(upfrontAmountCents) || upfrontAmountCents < 0) {
137
- issues.push(issue$2([...path, "upfrontAmountCents"], "Flexible balance upfrontAmountCents must be a non-negative integer"));
138
- } else {
139
- validatedUpfrontAmountCents = upfrontAmountCents;
140
- }
141
- if (!isDepositRefundability(depositRefundability)) {
142
- issues.push(issue$2([...path, "depositRefundability"], "Flexible balance depositRefundability must be refundable or non_refundable"));
143
- } else {
144
- validatedDepositRefundability = depositRefundability;
145
- }
146
- const dueDateRule = validateFlexibleBalanceDueDateRule(raw.dueDateRule, [...path, "dueDateRule"]);
147
- if (Array.isArray(dueDateRule)) {
148
- issues.push(...dueDateRule);
149
- }
150
- if (issues.length > 0 || Array.isArray(dueDateRule) || validatedUpfrontAmountCents === null || validatedDepositRefundability === null) {
151
- return issues;
152
- }
153
- return {
154
- upfrontAmountCents: validatedUpfrontAmountCents,
155
- depositRefundability: validatedDepositRefundability,
156
- dueDateRule
157
- };
158
- }
159
- function validateCollectionOption(raw, index) {
160
- const path = ["allowedOptions", String(index)];
161
- if (!isRecord$1(raw)) {
162
- return [issue$2(path, "Payment option must be an object")];
163
- }
164
- if (!isSupportedOptionKind(raw.kind)) {
165
- return [issue$2([...path, "kind"], "Payment option kind must be upfront, deferred_manual, instalment_plan, or flexible_balance")];
166
- }
167
- switch (raw.kind) {
168
- case "upfront":
169
- case "deferred_manual": return { kind: raw.kind };
170
- case "instalment_plan": {
171
- const plan = validateInstalmentPlan(raw.plan, [...path, "plan"]);
172
- return Array.isArray(plan) ? plan : {
173
- kind: "instalment_plan",
174
- plan
175
- };
176
- }
177
- case "flexible_balance": {
178
- const config = validateFlexibleBalanceConfig(raw.config, [...path, "config"]);
179
- if (Array.isArray(config)) {
180
- return config;
181
- }
182
- return {
183
- kind: "flexible_balance",
184
- config
185
- };
186
- }
187
- }
188
- }
189
- function optionCombinationIssues(options) {
190
- const issues = [];
191
- const seenKinds = new Set();
192
- options.forEach((option, index) => {
193
- if (seenKinds.has(option.kind)) {
194
- issues.push(issue$2([
195
- "allowedOptions",
196
- String(index),
197
- "kind"
198
- ], `Duplicate payment option kind "${option.kind}"`));
199
- }
200
- seenKinds.add(option.kind);
201
- });
202
- if (options.length > 2) {
203
- issues.push(issue$2(["allowedOptions"], "Event payment terms can offer one option, or a choice between upfront and one alternative option"));
204
- }
205
- if (options.length === 2 && (!seenKinds.has("upfront") || !seenKinds.has("deferred_manual") && !seenKinds.has("instalment_plan") && !seenKinds.has("flexible_balance"))) {
206
- issues.push(issue$2(["allowedOptions"], "Event payment terms with two options must combine upfront with one alternative option"));
207
- }
208
- return issues;
209
- }
210
- function classifySdkEventPaymentTerms(rawInput) {
211
- if (rawInput === undefined || rawInput === null) {
212
- return {
213
- ok: true,
214
- paymentTerms: undefined
215
- };
216
- }
217
- if (!isRecord$1(rawInput)) {
218
- return {
219
- ok: false,
220
- issues: [issue$2([], "Event paymentTerms must be an object")]
221
- };
222
- }
223
- if (!Array.isArray(rawInput.allowedOptions)) {
224
- return {
225
- ok: false,
226
- issues: [issue$2(["allowedOptions"], "Event paymentTerms.allowedOptions is required")]
227
- };
228
- }
229
- if (rawInput.allowedOptions.length === 0) {
230
- return {
231
- ok: false,
232
- issues: [issue$2(["allowedOptions"], "Event paymentTerms.allowedOptions must include at least one option")]
233
- };
234
- }
235
- const options = [];
236
- const issues = [];
237
- rawInput.allowedOptions.forEach((option, index) => {
238
- const validated = validateCollectionOption(option, index);
239
- if (Array.isArray(validated)) {
240
- issues.push(...validated);
241
- return;
242
- }
243
- options.push(validated);
244
- });
245
- issues.push(...optionCombinationIssues(options));
246
- if (issues.length > 0) {
247
- return {
248
- ok: false,
249
- issues
250
- };
251
- }
252
- return {
253
- ok: true,
254
- paymentTerms: { allowedOptions: options }
255
- };
256
- }
257
-
258
- //#endregion
259
- //#region ../api/src/sdk-event-pricing.ts
260
- const identifierRegex = /^[a-z0-9][a-z0-9-]*$/;
261
- const currencyRegex = /^[A-Z]{3}$/;
262
- const isoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T.+/;
263
- function issue$1(path, message) {
264
- return {
265
- path,
266
- message
267
- };
268
- }
269
- function isRecord(value) {
270
- return typeof value === "object" && value !== null && !Array.isArray(value);
271
- }
272
- function isCurrency(value) {
273
- return typeof value === "string" && currencyRegex.test(value);
274
- }
275
- function isWholeNumber(value) {
276
- return typeof value === "number" && Number.isInteger(value);
277
- }
278
- function isWholeMinuteIsoDateTime(value) {
279
- if (!isoDateTimeRegex.test(value)) return false;
280
- const epochMs = Date.parse(value);
281
- return Number.isFinite(epochMs) && epochMs % 6e4 === 0;
282
- }
283
- function validateTicketType(raw, index, currency) {
284
- const path = ["ticketTypes", String(index)];
285
- if (!isRecord(raw)) return [issue$1(path, "Ticket type must be an object")];
286
- const issues = [];
287
- const identifier = raw.identifier;
288
- if (typeof identifier !== "string" || !identifierRegex.test(identifier)) {
289
- issues.push(issue$1([...path, "identifier"], "Ticket identifier must use lowercase letters, numbers, and hyphens"));
290
- }
291
- const name = raw.name;
292
- if (typeof name !== "string" || name.trim().length === 0) {
293
- issues.push(issue$1([...path, "name"], "Ticket name is required"));
294
- }
295
- const priceAmount = raw.priceAmount;
296
- if (!isWholeNumber(priceAmount) || priceAmount < 0) {
297
- issues.push(issue$1([...path, "priceAmount"], "Ticket priceAmount must be a non-negative integer"));
298
- }
299
- const priceCurrency = raw.priceCurrency;
300
- if (priceCurrency !== undefined && priceCurrency !== null && priceCurrency !== currency) {
301
- issues.push(issue$1([...path, "priceCurrency"], "Ticket priceCurrency must match pricing.priceCurrency"));
302
- }
303
- const sortOrder = raw.sortOrder;
304
- if (sortOrder !== undefined && (!isWholeNumber(sortOrder) || sortOrder < 0)) {
305
- issues.push(issue$1([...path, "sortOrder"], "Ticket sortOrder must be a non-negative integer"));
306
- }
307
- const capacityLimit = raw.capacityLimit;
308
- if (capacityLimit !== undefined && capacityLimit !== null && (!isWholeNumber(capacityLimit) || capacityLimit < 1)) {
309
- issues.push(issue$1([...path, "capacityLimit"], "Ticket capacityLimit must be a positive integer"));
310
- }
311
- const attendanceScope = raw.attendanceScope;
312
- if (attendanceScope !== undefined && attendanceScope !== "in_person" && attendanceScope !== "online" && attendanceScope !== "both") {
313
- issues.push(issue$1([...path, "attendanceScope"], "Ticket attendanceScope must be in_person, online, or both"));
314
- }
315
- const saleStartsAt = raw.saleStartsAt;
316
- if (saleStartsAt !== undefined && saleStartsAt !== null && (typeof saleStartsAt !== "string" || !isWholeMinuteIsoDateTime(saleStartsAt))) {
317
- issues.push(issue$1([...path, "saleStartsAt"], "Ticket saleStartsAt must be an ISO datetime aligned to a whole minute"));
318
- }
319
- const saleEndsAt = raw.saleEndsAt;
320
- if (saleEndsAt !== undefined && saleEndsAt !== null && (typeof saleEndsAt !== "string" || !isWholeMinuteIsoDateTime(saleEndsAt))) {
321
- issues.push(issue$1([...path, "saleEndsAt"], "Ticket saleEndsAt must be an ISO datetime aligned to a whole minute"));
322
- }
323
- if (typeof saleStartsAt === "string" && typeof saleEndsAt === "string" && Date.parse(saleEndsAt) <= Date.parse(saleStartsAt)) {
324
- issues.push(issue$1([...path, "saleEndsAt"], "Ticket saleEndsAt must be after saleStartsAt"));
325
- }
326
- const cmsTicketTypeId = raw.cmsTicketTypeId;
327
- if (cmsTicketTypeId !== undefined && cmsTicketTypeId !== null && (typeof cmsTicketTypeId !== "string" || cmsTicketTypeId.length === 0)) {
328
- issues.push(issue$1([...path, "cmsTicketTypeId"], "Ticket cmsTicketTypeId must be a non-empty string"));
329
- }
330
- if (issues.length > 0) return issues;
331
- return {
332
- identifier,
333
- ...cmsTicketTypeId ? { cmsTicketTypeId } : {},
334
- name,
335
- description: typeof raw.description === "string" ? raw.description : null,
336
- priceAmount,
337
- isActive: typeof raw.isActive === "boolean" ? raw.isActive : true,
338
- sortOrder: isWholeNumber(sortOrder) ? sortOrder : index,
339
- capacityLimit: isWholeNumber(capacityLimit) ? capacityLimit : null,
340
- attendanceScope: attendanceScope === "in_person" || attendanceScope === "online" || attendanceScope === "both" ? attendanceScope : "both",
341
- saleStartsAt: typeof saleStartsAt === "string" ? saleStartsAt : null,
342
- saleEndsAt: typeof saleEndsAt === "string" ? saleEndsAt : null
343
- };
344
- }
345
- function validateTicketTypes(rawTicketTypes, currency) {
346
- if (!Array.isArray(rawTicketTypes) || rawTicketTypes.length === 0) {
347
- return [issue$1(["ticketTypes"], "Ticket-type pricing requires at least one ticket type")];
348
- }
349
- const issues = [];
350
- const ticketTypes = [];
351
- const seenIdentifiers = new Set();
352
- rawTicketTypes.forEach((rawTicketType, index) => {
353
- const rawIdentifier = isRecord(rawTicketType) ? rawTicketType.identifier : undefined;
354
- const hasValidIdentifier = typeof rawIdentifier === "string" && identifierRegex.test(rawIdentifier);
355
- const isDuplicateIdentifier = hasValidIdentifier && seenIdentifiers.has(rawIdentifier);
356
- if (isDuplicateIdentifier) {
357
- issues.push(issue$1([
358
- "ticketTypes",
359
- String(index),
360
- "identifier"
361
- ], `Duplicate ticket identifier "${rawIdentifier}"`));
362
- } else if (hasValidIdentifier) {
363
- seenIdentifiers.add(rawIdentifier);
364
- }
365
- const parsed = validateTicketType(rawTicketType, index, currency);
366
- if (Array.isArray(parsed)) {
367
- issues.push(...parsed);
368
- return;
369
- }
370
- if (isDuplicateIdentifier) {
371
- return;
372
- }
373
- ticketTypes.push(parsed);
374
- });
375
- if (issues.length > 0) return issues;
376
- return sortSdkEventTicketTypes(ticketTypes);
377
- }
378
- function isPricingIssueArray(value) {
379
- const first = value[0];
380
- return first !== undefined && "message" in first;
381
- }
382
- function sortSdkEventTicketTypes(ticketTypes) {
383
- return [...ticketTypes].sort((left, right) => {
384
- const leftOrder = left.sortOrder ?? Number.MAX_SAFE_INTEGER;
385
- const rightOrder = right.sortOrder ?? Number.MAX_SAFE_INTEGER;
386
- if (leftOrder !== rightOrder) return leftOrder - rightOrder;
387
- const identifierCompare = left.identifier.localeCompare(right.identifier);
388
- if (identifierCompare !== 0) return identifierCompare;
389
- return left.name.localeCompare(right.name);
390
- });
391
- }
392
- function suggestSdkEventTicketIdentifier(name, usedIdentifiers) {
393
- const base = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "ticket";
394
- let candidate = base;
395
- let suffix = 2;
396
- while (usedIdentifiers.has(candidate)) {
397
- candidate = `${base}-${suffix}`;
398
- suffix += 1;
399
- }
400
- return candidate;
401
- }
402
- function classifySdkEventPricing(rawInput) {
403
- if (rawInput === undefined || rawInput === null) {
404
- return {
405
- ok: true,
406
- pricing: { kind: "free" }
407
- };
408
- }
409
- if (!isRecord(rawInput)) {
410
- return {
411
- ok: false,
412
- issues: [issue$1([], "Pricing must be an object")]
413
- };
414
- }
415
- const record = rawInput;
416
- 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";
417
- const acceptsPasses = typeof record.acceptsPasses === "boolean" ? record.acceptsPasses : undefined;
418
- const acceptsMemberships = typeof record.acceptsMemberships === "boolean" ? record.acceptsMemberships : undefined;
419
- const priceDisplay = typeof record.priceDisplay === "string" ? record.priceDisplay : null;
420
- switch (kind) {
421
- case "free": return {
422
- ok: true,
423
- pricing: {
424
- kind: "free",
425
- priceDisplay,
426
- ...acceptsPasses !== undefined ? { acceptsPasses } : {},
427
- ...acceptsMemberships !== undefined ? { acceptsMemberships } : {}
428
- }
429
- };
430
- case "fixed_price": {
431
- const issues = [];
432
- const priceAmount = record.priceAmount;
433
- if (!isWholeNumber(priceAmount) || priceAmount < 0) {
434
- issues.push(issue$1(["priceAmount"], "Fixed-price events require a non-negative integer priceAmount"));
435
- }
436
- const rawPriceCurrency = record.priceCurrency;
437
- const priceCurrency = rawPriceCurrency === undefined || rawPriceCurrency === null ? "USD" : isCurrency(rawPriceCurrency) ? rawPriceCurrency : null;
438
- if (rawPriceCurrency !== undefined && rawPriceCurrency !== null && !isCurrency(rawPriceCurrency)) {
439
- issues.push(issue$1(["priceCurrency"], "priceCurrency must be a 3-letter uppercase currency code"));
440
- }
441
- if (issues.length > 0) return {
442
- ok: false,
443
- issues
444
- };
445
- if (!isWholeNumber(priceAmount) || priceCurrency === null) {
446
- return {
447
- ok: false,
448
- issues: [issue$1(["priceAmount"], "Invalid fixed-price event pricing")]
449
- };
450
- }
451
- return {
452
- ok: true,
453
- pricing: {
454
- kind: "fixed_price",
455
- priceAmount,
456
- priceCurrency,
457
- priceDisplay,
458
- ...acceptsPasses !== undefined ? { acceptsPasses } : {},
459
- ...acceptsMemberships !== undefined ? { acceptsMemberships } : {}
460
- }
461
- };
462
- }
463
- case "ticket_types": {
464
- const priceCurrency = record.priceCurrency;
465
- if (!isCurrency(priceCurrency)) {
466
- return {
467
- ok: false,
468
- issues: [issue$1(["priceCurrency"], "Ticket-type pricing requires a 3-letter uppercase priceCurrency")]
469
- };
470
- }
471
- const ticketTypes = record.ticketTypes === undefined || record.ticketTypes === null ? [] : validateTicketTypes(record.ticketTypes, priceCurrency);
472
- if (isPricingIssueArray(ticketTypes)) {
473
- return {
474
- ok: false,
475
- issues: ticketTypes
476
- };
477
- }
478
- return {
479
- ok: true,
480
- pricing: {
481
- kind: "ticket_types",
482
- priceCurrency,
483
- priceDisplay,
484
- ...acceptsPasses !== undefined ? { acceptsPasses } : {},
485
- ...acceptsMemberships !== undefined ? { acceptsMemberships } : {},
486
- ticketTypes
487
- }
488
- };
489
- }
490
- default: return {
491
- ok: false,
492
- issues: [issue$1(["kind"], "Pricing kind must be free, fixed_price, or ticket_types")]
493
- };
494
- }
495
- }
496
-
497
- //#endregion
498
- //#region ../api/src/sdk-event-schedule.ts
499
- const timeRegex = /^([01]\d|2[0-3]):([0-5]\d)(:([0-5]\d))?$/;
500
- 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)$/;
501
- const localDateTimeRegex = /^(\d{4})-(\d{2})-(\d{2})T([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?(\.\d{1,3})?$/;
502
- function issue(path, message) {
503
- return {
504
- path,
505
- message
506
- };
507
- }
508
- function isValidDateParts(year, month, day) {
509
- const date = new Date(Date.UTC(year, month - 1, day));
510
- return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
511
- }
512
- function isZeroFraction(fraction) {
513
- return fraction === undefined || /^\.0+$/.test(fraction);
514
- }
515
- function datePart(year, month, day) {
516
- return `${year}-${month}-${day}`;
517
- }
518
- function timePart(hours, minutes, seconds = "00") {
519
- return `${hours}:${minutes}:${seconds}`;
520
- }
521
- function parseSdkEventDateTime(value) {
522
- const explicitMatch = explicitOffsetDateTimeRegex.exec(value);
523
- if (explicitMatch) {
524
- const [, yearRaw$1, monthRaw$1, dayRaw$1, hours$1, minutes$1, seconds$1 = "00", fraction$1] = explicitMatch;
525
- if (!yearRaw$1 || !monthRaw$1 || !dayRaw$1 || !hours$1 || !minutes$1) return null;
526
- const year$1 = Number(yearRaw$1);
527
- const month$1 = Number(monthRaw$1);
528
- const day$1 = Number(dayRaw$1);
529
- if (!isValidDateParts(year$1, month$1, day$1) || !isZeroFraction(fraction$1)) return null;
530
- const epochMs$1 = Date.parse(value);
531
- if (!Number.isFinite(epochMs$1)) return null;
532
- return {
533
- epochMs: epochMs$1,
534
- date: datePart(yearRaw$1, monthRaw$1, dayRaw$1),
535
- time: timePart(hours$1, minutes$1, seconds$1),
536
- iso: new Date(epochMs$1).toISOString()
537
- };
538
- }
539
- const localMatch = localDateTimeRegex.exec(value);
540
- if (!localMatch) return null;
541
- const [, yearRaw, monthRaw, dayRaw, hours, minutes, seconds = "00", fraction] = localMatch;
542
- if (!yearRaw || !monthRaw || !dayRaw || !hours || !minutes) return null;
543
- const year = Number(yearRaw);
544
- const month = Number(monthRaw);
545
- const day = Number(dayRaw);
546
- if (!isValidDateParts(year, month, day) || !isZeroFraction(fraction)) return null;
547
- const epochMs = Date.UTC(year, month - 1, day, Number(hours), Number(minutes), Number(seconds));
548
- return {
549
- epochMs,
550
- date: datePart(yearRaw, monthRaw, dayRaw),
551
- time: timePart(hours, minutes, seconds),
552
- iso: new Date(epochMs).toISOString()
553
- };
554
- }
555
- function durationMinutesBetween(startsAt, endsAt) {
556
- if (endsAt.epochMs <= startsAt.epochMs) return null;
557
- const durationMs = endsAt.epochMs - startsAt.epochMs;
558
- if (durationMs % 6e4 !== 0) return null;
559
- return durationMs / 6e4;
560
- }
561
- function exceedsMaxDuration(durationMinutes, options) {
562
- return options.maxDurationMinutes !== undefined && durationMinutes > options.maxDurationMinutes;
563
- }
564
- function maxDurationMessage(options) {
565
- return options.maxDurationDays === undefined ? "Duration is above the allowed maximum" : `Duration must be ${options.maxDurationDays} days or less`;
566
- }
567
- function classifyCanonicalSchedule(input, options) {
568
- const issues = [];
569
- if (options.requireCanonicalFields && input.recurrenceRule === undefined) {
570
- issues.push(issue(["recurrenceRule"], "recurrenceRule is required"));
571
- }
572
- if (input.defaultStartTime !== undefined && input.defaultStartTime !== null && !timeRegex.test(input.defaultStartTime)) {
573
- issues.push(issue(["defaultStartTime"], "Invalid time format (HH:MM or HH:MM:SS)"));
574
- }
575
- if (options.requireCanonicalFields && !input.defaultStartTime) {
576
- issues.push(issue(["defaultStartTime"], "defaultStartTime is required"));
577
- }
578
- if (options.requireCanonicalFields && input.durationMinutes === undefined) {
579
- issues.push(issue(["durationMinutes"], "durationMinutes is required"));
580
- }
581
- if (input.durationMinutes !== undefined && input.durationMinutes !== null) {
582
- if (input.durationMinutes < 1) {
583
- issues.push(issue(["durationMinutes"], "durationMinutes must be at least 1"));
584
- } else if (exceedsMaxDuration(input.durationMinutes, options)) {
585
- issues.push(issue(["durationMinutes"], maxDurationMessage(options)));
586
- }
587
- }
588
- if (issues.length > 0) return {
589
- ok: false,
590
- issues
591
- };
592
- return {
593
- ok: true,
594
- kind: "canonical",
595
- canonical: {
596
- recurrenceRule: input.recurrenceRule ?? null,
597
- startDate: input.startDate,
598
- timezone: input.timezone,
599
- defaultStartTime: input.defaultStartTime ?? "09:00:00",
600
- durationMinutes: input.durationMinutes ?? 60
601
- }
602
- };
603
- }
604
- function classifySdkEventSchedule(input, options = {}) {
605
- const issues = [];
606
- const hasStartsAt = input.startsAt !== undefined && input.startsAt !== null;
607
- const hasEndsAt = input.endsAt !== undefined && input.endsAt !== null;
608
- const hasDateTimeDuration = hasStartsAt && input.durationMinutes !== undefined && input.durationMinutes !== null;
609
- const hasCanonicalStart = input.startDate !== undefined || input.defaultStartTime !== undefined;
610
- if (!hasStartsAt && !hasEndsAt) {
611
- return classifyCanonicalSchedule(input, options);
612
- }
613
- if (hasStartsAt && hasCanonicalStart) {
614
- issues.push(issue(["startsAt"], "Use either startsAt or startDate/defaultStartTime, not both"));
615
- }
616
- if (input.recurrenceRule) {
617
- issues.push(issue(["recurrenceRule"], "startsAt schedules are only supported for one-off events"));
618
- }
619
- if (!hasStartsAt) {
620
- issues.push(issue(["startsAt"], "startsAt is required when endsAt is provided"));
621
- }
622
- if (!hasEndsAt && !hasDateTimeDuration) {
623
- issues.push(issue(["endsAt"], "Provide either endsAt or durationMinutes with startsAt"));
624
- }
625
- if (hasEndsAt && hasDateTimeDuration) {
626
- issues.push(issue(["endsAt"], "Use either endsAt or durationMinutes with startsAt, not both"));
627
- }
628
- const startsAt = typeof input.startsAt === "string" ? parseSdkEventDateTime(input.startsAt) : null;
629
- const endsAt = typeof input.endsAt === "string" ? parseSdkEventDateTime(input.endsAt) : null;
630
- if (hasStartsAt && !startsAt) {
631
- issues.push(issue(["startsAt"], "Invalid datetime format. Use ISO datetime format, e.g. 2026-06-01T10:00:00Z"));
632
- }
633
- if (hasEndsAt && !endsAt) {
634
- issues.push(issue(["endsAt"], "Invalid datetime format. Use ISO datetime format, e.g. 2026-06-07T10:00:00Z"));
635
- }
636
- if (startsAt && endsAt) {
637
- const durationMinutes = durationMinutesBetween(startsAt, endsAt);
638
- if (durationMinutes === null) {
639
- issues.push(issue(["endsAt"], "endsAt must be after startsAt and align to a whole minute"));
640
- } else if (exceedsMaxDuration(durationMinutes, options)) {
641
- issues.push(issue(["endsAt"], maxDurationMessage(options)));
642
- }
643
- if (issues.length === 0 && durationMinutes !== null) {
644
- return {
645
- ok: true,
646
- kind: "start-end",
647
- canonical: {
648
- recurrenceRule: null,
649
- startDate: startsAt.date,
650
- timezone: input.timezone,
651
- defaultStartTime: startsAt.time,
652
- durationMinutes
653
- },
654
- startsAtIso: startsAt.iso,
655
- endsAtIso: endsAt.iso
656
- };
657
- }
658
- }
659
- if (startsAt && hasDateTimeDuration && input.durationMinutes !== undefined && input.durationMinutes !== null) {
660
- if (input.durationMinutes < 1) {
661
- issues.push(issue(["durationMinutes"], "durationMinutes must be at least 1"));
662
- } else if (exceedsMaxDuration(input.durationMinutes, options)) {
663
- issues.push(issue(["durationMinutes"], maxDurationMessage(options)));
664
- }
665
- if (issues.length === 0) {
666
- const endsAtMs = startsAt.epochMs + input.durationMinutes * 6e4;
667
- return {
668
- ok: true,
669
- kind: "start-duration",
670
- canonical: {
671
- recurrenceRule: null,
672
- startDate: startsAt.date,
673
- timezone: input.timezone,
674
- defaultStartTime: startsAt.time,
675
- durationMinutes: input.durationMinutes
676
- },
677
- startsAtIso: startsAt.iso,
678
- endsAtIso: new Date(endsAtMs).toISOString()
679
- };
680
- }
681
- }
682
- return {
683
- ok: false,
684
- issues
685
- };
686
- }
687
-
688
- //#endregion
689
- //#region ../api/src/sdk-event-upsert.ts
690
- const sdkEventOccurrenceWireSchema = z.object({
691
- startsAt: z.string(),
692
- endsAt: z.string(),
693
- timeZone: z.string()
694
- });
695
- const sdkEventOccurrenceInputSchema = z.object({
696
- startsAt: z.string().datetime({ offset: true }),
697
- endsAt: z.string().datetime({ offset: true }),
698
- timeZone: z.string().min(1).refine((value) => parseBookingTimeZone(value).ok, "Invalid IANA timezone")
699
- }).refine(({ startsAt, endsAt }) => Date.parse(endsAt) > Date.parse(startsAt), {
700
- path: ["endsAt"],
701
- message: "endsAt must be after startsAt"
702
- });
703
- const sdkEventUpsertOptionalFieldSchemas = {
704
- path: z.string().startsWith("/", "path must start with /").nullable().optional(),
705
- status: z.enum([
706
- "draft",
707
- "active",
708
- "archived",
709
- "cancelled",
710
- "completed"
711
- ]).optional(),
712
- visibility: z.enum([
713
- "private",
714
- "public",
715
- "unlisted"
716
- ]).optional(),
717
- preset: z.enum([
718
- "event",
719
- "class",
720
- "course",
721
- "term",
722
- "workshop",
723
- "retreat",
724
- "performance"
725
- ]).optional(),
726
- bookingScope: z.enum(["single_occurrence", "whole_series"]).optional(),
727
- occurrences: z.array(sdkEventOccurrenceWireSchema).optional(),
728
- capacity: z.number().int().positive().nullable().optional()
729
- };
730
- function timingIssue(path, message) {
731
- return {
732
- path,
733
- message
734
- };
735
- }
736
- function parseExplicitOccurrence(occurrence, index) {
737
- const startsAt = parseUtcIsoDateTimeString(occurrence.startsAt);
738
- const endsAt = parseUtcIsoDateTimeString(occurrence.endsAt);
739
- const timeZone = parseBookingTimeZone(occurrence.timeZone);
740
- const path = ["occurrences", String(index)];
741
- const issues = [];
742
- if (!startsAt.ok) issues.push(timingIssue([...path, "startsAt"], "startsAt must be a valid offset-aware ISO datetime"));
743
- if (!endsAt.ok) issues.push(timingIssue([...path, "endsAt"], "endsAt must be a valid offset-aware ISO datetime"));
744
- if (!timeZone.ok) issues.push(timingIssue([...path, "timeZone"], "Invalid IANA timezone"));
745
- if (!startsAt.ok || !endsAt.ok || !timeZone.ok) return {
746
- ok: false,
747
- issues
748
- };
749
- if (Date.parse(endsAt.value) <= Date.parse(startsAt.value)) {
750
- return {
751
- ok: false,
752
- issues: [timingIssue([...path, "endsAt"], "endsAt must be after startsAt")]
753
- };
754
- }
755
- return {
756
- ok: true,
757
- value: {
758
- startsAt: startsAt.value,
759
- endsAt: endsAt.value,
760
- timeZone: timeZone.value
761
- }
762
- };
763
- }
764
- function classifySdkEventTiming(input, options = {}) {
765
- if (input.occurrences && input.occurrences.length > 0) {
766
- if (input.bookingScope === undefined) {
767
- return {
768
- ok: false,
769
- issues: [timingIssue(["bookingScope"], "bookingScope is required when occurrences are provided")]
770
- };
771
- }
772
- const occurrences = [];
773
- const starts = new Set();
774
- let timeZone;
775
- const issues = [];
776
- input.occurrences.forEach((occurrence, index) => {
777
- const parsed = parseExplicitOccurrence(occurrence, index);
778
- if (!parsed.ok) {
779
- issues.push(...parsed.issues);
780
- return;
781
- }
782
- if (starts.has(parsed.value.startsAt)) {
783
- issues.push(timingIssue([
784
- "occurrences",
785
- String(index),
786
- "startsAt"
787
- ], "Occurrence start times must be unique within a series"));
788
- return;
789
- }
790
- if (timeZone && timeZone !== parsed.value.timeZone) {
791
- issues.push(timingIssue([
792
- "occurrences",
793
- String(index),
794
- "timeZone"
795
- ], "All event occurrences must use the same timezone"));
796
- return;
797
- }
798
- starts.add(parsed.value.startsAt);
799
- timeZone ??= parsed.value.timeZone;
800
- occurrences.push(parsed.value);
801
- });
802
- const [first, ...rest] = occurrences;
803
- if (issues.length > 0 || !first || !timeZone) return {
804
- ok: false,
805
- issues
806
- };
807
- return {
808
- ok: true,
809
- timingAuthority: {
810
- kind: "explicit_occurrences",
811
- bookingScope: input.bookingScope,
812
- occurrences: [first, ...rest],
813
- timeZone
814
- }
815
- };
816
- }
817
- if (input.schedule === undefined || typeof input.schedule !== "object" || input.schedule === null || Array.isArray(input.schedule)) {
818
- return {
819
- ok: false,
820
- issues: [timingIssue(["schedule"], "Either schedule or a non-empty occurrences list is required")]
821
- };
822
- }
823
- const schedule = classifySdkEventSchedule(input.schedule, options);
824
- return schedule.ok ? {
825
- ok: true,
826
- timingAuthority: {
827
- kind: "authored_schedule",
828
- schedule: schedule.canonical,
829
- bookingScope: input.bookingScope
830
- }
831
- } : {
832
- ok: false,
833
- issues: schedule.issues.map((issue$3) => timingIssue(["schedule", ...issue$3.path], issue$3.message))
834
- };
835
- }
836
- function parseSdkEventUpsertInput(input, options = {}) {
837
- const runRefs = new Set(input.runRefs);
838
- const runsByIdentifier = new Map(input.runs.map((run) => [run.identifier, run]));
839
- 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)) {
840
- return {
841
- ok: false,
842
- issues: [{
843
- source: "graph",
844
- path: ["runRefs"],
845
- message: "Event runRefs and runs must form one complete parent graph",
846
- issue: { kind: "incoherentParentRunGraph" }
847
- }]
848
- };
849
- }
850
- const runForRef = (ref) => {
851
- const run = runsByIdentifier.get(ref);
852
- if (!run) throw new Error("Unreachable missing Event run after graph validation");
853
- return run;
854
- };
855
- const firstRun = runForRef(input.runRefs[0]);
856
- const remainingRuns = input.runRefs.slice(1).map(runForRef);
857
- if (firstRun.bookingScope === "single_occurrence" && remainingRuns.length > 0 || remainingRuns.some((run) => run.bookingScope !== firstRun.bookingScope)) {
858
- return {
859
- ok: false,
860
- issues: [{
861
- source: "graph",
862
- path: ["runs"],
863
- message: "An Event must contain one ordinary run or one or more whole-series runs",
864
- issue: { kind: "incoherentParentRunGraph" }
865
- }]
866
- };
867
- }
868
- const firstResult = parseSdkEventRunUpsertInput(firstRun, 0, options);
869
- const remainingResults = remainingRuns.map((run, index) => parseSdkEventRunUpsertInput(run, index + 1, options));
870
- const issues = [firstResult, ...remainingResults].flatMap((result) => result.ok ? [] : result.issues);
871
- if (issues.length > 0) return {
872
- ok: false,
873
- issues
874
- };
875
- if (!firstResult.ok) return {
876
- ok: false,
877
- issues: firstResult.issues
878
- };
879
- const parsedRemaining = remainingResults.flatMap((result) => result.ok ? [result.value] : []);
880
- return {
881
- ok: true,
882
- value: {
883
- ...input,
884
- runs: [firstResult.value, ...parsedRemaining]
885
- }
886
- };
887
- }
888
- function toSdkEventRunUpsertBody(input) {
889
- const { timingAuthority, ...common } = input;
890
- switch (timingAuthority.kind) {
891
- case "authored_schedule": return {
892
- ...common,
893
- ...timingAuthority.bookingScope === undefined ? {} : { bookingScope: timingAuthority.bookingScope },
894
- schedule: timingAuthority.schedule
895
- };
896
- case "explicit_occurrences": return {
897
- ...common,
898
- bookingScope: timingAuthority.bookingScope,
899
- occurrences: timingAuthority.occurrences
900
- };
901
- default: return timingAuthority;
902
- }
903
- }
904
- function toSdkEventUpsertBody(input) {
905
- const [first, ...rest] = input.runs;
906
- return {
907
- ...input,
908
- runs: [toSdkEventRunUpsertBody(first), ...rest.map(toSdkEventRunUpsertBody)]
909
- };
910
- }
911
- function parseSdkEventRunUpsertInput(input, runIndex, options) {
912
- const timingResult = classifySdkEventTiming(input, options.schedule);
913
- const pricingResult = classifySdkEventPricing(input.pricing);
914
- const paymentTermsResult = classifySdkEventPaymentTerms(input.paymentTerms);
915
- const issues = [];
916
- if (timingResult.ok && pricingResult.ok && paymentTermsResult.ok) {
917
- const { schedule: _schedule, occurrences: _occurrences, bookingScope: _bookingScope, pricing: _pricing, paymentTerms: _paymentTerms, ...common } = input;
918
- return {
919
- ok: true,
920
- value: {
921
- ...common,
922
- timingAuthority: timingResult.timingAuthority,
923
- pricing: pricingResult.pricing,
924
- paymentTerms: paymentTermsResult.paymentTerms
925
- }
926
- };
927
- }
928
- if (!timingResult.ok) {
929
- issues.push(...timingResult.issues.map((timing) => ({
930
- runIndex,
931
- source: "timing",
932
- path: timing.path,
933
- message: timing.message,
934
- issue: timing
935
- })));
936
- }
937
- if (!pricingResult.ok) {
938
- issues.push(...pricingResult.issues.map((pricingIssue) => ({
939
- runIndex,
940
- source: "pricing",
941
- path: pricingIssue.path,
942
- message: pricingIssue.message,
943
- issue: pricingIssue
944
- })));
945
- }
946
- if (!paymentTermsResult.ok) {
947
- issues.push(...paymentTermsResult.issues.map((paymentTermsIssue) => ({
948
- runIndex,
949
- source: "paymentTerms",
950
- path: paymentTermsIssue.path,
951
- message: paymentTermsIssue.message,
952
- issue: paymentTermsIssue
953
- })));
954
- }
955
- return {
956
- ok: false,
957
- issues
958
- };
959
- }
960
-
961
- //#endregion
962
- export { classifySdkEventPricing as a, classifySdkEventSchedule as i, sdkEventOccurrenceInputSchema as n, classifySdkEventPaymentTerms as o, sdkEventUpsertOptionalFieldSchemas as r, classifySdkEventTiming as t };