@sneat/extension-eventus-contract 0.0.1

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.
@@ -0,0 +1,145 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ import { of } from 'rxjs';
3
+
4
+ // TypeScript mirror of the frozen Eventus bring-along contract
5
+ // (`typespec/api4eventus-bringalong.tsp`). Shapes MUST match the JSON the
6
+ // backend emits/accepts exactly. The host defines slots; responders claim an
7
+ // open slot and may release it back to open, preventing duplicate contributions.
8
+
9
+ // TypeScript mirror of the frozen Eventus wire contract (`typespec/api4eventus.tsp`).
10
+ // These shapes MUST match the JSON the backend emits/accepts exactly.
11
+
12
+ /**
13
+ * Pure join: the events list is the set of happenings that have an eventus
14
+ * overlay (interim "is-event" marker), projected to list rows. Kept pure so the
15
+ * join — the bug-prone part — is unit-testable without Firestore.
16
+ */
17
+ function selectEventListItems(overlays, happenings) {
18
+ const overlayById = new Map(overlays.map((o) => [o.id, o]));
19
+ return happenings
20
+ .filter((h) => overlayById.has(h.id))
21
+ .map((h) => ({
22
+ id: h.id,
23
+ title: h.dbo?.title ?? h.brief?.title ?? '',
24
+ start: eventStart(h.dbo),
25
+ happening: h,
26
+ overlay: overlayById.get(h.id),
27
+ }));
28
+ }
29
+ function eventStart(dbo) {
30
+ if (!dbo) {
31
+ return undefined;
32
+ }
33
+ const dtStarts = dbo.dtStarts;
34
+ if (dtStarts) {
35
+ return new Date(dtStarts).toISOString();
36
+ }
37
+ const slot = Object.values(dbo.slots ?? {})[0];
38
+ if (slot?.start?.date) {
39
+ return slot.start.time
40
+ ? `${slot.start.date} ${slot.start.time}`
41
+ : slot.start.date;
42
+ }
43
+ return undefined;
44
+ }
45
+
46
+ // TypeScript mirror of the frozen Eventus invitations contract
47
+ // (`typespec/api4eventus-invitations.tsp`). Shapes MUST match the JSON the
48
+ // backend emits/accepts exactly. An invitation references the invitee by id
49
+ // only — Sneat (`spaceus`/`contactus`) stays the source of truth for names.
50
+
51
+ // TypeScript mirror of the frozen Eventus RSVP contract
52
+ // (`typespec/api4eventus-rsvp.tsp`) plus the public resolve context from
53
+ // (`typespec/api4eventus-links.tsp`). Shapes MUST match the JSON the backend
54
+ // emits/accepts exactly. Submitting an RSVP is pure event-attendance and NEVER
55
+ // joins the host's space.
56
+ /**
57
+ * Build the submit body from raw form values. When `status == 'no'` the
58
+ * headcounts are forced to 0 (the backend zeroes them anyway), and blank
59
+ * dietary/comment/self-identified names are dropped. For an open-link response
60
+ * the trimmed `selfIdentifiedName` is carried through (the caller enforces it is
61
+ * non-empty). Pure so it can be unit-tested without rendering.
62
+ * (AC: no-rsvp-zeroes-headcount, rsvp-full-fields-captured, attribution-by-link-type)
63
+ */
64
+ function buildRsvpRequest(form) {
65
+ const attending = form.status !== 'no';
66
+ const dietary = form.dietary.trim();
67
+ const comment = form.comment.trim();
68
+ const name = form.selfIdentifiedName?.trim();
69
+ const familyName = form.selfIdentifiedFamilyName?.trim();
70
+ return {
71
+ status: form.status,
72
+ adults: attending ? form.adults : 0,
73
+ children: attending ? form.children : 0,
74
+ ...(dietary ? { dietary } : {}),
75
+ ...(comment ? { comment } : {}),
76
+ ...(name ? { selfIdentifiedName: name } : {}),
77
+ ...(familyName ? { selfIdentifiedFamilyName: familyName } : {}),
78
+ };
79
+ }
80
+
81
+ // TypeScript mirror of the frozen Eventus links contract
82
+ // (`typespec/api4eventus-links.tsp`). Shapes MUST match the JSON the backend
83
+ // emits exactly. The QR code is rendered client-side from `url`.
84
+
85
+ /**
86
+ * Base URL the Eventus API is served from (without a trailing slash), e.g.
87
+ * `https://api.sneat.app`. Defaults to '' so requests are relative to the
88
+ * app origin (`/api4eventus/...`). The app shell can override it.
89
+ */
90
+ const EVENTUS_API_BASE_URL = new InjectionToken('EVENTUS_API_BASE_URL', { providedIn: 'root', factory: () => '' });
91
+
92
+ /** Pulls a human-readable message from an HttpErrorResponse carrying IApiError. */
93
+ function extractErrorMessage(err, fallback) {
94
+ const body = err?.error;
95
+ return body?.message ?? fallback;
96
+ }
97
+
98
+ /**
99
+ * The identity used when the current user claims or releases a bring-along
100
+ * slot. Injecting this (rather than hard-coding) keeps claim/release testable
101
+ * and decoupled from RSVP identity.
102
+ *
103
+ * TODO: replace the stub default with the real responder identity once RSVP
104
+ * lands (Tasks 3/4) — claim/release should use the link-gated guest token or
105
+ * the signed-in family/person ref instead of this placeholder.
106
+ */
107
+ const EVENTUS_CURRENT_CLAIMANT = new InjectionToken('EVENTUS_CURRENT_CLAIMANT', {
108
+ providedIn: 'root',
109
+ factory: () => ({ ref: 'stub-claimant', label: 'Smith Family' }),
110
+ });
111
+
112
+ const EVENT_SERVICE = new InjectionToken('EventService');
113
+ const EVENTUS_EVENT_SERVICE = new InjectionToken('EventusEventService');
114
+ const RSVP_SERVICE = new InjectionToken('RsvpService');
115
+ const INVITATION_SERVICE = new InjectionToken('InvitationService');
116
+ const LINK_SERVICE = new InjectionToken('LinkService');
117
+ const BRING_ALONG_SERVICE = new InjectionToken('BringAlongService');
118
+
119
+ const EVENTUS_INVITEE_SOURCE = new InjectionToken('EVENTUS_INVITEE_SOURCE', { providedIn: 'root', factory: () => new StubInviteeSource() });
120
+ /**
121
+ * Default stub returning sample data so the picker is usable end-to-end before
122
+ * real wiring. TODO: replace with an adapter over `spaceus` (family spaces) and
123
+ * `contactus` (contacts) once those modules are available to the app shell.
124
+ */
125
+ class StubInviteeSource {
126
+ families() {
127
+ return of([
128
+ { id: 'fam-sample-1', title: 'The Smith Family' },
129
+ { id: 'fam-sample-2', title: 'The Brown Family' },
130
+ ]);
131
+ }
132
+ persons() {
133
+ return of([
134
+ { id: 'person-sample-1', name: 'Alice Smith' },
135
+ { id: 'person-sample-2', name: 'Bob Brown' },
136
+ ]);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Generated bundle index. Do not edit.
142
+ */
143
+
144
+ export { BRING_ALONG_SERVICE, EVENTUS_API_BASE_URL, EVENTUS_CURRENT_CLAIMANT, EVENTUS_EVENT_SERVICE, EVENTUS_INVITEE_SOURCE, EVENT_SERVICE, INVITATION_SERVICE, LINK_SERVICE, RSVP_SERVICE, StubInviteeSource, buildRsvpRequest, extractErrorMessage, selectEventListItems };
145
+ //# sourceMappingURL=sneat-extension-eventus-contract.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-eventus-contract.mjs","sources":["../../../../../../libs/extensions/eventus/contract/src/lib/models/bring-along.ts","../../../../../../libs/extensions/eventus/contract/src/lib/models/event.ts","../../../../../../libs/extensions/eventus/contract/src/lib/models/eventus-event.ts","../../../../../../libs/extensions/eventus/contract/src/lib/models/invitation.ts","../../../../../../libs/extensions/eventus/contract/src/lib/models/rsvp.ts","../../../../../../libs/extensions/eventus/contract/src/lib/models/rsvp-link.ts","../../../../../../libs/extensions/eventus/contract/src/lib/services/api-base-url.ts","../../../../../../libs/extensions/eventus/contract/src/lib/services/api-error.ts","../../../../../../libs/extensions/eventus/contract/src/lib/services/claimant.ts","../../../../../../libs/extensions/eventus/contract/src/lib/services/interfaces.ts","../../../../../../libs/extensions/eventus/contract/src/lib/services/invitee-source.ts","../../../../../../libs/extensions/eventus/contract/src/sneat-extension-eventus-contract.ts"],"sourcesContent":["// TypeScript mirror of the frozen Eventus bring-along contract\n// (`typespec/api4eventus-bringalong.tsp`). Shapes MUST match the JSON the\n// backend emits/accepts exactly. The host defines slots; responders claim an\n// open slot and may release it back to open, preventing duplicate contributions.\n\n/**\n * Who claimed a slot. Stored as an opaque reference plus a display label so the\n * slot can render \"Drinks — Smith Family\" without resolving the reference.\n */\nexport interface ISlotClaimant {\n /** Opaque claimant reference (e.g. a family space id, contact id, or guest token). */\n readonly ref: string;\n\n /** Display label shown next to the slot, e.g. \"Smith Family\". */\n readonly label: string;\n}\n\n/** A bring-along item slot on an event. `claimedBy` absent => the slot is open. */\nexport interface IBringAlongSlot {\n /** Slot id, unique within its event. */\n readonly id: string;\n\n /** What's needed, e.g. \"Drinks\". */\n readonly label: string;\n\n /** Optional free-text quantity/notes, e.g. \"2 bottles\". */\n readonly note?: string;\n\n /** Present when claimed; absent means the slot is open. */\n readonly claimedBy?: ISlotClaimant;\n\n /** Audit: when the slot was defined (RFC3339). */\n readonly createdAt: string;\n}\n\n/** POST body to define a slot. Host-only. */\nexport interface IDefineSlotRequest {\n label: string;\n note?: string;\n}\n\n/** POST body to claim a slot. */\nexport interface IClaimSlotRequest {\n ref: string;\n label: string;\n}\n\n/** POST body to release a slot — only the claiming `ref` may release. */\nexport interface IReleaseSlotRequest {\n ref: string;\n}\n","// TypeScript mirror of the frozen Eventus wire contract (`typespec/api4eventus.tsp`).\n// These shapes MUST match the JSON the backend emits/accepts exactly.\n\n/** Lifecycle status of an event. */\nexport type EventStatus = 'active' | 'cancelled';\n\n/**\n * An Eventus event. Persisted by the backend at\n * `/spaces/{spaceID}/ext/eventus/events/{eventID}`.\n */\nexport interface IEvent {\n /** Event id, unique within its host space. */\n readonly id: string;\n\n /** Host space id this event belongs to. */\n readonly spaceID: string;\n\n /** Display name, e.g. \"Sophie's 10th Birthday\". */\n readonly title: string;\n\n /** Event start date/time as an RFC3339 / ISO string. */\n readonly start: string;\n\n /** Free-text location. */\n readonly location: string;\n\n /** Optional free-text description. */\n readonly description?: string;\n\n /** Lifecycle status; `active` on creation. */\n readonly status: EventStatus;\n\n /** Audit: creation timestamp (RFC3339). */\n readonly createdAt: string;\n\n /** Audit: last-modification timestamp (RFC3339). */\n readonly updatedAt: string;\n}\n\n/** POST body to create an event. */\nexport interface ICreateEventRequest {\n /** Display name (required, non-empty). */\n title: string;\n\n /** Event start date/time (RFC3339). */\n start: string;\n\n /** Free-text location (required, non-empty). */\n location: string;\n\n /** Optional free-text description. */\n description?: string;\n\n /**\n * Event length in minutes. Optional — when omitted the backend's calendarius\n * facade applies its default (60m).\n */\n durationMinutes?: number;\n}\n\n/**\n * Response of the create-event endpoint. The event itself is read\n * Firestore-direct (happening + overlay); the create response only identifies\n * the new happening so the UI can navigate to it.\n */\nexport interface ICreateEventResponse {\n /** New event/happening id. */\n readonly id: string;\n}\n\n/**\n * PUT body to edit an event. All fields optional; only present fields are\n * updated. Does not change `spaceID` or `status` (use cancel for status).\n */\nexport interface IUpdateEventRequest {\n title?: string;\n start?: string;\n location?: string;\n description?: string;\n}\n\n/** Standard error body returned by the backend with a non-2xx status. */\nexport interface IApiError {\n /** Machine-readable error code, e.g. `unauthorized`, `not_found`. */\n code: string;\n\n /** Human-readable message. */\n message: string;\n}\n","import { IIdAndBriefAndDbo } from '@sneat/core';\nimport { IWithCreated, IWithSpaceIDs } from '@sneat/dto';\nimport {\n IHappeningBrief,\n IHappeningDbo,\n ISingleHappeningDbo,\n} from '@sneat/extension-calendarius-contract';\n\n// Eventus hosting overlay on a Calendarius happening.\n//\n// The overlay document id EQUALS the happeningID, so the overlay both (a) marks\n// a happening as an eventus-hosted event — the interim \"is this an event?\"\n// marker until the shared `'event'` HappeningKind lands (deferred with the\n// sneat-libs batch) — and (b) carries eventus-only extras. Display fields\n// (title, start, location, status) live on the happening, NOT here, so there is\n// no duplication of the calendar entity.\n//\n// Stored at `spaces/{spaceID}/ext/eventus/events/{happeningID}`.\n\nexport interface IEventusEventBrief {\n /** listus list id holding the bring-along / give-away items, when attached. */\n readonly bringAlongListID?: string;\n}\n\nexport type IEventusEventDbo = IEventusEventBrief & IWithSpaceIDs & IWithCreated;\n\ntype EventOverlay = IIdAndBriefAndDbo<IEventusEventBrief, IEventusEventDbo>;\ntype Happening = IIdAndBriefAndDbo<IHappeningBrief, IHappeningDbo>;\n\n/** A row for the eventus events list: a happening that is an eventus event. */\nexport interface IEventusEventListItem {\n /** happeningID (overlay and happening share this id). */\n readonly id: string;\n readonly title: string;\n /** Display start: ISO from the single happening, or `date[ time]` from its slot. */\n readonly start?: string;\n readonly happening: Happening;\n readonly overlay?: EventOverlay;\n}\n\n/**\n * Pure join: the events list is the set of happenings that have an eventus\n * overlay (interim \"is-event\" marker), projected to list rows. Kept pure so the\n * join — the bug-prone part — is unit-testable without Firestore.\n */\nexport function selectEventListItems(\n overlays: readonly EventOverlay[],\n happenings: readonly Happening[],\n): IEventusEventListItem[] {\n const overlayById = new Map(overlays.map((o) => [o.id, o]));\n return happenings\n .filter((h) => overlayById.has(h.id))\n .map((h) => ({\n id: h.id,\n title: h.dbo?.title ?? h.brief?.title ?? '',\n start: eventStart(h.dbo),\n happening: h,\n overlay: overlayById.get(h.id),\n }));\n}\n\nfunction eventStart(dbo?: IHappeningDbo | null): string | undefined {\n if (!dbo) {\n return undefined;\n }\n const dtStarts = (dbo as ISingleHappeningDbo).dtStarts;\n if (dtStarts) {\n return new Date(dtStarts).toISOString();\n }\n const slot = Object.values(dbo.slots ?? {})[0];\n if (slot?.start?.date) {\n return slot.start.time\n ? `${slot.start.date} ${slot.start.time}`\n : slot.start.date;\n }\n return undefined;\n}\n","// TypeScript mirror of the frozen Eventus invitations contract\n// (`typespec/api4eventus-invitations.tsp`). Shapes MUST match the JSON the\n// backend emits/accepts exactly. An invitation references the invitee by id\n// only — Sneat (`spaceus`/`contactus`) stays the source of truth for names.\n\n/** Whether an invitation targets a whole family (space) or a single person. */\nexport type InviteeType = 'family' | 'person';\n\n/** An invitation of a family or person to an event. */\nexport interface IInvitation {\n /** Invitation id, unique within its event. */\n readonly id: string;\n\n /** Discriminates which reference field is populated. */\n readonly inviteeType: InviteeType;\n\n /** Set when `inviteeType === 'family'`: the invited family's space id. */\n readonly familySpaceID?: string;\n\n /** Set when `inviteeType === 'person'`: the invited contact's id. */\n readonly contactID?: string;\n\n /** Audit: when the invitation was added (RFC3339). */\n readonly createdAt: string;\n}\n\n/**\n * POST body to add one invitee. Exactly one of `familySpaceID` / `contactID`\n * is set, matching `inviteeType`. No name or contact detail is sent — only id.\n */\nexport interface IAddInviteeRequest {\n inviteeType: InviteeType;\n familySpaceID?: string;\n contactID?: string;\n}\n","// TypeScript mirror of the frozen Eventus RSVP contract\n// (`typespec/api4eventus-rsvp.tsp`) plus the public resolve context from\n// (`typespec/api4eventus-links.tsp`). Shapes MUST match the JSON the backend\n// emits/accepts exactly. Submitting an RSVP is pure event-attendance and NEVER\n// joins the host's space.\n\nimport { LinkKind } from './rsvp-link';\n\n/** Responder's attendance answer. */\nexport type RsvpStatus = 'yes' | 'no' | 'maybe';\n\n/** A submitted RSVP. */\nexport interface IRsvp {\n /** RSVP id, unique within its event. */\n readonly id: string;\n\n /** Invitation this RSVP answers (per-invitee link); empty for an open link. */\n readonly invitationID?: string;\n\n /** Attribution for an open-link response: the responder's self-identified name. */\n readonly selfIdentifiedName?: string;\n\n /** Optional self-identified family name (open link). */\n readonly selfIdentifiedFamilyName?: string;\n\n readonly status: RsvpStatus;\n\n /** Adults attending. Forced to 0 by the backend when `status == 'no'`. */\n readonly adults: number;\n\n /** Children attending. Forced to 0 by the backend when `status == 'no'`. */\n readonly children: number;\n\n /** Optional dietary/allergy notes. */\n readonly dietary?: string;\n\n /** Optional free-text comment. */\n readonly comment?: string;\n\n /** True when submitted by a signed-in Sneat account. */\n readonly viaAccount: boolean;\n\n /** Audit: submission time (RFC3339). */\n readonly submittedAt: string;\n}\n\n/** POST body to submit an RSVP. Headcounts are zeroed by the backend when `status == 'no'`. */\nexport interface ISubmitRsvpRequest {\n status: RsvpStatus;\n adults: number;\n children: number;\n dietary?: string;\n comment?: string;\n\n /**\n * REQUIRED for an open-link token (the responder self-identifies); omitted for\n * a per-invitee token (attribution comes from the invitation). Never a\n * guest-list selection — only this free-text name. (AC: attribution-by-link-type)\n */\n selfIdentifiedName?: string;\n\n /** Optional self-identified family name (open link). */\n selfIdentifiedFamilyName?: string;\n}\n\n/**\n * Build the submit body from raw form values. When `status == 'no'` the\n * headcounts are forced to 0 (the backend zeroes them anyway), and blank\n * dietary/comment/self-identified names are dropped. For an open-link response\n * the trimmed `selfIdentifiedName` is carried through (the caller enforces it is\n * non-empty). Pure so it can be unit-tested without rendering.\n * (AC: no-rsvp-zeroes-headcount, rsvp-full-fields-captured, attribution-by-link-type)\n */\nexport function buildRsvpRequest(form: {\n status: RsvpStatus;\n adults: number;\n children: number;\n dietary: string;\n comment: string;\n selfIdentifiedName?: string;\n selfIdentifiedFamilyName?: string;\n}): ISubmitRsvpRequest {\n const attending = form.status !== 'no';\n const dietary = form.dietary.trim();\n const comment = form.comment.trim();\n const name = form.selfIdentifiedName?.trim();\n const familyName = form.selfIdentifiedFamilyName?.trim();\n return {\n status: form.status,\n adults: attending ? form.adults : 0,\n children: attending ? form.children : 0,\n ...(dietary ? { dietary } : {}),\n ...(comment ? { comment } : {}),\n ...(name ? { selfIdentifiedName: name } : {}),\n ...(familyName ? { selfIdentifiedFamilyName: familyName } : {}),\n };\n}\n\n/**\n * Public context returned when a token is resolved — only what the RSVP form\n * needs. NEVER includes the guest list or any other invitee.\n */\nexport interface IRsvpContext {\n /** Display fields for the event. */\n readonly eventTitle: string;\n readonly eventStart: string;\n readonly eventLocation: string;\n\n /** Whether the event still accepts RSVPs (false once cancelled). */\n readonly open: boolean;\n\n /** `invitee` → pre-attributed to one invitee; `open` → responder self-identifies. */\n readonly kind: LinkKind;\n\n /** Set only when `kind == 'invitee'`: the invitation this link is bound to. */\n readonly invitationID?: string;\n}\n","// TypeScript mirror of the frozen Eventus links contract\n// (`typespec/api4eventus-links.tsp`). Shapes MUST match the JSON the backend\n// emits exactly. The QR code is rendered client-side from `url`.\n\n/** `invitee` = bound to a single invitation; `open` = shared event link. */\nexport type LinkKind = 'invitee' | 'open';\n\n/** A tokenized RSVP link (+ url for QR). */\nexport interface IRsvpLink {\n /** Opaque link token. */\n readonly token: string;\n\n /** Full shareable RSVP URL, e.g. `https://eventus.sneat.app/r/{token}`. */\n readonly url: string;\n\n /** Whether the link targets one invitee or is the open event link. */\n readonly kind: LinkKind;\n}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * Base URL the Eventus API is served from (without a trailing slash), e.g.\n * `https://api.sneat.app`. Defaults to '' so requests are relative to the\n * app origin (`/api4eventus/...`). The app shell can override it.\n */\nexport const EVENTUS_API_BASE_URL = new InjectionToken<string>(\n 'EVENTUS_API_BASE_URL',\n { providedIn: 'root', factory: () => '' },\n);\n","/** Pulls a human-readable message from an HttpErrorResponse carrying IApiError. */\nexport function extractErrorMessage(err: unknown, fallback: string): string {\n const body = (err as { error?: { message?: string } })?.error;\n return body?.message ?? fallback;\n}\n","import { InjectionToken } from '@angular/core';\nimport { ISlotClaimant } from '../models/bring-along';\n\n/**\n * The identity used when the current user claims or releases a bring-along\n * slot. Injecting this (rather than hard-coding) keeps claim/release testable\n * and decoupled from RSVP identity.\n *\n * TODO: replace the stub default with the real responder identity once RSVP\n * lands (Tasks 3/4) — claim/release should use the link-gated guest token or\n * the signed-in family/person ref instead of this placeholder.\n */\nexport const EVENTUS_CURRENT_CLAIMANT = new InjectionToken<ISlotClaimant>(\n 'EVENTUS_CURRENT_CLAIMANT',\n {\n providedIn: 'root',\n factory: () => ({ ref: 'stub-claimant', label: 'Smith Family' }),\n },\n);\n","import { InjectionToken } from '@angular/core';\nimport { IIdAndBriefAndDbo } from '@sneat/core';\nimport { Observable } from 'rxjs';\nimport {\n IBringAlongSlot,\n IClaimSlotRequest,\n IDefineSlotRequest,\n IReleaseSlotRequest,\n} from '../models/bring-along';\nimport {\n ICreateEventResponse,\n ICreateEventRequest,\n IEvent,\n IUpdateEventRequest,\n} from '../models/event';\nimport {\n IEventusEventBrief,\n IEventusEventDbo,\n IEventusEventListItem,\n} from '../models/eventus-event';\nimport { IAddInviteeRequest, IInvitation } from '../models/invitation';\nimport { IRsvp, IRsvpContext, ISubmitRsvpRequest } from '../models/rsvp';\nimport { IRsvpLink } from '../models/rsvp-link';\n\n// Runtime-light service contracts the eventus pages/space-menu depend on. Each\n// interface mirrors the public surface of the concrete service in the internal\n// lib; the implementation is bound to the matching token below via\n// provideEventusInternal(). Contract must never import from internal.\n\n/** Bespoke HTTP client for the Eventus event API (read/edit). */\nexport interface IEventService {\n listEvents(spaceID: string): Observable<IEvent[]>;\n getEvent(spaceID: string, eventID: string): Observable<IEvent>;\n updateEvent(\n spaceID: string,\n eventID: string,\n request: IUpdateEventRequest,\n ): Observable<IEvent>;\n cancelEvent(spaceID: string, eventID: string): Observable<IEvent>;\n}\n\nexport const EVENT_SERVICE = new InjectionToken<IEventService>('EventService');\n\n/** Firestore-direct access to the eventus hosting overlays + event creation. */\nexport interface IEventusEventService {\n watchEvents(spaceID: string): Observable<IEventusEventListItem[]>;\n createEvent(\n spaceID: string,\n request: ICreateEventRequest,\n ): Observable<ICreateEventResponse>;\n watchEventOverlays(\n spaceID: string,\n ): Observable<IIdAndBriefAndDbo<IEventusEventBrief, IEventusEventDbo>[]>;\n}\n\nexport const EVENTUS_EVENT_SERVICE = new InjectionToken<IEventusEventService>(\n 'EventusEventService',\n);\n\n/** Token-gated, public RSVP API client. */\nexport interface IRsvpService {\n resolveToken(token: string): Observable<IRsvpContext>;\n submitRsvp(token: string, request: ISubmitRsvpRequest): Observable<IRsvp>;\n updateRsvp(\n token: string,\n rsvpID: string,\n request: ISubmitRsvpRequest,\n ): Observable<IRsvp>;\n listRsvps(spaceID: string, eventID: string): Observable<IRsvp[]>;\n}\n\nexport const RSVP_SERVICE = new InjectionToken<IRsvpService>('RsvpService');\n\n/** Eventus invitations API client. */\nexport interface IInvitationService {\n addInvitee(\n spaceID: string,\n eventID: string,\n request: IAddInviteeRequest,\n ): Observable<IInvitation>;\n listInvitations(\n spaceID: string,\n eventID: string,\n ): Observable<IInvitation[]>;\n}\n\nexport const INVITATION_SERVICE = new InjectionToken<IInvitationService>(\n 'InvitationService',\n);\n\n/** Eventus links API client (host-only). */\nexport interface ILinkService {\n issueInviteeLink(\n spaceID: string,\n eventID: string,\n invitationID: string,\n ): Observable<IRsvpLink>;\n issueOpenLink(spaceID: string, eventID: string): Observable<IRsvpLink>;\n}\n\nexport const LINK_SERVICE = new InjectionToken<ILinkService>('LinkService');\n\n/** Eventus bring-along (slots) API client. */\nexport interface IBringAlongService {\n defineSlot(\n spaceID: string,\n eventID: string,\n request: IDefineSlotRequest,\n ): Observable<IBringAlongSlot>;\n listSlots(spaceID: string, eventID: string): Observable<IBringAlongSlot[]>;\n claimSlot(\n spaceID: string,\n eventID: string,\n slotID: string,\n request: IClaimSlotRequest,\n ): Observable<IBringAlongSlot>;\n releaseSlot(\n spaceID: string,\n eventID: string,\n slotID: string,\n request: IReleaseSlotRequest,\n ): Observable<IBringAlongSlot>;\n}\n\nexport const BRING_ALONG_SERVICE = new InjectionToken<IBringAlongService>(\n 'BringAlongService',\n);\n","import { InjectionToken } from '@angular/core';\nimport { Observable, of } from 'rxjs';\n\n/** A family the host can invite — id + display title only (no contact detail). */\nexport interface IInviteeFamily {\n readonly id: string;\n readonly title: string;\n}\n\n/** A person the host can invite — id + display name only (no contact detail). */\nexport interface IInviteePerson {\n readonly id: string;\n readonly name: string;\n}\n\n/**\n * Supplies the families and persons the host can pick from. Injecting this\n * (rather than calling `spaceus`/`contactus` directly) keeps the picker\n * testable and decoupled from those modules.\n */\nexport interface InviteeSource {\n /** Families (Sneat `family` spaces) available to invite. */\n families(spaceID: string): Observable<IInviteeFamily[]>;\n\n /** Persons (Sneat `contactus` contacts) available to invite. */\n persons(spaceID: string): Observable<IInviteePerson[]>;\n}\n\nexport const EVENTUS_INVITEE_SOURCE = new InjectionToken<InviteeSource>(\n 'EVENTUS_INVITEE_SOURCE',\n { providedIn: 'root', factory: () => new StubInviteeSource() },\n);\n\n/**\n * Default stub returning sample data so the picker is usable end-to-end before\n * real wiring. TODO: replace with an adapter over `spaceus` (family spaces) and\n * `contactus` (contacts) once those modules are available to the app shell.\n */\nexport class StubInviteeSource implements InviteeSource {\n families(): Observable<IInviteeFamily[]> {\n return of([\n { id: 'fam-sample-1', title: 'The Smith Family' },\n { id: 'fam-sample-2', title: 'The Brown Family' },\n ]);\n }\n\n persons(): Observable<IInviteePerson[]> {\n return of([\n { id: 'person-sample-1', name: 'Alice Smith' },\n { id: 'person-sample-2', name: 'Bob Brown' },\n ]);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@sneat/extension-eventus-contract",
3
+ "version": "0.0.1",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "peerDependencies": {
8
+ "@angular/core": "^21.0.0",
9
+ "rxjs": "^7.0.0",
10
+ "@sneat/core": "^0.11.0",
11
+ "@sneat/dto": "^0.11.0",
12
+ "@sneat/extension-calendarius-contract": "^0.12.1",
13
+ "vitest": "^4.0.9"
14
+ },
15
+ "sideEffects": false,
16
+ "module": "fesm2022/sneat-extension-eventus-contract.mjs",
17
+ "typings": "types/sneat-extension-eventus-contract.d.ts",
18
+ "exports": {
19
+ "./package.json": {
20
+ "default": "./package.json"
21
+ },
22
+ ".": {
23
+ "types": "./types/sneat-extension-eventus-contract.d.ts",
24
+ "default": "./fesm2022/sneat-extension-eventus-contract.mjs"
25
+ }
26
+ },
27
+ "type": "module",
28
+ "dependencies": {
29
+ "tslib": "^2.3.0"
30
+ }
31
+ }
@@ -0,0 +1,349 @@
1
+ import { IIdAndBriefAndDbo } from '@sneat/core';
2
+ import { IWithSpaceIDs, IWithCreated } from '@sneat/dto';
3
+ import { IHappeningBrief, IHappeningDbo } from '@sneat/extension-calendarius-contract';
4
+ import { InjectionToken } from '@angular/core';
5
+ import { Observable } from 'rxjs';
6
+
7
+ /**
8
+ * Who claimed a slot. Stored as an opaque reference plus a display label so the
9
+ * slot can render "Drinks — Smith Family" without resolving the reference.
10
+ */
11
+ interface ISlotClaimant {
12
+ /** Opaque claimant reference (e.g. a family space id, contact id, or guest token). */
13
+ readonly ref: string;
14
+ /** Display label shown next to the slot, e.g. "Smith Family". */
15
+ readonly label: string;
16
+ }
17
+ /** A bring-along item slot on an event. `claimedBy` absent => the slot is open. */
18
+ interface IBringAlongSlot {
19
+ /** Slot id, unique within its event. */
20
+ readonly id: string;
21
+ /** What's needed, e.g. "Drinks". */
22
+ readonly label: string;
23
+ /** Optional free-text quantity/notes, e.g. "2 bottles". */
24
+ readonly note?: string;
25
+ /** Present when claimed; absent means the slot is open. */
26
+ readonly claimedBy?: ISlotClaimant;
27
+ /** Audit: when the slot was defined (RFC3339). */
28
+ readonly createdAt: string;
29
+ }
30
+ /** POST body to define a slot. Host-only. */
31
+ interface IDefineSlotRequest {
32
+ label: string;
33
+ note?: string;
34
+ }
35
+ /** POST body to claim a slot. */
36
+ interface IClaimSlotRequest {
37
+ ref: string;
38
+ label: string;
39
+ }
40
+ /** POST body to release a slot — only the claiming `ref` may release. */
41
+ interface IReleaseSlotRequest {
42
+ ref: string;
43
+ }
44
+
45
+ /** Lifecycle status of an event. */
46
+ type EventStatus = 'active' | 'cancelled';
47
+ /**
48
+ * An Eventus event. Persisted by the backend at
49
+ * `/spaces/{spaceID}/ext/eventus/events/{eventID}`.
50
+ */
51
+ interface IEvent {
52
+ /** Event id, unique within its host space. */
53
+ readonly id: string;
54
+ /** Host space id this event belongs to. */
55
+ readonly spaceID: string;
56
+ /** Display name, e.g. "Sophie's 10th Birthday". */
57
+ readonly title: string;
58
+ /** Event start date/time as an RFC3339 / ISO string. */
59
+ readonly start: string;
60
+ /** Free-text location. */
61
+ readonly location: string;
62
+ /** Optional free-text description. */
63
+ readonly description?: string;
64
+ /** Lifecycle status; `active` on creation. */
65
+ readonly status: EventStatus;
66
+ /** Audit: creation timestamp (RFC3339). */
67
+ readonly createdAt: string;
68
+ /** Audit: last-modification timestamp (RFC3339). */
69
+ readonly updatedAt: string;
70
+ }
71
+ /** POST body to create an event. */
72
+ interface ICreateEventRequest {
73
+ /** Display name (required, non-empty). */
74
+ title: string;
75
+ /** Event start date/time (RFC3339). */
76
+ start: string;
77
+ /** Free-text location (required, non-empty). */
78
+ location: string;
79
+ /** Optional free-text description. */
80
+ description?: string;
81
+ /**
82
+ * Event length in minutes. Optional — when omitted the backend's calendarius
83
+ * facade applies its default (60m).
84
+ */
85
+ durationMinutes?: number;
86
+ }
87
+ /**
88
+ * Response of the create-event endpoint. The event itself is read
89
+ * Firestore-direct (happening + overlay); the create response only identifies
90
+ * the new happening so the UI can navigate to it.
91
+ */
92
+ interface ICreateEventResponse {
93
+ /** New event/happening id. */
94
+ readonly id: string;
95
+ }
96
+ /**
97
+ * PUT body to edit an event. All fields optional; only present fields are
98
+ * updated. Does not change `spaceID` or `status` (use cancel for status).
99
+ */
100
+ interface IUpdateEventRequest {
101
+ title?: string;
102
+ start?: string;
103
+ location?: string;
104
+ description?: string;
105
+ }
106
+ /** Standard error body returned by the backend with a non-2xx status. */
107
+ interface IApiError {
108
+ /** Machine-readable error code, e.g. `unauthorized`, `not_found`. */
109
+ code: string;
110
+ /** Human-readable message. */
111
+ message: string;
112
+ }
113
+
114
+ interface IEventusEventBrief {
115
+ /** listus list id holding the bring-along / give-away items, when attached. */
116
+ readonly bringAlongListID?: string;
117
+ }
118
+ type IEventusEventDbo = IEventusEventBrief & IWithSpaceIDs & IWithCreated;
119
+ type EventOverlay = IIdAndBriefAndDbo<IEventusEventBrief, IEventusEventDbo>;
120
+ type Happening = IIdAndBriefAndDbo<IHappeningBrief, IHappeningDbo>;
121
+ /** A row for the eventus events list: a happening that is an eventus event. */
122
+ interface IEventusEventListItem {
123
+ /** happeningID (overlay and happening share this id). */
124
+ readonly id: string;
125
+ readonly title: string;
126
+ /** Display start: ISO from the single happening, or `date[ time]` from its slot. */
127
+ readonly start?: string;
128
+ readonly happening: Happening;
129
+ readonly overlay?: EventOverlay;
130
+ }
131
+ /**
132
+ * Pure join: the events list is the set of happenings that have an eventus
133
+ * overlay (interim "is-event" marker), projected to list rows. Kept pure so the
134
+ * join — the bug-prone part — is unit-testable without Firestore.
135
+ */
136
+ declare function selectEventListItems(overlays: readonly EventOverlay[], happenings: readonly Happening[]): IEventusEventListItem[];
137
+
138
+ /** Whether an invitation targets a whole family (space) or a single person. */
139
+ type InviteeType = 'family' | 'person';
140
+ /** An invitation of a family or person to an event. */
141
+ interface IInvitation {
142
+ /** Invitation id, unique within its event. */
143
+ readonly id: string;
144
+ /** Discriminates which reference field is populated. */
145
+ readonly inviteeType: InviteeType;
146
+ /** Set when `inviteeType === 'family'`: the invited family's space id. */
147
+ readonly familySpaceID?: string;
148
+ /** Set when `inviteeType === 'person'`: the invited contact's id. */
149
+ readonly contactID?: string;
150
+ /** Audit: when the invitation was added (RFC3339). */
151
+ readonly createdAt: string;
152
+ }
153
+ /**
154
+ * POST body to add one invitee. Exactly one of `familySpaceID` / `contactID`
155
+ * is set, matching `inviteeType`. No name or contact detail is sent — only id.
156
+ */
157
+ interface IAddInviteeRequest {
158
+ inviteeType: InviteeType;
159
+ familySpaceID?: string;
160
+ contactID?: string;
161
+ }
162
+
163
+ /** `invitee` = bound to a single invitation; `open` = shared event link. */
164
+ type LinkKind = 'invitee' | 'open';
165
+ /** A tokenized RSVP link (+ url for QR). */
166
+ interface IRsvpLink {
167
+ /** Opaque link token. */
168
+ readonly token: string;
169
+ /** Full shareable RSVP URL, e.g. `https://eventus.sneat.app/r/{token}`. */
170
+ readonly url: string;
171
+ /** Whether the link targets one invitee or is the open event link. */
172
+ readonly kind: LinkKind;
173
+ }
174
+
175
+ /** Responder's attendance answer. */
176
+ type RsvpStatus = 'yes' | 'no' | 'maybe';
177
+ /** A submitted RSVP. */
178
+ interface IRsvp {
179
+ /** RSVP id, unique within its event. */
180
+ readonly id: string;
181
+ /** Invitation this RSVP answers (per-invitee link); empty for an open link. */
182
+ readonly invitationID?: string;
183
+ /** Attribution for an open-link response: the responder's self-identified name. */
184
+ readonly selfIdentifiedName?: string;
185
+ /** Optional self-identified family name (open link). */
186
+ readonly selfIdentifiedFamilyName?: string;
187
+ readonly status: RsvpStatus;
188
+ /** Adults attending. Forced to 0 by the backend when `status == 'no'`. */
189
+ readonly adults: number;
190
+ /** Children attending. Forced to 0 by the backend when `status == 'no'`. */
191
+ readonly children: number;
192
+ /** Optional dietary/allergy notes. */
193
+ readonly dietary?: string;
194
+ /** Optional free-text comment. */
195
+ readonly comment?: string;
196
+ /** True when submitted by a signed-in Sneat account. */
197
+ readonly viaAccount: boolean;
198
+ /** Audit: submission time (RFC3339). */
199
+ readonly submittedAt: string;
200
+ }
201
+ /** POST body to submit an RSVP. Headcounts are zeroed by the backend when `status == 'no'`. */
202
+ interface ISubmitRsvpRequest {
203
+ status: RsvpStatus;
204
+ adults: number;
205
+ children: number;
206
+ dietary?: string;
207
+ comment?: string;
208
+ /**
209
+ * REQUIRED for an open-link token (the responder self-identifies); omitted for
210
+ * a per-invitee token (attribution comes from the invitation). Never a
211
+ * guest-list selection — only this free-text name. (AC: attribution-by-link-type)
212
+ */
213
+ selfIdentifiedName?: string;
214
+ /** Optional self-identified family name (open link). */
215
+ selfIdentifiedFamilyName?: string;
216
+ }
217
+ /**
218
+ * Build the submit body from raw form values. When `status == 'no'` the
219
+ * headcounts are forced to 0 (the backend zeroes them anyway), and blank
220
+ * dietary/comment/self-identified names are dropped. For an open-link response
221
+ * the trimmed `selfIdentifiedName` is carried through (the caller enforces it is
222
+ * non-empty). Pure so it can be unit-tested without rendering.
223
+ * (AC: no-rsvp-zeroes-headcount, rsvp-full-fields-captured, attribution-by-link-type)
224
+ */
225
+ declare function buildRsvpRequest(form: {
226
+ status: RsvpStatus;
227
+ adults: number;
228
+ children: number;
229
+ dietary: string;
230
+ comment: string;
231
+ selfIdentifiedName?: string;
232
+ selfIdentifiedFamilyName?: string;
233
+ }): ISubmitRsvpRequest;
234
+ /**
235
+ * Public context returned when a token is resolved — only what the RSVP form
236
+ * needs. NEVER includes the guest list or any other invitee.
237
+ */
238
+ interface IRsvpContext {
239
+ /** Display fields for the event. */
240
+ readonly eventTitle: string;
241
+ readonly eventStart: string;
242
+ readonly eventLocation: string;
243
+ /** Whether the event still accepts RSVPs (false once cancelled). */
244
+ readonly open: boolean;
245
+ /** `invitee` → pre-attributed to one invitee; `open` → responder self-identifies. */
246
+ readonly kind: LinkKind;
247
+ /** Set only when `kind == 'invitee'`: the invitation this link is bound to. */
248
+ readonly invitationID?: string;
249
+ }
250
+
251
+ /**
252
+ * Base URL the Eventus API is served from (without a trailing slash), e.g.
253
+ * `https://api.sneat.app`. Defaults to '' so requests are relative to the
254
+ * app origin (`/api4eventus/...`). The app shell can override it.
255
+ */
256
+ declare const EVENTUS_API_BASE_URL: InjectionToken<string>;
257
+
258
+ /** Pulls a human-readable message from an HttpErrorResponse carrying IApiError. */
259
+ declare function extractErrorMessage(err: unknown, fallback: string): string;
260
+
261
+ /**
262
+ * The identity used when the current user claims or releases a bring-along
263
+ * slot. Injecting this (rather than hard-coding) keeps claim/release testable
264
+ * and decoupled from RSVP identity.
265
+ *
266
+ * TODO: replace the stub default with the real responder identity once RSVP
267
+ * lands (Tasks 3/4) — claim/release should use the link-gated guest token or
268
+ * the signed-in family/person ref instead of this placeholder.
269
+ */
270
+ declare const EVENTUS_CURRENT_CLAIMANT: InjectionToken<ISlotClaimant>;
271
+
272
+ /** Bespoke HTTP client for the Eventus event API (read/edit). */
273
+ interface IEventService {
274
+ listEvents(spaceID: string): Observable<IEvent[]>;
275
+ getEvent(spaceID: string, eventID: string): Observable<IEvent>;
276
+ updateEvent(spaceID: string, eventID: string, request: IUpdateEventRequest): Observable<IEvent>;
277
+ cancelEvent(spaceID: string, eventID: string): Observable<IEvent>;
278
+ }
279
+ declare const EVENT_SERVICE: InjectionToken<IEventService>;
280
+ /** Firestore-direct access to the eventus hosting overlays + event creation. */
281
+ interface IEventusEventService {
282
+ watchEvents(spaceID: string): Observable<IEventusEventListItem[]>;
283
+ createEvent(spaceID: string, request: ICreateEventRequest): Observable<ICreateEventResponse>;
284
+ watchEventOverlays(spaceID: string): Observable<IIdAndBriefAndDbo<IEventusEventBrief, IEventusEventDbo>[]>;
285
+ }
286
+ declare const EVENTUS_EVENT_SERVICE: InjectionToken<IEventusEventService>;
287
+ /** Token-gated, public RSVP API client. */
288
+ interface IRsvpService {
289
+ resolveToken(token: string): Observable<IRsvpContext>;
290
+ submitRsvp(token: string, request: ISubmitRsvpRequest): Observable<IRsvp>;
291
+ updateRsvp(token: string, rsvpID: string, request: ISubmitRsvpRequest): Observable<IRsvp>;
292
+ listRsvps(spaceID: string, eventID: string): Observable<IRsvp[]>;
293
+ }
294
+ declare const RSVP_SERVICE: InjectionToken<IRsvpService>;
295
+ /** Eventus invitations API client. */
296
+ interface IInvitationService {
297
+ addInvitee(spaceID: string, eventID: string, request: IAddInviteeRequest): Observable<IInvitation>;
298
+ listInvitations(spaceID: string, eventID: string): Observable<IInvitation[]>;
299
+ }
300
+ declare const INVITATION_SERVICE: InjectionToken<IInvitationService>;
301
+ /** Eventus links API client (host-only). */
302
+ interface ILinkService {
303
+ issueInviteeLink(spaceID: string, eventID: string, invitationID: string): Observable<IRsvpLink>;
304
+ issueOpenLink(spaceID: string, eventID: string): Observable<IRsvpLink>;
305
+ }
306
+ declare const LINK_SERVICE: InjectionToken<ILinkService>;
307
+ /** Eventus bring-along (slots) API client. */
308
+ interface IBringAlongService {
309
+ defineSlot(spaceID: string, eventID: string, request: IDefineSlotRequest): Observable<IBringAlongSlot>;
310
+ listSlots(spaceID: string, eventID: string): Observable<IBringAlongSlot[]>;
311
+ claimSlot(spaceID: string, eventID: string, slotID: string, request: IClaimSlotRequest): Observable<IBringAlongSlot>;
312
+ releaseSlot(spaceID: string, eventID: string, slotID: string, request: IReleaseSlotRequest): Observable<IBringAlongSlot>;
313
+ }
314
+ declare const BRING_ALONG_SERVICE: InjectionToken<IBringAlongService>;
315
+
316
+ /** A family the host can invite — id + display title only (no contact detail). */
317
+ interface IInviteeFamily {
318
+ readonly id: string;
319
+ readonly title: string;
320
+ }
321
+ /** A person the host can invite — id + display name only (no contact detail). */
322
+ interface IInviteePerson {
323
+ readonly id: string;
324
+ readonly name: string;
325
+ }
326
+ /**
327
+ * Supplies the families and persons the host can pick from. Injecting this
328
+ * (rather than calling `spaceus`/`contactus` directly) keeps the picker
329
+ * testable and decoupled from those modules.
330
+ */
331
+ interface InviteeSource {
332
+ /** Families (Sneat `family` spaces) available to invite. */
333
+ families(spaceID: string): Observable<IInviteeFamily[]>;
334
+ /** Persons (Sneat `contactus` contacts) available to invite. */
335
+ persons(spaceID: string): Observable<IInviteePerson[]>;
336
+ }
337
+ declare const EVENTUS_INVITEE_SOURCE: InjectionToken<InviteeSource>;
338
+ /**
339
+ * Default stub returning sample data so the picker is usable end-to-end before
340
+ * real wiring. TODO: replace with an adapter over `spaceus` (family spaces) and
341
+ * `contactus` (contacts) once those modules are available to the app shell.
342
+ */
343
+ declare class StubInviteeSource implements InviteeSource {
344
+ families(): Observable<IInviteeFamily[]>;
345
+ persons(): Observable<IInviteePerson[]>;
346
+ }
347
+
348
+ export { BRING_ALONG_SERVICE, EVENTUS_API_BASE_URL, EVENTUS_CURRENT_CLAIMANT, EVENTUS_EVENT_SERVICE, EVENTUS_INVITEE_SOURCE, EVENT_SERVICE, INVITATION_SERVICE, LINK_SERVICE, RSVP_SERVICE, StubInviteeSource, buildRsvpRequest, extractErrorMessage, selectEventListItems };
349
+ export type { EventStatus, IAddInviteeRequest, IApiError, IBringAlongService, IBringAlongSlot, IClaimSlotRequest, ICreateEventRequest, ICreateEventResponse, IDefineSlotRequest, IEvent, IEventService, IEventusEventBrief, IEventusEventDbo, IEventusEventListItem, IEventusEventService, IInvitation, IInvitationService, IInviteeFamily, IInviteePerson, ILinkService, IReleaseSlotRequest, IRsvp, IRsvpContext, IRsvpLink, IRsvpService, ISlotClaimant, ISubmitRsvpRequest, IUpdateEventRequest, InviteeSource, InviteeType, LinkKind, RsvpStatus };