riverbankcms 0.11.0 → 0.13.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.
- package/dist/acceptance/matrix.mjs +3 -3
- package/dist/cli/index.mjs +31164 -30936
- package/dist/cli/init-docs/content/cli-reference.md +8 -1
- package/dist/cli/init-docs/content/content-management.md +7 -1
- package/dist/cli/init-docs/content/workflow-add-block.md +6 -0
- package/dist/cli/init-docs/content/workflow-create-page.md +6 -0
- package/dist/cli/init-docs/content/workflow-editor-workflows.md +2 -2
- package/dist/{eventUpsertInputSchema-DFPgv4ay.mjs → eventUpsertInputSchema-XM4oaNoq.mjs} +37 -85
- package/dist/load-config-CoGYSr5z.mjs +4 -0
- package/dist/{load-config-B7AiZalH.mjs → load-config-DidU5HfW.mjs} +2 -2
- package/dist/{local-sync-7ecbT9He.mjs → local-sync-DQcLxBg3.mjs} +68 -26
- package/dist/{plan-BIwseF-X.mjs → plan-mb_YslEh.mjs} +1 -1
- package/dist/scaffold/index.mjs +2 -2
- package/dist/{sdk-event-upsert-CRQseim3.mjs → sdk-event-upsert-D48eLVOi.mjs} +881 -495
- package/dist/{src-CiZrHLCj.mjs → src-CxEAuQpf.mjs} +303 -270
- package/dist/testing.mjs +3 -3
- package/package.json +1 -1
- package/dist/load-config-ELWKxyb5.mjs +0 -4
|
@@ -48,6 +48,8 @@ const unsafeAsApiKeyId = (id) => id;
|
|
|
48
48
|
const unsafeAsSiteRouteId = (id) => id;
|
|
49
49
|
const unsafeAsPageId = (id) => id;
|
|
50
50
|
const unsafeAsPageSurfaceId = (id) => id;
|
|
51
|
+
const unsafeAsEditLeaseToken = (id) => id;
|
|
52
|
+
const unsafeAsEditorClientId = (id) => id;
|
|
51
53
|
const unsafeAsOccurrenceId = (id) => id;
|
|
52
54
|
const unsafeAsEventOfferingId = (id) => id;
|
|
53
55
|
const unsafeAsEventSeriesId = (id) => id;
|
|
@@ -120,6 +122,8 @@ const unsafeAsContentBlockId = (id) => id;
|
|
|
120
122
|
const unsafeAsContentVersionId = (id) => id;
|
|
121
123
|
const unsafeAsEntryContentHash = (value) => value;
|
|
122
124
|
const unsafeAsStripeInvoiceId = (id) => id;
|
|
125
|
+
const unsafeAsStripeCustomerId = (id) => id;
|
|
126
|
+
const unsafeAsStripeSubscriptionId = (id) => id;
|
|
123
127
|
const unsafeAsNewsletterSubscriberId = (id) => id;
|
|
124
128
|
const unsafeAsNavigationMenuId = (id) => id;
|
|
125
129
|
const unsafeAsNavigationItemId = (id) => id;
|
|
@@ -156,6 +160,8 @@ const parseApiKeyId = (value) => parseUuidBrand(value, unsafeAsApiKeyId);
|
|
|
156
160
|
const parseSiteRouteId = (value) => parseUuidBrand(value, unsafeAsSiteRouteId);
|
|
157
161
|
const parsePageId = (value) => parseUuidBrand(value, unsafeAsPageId);
|
|
158
162
|
const parsePageSurfaceId = (value) => parseUuidBrand(value, unsafeAsPageSurfaceId);
|
|
163
|
+
const parseEditLeaseToken = (value) => parseUuidBrand(value, unsafeAsEditLeaseToken);
|
|
164
|
+
const parseEditorClientId = (value) => parseUuidBrand(value, unsafeAsEditorClientId);
|
|
159
165
|
const parseOccurrenceId = (value) => parseUuidBrand(value, unsafeAsOccurrenceId);
|
|
160
166
|
const parseEventOfferingId = (value) => parseUuidBrand(value, unsafeAsEventOfferingId);
|
|
161
167
|
const parseEventAttendeeTicketLineId = (value) => parseUuidBrand(value, unsafeAsEventAttendeeTicketLineId);
|
|
@@ -223,6 +229,29 @@ function subtractDaysFromIso(isoString, dayCount) {
|
|
|
223
229
|
return addDaysToIso(isoString, -dayCount);
|
|
224
230
|
}
|
|
225
231
|
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region ../core/src/html-entities.ts
|
|
234
|
+
const NAMED_ENTITIES = {
|
|
235
|
+
amp: "&",
|
|
236
|
+
lt: "<",
|
|
237
|
+
gt: ">",
|
|
238
|
+
quot: "\"",
|
|
239
|
+
apos: "'",
|
|
240
|
+
nbsp: "\xA0"
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Decodes the HTML entities rendered pages commonly contain (the basic named
|
|
244
|
+
* set plus decimal and hex numeric references), leaving anything else as is.
|
|
245
|
+
* For reading text back out of server-rendered HTML, not for sanitising input.
|
|
246
|
+
*/
|
|
247
|
+
function decodeHtmlEntities(text) {
|
|
248
|
+
return text.replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi, (entity, body) => {
|
|
249
|
+
if (body[0] !== "#") return NAMED_ENTITIES[body.toLowerCase()] ?? entity;
|
|
250
|
+
const codePoint = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
|
|
251
|
+
return Number.isInteger(codePoint) && codePoint > 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : entity;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
226
255
|
//#endregion
|
|
227
256
|
//#region ../core/src/duration.ts
|
|
228
257
|
const DURATION_UNIT_MINUTES = {
|
|
@@ -357,7 +386,7 @@ const demoInstanceStatusValues = [
|
|
|
357
386
|
"available",
|
|
358
387
|
"claimed",
|
|
359
388
|
"expired",
|
|
360
|
-
"
|
|
389
|
+
"showcase",
|
|
361
390
|
"failed"
|
|
362
391
|
];
|
|
363
392
|
const demoClaimStatusValues = [
|
|
@@ -365,7 +394,7 @@ const demoClaimStatusValues = [
|
|
|
365
394
|
"expired",
|
|
366
395
|
"released"
|
|
367
396
|
];
|
|
368
|
-
const demoBlueprintArtifactKindValues = ["
|
|
397
|
+
const demoBlueprintArtifactKindValues = ["recipe"];
|
|
369
398
|
function narrowDemoInstanceStatus(value) {
|
|
370
399
|
if (demoInstanceStatusValues.includes(value)) {
|
|
371
400
|
return value;
|
|
@@ -385,6 +414,28 @@ function narrowDemoBlueprintArtifactKind(value) {
|
|
|
385
414
|
throw new Error(`Unexpected demo blueprint artifact kind: ${value}`);
|
|
386
415
|
}
|
|
387
416
|
|
|
417
|
+
//#endregion
|
|
418
|
+
//#region ../core/src/demo-warm-pool-target.ts
|
|
419
|
+
const DEFAULT_DEMO_WARM_POOL_TARGET = 0;
|
|
420
|
+
const MAX_DEMO_WARM_POOL_TARGET = 20;
|
|
421
|
+
function parseDemoWarmPoolTarget(value) {
|
|
422
|
+
if (value === undefined || value === null || value.trim() === "") {
|
|
423
|
+
return ok(DEFAULT_DEMO_WARM_POOL_TARGET);
|
|
424
|
+
}
|
|
425
|
+
const normalized = value.trim();
|
|
426
|
+
if (!/^(0|[1-9]\d*)$/u.test(normalized)) {
|
|
427
|
+
return err(invalidTargetError());
|
|
428
|
+
}
|
|
429
|
+
const target = Number(normalized);
|
|
430
|
+
return Number.isSafeInteger(target) && target <= MAX_DEMO_WARM_POOL_TARGET ? ok(target) : err(invalidTargetError());
|
|
431
|
+
}
|
|
432
|
+
function invalidTargetError() {
|
|
433
|
+
return {
|
|
434
|
+
kind: "invalid_demo_warm_pool_target",
|
|
435
|
+
message: "DEMO_WARM_POOL_TARGET must be a whole number from 0 to 20."
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
388
439
|
//#endregion
|
|
389
440
|
//#region ../core/src/demo-runtime.ts
|
|
390
441
|
const RIVERBANK_RUNTIME_ENV_VAR = "RIVERBANK_RUNTIME_ENV";
|
|
@@ -544,6 +595,19 @@ const CURRENCY_LOCALE_BY_CODE = {
|
|
|
544
595
|
function resolveCurrencyLocale(currencyCode, override) {
|
|
545
596
|
return override ?? CURRENCY_LOCALE_BY_CODE[currencyCode] ?? "en";
|
|
546
597
|
}
|
|
598
|
+
function fractionDigitsFor(decimals, exactFormatter) {
|
|
599
|
+
switch (decimals) {
|
|
600
|
+
case "exact": return {};
|
|
601
|
+
case "auto": return {
|
|
602
|
+
minimumFractionDigits: 0,
|
|
603
|
+
maximumFractionDigits: exactFormatter.resolvedOptions().maximumFractionDigits
|
|
604
|
+
};
|
|
605
|
+
case "whole": return {
|
|
606
|
+
minimumFractionDigits: 0,
|
|
607
|
+
maximumFractionDigits: 0
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
547
611
|
/**
|
|
548
612
|
* The single currency-amount formatter for the product: minor units in,
|
|
549
613
|
* localized string out.
|
|
@@ -554,6 +618,13 @@ function resolveCurrencyLocale(currencyCode, override) {
|
|
|
554
618
|
* own `Intl.NumberFormat`.
|
|
555
619
|
*/
|
|
556
620
|
function formatCurrencyAmount(amountCents, currency, options) {
|
|
621
|
+
return formatCurrencyMajorUnits(amountCents / 100, currency, options);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* `formatCurrencyAmount` for an amount already in MAJOR units (29.99 = £29.99),
|
|
625
|
+
* such as a registrar price. Prefer minor units everywhere else.
|
|
626
|
+
*/
|
|
627
|
+
function formatCurrencyMajorUnits(amount, currency, options) {
|
|
557
628
|
const currencyCode = currency.toUpperCase();
|
|
558
629
|
const locale = resolveCurrencyLocale(currencyCode, options?.locale);
|
|
559
630
|
try {
|
|
@@ -561,17 +632,13 @@ function formatCurrencyAmount(amountCents, currency, options) {
|
|
|
561
632
|
style: "currency",
|
|
562
633
|
currency: currencyCode
|
|
563
634
|
});
|
|
564
|
-
const fractionDigits = options?.decimals === "auto" ? {
|
|
565
|
-
minimumFractionDigits: 0,
|
|
566
|
-
maximumFractionDigits: exactFormatter.resolvedOptions().maximumFractionDigits
|
|
567
|
-
} : {};
|
|
568
635
|
return new Intl.NumberFormat(locale, {
|
|
569
636
|
style: "currency",
|
|
570
637
|
currency: currencyCode,
|
|
571
|
-
...
|
|
572
|
-
}).format(
|
|
638
|
+
...fractionDigitsFor(options?.decimals ?? "exact", exactFormatter)
|
|
639
|
+
}).format(amount);
|
|
573
640
|
} catch {
|
|
574
|
-
return `${
|
|
641
|
+
return `${amount.toFixed(2)} ${currencyCode}`;
|
|
575
642
|
}
|
|
576
643
|
}
|
|
577
644
|
|
|
@@ -2446,6 +2513,18 @@ const unsafeAsEditorSiteId = (value) => value;
|
|
|
2446
2513
|
const unsafeAsEditorPageId = (value) => value;
|
|
2447
2514
|
const unsafeAsEditorEntryId = (value) => value;
|
|
2448
2515
|
const unsafeAsEditorBlockId = (value) => value;
|
|
2516
|
+
function editorBlockCollectionOwnerKey(owner) {
|
|
2517
|
+
switch (owner.kind) {
|
|
2518
|
+
case "page": return `page:${owner.pageId}`;
|
|
2519
|
+
case "entry-slot": return `entry-slot:${owner.type}:${owner.entryId}:${owner.slotKey}`;
|
|
2520
|
+
case "surface-slot": return `surface-slot:${owner.pageSurfaceId}:${owner.slotKey}`;
|
|
2521
|
+
case "site-footer": return `site-footer:${owner.siteId}`;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
function editorBlockCollectionOwnersEqual(left, right) {
|
|
2525
|
+
if (left === right) return true;
|
|
2526
|
+
return left !== null && right !== null && editorBlockCollectionOwnerKey(left) === editorBlockCollectionOwnerKey(right);
|
|
2527
|
+
}
|
|
2449
2528
|
const unsafeAsEditorMetadataValues = (value) => value;
|
|
2450
2529
|
const unsafeAsEditorContentValues = (value) => value;
|
|
2451
2530
|
const unsafeAsEditorBlockValues = (value) => value;
|
|
@@ -2470,6 +2549,151 @@ function createEditorReorderBlocksCommand(blockIds) {
|
|
|
2470
2549
|
};
|
|
2471
2550
|
}
|
|
2472
2551
|
|
|
2552
|
+
//#endregion
|
|
2553
|
+
//#region ../core/src/editor-value-sync.ts
|
|
2554
|
+
const unsafeAsSendId = (value) => value;
|
|
2555
|
+
const UNKNOWN_REVISION = { kind: "unknown" };
|
|
2556
|
+
function knownRevision(value) {
|
|
2557
|
+
return {
|
|
2558
|
+
kind: "known",
|
|
2559
|
+
value
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
/** A pristine value at the given baseline. */
|
|
2563
|
+
function pristineValueSync(values, revision) {
|
|
2564
|
+
return {
|
|
2565
|
+
baseline: {
|
|
2566
|
+
values,
|
|
2567
|
+
revision
|
|
2568
|
+
},
|
|
2569
|
+
local: {
|
|
2570
|
+
kind: "pristine",
|
|
2571
|
+
request: { kind: "idle" }
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
/** What the person sees: their edits, or the baseline when there are none. */
|
|
2576
|
+
function displayedValues(sync) {
|
|
2577
|
+
return sync.local.kind === "edited" ? sync.local.values : sync.baseline.values;
|
|
2578
|
+
}
|
|
2579
|
+
/** Pristine counts as saved even with an abandoned request: nothing the person wants is unsaved. */
|
|
2580
|
+
function valueStatus(sync) {
|
|
2581
|
+
if (sync.local.kind === "pristine") return "saved";
|
|
2582
|
+
switch (sync.local.request.kind) {
|
|
2583
|
+
case "idle":
|
|
2584
|
+
case "abandoned": return "unsaved";
|
|
2585
|
+
case "in-flight": return "saving";
|
|
2586
|
+
case "failed": return "failed";
|
|
2587
|
+
default: return assertNever(sync.local.request);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
/** Projects the model onto the presentation sync state: unsaved and saving are both `pending`. */
|
|
2591
|
+
function syncStateOf(sync) {
|
|
2592
|
+
if (sync.local.kind === "edited" && sync.local.request.kind === "failed") {
|
|
2593
|
+
return {
|
|
2594
|
+
kind: "failed",
|
|
2595
|
+
error: sync.local.request.error
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
return valueStatus(sync) === "saved" ? { kind: "baseline" } : { kind: "pending" };
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
//#endregion
|
|
2602
|
+
//#region ../core/src/edit-lease.ts
|
|
2603
|
+
function parseEditLeaseScope(input) {
|
|
2604
|
+
switch (input.nodeType) {
|
|
2605
|
+
case "page": {
|
|
2606
|
+
const id = parsePageId(input.nodeId);
|
|
2607
|
+
return id.ok ? {
|
|
2608
|
+
nodeType: "page",
|
|
2609
|
+
nodeId: id.value
|
|
2610
|
+
} : null;
|
|
2611
|
+
}
|
|
2612
|
+
case "content_entry": {
|
|
2613
|
+
const id = parseContentEntryId(input.nodeId);
|
|
2614
|
+
return id.ok ? {
|
|
2615
|
+
nodeType: "content_entry",
|
|
2616
|
+
nodeId: id.value
|
|
2617
|
+
} : null;
|
|
2618
|
+
}
|
|
2619
|
+
case "page_surface": {
|
|
2620
|
+
const id = parsePageSurfaceId(input.nodeId);
|
|
2621
|
+
return id.ok ? {
|
|
2622
|
+
nodeType: "page_surface",
|
|
2623
|
+
nodeId: id.value
|
|
2624
|
+
} : null;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
//#endregion
|
|
2630
|
+
//#region ../core/src/block-content-identity.ts
|
|
2631
|
+
function readContentVersionId(value) {
|
|
2632
|
+
const parsed = parseContentVersionId(value);
|
|
2633
|
+
return parsed.ok ? parsed.value : null;
|
|
2634
|
+
}
|
|
2635
|
+
/** The one pairing of a published id with its content. */
|
|
2636
|
+
function readPublishedBlockPlacement(id, content$1) {
|
|
2637
|
+
if (id == null && content$1 == null) return null;
|
|
2638
|
+
const versionId = readContentVersionId(id);
|
|
2639
|
+
return versionId !== null && content$1 != null ? {
|
|
2640
|
+
kind: "published",
|
|
2641
|
+
id: versionId,
|
|
2642
|
+
content: content$1
|
|
2643
|
+
} : {
|
|
2644
|
+
kind: "incomplete",
|
|
2645
|
+
id: id ?? null,
|
|
2646
|
+
content: content$1 ?? null
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
const NO_BLOCK_VERSIONS = {
|
|
2650
|
+
draftContentId: null,
|
|
2651
|
+
publishedPlacement: null
|
|
2652
|
+
};
|
|
2653
|
+
/** The one conversion from wire identity to editor identity. */
|
|
2654
|
+
function readEditorBlockVersions(wire) {
|
|
2655
|
+
return {
|
|
2656
|
+
draftContentId: readContentVersionId(wire.draftContentId),
|
|
2657
|
+
publishedPlacement: readPublishedBlockPlacement(wire.publishedContentId, wire.publishedContent)
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
/** Editor identity from a render block, whose published placement is already paired. */
|
|
2661
|
+
function readEditorBlockVersionsFromPlacement(draftContentId, publishedPlacement) {
|
|
2662
|
+
return {
|
|
2663
|
+
draftContentId: readContentVersionId(draftContentId),
|
|
2664
|
+
publishedPlacement
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
/** The published version a block can cite, if it has a complete one. */
|
|
2668
|
+
function publishedVersionOf(versions) {
|
|
2669
|
+
const placement = versions?.publishedPlacement ?? null;
|
|
2670
|
+
return placement?.kind === "published" ? placement : null;
|
|
2671
|
+
}
|
|
2672
|
+
/** The content version a block renders: its draft if it has one, else its published version. */
|
|
2673
|
+
function selectedContentVersionId(versions) {
|
|
2674
|
+
return versions.draftContentId ?? publishedVersionOf(versions)?.id ?? null;
|
|
2675
|
+
}
|
|
2676
|
+
/** Published content the editor treats as its dirty-state baseline. */
|
|
2677
|
+
function publishedContentOf(versions) {
|
|
2678
|
+
return publishedVersionOf(versions)?.content ?? null;
|
|
2679
|
+
}
|
|
2680
|
+
function readContentRecord(value) {
|
|
2681
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
2682
|
+
}
|
|
2683
|
+
/** A block's versions after the server acknowledges one content save. */
|
|
2684
|
+
function applySavedBlockContentVersion(versions, saved) {
|
|
2685
|
+
switch (saved.stage) {
|
|
2686
|
+
case "draft": return {
|
|
2687
|
+
...versions,
|
|
2688
|
+
draftContentId: readContentVersionId(saved.contentId)
|
|
2689
|
+
};
|
|
2690
|
+
case "published": return {
|
|
2691
|
+
draftContentId: null,
|
|
2692
|
+
publishedPlacement: readPublishedBlockPlacement(saved.contentId, readContentRecord(saved.data))
|
|
2693
|
+
};
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2473
2697
|
//#endregion
|
|
2474
2698
|
//#region ../core/src/sensitive-collection.ts
|
|
2475
2699
|
const SENSITIVE_COLLECTION_MODES = ["standard", "sensitive"];
|
|
@@ -2770,16 +2994,6 @@ function projectSingleSubjectParticipantSlot(input) {
|
|
|
2770
2994
|
default: return assertNever$1(input.target);
|
|
2771
2995
|
}
|
|
2772
2996
|
}
|
|
2773
|
-
function projectEventSeriesSessionAttendanceParticipantSlot(input) {
|
|
2774
|
-
return {
|
|
2775
|
-
slotKey: unsafeAsParticipantSlotKey(`event_series_session_attendance:${input.attendeeId}`),
|
|
2776
|
-
participation: {
|
|
2777
|
-
kind: "event_series_session_attendance",
|
|
2778
|
-
attendeeId: input.attendeeId,
|
|
2779
|
-
registrationId: input.registrationId
|
|
2780
|
-
}
|
|
2781
|
-
};
|
|
2782
|
-
}
|
|
2783
2997
|
function projectEventRegistrationParticipantRoster(input) {
|
|
2784
2998
|
const bookerSlot = buildSlot({
|
|
2785
2999
|
slotKey: unsafeAsParticipantSlotKey(`event_registration_booker:${input.attendeeId}`),
|
|
@@ -2829,43 +3043,6 @@ function reconcileParticipantRoster(input) {
|
|
|
2829
3043
|
issues.push(...findDuplicateParticipantEmails(input.slots));
|
|
2830
3044
|
return issues;
|
|
2831
3045
|
}
|
|
2832
|
-
function planParticipantSubjectResolution(input) {
|
|
2833
|
-
const existingParticipantIdByEmail = new Map(input.existingParticipantsByEmail.map((participant) => [participant.email, participant.participantId]));
|
|
2834
|
-
const duplicateSlotKeysByEmail = new Map(input.projection.issues.filter((issue) => issue.kind === "duplicate_email_in_booking_context").map((issue) => [issue.email, issue.slotKeys]));
|
|
2835
|
-
return input.projection.slots.map((slot) => {
|
|
2836
|
-
if (slot.email === null) {
|
|
2837
|
-
return {
|
|
2838
|
-
kind: "staff_managed_slot",
|
|
2839
|
-
slotKey: slot.slotKey
|
|
2840
|
-
};
|
|
2841
|
-
}
|
|
2842
|
-
const duplicateSlotKeys = duplicateSlotKeysByEmail.get(slot.email);
|
|
2843
|
-
if (duplicateSlotKeys !== undefined) {
|
|
2844
|
-
return {
|
|
2845
|
-
kind: "blocked_duplicate_participant_in_booking",
|
|
2846
|
-
slotKey: slot.slotKey,
|
|
2847
|
-
email: slot.email,
|
|
2848
|
-
conflictingSlotKeys: duplicateSlotKeys
|
|
2849
|
-
};
|
|
2850
|
-
}
|
|
2851
|
-
const participantId = existingParticipantIdByEmail.get(slot.email);
|
|
2852
|
-
if (participantId !== undefined) {
|
|
2853
|
-
return {
|
|
2854
|
-
kind: "reuse_participant_by_site_email",
|
|
2855
|
-
slotKey: slot.slotKey,
|
|
2856
|
-
email: slot.email,
|
|
2857
|
-
participantId
|
|
2858
|
-
};
|
|
2859
|
-
}
|
|
2860
|
-
return {
|
|
2861
|
-
kind: "create_participant",
|
|
2862
|
-
slotKey: slot.slotKey,
|
|
2863
|
-
email: slot.email,
|
|
2864
|
-
displayName: slot.displayName,
|
|
2865
|
-
phone: slot.phone
|
|
2866
|
-
};
|
|
2867
|
-
});
|
|
2868
|
-
}
|
|
2869
3046
|
function buildSlot(input) {
|
|
2870
3047
|
const email = normalizeParticipantEmail(input.contact.email);
|
|
2871
3048
|
return {
|
|
@@ -2904,213 +3081,6 @@ function assertNever$1(value) {
|
|
|
2904
3081
|
throw new Error(`Unexpected participant variant: ${JSON.stringify(value)}`);
|
|
2905
3082
|
}
|
|
2906
3083
|
|
|
2907
|
-
//#endregion
|
|
2908
|
-
//#region ../core/src/participant-identity.ts
|
|
2909
|
-
function planPersonResolution(input) {
|
|
2910
|
-
switch (input.kind) {
|
|
2911
|
-
case "with_email":
|
|
2912
|
-
if (input.existingParticipantIdByEmail !== null) {
|
|
2913
|
-
return {
|
|
2914
|
-
kind: "reuse_existing_person_by_email",
|
|
2915
|
-
participantId: input.existingParticipantIdByEmail,
|
|
2916
|
-
email: input.email
|
|
2917
|
-
};
|
|
2918
|
-
}
|
|
2919
|
-
return {
|
|
2920
|
-
kind: "create_contactable_person",
|
|
2921
|
-
seed: {
|
|
2922
|
-
displayName: input.displayName,
|
|
2923
|
-
email: input.email,
|
|
2924
|
-
phone: input.phone
|
|
2925
|
-
}
|
|
2926
|
-
};
|
|
2927
|
-
case "without_email": return {
|
|
2928
|
-
kind: "create_provisional_person",
|
|
2929
|
-
seed: {
|
|
2930
|
-
displayName: input.displayName,
|
|
2931
|
-
phone: input.phone
|
|
2932
|
-
}
|
|
2933
|
-
};
|
|
2934
|
-
default: return assertNever(input);
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
function planCustomerPersonResolution(input) {
|
|
2938
|
-
const resolution = planPersonResolution(toCustomerPersonResolutionInput(input));
|
|
2939
|
-
switch (resolution.kind) {
|
|
2940
|
-
case "reuse_existing_person_by_email": return {
|
|
2941
|
-
kind: "link_existing_person_by_email",
|
|
2942
|
-
customerId: input.customerId,
|
|
2943
|
-
participantId: resolution.participantId,
|
|
2944
|
-
email: resolution.email
|
|
2945
|
-
};
|
|
2946
|
-
case "create_contactable_person": return {
|
|
2947
|
-
kind: "create_contactable_person_for_customer",
|
|
2948
|
-
customerId: input.customerId,
|
|
2949
|
-
seed: resolution.seed
|
|
2950
|
-
};
|
|
2951
|
-
case "create_provisional_person": return {
|
|
2952
|
-
kind: "create_provisional_person_for_customer",
|
|
2953
|
-
customerId: input.customerId,
|
|
2954
|
-
seed: resolution.seed
|
|
2955
|
-
};
|
|
2956
|
-
default: return assertNever(resolution);
|
|
2957
|
-
}
|
|
2958
|
-
}
|
|
2959
|
-
function toCustomerPersonResolutionInput(input) {
|
|
2960
|
-
switch (input.kind) {
|
|
2961
|
-
case "with_email": return {
|
|
2962
|
-
kind: "with_email",
|
|
2963
|
-
displayName: input.displayName,
|
|
2964
|
-
email: input.email,
|
|
2965
|
-
phone: input.phone,
|
|
2966
|
-
existingParticipantIdByEmail: input.existingParticipantIdByEmail
|
|
2967
|
-
};
|
|
2968
|
-
case "without_email": return {
|
|
2969
|
-
kind: "without_email",
|
|
2970
|
-
displayName: input.displayName,
|
|
2971
|
-
phone: input.phone
|
|
2972
|
-
};
|
|
2973
|
-
default: return assertNever(input);
|
|
2974
|
-
}
|
|
2975
|
-
}
|
|
2976
|
-
function planParticipationPersonResolution(input) {
|
|
2977
|
-
switch (input.resolution.kind) {
|
|
2978
|
-
case "blocked_duplicate_participant_in_booking": return {
|
|
2979
|
-
kind: "blocked_duplicate_participant_in_booking",
|
|
2980
|
-
slotKey: input.resolution.slotKey,
|
|
2981
|
-
email: input.resolution.email,
|
|
2982
|
-
conflictingSlotKeys: input.resolution.conflictingSlotKeys
|
|
2983
|
-
};
|
|
2984
|
-
case "create_participant":
|
|
2985
|
-
case "reuse_participant_by_site_email":
|
|
2986
|
-
case "staff_managed_slot": return applyParticipationResolutionPlan({
|
|
2987
|
-
slotKey: input.resolution.slotKey,
|
|
2988
|
-
participation: input.slot.participation,
|
|
2989
|
-
resolution: planPersonResolution(toSlotPersonResolutionInput({
|
|
2990
|
-
slot: input.slot,
|
|
2991
|
-
resolution: input.resolution
|
|
2992
|
-
}))
|
|
2993
|
-
});
|
|
2994
|
-
default: return assertNever(input.resolution);
|
|
2995
|
-
}
|
|
2996
|
-
}
|
|
2997
|
-
function toSlotPersonResolutionInput(input) {
|
|
2998
|
-
switch (input.resolution.kind) {
|
|
2999
|
-
case "create_participant": return {
|
|
3000
|
-
kind: "with_email",
|
|
3001
|
-
displayName: input.resolution.displayName,
|
|
3002
|
-
email: input.resolution.email,
|
|
3003
|
-
phone: input.resolution.phone,
|
|
3004
|
-
existingParticipantIdByEmail: null
|
|
3005
|
-
};
|
|
3006
|
-
case "reuse_participant_by_site_email": return {
|
|
3007
|
-
kind: "with_email",
|
|
3008
|
-
displayName: input.slot.displayName,
|
|
3009
|
-
email: input.resolution.email,
|
|
3010
|
-
phone: input.slot.phone,
|
|
3011
|
-
existingParticipantIdByEmail: input.resolution.participantId
|
|
3012
|
-
};
|
|
3013
|
-
case "staff_managed_slot": return {
|
|
3014
|
-
kind: "without_email",
|
|
3015
|
-
displayName: input.slot.displayName,
|
|
3016
|
-
phone: input.slot.phone
|
|
3017
|
-
};
|
|
3018
|
-
default: return assertNever(input.resolution);
|
|
3019
|
-
}
|
|
3020
|
-
}
|
|
3021
|
-
function applyParticipationResolutionPlan(input) {
|
|
3022
|
-
switch (input.resolution.kind) {
|
|
3023
|
-
case "reuse_existing_person_by_email": return {
|
|
3024
|
-
kind: "reuse_existing_person_for_slot",
|
|
3025
|
-
slotKey: input.slotKey,
|
|
3026
|
-
participantId: input.resolution.participantId,
|
|
3027
|
-
participation: input.participation
|
|
3028
|
-
};
|
|
3029
|
-
case "create_contactable_person": return {
|
|
3030
|
-
kind: "create_contactable_person_for_slot",
|
|
3031
|
-
slotKey: input.slotKey,
|
|
3032
|
-
seed: input.resolution.seed,
|
|
3033
|
-
participation: input.participation
|
|
3034
|
-
};
|
|
3035
|
-
case "create_provisional_person": return {
|
|
3036
|
-
kind: "create_provisional_person_for_slot",
|
|
3037
|
-
slotKey: input.slotKey,
|
|
3038
|
-
seed: input.resolution.seed,
|
|
3039
|
-
participation: input.participation
|
|
3040
|
-
};
|
|
3041
|
-
default: return assertNever(input.resolution);
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
3044
|
-
function planSingleSubjectParticipantMaterialization(input) {
|
|
3045
|
-
const resolution = planPersonResolution(toSingleSubjectPersonResolutionInput(input));
|
|
3046
|
-
switch (resolution.kind) {
|
|
3047
|
-
case "reuse_existing_person_by_email": return {
|
|
3048
|
-
kind: "reuse_existing_person_and_participation",
|
|
3049
|
-
customerId: input.customerId,
|
|
3050
|
-
participantId: resolution.participantId,
|
|
3051
|
-
participation: input.participation
|
|
3052
|
-
};
|
|
3053
|
-
case "create_contactable_person": return {
|
|
3054
|
-
kind: "create_contactable_person_and_participation",
|
|
3055
|
-
customerId: input.customerId,
|
|
3056
|
-
seed: resolution.seed,
|
|
3057
|
-
participation: input.participation
|
|
3058
|
-
};
|
|
3059
|
-
case "create_provisional_person": return {
|
|
3060
|
-
kind: "create_provisional_person_and_participation",
|
|
3061
|
-
customerId: input.customerId,
|
|
3062
|
-
seed: resolution.seed,
|
|
3063
|
-
participation: input.participation
|
|
3064
|
-
};
|
|
3065
|
-
default: return assertNever(resolution);
|
|
3066
|
-
}
|
|
3067
|
-
}
|
|
3068
|
-
function toSingleSubjectPersonResolutionInput(input) {
|
|
3069
|
-
switch (input.kind) {
|
|
3070
|
-
case "participant_subject": return {
|
|
3071
|
-
kind: "with_email",
|
|
3072
|
-
displayName: input.displayName,
|
|
3073
|
-
email: input.email,
|
|
3074
|
-
phone: input.phone,
|
|
3075
|
-
existingParticipantIdByEmail: input.existingParticipantIdByEmail
|
|
3076
|
-
};
|
|
3077
|
-
case "provisional_single_subject_participant": return {
|
|
3078
|
-
kind: "without_email",
|
|
3079
|
-
displayName: input.displayName,
|
|
3080
|
-
phone: input.phone
|
|
3081
|
-
};
|
|
3082
|
-
default: return assertNever(input);
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
function planCleanupReadiness(evidence) {
|
|
3086
|
-
const blockers = collectCleanupReadinessBlockers(evidence);
|
|
3087
|
-
if (blockers.length === 0) {
|
|
3088
|
-
return {
|
|
3089
|
-
kind: "ready",
|
|
3090
|
-
evidence,
|
|
3091
|
-
blockers: []
|
|
3092
|
-
};
|
|
3093
|
-
}
|
|
3094
|
-
return {
|
|
3095
|
-
kind: "blocked",
|
|
3096
|
-
evidence,
|
|
3097
|
-
blockers
|
|
3098
|
-
};
|
|
3099
|
-
}
|
|
3100
|
-
function collectCleanupReadinessBlockers(evidence) {
|
|
3101
|
-
const blockers = [];
|
|
3102
|
-
if (evidence.legacyCustomerEmailCallSiteCount > 0) {
|
|
3103
|
-
blockers.push("legacy_customer_email_call_sites_remaining");
|
|
3104
|
-
}
|
|
3105
|
-
if (evidence.parityFailureSurfaces.length > 0) {
|
|
3106
|
-
blockers.push("parity_failures_remaining");
|
|
3107
|
-
}
|
|
3108
|
-
if (evidence.unresolvedHealthRowCount > 0) {
|
|
3109
|
-
blockers.push("unresolved_health_rows_remaining");
|
|
3110
|
-
}
|
|
3111
|
-
return blockers;
|
|
3112
|
-
}
|
|
3113
|
-
|
|
3114
3084
|
//#endregion
|
|
3115
3085
|
//#region ../core/src/public-url-validation.ts
|
|
3116
3086
|
function buildPublicUrlValidationError(message, errorFactory) {
|
|
@@ -4836,6 +4806,10 @@ function parseMailingListSiteSetup(value) {
|
|
|
4836
4806
|
});
|
|
4837
4807
|
}
|
|
4838
4808
|
if (value.tag !== "configured") return setupError("tag");
|
|
4809
|
+
return parseConfiguredSiteSetup(value, siteId.value);
|
|
4810
|
+
}
|
|
4811
|
+
function parseConfiguredSiteSetup(value, siteId) {
|
|
4812
|
+
if (typeof value.bookingOptInEnabled !== "boolean") return setupError("shape");
|
|
4839
4813
|
const revision = parseMailingListSetupRevision(value.revision);
|
|
4840
4814
|
if (revision === null) return setupError("revision");
|
|
4841
4815
|
const form = parseMailingListGlobalForm(value.form);
|
|
@@ -4844,8 +4818,9 @@ function parseMailingListSiteSetup(value) {
|
|
|
4844
4818
|
if (!provider.ok) return provider;
|
|
4845
4819
|
return ok({
|
|
4846
4820
|
tag: "configured",
|
|
4847
|
-
siteId
|
|
4821
|
+
siteId,
|
|
4848
4822
|
revision,
|
|
4823
|
+
bookingOptInEnabled: value.bookingOptInEnabled,
|
|
4849
4824
|
form: form.value,
|
|
4850
4825
|
provider: provider.value
|
|
4851
4826
|
});
|
|
@@ -5164,19 +5139,61 @@ function parseMailingListRuntimeContext(value) {
|
|
|
5164
5139
|
if (!isRecord$1(value)) return invalidRuntimeContext();
|
|
5165
5140
|
if (value.tag === "not_configured") return ok({ tag: "not_configured" });
|
|
5166
5141
|
if (value.tag !== "configured") return invalidRuntimeContext();
|
|
5142
|
+
return parseConfiguredRuntimeContext(value);
|
|
5143
|
+
}
|
|
5144
|
+
function parseConfiguredRuntimeContext(value) {
|
|
5167
5145
|
const revision = parseMailingListSetupRevision(value.revision);
|
|
5168
5146
|
const form = parseMailingListGlobalForm(value.form);
|
|
5169
5147
|
const provider = parseRuntimeProvider(value.provider);
|
|
5170
|
-
|
|
5148
|
+
const bookingSignup = parseBookingSignupOffer(value.bookingSignup);
|
|
5149
|
+
if (revision === null || !form.ok || provider === null || bookingSignup === null) {
|
|
5171
5150
|
return invalidRuntimeContext();
|
|
5172
5151
|
}
|
|
5173
5152
|
return ok({
|
|
5174
5153
|
tag: "configured",
|
|
5175
5154
|
revision,
|
|
5155
|
+
bookingSignup,
|
|
5176
5156
|
form: form.value,
|
|
5177
5157
|
provider
|
|
5178
5158
|
});
|
|
5179
5159
|
}
|
|
5160
|
+
/** Destination facts are resolved once by the provider adapter, before projection. */
|
|
5161
|
+
function planBookingSignupOffer(setup, destination) {
|
|
5162
|
+
if (destination === "unavailable") return {
|
|
5163
|
+
tag: "unavailable",
|
|
5164
|
+
reason: "destination"
|
|
5165
|
+
};
|
|
5166
|
+
const required = resolveRequiredGlobalFields(setup.form.fields);
|
|
5167
|
+
if (setup.form.fields.preset !== "emailOnly" && setup.form.fields.preset !== "emailName" || required.some((field) => field !== "name")) {
|
|
5168
|
+
return {
|
|
5169
|
+
tag: "unavailable",
|
|
5170
|
+
reason: "fields"
|
|
5171
|
+
};
|
|
5172
|
+
}
|
|
5173
|
+
return setup.bookingOptInEnabled ? { tag: "offered" } : {
|
|
5174
|
+
tag: "unavailable",
|
|
5175
|
+
reason: "disabled"
|
|
5176
|
+
};
|
|
5177
|
+
}
|
|
5178
|
+
function resolveBookingSignupOffer(context, siteName) {
|
|
5179
|
+
const name = siteName?.trim() ?? "";
|
|
5180
|
+
return context?.tag === "configured" && context.bookingSignup.tag === "offered" ? { siteName: name.length > 0 ? name : "this site" } : null;
|
|
5181
|
+
}
|
|
5182
|
+
function parseBookingSignupOffer(value) {
|
|
5183
|
+
if (value === undefined) return {
|
|
5184
|
+
tag: "unavailable",
|
|
5185
|
+
reason: "disabled"
|
|
5186
|
+
};
|
|
5187
|
+
if (!isRecord$1(value)) return null;
|
|
5188
|
+
if (value.tag === "offered") return { tag: "offered" };
|
|
5189
|
+
if (value.tag === "unavailable" && (value.reason === "disabled" || value.reason === "fields" || value.reason === "destination")) {
|
|
5190
|
+
return {
|
|
5191
|
+
tag: "unavailable",
|
|
5192
|
+
reason: value.reason
|
|
5193
|
+
};
|
|
5194
|
+
}
|
|
5195
|
+
return null;
|
|
5196
|
+
}
|
|
5180
5197
|
function parseRuntimeProvider(value) {
|
|
5181
5198
|
if (!isRecord$1(value)) return null;
|
|
5182
5199
|
switch (value.provider) {
|
|
@@ -6057,14 +6074,18 @@ function generateUniqueSlug(text, existingSlugs) {
|
|
|
6057
6074
|
|
|
6058
6075
|
//#endregion
|
|
6059
6076
|
//#region ../core/src/uuid.ts
|
|
6060
|
-
/**
|
|
6061
|
-
* Validates whether a string matches UUID v4 format
|
|
6062
|
-
* Format: 8-4-4-4-12 hexadecimal digits
|
|
6063
|
-
* Example: 550e8400-e29b-41d4-a716-446655440000
|
|
6064
|
-
*/
|
|
6077
|
+
/** Validate a UUID in the existing 8-4-4-4-12 hexadecimal format. */
|
|
6065
6078
|
function isValidUuid(str) {
|
|
6066
6079
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
|
|
6067
6080
|
}
|
|
6081
|
+
function generateUuidV4(cryptoSource = globalThis.crypto) {
|
|
6082
|
+
if (typeof cryptoSource.randomUUID === "function") return cryptoSource.randomUUID();
|
|
6083
|
+
const bytes = cryptoSource.getRandomValues(new Uint8Array(16));
|
|
6084
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 64;
|
|
6085
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
6086
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
|
|
6087
|
+
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
|
|
6088
|
+
}
|
|
6068
6089
|
|
|
6069
6090
|
//#endregion
|
|
6070
6091
|
//#region ../core/src/stable-json.ts
|
|
@@ -7137,6 +7158,18 @@ function parseSiteNavigationLocations(rows) {
|
|
|
7137
7158
|
return ok(locations);
|
|
7138
7159
|
}
|
|
7139
7160
|
|
|
7161
|
+
//#endregion
|
|
7162
|
+
//#region ../core/src/navigation-portal-labels.ts
|
|
7163
|
+
/**
|
|
7164
|
+
* What a customer sign-in link in a site's menu says when the site hasn't set
|
|
7165
|
+
* its own text. The site renderer falls back to these, and the dashboard's
|
|
7166
|
+
* menu editor shows them, so what an editor sees is what visitors get.
|
|
7167
|
+
*/
|
|
7168
|
+
const PORTAL_NAV_DEFAULT_LABELS = {
|
|
7169
|
+
loggedOut: "Login",
|
|
7170
|
+
loggedIn: "My Account"
|
|
7171
|
+
};
|
|
7172
|
+
|
|
7140
7173
|
//#endregion
|
|
7141
7174
|
//#region ../core/src/event-preset-vocabulary.ts
|
|
7142
7175
|
const EVENT_PRESET_VOCABULARY = {
|