Skip to content

feat(sub-event): event sub-event counts + guest participations (AIRLST-5445) - #145

Open
mish-ka-mishka wants to merge 38 commits into
masterfrom
feat/AIRLST-5445-event-subevent-counts
Open

mish-ka-mishka wants to merge 38 commits into
masterfrom
feat/AIRLST-5445-event-subevent-counts

Conversation

@mish-ka-mishka

@mish-ka-mishka mish-ka-mishka commented Aug 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Core changes from airlst/core#5649 (AIRLST-5445, SubEvents M1):

  1. Event objects gain is_parent (boolean) and sub_events_count (integer) — on Event.get(), Event.list() and Contact.getEvents() (all serialize through EventApiResource).
  2. Guest read (Guest.get()) gains optional sub_event_participations — the guest's sub-event participations with their per-sub-event status. The key is present only while the company's sub-events module is active.
  3. New SubEvent resource: new SubEvent('event-uuid').list() → GET /events/{uuid}/sub-events (key-authed). Each sub-event carries its quota state: participations_count (occupying = invited + confirmed) and quotas with limit/used; guest_group_id: null marks the default quota. Group quotas also carry guest_group_name (locale-keyed).
  4. New Guest.assignSubEvents(code, subEventIds) → POST /events/{uuid}/guests/{code}/sub-events (key-authed). Assigns the guest to the sub-events in one transaction and returns the created participations with their per-sub-event status — invited, or waitlisted when the quota is full. Any error rolls back the whole batch; a sub-event of another event, or one the guest already participates in, responds 422.

M3 additions (AIRLST-5447)

  1. New Guest.listSubEvents(code) → GET /events/{uuid}/guests/{code}/sub-events (key-authed). The registration-form read (R40): every sub-event with the guest's participation (null while not assigned) and a guest-specific has_free_seat — false means a confirm or a new assignment for this guest would be waitlisted.
  2. New Guest.updateSubEventParticipation(code, participationId, status, sendAutomatedEmail?) → PATCH /events/{uuid}/guests/{code}/sub-events/participations/{id} (key-authed). The guest RSVP: confirmed or declined only. A confirm from the waitlist re-checks quotas (422 while full — the guest stays waitlisted); a cancelled parent guest blocks a confirm (422, reactivate on the parent event first); declines are always allowed; a foreign guest's participation is 404.
  3. SubEventInterface.send_status_emails — the per-SubEvent auto-send switch for status emails (default off).
  4. EmailTemplateInterface.sub_event_id + sub_event_status_hook — the per-SubEvent status email hook, mutually exclusive with booking_status_hook.

Same version (0.0.25-beta), unreleased.

Version

0.0.25-beta. npm latest is now the stable 0.0.24 (republished 2026-08-24), so
0.0.25-beta is the correct next pre-release and never published — the merge from master
kept it over master's 0.0.24.

Release with --prerelease (the version carries -beta), and only once 0.0.25-beta
is on master — the publish workflow reads the version from master's package.json,
not from the tag.

⚠️ Do not merge/release before the core PR is deployed — until then the live API does not serve these fields/endpoints.

🤖 Generated with Claude Code

mish-ka-mishka and others added 2 commits August 11, 2026 18:33
…s (AIRLST-5445)

Core adds two fields to every event object (EventApiResource): is_parent
(boolean) and sub_events_count (integer). They appear on GET /events/{uuid},
the company events list and the contact events list. The sub-events list
endpoint itself is session-authed and stays outside this SDK.

Core PR: airlst/core#5649

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…AIRLST-5445)

Core M1.2 adds sub_event_participations to GET /events/{uuid}/guests/{code}
(GuestApiResource): the guest's sub-event participations with their
per-sub-event status. The key is present only while the company's
sub-events module is active, so the field is optional. The assignment
endpoint is session-authed and stays outside this SDK.

Core PR: airlst/core#5649

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mish-ka-mishka mish-ka-mishka changed the title feat(events): expose is_parent and sub_events_count on event responses (AIRLST-5445) feat(sub-event): event sub-event counts + guest participations (AIRLST-5445) Aug 12, 2026
mish-ka-mishka and others added 27 commits August 12, 2026 22:25
…PI (AIRLST-5445)

GET /api/events/{event}/sub-events is now key-authed in core (the
ticket's two-surface rewording), so it enters SDK scope. The resource
returns sub-events with quota state; assigning guests ships with M1.2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ment (AIRLST-5445)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (AIRLST-5445)

Core M1.2 ships POST /events/{uuid}/guests/{code}/sub-events on the
key-authed Suite API. Guest.assignSubEvents(code, subEventIds) posts the
batch and returns the created participations with their per-sub-event
status (invited, or waitlisted when the quota is full). The API runs the
batch in one transaction and rolls back on any error.

Core PR: airlst/core#5649

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…age root (AIRLST-5445)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The comments predate the key-authed API surface. SubEvent.list() and
Guest.assignSubEvents() now cover both operations in this SDK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…(AIRLST-5445)

SubEventQuotaInterface types `guest_group_name`, but no test exercised it and the
README never mentioned it, so the one field whose presence on this endpoint is
new went unverified.

Core only started returning it here once the guestGroup eager load moved out of
the suite page and into the action that the page and the key-authed endpoint
share — before that the endpoint omitted the key while the page emitted it. The
test asserts both halves of the contract: absent on the default quota, present
as a locale-keyed object on a group quota.
…ngs, participation extended fields (AIRLST-5446)

Mirror the core M2 foundation on the key-authed API:

- SubEventInterface gains released_at (GM-facing bookability gate, null
  while unreleased).
- SubEventQuotaInterface gains guest_manager_id and guest_manager_name —
  a quota row is now a group row, a guest-manager row or the default row.
- assignSubEvents() accepts optional participation extended-field values
  keyed by sub-event UUID, and its response carries overlap_warnings
  (groups of overlapping sub-events; a warning, never a block).

No version bump — ships in the open 0.0.25-beta PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…IRLST-5446)

Mirror the guest-manager surface of the SubEvents API. Guest managers exist only
on the parent event. A parent-scoped manager acts across the parent and its
released sub-events, so the manager is named by its UUID in the path.

Add three GuestManager methods:
- listSubEvents() reads the released sub-events with the manager's own contingent.
  It reports booked, limit and remaining. limit and remaining are null when the
  manager has no quota row.
- assignGuestSubEvents() books one of the manager's guests onto released
  sub-events. It uses the same transaction and response body as
  Guest.assignSubEvents().
- promoteSubEventParticipation() promotes a waitlisted participation.

Export GuestManagerSubEventContingentInterface from the package root.

SubEvent.list() stays the full integrator view. It returns released and
unreleased sub-events alike. The README now names the guest-manager methods as
the surface a guest-manager consumer must use instead.

The version stays 0.0.25-beta. This change ships in the open PR #145.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…RLST-5446)

GuestManager.assignGuestsSubEvents() books up to 200 of the manager's guests onto
the same released sub-events in one call. It mirrors the core endpoint core#5649
adds: bounded and synchronous, one result entry per guest carrying the same
participations/overlap_warnings pair as the single-guest method plus guest_code
and error, and in the order the codes were posted.

Isolation is per guest: a guest that fails carries its own error while the rest
of the call still books, and a guest that already participates is skipped rather
than rejected, so a pre-assignment is safe to run twice.

Bulk assignment exists on the guest-manager surface only — an integrator key uses
Guest.assignSubEvents() one guest at a time, because a bulk route outside that
surface would skip the manager-ownership and released-sub-event rules.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ion text (AIRLST-5446)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… (AIRLST-5447)

Mirrors the M3 core additions (airlst/core#5649):

- Guest.listSubEvents(): GET /events/{event}/guests/{code}/sub-events —
  every sub-event with the guest's participation (null while not
  assigned) and a guest-specific has_free_seat (R40). false means a
  confirm or a new assignment for THIS guest would be waitlisted.
- Guest.updateSubEventParticipation(): PATCH .../sub-events/
  participations/{id} with confirmed|declined. A confirm from the
  waitlist re-checks quotas (422 while full, the guest stays
  waitlisted); a cancelled parent guest blocks a confirm (422);
  declines are always allowed; a foreign guest's participation is 404.
  Optional sendAutomatedEmail (API default true).
- SubEventInterface.send_status_emails: the per-SubEvent auto-send
  switch (default off).
- EmailTemplateInterface.sub_event_id + sub_event_status_hook: the
  per-SubEvent status email hook, mutually exclusive with
  booking_status_hook.

Same branch and version (0.0.25-beta) as the rest of the sub-events
work; release only after the core PR deploys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ent-subevent-counts

# Conflicts:
#	package.json
…response (AIRLST-5447)

Core now derives the guest's status on the parent event from its sub-event
participations, in the same transaction as the answer, and the RSVP endpoint
reports it as `data.guest.status`. The landing page renders the new state
without a second request.

The status is the guest's real one, not always one of the three derived values:
a guest held at `listed` or `requested` is not moved to `invited`, and one in
the payment-owned `unpaid` or `checkout` states is not moved at all. The new
exported `BookingStatus` type carries all nine cases.

Only this endpoint returns the field. The assign endpoints do not: at assign
time the derived value carries no information, and their response type is shared
with the guest-manager resource, which must stay byte-identical.

Core PR: airlst/core (AIRLST-5447, M3b).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ign endpoint (AIRLST-5447)

Review follow-up. The assign endpoint derives the guest's parent status too, so
it gained a 422 on `limits` that callers have to expect: a request that used to
answer 201 can now be refused when the derived status no longer fits the parent
event's guest limit, and nothing is written.

The derived write is silent there — an assignment is an administrative act and
must not mail an invitation to a guest who already answered. Only the guest's own
RSVP sends a parent status email.

The response deliberately does not report the derived status: a fresh assignment
derives `invited`, which tells the caller nothing it does not already know.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…st (AIRLST-5534)

GET /events/{event}/guest-managers/{manager}/sub-events gains three
nullable numbers: guest_group_limit, guest_group_used and
guest_group_remaining. A manager that reaches a SubEvent through its own
guest group needs the numbers of the contingent it books against; its own
limit and remaining are null in that case. guest_group_used counts every
guest of the group, not only the ones this manager brought.

Core also narrows what the endpoint returns: a manager reaches a SubEvent
only when it is released AND carries an applicable quota row — one for the
manager, or one for its guest group. The default row grants nothing; an
exhausted row still grants access. The docs say so on the method, the
interface and in the README.

Quota rows themselves are still never exposed on this surface.
…nt and import (AIRLST-5578)

Core adds an optional boolean waitlist_all_subevents_on_limit. When any targeted
SubEvent has no free seat, every participation of the call is created as waitlisted
instead of only the exhausted one. It defaults to false, so an existing caller sees
no change.

Endpoints that accept it, and the SDK methods that carry it:

- Guest.assignSubEvents, as an optional fourth argument
- GuestManager.assignGuestSubEvents, as an optional fifth argument
- GuestManager.assignGuestsSubEvents, as an optional fifth argument, per guest
- Guest.processImport, as waitlist_all_subevents_on_limit on the body interface

The key is omitted from the request body when the argument is not given, so the
serialized payload of an existing call is unchanged.

On the import the flag governs the SubEvent membership columns only. A per-SubEvent
column that names an explicit status still wins. It does not make the import itself
transactional.

Version stays 0.0.25-beta: this rides on the open sub-events SDK branch and is not
released on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…irror guest promote (AIRLST-5448, ACP-30143)

Mirrors core M4a. Every participation object carries `waitlist_cause`,
`waitlist_cause_quota_id` and `queue_position`. The guest-manager contingent
reports `waitlisted` and `guest_group_waitlisted`. Both promote endpoints
answer with `data.warnings` (`queue_skipped`, a warning only).

Adds `Guest.promoteSubEventParticipation()` for the key-authed guest promote
endpoint that core shipped in 2094d90047 (ACP-30143) and that the SDK did not
mirror yet. The response carries the participation, the derived parent guest
status and the warnings.

No version bump: the branch already carries 0.0.25-beta.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-5842 C9)

Guest.list() accepts includeSubEventParticipations. With it, core adds
sub_event_participations to every guest of the page, in the same shape as
Guest.get(), while the company's sub-events module is active.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…C10)

SubEventInterface gets response_deadline_at, reminder_at and
reminder_sent_at. GuestSubEventInterface gets response_deadline_at. The
docs of the guest RSVP and the guest promote name the new 422 after the
deadline; a decline stays allowed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ilter (AIRLST-5842 C2)

SubEvent.create() and SubEvent.update() call the new key-authed core
endpoints POST /sub-events and PATCH /sub-events/{subEvent}.
SubEvent.list() takes an optional key filter. The sub-event and the
guest-manager contingent carry the integration key and the descriptive
content. The create and update bodies accept the C10 deadline fields.

Core: airlst/core 883a9d4fe0 on AIRLST-5842-side-events (PR #6255).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SubEvent gets uploadImage() and deleteImage(). The upload sends the file as
multipart with POST, because core reads multipart bodies on POST only. The
sub-event, guest sub-event and guest-manager contingent interfaces get
image_url.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…irlst/sdk-js into HEAD

# Conflicts:
#	README.md
#	src/resources/SubEvent.ts
#	tests/resources/SubEvent.spec.ts
SubEvent.syncQuotas() calls the new key-authed core endpoint
PUT /sub-events/{subEvent}/quotas. It replaces the complete matrix, and
an empty array clears it.

Core: airlst/core e243696c94 on AIRLST-5842-side-events (PR #6255).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…irlst/sdk-js into HEAD

# Conflicts:
#	README.md
#	src/resources/SubEvent.ts
#	tests/resources/SubEvent.spec.ts
The participation status union gets planned: a row assigned while its
sub-event is in planning. SubEventInterface reports
invitations_sent_at, null while the sub-event is in planning.
SubEvent.create() takes plan_before_invitation; update() does not.

SubEvent.sendInvitations() calls the new key-authed core endpoint
POST /sub-events/{subEvent}/send-invitations (202, planned_count).
GuestManager.promoteSubEventParticipation() takes planned, the only
target while the sub-event is in planning. The guest promote and the
email template hook stay without planned, as in core.

Core: airlst/core c24d363524 on AIRLST-5842-side-events (PR #6255).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SubEvent.updateParticipation() writes one participation as the
integrator, mainly { status: 'cancelled' }. GuestManager
.cancelSubEventParticipation() cancels a participation of the manager's
own guest. Both keep the row as cancelled and free its seat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mish-ka-mishka and others added 9 commits October 26, 2026 14:04
Guest.assignSubEvents(), GuestManager.assignGuestSubEvents() and
GuestManager.assignGuestsSubEvents() take mandatory as a new last
argument. A mandatory row is confirmed without an RSVP and never goes on
the waitlist. The participation reports is_mandatory.

Quota rows report mandatory, and the syncQuotas() and create() bodies
accept it. The JSDoc says that only a guest-group row can be mandatory,
because the API responds 422 otherwise. The RSVP JSDoc says that a
mandatory row responds 422.

Core: airlst/core 8c6804f43e on AIRLST-5842-side-events (PR #6255).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ST-5842 C11)

Guest.bookSubEvent(code, subEventId, { extendedFields, sendAutomatedEmail })
books one open sub-event as the guest. A free seat confirms at once; a full
quota row or a full capacity waitlists with a cause. The JSDoc lists the
422 and 404 cases.

SubEventInterface gets capacity, and the create and update bodies accept
it. SubEventWaitlistCause gets 'capacity' for the overall cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every object payload in the SDK is a snake_case body interface that goes
to the API as it is (JSON.stringify(body)). bookSubEvent() now follows
that convention: BookSubEventBodyInterface with extended_fields and
send_automated_email replaces the camelCase options object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SubEventParticipationInterface gets the read-only booked_by_guest_at. Core
sets it when the guest books the seat through bookSubEvent(), also on a
re-booking. It stays null for rows that an integrator, a guest manager or
an admin assigned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mirror core F3. The participation status `planned` is now `listed`.
The create flag `plan_before_invitation` is replaced by
`default_assignment_status` (`listed` or `invited`) on create and update.
SubEventInterface and the guest-manager contingent return the field.
The sendInvitations() response reports `listed_count`. The API no longer
accepts the old names. The version stays 0.0.25-beta.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SubEventInterface and the create and update bodies get assignment_rule
and assignment_mode, with the rule tree types. Participations get the
read-only assigned_by_rule_at and rule_mismatch_at. The quota row loses
mandatory: the sub-event rule replaces it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every sub-event interface carries extended_fields: the values of the
event's subEventDetails extended fields, keyed by field key. The create
and update bodies accept extended_fields. Core checks each value against
its definition and refuses an unknown key with 422. An update merges the
values, and a key with null clears that one value.

Core: airlst/core 149b431901 on AIRLST-5842-feedback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…irlst/sdk-js into lane-b-sdk

# Conflicts:
#	src/interfaces.ts
Mirror core d49bd4f032. Every sub-event response carries waitlist_enabled:
SubEventInterface, GuestManagerSubEventContingentInterface and
GuestSubEventInterface. SubEvent.create() and update() accept it (default
true on create).

With false, an assignment without a free seat responds 422 on
sub_event_ids.{index} ("This SubEvent is fully booked.") instead of a
waitlisted row. The docs on Guest.assignSubEvents(), bookSubEvent(),
listSubEvents() and the guest-manager assign methods say so. The bulk
guest-manager call reports the refused guest in its own entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant