@go-labs-sg/rsvp 1.3.0 → 1.5.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.
Files changed (3) hide show
  1. package/README.md +152 -7
  2. package/dist/index.js +131 -30
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -108,7 +108,7 @@ rsvp event seating draft preview --organization-id org_123 --event-id evt_123 \
108
108
  --expected-version 1 --ack-sensitive-data
109
109
  rsvp event seating draft publish --organization-id org_123 --event-id evt_123 \
110
110
  --expected-version 1 --request-id 123e4567-e89b-42d3-a456-426614174000 \
111
- --reason 'Seat the confirmed guest' --allow-state-change --target evt_123
111
+ --reason 'Seat the confirmed guest' --allow-state-change --allow-email --target evt_123
112
112
  ```
113
113
 
114
114
  Save and Publish require the latest `expectedVersion`. A successful Publish applies
@@ -116,12 +116,37 @@ all changed guests atomically and clears only that version of your draft. Resolv
116
116
  preview issues before publishing; conflicts leave all bookings and the draft intact.
117
117
  For an unknown outcome, retry the same UUID `requestId`, version, reason and email
118
118
  choice: the durable receipt returns the previous counts without sending again or
119
- clearing a newer draft. A different intent needs a new request ID. Email defaults
120
- off. To opt in, use `--allow-email` and a full JSON payload including organizationId,
121
- eventId, expectedVersion, requestId, reason and `sendSeatChangeEmail: true`. Do not
122
- combine `--payload` with command-local version/reason/request flags;
123
- the server additionally requires notice permission and enabled recipient policy.
124
- Preview counts recipient registrations, not guests. On realtime-enabled servers,
119
+ clearing a newer draft. A different intent needs a new request ID. Preserve the
120
+ exact `seatChangeEmailRegistrationIds` set on retry (order is immaterial); changing
121
+ it, including changing between omitted and `[]`, rejects reuse of the request ID.
122
+
123
+ Preview returns `noticeRecipients`: one row per affected registration, with a
124
+ registration ID, a grant-scoped name, `eligible`, and an `ineligibleReason` when
125
+ unavailable. No email addresses are exposed by this seating response. Review both
126
+ eligible and ineligible rows before choosing who receives mail; several moving
127
+ party members still produce only one recipient registration.
128
+
129
+ Use `seatChangeEmailRegistrationIds` in a full `--payload` to choose the exact
130
+ eligible affected registrations. Leave a registration out to publish its seat
131
+ changes without email. `[]` explicitly sends no email, **including when the event
132
+ has automatic seat-change notifications**. Selecting an unaffected registration,
133
+ an ineligible recipient or duplicate IDs is rejected before publishing. The API
134
+ rechecks permissions and eligibility under the publish transaction, not just in
135
+ preview. Sending requires notice permission. An explicit empty array does not.
136
+
137
+ ```sh
138
+ rsvp event seating draft publish --allow-state-change --allow-email --target evt_123 \
139
+ --payload '{"organizationId":"org_123","eventId":"evt_123","expectedVersion":1,"requestId":"123e4567-e89b-42d3-a456-426614174000","reason":"Seat the confirmed party","sendSeatChangeEmail":true,"seatChangeEmailRegistrationIds":["reg_123"]}'
140
+ ```
141
+
142
+ Do not combine `--payload` with command-local version/reason/request flags. If the
143
+ recipient field is omitted, existing behavior remains: `sendSeatChangeEmail: true`
144
+ requests all affected registrations, otherwise the automatic event policy still
145
+ applies. Every draft publish conservatively requires CLI `--allow-email` (or the
146
+ interactive effects confirmation), even for explicit `[]`; it approves an effect
147
+ but does not enable sending. Use the matching server/client revision for explicit
148
+ recipient choices; older servers reject the new field rather than silently
149
+ ignoring it. Preview counts recipient registrations, not guests. On realtime-enabled servers,
125
150
  free new targets require validated per-registration holds at Publish, supplied as
126
151
  `seatHolds: [{registrationId, holds: [{seatMapId, clientId, holdToken}]}]` in the
127
152
  payload. Acquire these through the existing organizer hold-token flow; Stage never
@@ -237,6 +262,14 @@ Replace illustrative IDs and revisions with current readback values. Deploy API
237
262
  policy enforcement before clients; an old API does not understand this field.
238
263
  No authentication, consent flags, command names, or credential protocol changes.
239
264
 
265
+ ### Registration-flow choice labels
266
+
267
+ `SINGLE_SELECT`, `VISUAL_SINGLE_SELECT` and `MULTI_SELECT` choices accept a
268
+ trimmed `label` of 1–500 characters, so a required single-choice `MULTI_SELECT`
269
+ can show a full-sentence declaration beside its tick box. Choice `value` stays a
270
+ stable machine value of 1–160 characters. Older APIs reject labels over 160
271
+ characters; deploy the API before saving longer labels.
272
+
240
273
  ### Registration-flow date-limit messages
241
274
 
242
275
  `DATE` and `DATETIME` custom fields accept optional
@@ -537,6 +570,56 @@ it for the active registration/edit definition. Deploy the updated server before
537
570
  saving these settings because older validators discard unsupported fields.
538
571
  No database schema migration or attendee backfill is required.
539
572
 
573
+ ## Confirmation email layout, name, QR codes and attachments
574
+
575
+ In the form builder's **Flow settings → Confirmation email layout**, or through
576
+ `definition.communications.participant.email` with `event flow draft save`,
577
+ customise the participant confirmation email for every outcome of the form:
578
+
579
+ ```json
580
+ {
581
+ "communications": {
582
+ "participant": {
583
+ "email": {
584
+ "heading": "Hello {{name}}!",
585
+ "eventLine": null,
586
+ "confirmationButtonLabel": "View my Quest pass",
587
+ "supportFooter": "Questions? Email quest@example.com",
588
+ "showEventDetails": false,
589
+ "includeCheckInQrCodes": true,
590
+ "attachments": []
591
+ }
592
+ }
593
+ }
594
+ }
595
+ ```
596
+
597
+ - `{{name}}` works in template `subject`, `body`, `bodyRichText` and every
598
+ text field above. It uses the primary attendee's name, then the invitee's
599
+ name, and falls back to `there`.
600
+ - `heading`, `eventLine`, `confirmationButtonLabel` and `supportFooter`: omit to
601
+ keep the built-in text, `null` to hide that part, or a string to replace it.
602
+ A custom or hidden `supportFooter` also replaces the resubmission footer.
603
+ - `showEventDetails` (default `true`) controls the When/Where lines.
604
+ - `includeCheckInQrCodes` (default `false`) embeds each attendee's check-in QR
605
+ image once the event's check-in QR codes are released; before release the
606
+ email keeps the release message.
607
+ - `attachments`: up to 5 PDF, PNG or JPEG files, 10 MB each and 15 MB total.
608
+ Create an upload with `event flow confirmation attachment upload create`
609
+ (`organizationId`, `eventId`, `filename`, `contentType`, `byteSize`), `PUT`
610
+ the file unchanged to the returned `uploadUrl` with its `Content-Type`, then
611
+ add the returned `attachment` object to the list. Preview/publish reject keys
612
+ not uploaded for the same event, and publish verifies each uploaded object's
613
+ type and size.
614
+
615
+ Merge these fields into the complete current definition and publish as for
616
+ other form settings. Omitting `email` keeps existing emails unchanged. Deploy
617
+ the updated server before saving these settings. No database migration is
618
+ required.
619
+
620
+ `event flow preview` sends the definition as a POST body so large formatted
621
+ emails no longer exceed URL limits; it requires a server with this change.
622
+
540
623
  ## Administrator registration for an invitee
541
624
 
542
625
  Organizers can register someone on their behalf at any time, including after
@@ -836,6 +919,49 @@ rsvp people registration update \
836
919
  --payload '{"organizationId":"org_123","eventId":"evt_123","registrationId":"reg_123","expectedVersion":4,"reason":"Moved to finance table","seats":[{"registrationAttendeeId":"att_1","seatId":"seat_9"},{"registrationAttendeeId":"att_2","seatId":"seat_2"}],"seatChangeNotice":{"idempotencyKey":"4d68a320-013d-43c7-bfcc-d1126827d8c5"}}'
837
920
  ```
838
921
 
922
+ ### Archive or cancel registrations
923
+
924
+ `people registration archive` and `people registration cancel` forward the same
925
+ permissioned, audited `event.bulkManageRegistrations` operation as the web app.
926
+ Both require server-side `rsvp.manage` permission and `--allow-state-change`;
927
+ `--target` must match the payload's **event ID**, not a registration ID.
928
+
929
+ Supply `organizationId`, `eventId`, an audit `reason` (3–500 characters after
930
+ trimming), and 1–100 unique `registrations`, each with `id` and its current
931
+ positive-integer `expectedVersion`. Get revisions from `people registration list`
932
+ or `people registration edit get`. The command sets `action` to `ARCHIVE` or
933
+ `CANCEL` when omitted; explicitly supplying any other action is rejected. Missing
934
+ or invalid fields, duplicate IDs and oversized batches fail before dispatch.
935
+ Stale revisions are checked by the server; refresh and review before retrying.
936
+
937
+ ```sh
938
+ rsvp people registration archive \
939
+ --allow-state-change --target evt_123 \
940
+ --payload '{"organizationId":"org_123","eventId":"evt_123","reason":"Remove refunded test registrations","registrations":[{"id":"reg_123","expectedVersion":6},{"id":"reg_456","expectedVersion":2}]}'
941
+
942
+ rsvp people registration cancel \
943
+ --allow-state-change --target evt_123 \
944
+ --payload '{"organizationId":"org_123","eventId":"evt_123","reason":"Participant withdrew","registrations":[{"id":"reg_789","expectedVersion":3}]}'
945
+ ```
946
+
947
+ - **Archive** sets `archivedAt` and removes the registrations from default lists
948
+ and exports. It also cancels active registrations through the normal release
949
+ path; this is not merely hiding an attending registration.
950
+ - **Cancel** cancels active registrations without changing `archivedAt`. Cancelled
951
+ registrations remain visible unless already archived.
952
+ - Both release consumed event capacity, active seat bookings and holds, cancel
953
+ session selections and waiting/offered waitlist entries, and run the existing
954
+ event/session waitlist maintenance. Released places may generate waitlist
955
+ offers and **emails to other participants**; neither command sends a
956
+ cancellation email to the affected registrants. No `--allow-email` flag is
957
+ required by these commands' existing `state-change` contract.
958
+ - Neither command automatically refunds a payment or deletes records. Payment,
959
+ refund and audit history are retained. Pending checkouts linked to a provider,
960
+ processing payments and payments requiring review must be reconciled first;
961
+ unlinked pending payment attempts are cancelled by the existing server logic.
962
+ - Use **Unarchive** below for archive visibility/fresh-response recovery, or
963
+ **Restore** for attending recovery. Unarchive does not undo cancellation.
964
+
839
965
  ### Unarchive for a fresh response versus attending Restore
840
966
 
841
967
  `people registration unarchive` forwards the permissioned, audited
@@ -936,6 +1062,25 @@ previous effects.
936
1062
 
937
1063
  Optional `registrationEmail` and `registrationMasterList` presentation settings
938
1064
  are also written through `REGISTRATION_RULES`; omitted settings are preserved.
1065
+
1066
+ ### Registration email sender
1067
+
1068
+ Confirmation and seat-change emails send from the server default
1069
+ (`RSVP_EMAIL_FROM`) unless the event sets a sender in `registrationEmail`:
1070
+
1071
+ - `senderName` changes only the display name.
1072
+ - `senderLocalPart` and `senderDomain` (set both or neither) change the address,
1073
+ for example `"senderLocalPart": "nelbd", "senderDomain": "getout.sg"` sends
1074
+ from `"NELBD Quest" <nelbd@getout.sg>`. `senderDomain` must be a Resend domain
1075
+ that is verified with sending enabled when the update is saved; list them with
1076
+ `rsvp event email sender-domains`. Invalid local parts, a missing half of the
1077
+ pair or an unverified domain are rejected.
1078
+
1079
+ `registrationEmail` replaces the whole presentation object, so read the current
1080
+ event first and resend every key you want to keep. Reply-to and support email
1081
+ are unchanged. If a saved domain is later unverified in Resend, those emails
1082
+ fail and are recorded as failed deliveries; they never fall back silently to a
1083
+ different sender. Fix the domain or remove the address fields to resume.
939
1084
  See `examples/amgen-rsvp-copy/registration-presentation-patch.json` and its README
940
1085
  for a reviewed activation fragment, output-key semantics, receipt/header behavior
941
1086
  and the separate deployment/configuration/live-send approval gates. No event
package/dist/index.js CHANGED
@@ -7195,7 +7195,8 @@ var logoutCliSession = async ({
7195
7195
  // src/api-client.ts
7196
7196
  import {
7197
7197
  createTRPCProxyClient,
7198
- httpBatchLink
7198
+ httpBatchLink,
7199
+ splitLink
7199
7200
  } from "@trpc/client";
7200
7201
  import SuperJSON from "superjson";
7201
7202
 
@@ -7213,18 +7214,28 @@ var normalizeApiBaseUrl = (value) => {
7213
7214
  var getApiBaseUrl = (override) => normalizeApiBaseUrl(override ?? process.env.RSVP_API_URL ?? DEFAULT_RSVP_API_BASE_URL);
7214
7215
 
7215
7216
  // src/api-client.ts
7216
- var createRsvpClient = (apiBaseUrl = getApiBaseUrl(), accessToken) => createTRPCProxyClient({
7217
- links: [
7218
- httpBatchLink({
7219
- headers: () => accessToken ? {
7220
- authorization: `Bearer ${accessToken}`,
7221
- "x-trpc-source": "rsvp-cli"
7222
- } : { "x-trpc-source": "rsvp-cli" },
7223
- transformer: SuperJSON,
7224
- url: `${apiBaseUrl}/api/trpc`
7225
- })
7226
- ]
7227
- });
7217
+ var POST_QUERY_PATHS = new Set([
7218
+ "eventFlow.preview"
7219
+ ]);
7220
+ var createRsvpClient = (apiBaseUrl = getApiBaseUrl(), accessToken) => {
7221
+ const linkOptions = {
7222
+ headers: () => accessToken ? {
7223
+ authorization: `Bearer ${accessToken}`,
7224
+ "x-trpc-source": "rsvp-cli"
7225
+ } : { "x-trpc-source": "rsvp-cli" },
7226
+ transformer: SuperJSON,
7227
+ url: `${apiBaseUrl}/api/trpc`
7228
+ };
7229
+ return createTRPCProxyClient({
7230
+ links: [
7231
+ splitLink({
7232
+ condition: (op) => POST_QUERY_PATHS.has(op.path),
7233
+ true: httpBatchLink({ ...linkOptions, methodOverride: "POST" }),
7234
+ false: httpBatchLink(linkOptions)
7235
+ })
7236
+ ]
7237
+ });
7238
+ };
7228
7239
 
7229
7240
  // ../validators/src/assign-guests.ts
7230
7241
  var assignGuestFiltersInputSchema = object({
@@ -7715,11 +7726,13 @@ var flowBlockBaseShape = {
7715
7726
  title: string2().trim().min(1).max(240).optional(),
7716
7727
  visibleWhen: eventFlowConditionGroupSchema.optional()
7717
7728
  };
7729
+ var maxEventFlowChoiceLabelLength = 500;
7730
+ var maxEventFlowChoiceValueLength = 160;
7718
7731
  var customFieldChoiceSchema = object({
7719
7732
  assetId: eventFlowEntityIdSchema.optional(),
7720
7733
  description: string2().trim().max(500).optional(),
7721
- label: string2().trim().min(1).max(160),
7722
- value: string2().trim().min(1).max(160)
7734
+ label: string2().trim().min(1).max(maxEventFlowChoiceLabelLength),
7735
+ value: string2().trim().min(1).max(maxEventFlowChoiceValueLength)
7723
7736
  });
7724
7737
  var maxEventFlowDateBoundMessageLength = 500;
7725
7738
  var eventFlowDateBoundMessageSchema = string2().trim().min(1).max(maxEventFlowDateBoundMessageLength);
@@ -8030,7 +8043,53 @@ var confirmationBodyRichTextSchema = unknown().superRefine((value, ctx) => {
8030
8043
  message: "Confirmation body must contain 1\u201310,000 characters"
8031
8044
  });
8032
8045
  });
8046
+ var eventFlowConfirmationAttachmentContentTypes = [
8047
+ "application/pdf",
8048
+ "image/jpeg",
8049
+ "image/png"
8050
+ ];
8051
+ var maxEventFlowConfirmationAttachmentCount = 5;
8052
+ var maxEventFlowConfirmationAttachmentSize = 10 * 1024 * 1024;
8053
+ var maxEventFlowConfirmationAttachmentTotalSize = 15 * 1024 * 1024;
8054
+ var eventFlowConfirmationAttachmentFilenameSchema = string2().trim().min(1).max(120).regex(/^[^\\/:*?"<>|\r\n\t]+$/, "Attachment file names cannot contain path or control characters");
8055
+ var eventFlowConfirmationAttachmentSchema = strictObject({
8056
+ byteSize: number2().int().positive().max(maxEventFlowConfirmationAttachmentSize),
8057
+ contentType: _enum(eventFlowConfirmationAttachmentContentTypes),
8058
+ filename: eventFlowConfirmationAttachmentFilenameSchema,
8059
+ key: string2().trim().min(1).max(300)
8060
+ });
8061
+ var editableEmailCopySchema = (maxLength) => string2().trim().min(1).max(maxLength).nullable().optional();
8062
+ var eventFlowParticipantConfirmationEmailSchema = strictObject({
8063
+ attachments: array(eventFlowConfirmationAttachmentSchema).max(maxEventFlowConfirmationAttachmentCount).default([]),
8064
+ confirmationButtonLabel: editableEmailCopySchema(60),
8065
+ eventLine: editableEmailCopySchema(500),
8066
+ heading: editableEmailCopySchema(120),
8067
+ includeCheckInQrCodes: boolean2().default(false),
8068
+ showEventDetails: boolean2().default(true),
8069
+ supportFooter: editableEmailCopySchema(1000)
8070
+ }).superRefine((email, ctx) => {
8071
+ const totalSize = email.attachments.reduce((total, attachment) => total + attachment.byteSize, 0);
8072
+ if (totalSize > maxEventFlowConfirmationAttachmentTotalSize) {
8073
+ ctx.addIssue({
8074
+ code: "custom",
8075
+ message: "Confirmation attachments must total 15 MB or less",
8076
+ path: ["attachments"]
8077
+ });
8078
+ }
8079
+ const keys = new Set;
8080
+ for (const [index, attachment] of email.attachments.entries()) {
8081
+ if (keys.has(attachment.key)) {
8082
+ ctx.addIssue({
8083
+ code: "custom",
8084
+ message: "Each confirmation attachment can be added only once",
8085
+ path: ["attachments", index, "key"]
8086
+ });
8087
+ }
8088
+ keys.add(attachment.key);
8089
+ }
8090
+ });
8033
8091
  var participantConfirmationSchema = object({
8092
+ email: eventFlowParticipantConfirmationEmailSchema.optional(),
8034
8093
  enabled: boolean2(),
8035
8094
  includeResponseDetails: boolean2().default(false),
8036
8095
  replyTo: email2().optional(),
@@ -10052,10 +10111,17 @@ var PUBLIC_EVENT_VALIDATED_FONT_FAMILIES = [
10052
10111
  "Zilla Slab Highlight"
10053
10112
  ];
10054
10113
 
10114
+ // ../validators/src/email-sender.ts
10115
+ var emailSenderLocalPartSchema = string2().trim().max(64, "Sender address must be 64 characters or fewer").regex(/^(?!.*\.\.)[a-z\d](?:[a-z\d._+-]{0,62}[a-z\d])?$/i, "Enter a valid sender address before @");
10116
+ var emailSenderDomainSchema = string2().trim().toLowerCase().regex(/^(?:[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?\.)+[a-z]{2,63}$/, "Select a valid sending domain");
10117
+ var EMAIL_SENDER_PAIR_MESSAGE = "Choose a sender address and domain together";
10118
+
10055
10119
  // ../validators/src/registration-email-presentation.ts
10056
10120
  var registrationEmailPresentationSchema = strictObject({
10057
10121
  layout: _enum(["DEFAULT", "GROUPED"]).default("DEFAULT"),
10058
10122
  senderName: string2().trim().min(1).max(120).regex(/^[^<>\r\n]+$/).optional(),
10123
+ senderLocalPart: emailSenderLocalPartSchema.optional(),
10124
+ senderDomain: emailSenderDomainSchema.optional(),
10059
10125
  introText: string2().trim().min(1).max(1e4).optional(),
10060
10126
  menuDetailOutputKeys: array(string2().trim().min(1).max(120)).max(100).default([]),
10061
10127
  submittedConsentOutputKeys: array(string2().trim().min(1).max(120)).max(100).default([]),
@@ -10063,6 +10129,13 @@ var registrationEmailPresentationSchema = strictObject({
10063
10129
  useResubmissionFooter: boolean2().default(false),
10064
10130
  supportEmail: email2().optional(),
10065
10131
  showLuckyDrawId: boolean2().optional()
10132
+ }).superRefine((value, ctx) => {
10133
+ if (Boolean(value.senderLocalPart) !== Boolean(value.senderDomain))
10134
+ ctx.addIssue({
10135
+ code: "custom",
10136
+ message: EMAIL_SENDER_PAIR_MESSAGE,
10137
+ path: [value.senderDomain ? "senderLocalPart" : "senderDomain"]
10138
+ });
10066
10139
  });
10067
10140
 
10068
10141
  // ../validators/src/registration-master-list-presentation.ts
@@ -10390,7 +10463,10 @@ var eventTablePaginationSchema = object({
10390
10463
  var listEventInviteesInputSchema = organizationEventByIdInputSchema.extend({
10391
10464
  ...eventTablePaginationSchema.shape,
10392
10465
  audienceTierId: string2().min(1).nullable().optional(),
10393
- status: _enum(InviteeStatus).optional()
10466
+ status: _enum(InviteeStatus).optional(),
10467
+ statuses: array(_enum(InviteeStatus)).min(1).max(7).optional(),
10468
+ cursor: string2().min(1).max(64).optional(),
10469
+ direction: _enum(["NEXT", "PREVIOUS"]).default("NEXT")
10394
10470
  });
10395
10471
  var listEventRegistrationsInputSchema = organizationEventByIdInputSchema.extend({
10396
10472
  archive: _enum(["ACTIVE", "ARCHIVED", "ALL"]).default("ACTIVE"),
@@ -10674,8 +10750,8 @@ var emailCampaignDraftFieldsSchema = object({
10674
10750
  audienceMode: _enum(["SEGMENT", "SELECTED"]).optional(),
10675
10751
  selectedInviteeIds: array(string2().min(1)).max(1e4).optional(),
10676
10752
  fromName: string2().trim().max(120, "From name must be 120 characters or fewer").regex(/^[^\r\n<>]*$/, "From name contains unsupported characters").optional(),
10677
- fromSubDomain: string2().trim().max(64, "Sender address must be 64 characters or fewer").regex(/^(?!.*\.\.)[a-z\d](?:[a-z\d._+-]{0,62}[a-z\d])?$/i, "Enter a valid sender address before @").optional(),
10678
- fromDomain: string2().trim().toLowerCase().regex(/^(?:[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?\.)+[a-z]{2,63}$/, "Select a valid sending domain").optional(),
10753
+ fromSubDomain: emailSenderLocalPartSchema.optional(),
10754
+ fromDomain: emailSenderDomainSchema.optional(),
10679
10755
  html: string2().trim().min(1, "Email body is required").max(1e5, "Email body is too long"),
10680
10756
  name: string2().trim().min(1, "Campaign name is required").max(160, "Campaign name must be 160 characters or fewer"),
10681
10757
  previewText: string2().trim().max(240, "Preview text must be 240 characters or fewer").optional(),
@@ -10693,7 +10769,7 @@ var validateSelectedCampaignAudience = (input, ctx) => {
10693
10769
  ctx.addIssue({
10694
10770
  code: "custom",
10695
10771
  path: [input.fromDomain ? "fromSubDomain" : "fromDomain"],
10696
- message: "Choose a sender address and domain together"
10772
+ message: EMAIL_SENDER_PAIR_MESSAGE
10697
10773
  });
10698
10774
  };
10699
10775
  var createEmailCampaignInputSchema = organizationEventByIdInputSchema.extend(emailCampaignDraftFieldsSchema.shape).superRefine(validateSelectedCampaignAudience);
@@ -10865,6 +10941,11 @@ var createEventFlowImageAssetUploadInputSchema = organizationEventByIdInputSchem
10865
10941
  byteSize: number2().int().positive().max(maxEventFlowImageAssetSize),
10866
10942
  mimeType: eventFlowImageAssetMimeTypeSchema
10867
10943
  });
10944
+ var createEventFlowConfirmationAttachmentUploadInputSchema = organizationEventByIdInputSchema.extend({
10945
+ byteSize: number2().int().positive().max(maxEventFlowConfirmationAttachmentSize),
10946
+ contentType: _enum(eventFlowConfirmationAttachmentContentTypes),
10947
+ filename: eventFlowConfirmationAttachmentFilenameSchema
10948
+ });
10868
10949
  var finalizeEventFlowImageAssetInputSchema = organizationEventByIdInputSchema.extend({
10869
10950
  assetId: eventFlowImageAssetIdSchema
10870
10951
  });
@@ -10968,6 +11049,10 @@ var createEventFlowConsentVersionInputSchema = organizationEventByIdInputSchema.
10968
11049
  var setEventFlowGroupPasscodeInputSchema = organizationEventFlowInputSchema.extend({
10969
11050
  passcode: string2().trim().min(8).max(160)
10970
11051
  });
11052
+ var getEventFlowSeatCategoryColorsInputSchema = eventBySlugInputSchema.extend({
11053
+ flowVersionId: flowVersionIdSchema,
11054
+ seatMapId: flowVersionIdSchema
11055
+ }).strict();
10971
11056
  var issueEventFlowSeatHoldTokenInputSchema = eventBySlugInputSchema.extend({
10972
11057
  clientId: uuid2(),
10973
11058
  flowVersionId: flowVersionIdSchema,
@@ -11961,6 +12046,9 @@ var publishSeatingDraftInputSchema = seatingDraftScopeSchema.extend({
11961
12046
  requestId: uuid2(),
11962
12047
  reason: string2().trim().min(1).max(500),
11963
12048
  sendSeatChangeEmail: boolean2().default(false),
12049
+ seatChangeEmailRegistrationIds: array(string2().min(1)).max(100).refine((ids) => new Set(ids).size === ids.length, {
12050
+ message: "Each recipient registration may appear only once."
12051
+ }).optional(),
11964
12052
  seatHolds: array(object({
11965
12053
  registrationId: string2().min(1),
11966
12054
  holds: array(object({
@@ -12048,7 +12136,7 @@ var commands = {
12048
12136
  effects: [],
12049
12137
  operation: (api, input) => api.eventFlow.preview.query(input),
12050
12138
  payloadOnly: true,
12051
- summary: "Validate and preview an event flow, including date-limit messages (--payload JSON)."
12139
+ summary: "Validate and preview an event flow, including date-limit messages; the definition is sent as a POST body (--payload JSON)."
12052
12140
  },
12053
12141
  "event flow draft get": {
12054
12142
  effects: [],
@@ -12321,18 +12409,21 @@ var commands = {
12321
12409
  summary: "Update a user's global role (--payload JSON)."
12322
12410
  }
12323
12411
  };
12324
- var unarchiveRegistrationsInputSchema = bulkManageRegistrationsInputSchema.safeExtend({
12325
- action: bulkManageRegistrationsInputSchema.shape.action.extract([
12326
- "RESTORE"
12327
- ])
12328
- });
12329
- var parseUnarchiveRegistrationsInput = (input) => {
12412
+ var parseRegistrationManagementInput = (input, expectedAction) => {
12413
+ const inputSchema = bulkManageRegistrationsInputSchema.safeExtend({
12414
+ action: bulkManageRegistrationsInputSchema.shape.action.extract([
12415
+ expectedAction
12416
+ ])
12417
+ });
12330
12418
  const action = input ? Reflect.get(input, "action") : undefined;
12331
- const parsed = unarchiveRegistrationsInputSchema.safeParse(input ? { ...input, action: action === undefined ? "RESTORE" : action } : input);
12419
+ const parsed = inputSchema.safeParse(input ? { ...input, action: action === undefined ? expectedAction : action } : input);
12332
12420
  if (!parsed.success)
12333
12421
  throw new CliUsageError(parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
12334
12422
  return parsed.data;
12335
12423
  };
12424
+ var parseArchiveRegistrationsInput = (input) => parseRegistrationManagementInput(input, "ARCHIVE");
12425
+ var parseCancelRegistrationsInput = (input) => parseRegistrationManagementInput(input, "CANCEL");
12426
+ var parseUnarchiveRegistrationsInput = (input) => parseRegistrationManagementInput(input, "RESTORE");
12336
12427
  var payloadCommand = (operation, summary, effects = [], targetKey) => ({
12337
12428
  effects,
12338
12429
  operation,
@@ -12403,6 +12494,7 @@ Object.assign(commands, {
12403
12494
  "audit list": payloadCommand((api, input) => api.audit.listEventActivity.query(input), "List event audit activity (--payload JSON).", ["sensitive-data"]),
12404
12495
  "event check-in qr": payloadCommand((api, input) => api.event.checkInByQr.mutate(input), "Check in using a QR token (--payload JSON).", ["state-change"]),
12405
12496
  "event flow image upload create": payloadCommand((api, input) => api.eventFlow.createImageAssetUpload.mutate(input), "Create an event-local registration-flow image upload URL (--payload JSON).", ["external-write", "state-change"], "eventId"),
12497
+ "event flow confirmation attachment upload create": payloadCommand((api, input) => api.eventFlow.createConfirmationAttachmentUpload.mutate(input), "Create a confirmation-email attachment upload URL for a PDF, PNG or JPEG up to 10 MB (filename, contentType, byteSize); PUT the file there, then add the returned attachment to communications.participant.email.attachments (--payload JSON).", ["external-write", "state-change"], "eventId"),
12406
12498
  "event flow image finalize": payloadCommand((api, input) => api.eventFlow.finalizeImageAsset.mutate(input), "Finalize an event-local registration-flow image after the external upload (--payload JSON).", ["state-change"], "assetId"),
12407
12499
  "event logo upload create": payloadCommand((api, input) => api.event.createLogoUpload.mutate(input), "Create an event-logo upload URL (--payload JSON).", ["external-write", "state-change"], "eventId"),
12408
12500
  "event logo update": payloadCommand((api, input) => api.event.updateLogo.mutate(input), "Set or clear the event logo (--payload JSON).", ["state-change"], "eventId"),
@@ -12418,6 +12510,7 @@ Object.assign(commands, {
12418
12510
  "event seating table delete": payloadCommand((api, input) => api.event.deleteSeatTable.mutate(input), "Delete a seating table (--payload JSON).", ["delete", "state-change"]),
12419
12511
  "event seating layout update": payloadCommand((api, input) => api.event.updateSeatTableLayout.mutate(input), "Update the seating layout and floor plan (--payload JSON).", ["state-change"], "eventId"),
12420
12512
  "event seating table update": payloadCommand((api, input) => api.event.updateSeatTable.mutate(input), "Update a seating table (--payload JSON).", ["delete", "state-change"], "tableId"),
12513
+ "event email sender-domains": eventScopeQueryCommand((api, input) => api.event.getEmailSenderDomains.query(input), "List verified Resend sending domains for campaign and registration email sender addresses."),
12421
12514
  "event audience-tier list": eventScopeQueryCommand((api, input) => api.event.listAudienceTiers.query(input), "List event audience tiers and assignment counts."),
12422
12515
  "event audience-tier create": payloadCommand((api, input) => api.event.createAudienceTier.mutate(input), "Create an event audience tier (--payload JSON).", ["state-change"], "eventId"),
12423
12516
  "event audience-tier update": payloadCommand((api, input) => api.event.updateAudienceTier.mutate(input), "Update an event audience tier (--payload JSON).", ["state-change"], "tierId"),
@@ -12450,6 +12543,14 @@ Object.assign(commands, {
12450
12543
  { effect: "email", whenPayloadField: "seatChangeNotice" }
12451
12544
  ]
12452
12545
  },
12546
+ "people registration archive": {
12547
+ ...payloadCommand((api, input) => api.event.bulkManageRegistrations.mutate(parseArchiveRegistrationsInput(input)), "Archive up to 100 unique registrations with expectedVersion and reason (3\u2013500 characters); action defaults to ARCHIVE and other actions are rejected. Requires rsvp.manage; target the eventId. Cancels active registrations, releases capacity, seats and holds, cancels session selections and waitlist entries/offers, and hides archived rows from default lists/exports. Unresolved checkouts must be reconciled first; no automatic refund. Not a delete: payment, refund and audit history remain. No cancellation email; released places may trigger waitlist-offer emails (--payload JSON).", ["state-change"], "eventId"),
12548
+ validateInput: parseArchiveRegistrationsInput
12549
+ },
12550
+ "people registration cancel": {
12551
+ ...payloadCommand((api, input) => api.event.bulkManageRegistrations.mutate(parseCancelRegistrationsInput(input)), "Cancel up to 100 unique registrations with expectedVersion and reason (3\u2013500 characters); action defaults to CANCEL and other actions are rejected. Requires rsvp.manage; target the eventId. Cancels active registrations, releases capacity, seats and holds, and cancels session selections and waitlist entries/offers without changing archive visibility. Unresolved checkouts must be reconciled first; no automatic refund. Not a delete: payment, refund and audit history remain. No cancellation email; released places may trigger waitlist-offer emails (--payload JSON).", ["state-change"], "eventId"),
12552
+ validateInput: parseCancelRegistrationsInput
12553
+ },
12453
12554
  "people registration unarchive": {
12454
12555
  ...payloadCommand((api, input) => api.event.bulkManageRegistrations.mutate(parseUnarchiveRegistrationsInput(input)), "Unarchive up to 100 unique registrations with expectedVersion and reason; action defaults to RESTORE and other actions are rejected. Target the eventId. Eligible cancelled nonattending invitees can submit afresh; historical registration, payment and QR are not reinstated. Fresh submissions use normal checks and new payment/credentials; attending recovery uses people registration restore (--payload JSON).", ["state-change"], "eventId"),
12455
12556
  validateInput: parseUnarchiveRegistrationsInput
@@ -12463,7 +12564,7 @@ Object.assign(commands, {
12463
12564
  "event mine list": payloadCommand((api, input) => api.event.listMyEvents.query(input), "List the caller's events (--payload JSON)."),
12464
12565
  "event reminder list": payloadCommand((api, input) => api.event.listReminderSegments.query(input), "List reminder segments (--payload JSON).", ["sensitive-data"]),
12465
12566
  "event rsvp submit": payloadCommand((api, input) => api.event.submitRsvp.mutate(input), "Submit the caller's registration (--payload JSON).", ["sensitive-data", "state-change"]),
12466
- "event setup step update": payloadCommand((api, input) => api.event.updateSetupStep.mutate(input), "Update one revision-checked event setup step (--payload JSON). REGISTRATION_RULES accepts registrationEmail presentation, registrationMasterList output-key ordering/omissions and seatChangeNotification automatic policy; omitted settings are preserved.", ["state-change"]),
12567
+ "event setup step update": payloadCommand((api, input) => api.event.updateSetupStep.mutate(input), "Update one revision-checked event setup step (--payload JSON). REGISTRATION_RULES accepts registrationEmail presentation (including senderName and a senderLocalPart+senderDomain address on a verified Resend sending domain), registrationMasterList output-key ordering/omissions and seatChangeNotification automatic policy; omitted settings are preserved.", ["state-change"]),
12467
12568
  "event page update": payloadCommand((api, input) => api.event.updateSetupStep.mutate({
12468
12569
  ...input,
12469
12570
  section: "DETAILS_PAGE"
@@ -12541,7 +12642,7 @@ Object.assign(commands, {
12541
12642
  "--expected-version": { key: "expectedVersion", type: "number" }
12542
12643
  },
12543
12644
  operation: (api, input) => api.event.getSeatingDraftPreview.query(getSeatingDraftPreviewInputSchema.parse(input)),
12544
- summary: "Check a saved draft against fresh live seats, grants and notification policy; --expected-version is required."
12645
+ summary: "Check fresh seats, grants and notification policy. Returns noticeRecipients: one eligible/ineligible row per affected registration with grant-scoped names, never email addresses; --expected-version is required."
12545
12646
  },
12546
12647
  "event seating draft publish": {
12547
12648
  scope: "event",
@@ -12557,7 +12658,7 @@ Object.assign(commands, {
12557
12658
  },
12558
12659
  validateInput: (input) => validateSeatingDraftInput(publishSeatingDraftInputSchema, input),
12559
12660
  operation: (api, input) => api.event.publishSeatingDraft.mutate(publishSeatingDraftInputSchema.parse(input)),
12560
- summary: "Atomically publish all draft changes with expectedVersion, UUID requestId and reason. Requires --allow-email: event policy can automatically send notices even when sendSeatChangeEmail is false. Retries reuse requestId and intent. Realtime-enabled free targets need per-registration seatHolds (--payload JSON)."
12661
+ summary: "Atomically publish with expectedVersion, UUID requestId and reason. Optional seatChangeEmailRegistrationIds in --payload chooses exact affected eligible registration recipients; [] sends none, including automatic policy. Omit to retain sendSeatChangeEmail/event-policy behavior. Requires --allow-email. Retry the same requestId and exact recipient set. Realtime free targets need per-registration seatHolds."
12561
12662
  }
12562
12663
  });
12563
12664
  var getCommandManifest = () => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/rsvp",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "RSVP CLI for authorized event operations.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",