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