@acms-subir/client 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,7 +45,8 @@ site before shipping an app build with a newer client.
45
45
  | `calendars()` | Public calendars |
46
46
  | `resources(opts)`, `resource(slug, opts)` | Public resources; slug lookup scans list pages |
47
47
  | `availability(opts)` | Required `resource`, `link`; optional `from`, `days`, `minutes` |
48
- | `book(opts)` | Resource booking or RSVP input, optional `idempotencyKey` |
48
+ | `rsvpStatus(slug, { start })` | One event date's RSVP status: `open` or `reason` (`disabled`, `ended`, `cancelled`, `full`), live `going`/`left`, `maxSeats` for one RSVP right now, `autoConfirm`, and `form` (questions and `version`, or `null`). `start` is the occurrence's start exactly as `events()` returns it (a UTC ISO string such as `2026-10-05T14:00:00.000Z`). Never cached |
49
+ | `book(opts)` | Resource booking or RSVP input (`RsvpInput` for an RSVP), optional `idempotencyKey` |
49
50
  | `openapi()` | Raw OpenAPI document |
50
51
  | `registerDevice({ token, platform, locale?, topics?, channels? })` | Register or refresh an Expo push token (call on every app start); `{ created, device }`. `channels`: Android channels the app created (e.g. `['urgent']`) |
51
52
  | `updateDevice(token, { topics?, locale?, channels? })` | Change a device's topics, language or channels |
@@ -87,6 +88,8 @@ server results or provide snapshot consistency; deduplicate IDs in your app.
87
88
  ## Bookings, errors and cancellation
88
89
 
89
90
  ```ts
91
+ import type { RsvpInput } from '@acms-subir/client';
92
+
90
93
  // Persist a UUID v4 with this booking intent before submitting; reuse it until
91
94
  // the outcome is known. Use your platform's secure UUID/storage facilities.
92
95
  const idempotencyKey = crypto.randomUUID();
@@ -103,6 +106,25 @@ try {
103
106
  }
104
107
  }
105
108
 
109
+ // RSVP to one date of an event (needs only the calendar: site.features.rsvp)
110
+ const { occurrences } = await client.events({ q: 'supper' });
111
+ const date = occurrences[0];
112
+ const status = await client.rsvpStatus(date.event.slug, { start: date.start });
113
+ if (status.open) {
114
+ const rsvp: RsvpInput = {
115
+ event: date.event.slug, start: date.start, seats: 2, name: 'Example Guest', email: 'guest@example.com',
116
+ // field id → answer; checkbox groups send a list
117
+ answers: { meal: 'Vegetarian', help: ['Setup', 'Music'] },
118
+ // required when the event asks questions: a changed form answers 409, so read the status again
119
+ ...(status.form ? { formVersion: status.form.version } : {}),
120
+ };
121
+ const result = await client.book({ ...rsvp, idempotencyKey: crypto.randomUUID() });
122
+ // pending when the organiser approves RSVPs; status.form?.confirmation is the thank-you text.
123
+ // result.manageToken changes or cancels it later (manageBooking, cancelBooking), like the email's link:
124
+ // keep it in secure storage only.
125
+ console.log(result.booking.status);
126
+ }
127
+
106
128
  const controller = new AbortController();
107
129
  const pending = client.home({ signal: controller.signal });
108
130
  controller.abort();
@@ -172,7 +194,7 @@ if (current?.status === 'live') console.log(`Live: ${current.title} → ${curren
172
194
  ETag revalidation defaults to an in-memory URL-keyed cache. Each read contacts
173
195
  the server with `If-None-Match`; a 304 reuses the saved data. This is not an
174
196
  offline fallback. Treat returned cached objects as immutable. Use `cache: false`
175
- to disable it. Availability, live status, booking results and errors are never cached.
197
+ to disable it. Availability, live status, RSVP status, booking results and errors are never cached.
176
198
  Responses without an ETag or with `no-store` invalidate an older entry.
177
199
 
178
200
  ```ts
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Site, Theme, Page, Content, Post, ContentList, SearchResults, Links, Form, FormReceipt, FormResponseInput, ManagedBooking, RescheduleInput, ManageAvailabilityQuery, GuestCodeSent, GuestToken, GuestBookings, GuestBookingsQuery, GuestSignedOut, Events, Event, Calendars, Resources, Resource, Availability, BookingInput, BookingResult, Device, DeviceInput, DeviceUpdate, DeviceRegistration, DeviceRemoved, PushTopics, LiveStatus, Announcements, AnnouncementDetail } from './types.js';
1
+ import type { Site, Theme, Page, Content, Post, ContentList, SearchResults, Links, Form, FormReceipt, FormResponseInput, ManagedBooking, RescheduleInput, ManageAvailabilityQuery, GuestCodeSent, GuestToken, GuestBookings, GuestBookingsQuery, GuestSignedOut, Events, Event, Calendars, Resources, Resource, Availability, BookingInput, BookingResult, RsvpStatus, Device, DeviceInput, DeviceUpdate, DeviceRegistration, DeviceRemoved, PushTopics, LiveStatus, Announcements, AnnouncementDetail } from './types.js';
2
2
  export { AcmsError, isAcmsError } from './errors.js';
3
3
  export type * from './types.js';
4
4
  export * from './editor.js';
@@ -56,6 +56,10 @@ export interface AvailabilityOptions extends RequestOptions {
56
56
  days?: number;
57
57
  minutes?: number;
58
58
  }
59
+ /** `start`: the occurrence's start exactly as `events()` returns it, a UTC ISO string such as `'2026-10-05T14:00:00.000Z'`. */
60
+ export interface RsvpStatusOptions extends RequestOptions {
61
+ start: string;
62
+ }
59
63
  export type BookOptions = BookingInput & RequestOptions & {
60
64
  idempotencyKey?: string;
61
65
  };
@@ -125,6 +129,10 @@ export declare const endpoints: {
125
129
  readonly method: "GET";
126
130
  readonly path: "/calendars";
127
131
  };
132
+ readonly rsvpStatus: {
133
+ readonly method: "GET";
134
+ readonly path: "/events/{slug}/rsvp";
135
+ };
128
136
  readonly resources: {
129
137
  readonly method: "GET";
130
138
  readonly path: "/resources";
@@ -241,6 +249,12 @@ export interface AcmsClient {
241
249
  resources(o?: ListOptions): Promise<Resources>;
242
250
  resource(slug: string, o?: RequestOptions): Promise<Resource>;
243
251
  availability(o: AvailabilityOptions): Promise<Availability>;
252
+ /**
253
+ * One date's RSVP status: `open` (or `reason`: disabled, ended, cancelled, full), live `going`/`left`,
254
+ * `maxSeats` for one RSVP right now, and `form` (questions and `version`, or null). Never cached.
255
+ * RSVP with `book({ event, start, seats, name, email, answers, formVersion: form.version })`.
256
+ */
257
+ rsvpStatus(slug: string, o: RsvpStatusOptions): Promise<RsvpStatus>;
244
258
  book(o: BookOptions): Promise<BookingResult>;
245
259
  openapi(o?: RequestOptions): Promise<OpenApiDocument>;
246
260
  /** Register or refresh this device's Expo push token. Call on every app start. Requires push on the site. */
package/dist/index.js CHANGED
@@ -8,6 +8,8 @@ export const endpoints = {
8
8
  content: { method: 'GET', path: '/content' }, contentById: { method: 'GET', path: '/content/{id}' },
9
9
  search: { method: 'GET', path: '/search' },
10
10
  events: { method: 'GET', path: '/events' }, calendars: { method: 'GET', path: '/calendars' },
11
+ // an event date's RSVP status: live seats and questions (never cached); RSVP with book()
12
+ rsvpStatus: { method: 'GET', path: '/events/{slug}/rsvp' },
11
13
  resources: { method: 'GET', path: '/resources' }, availability: { method: 'GET', path: '/availability' },
12
14
  book: { method: 'POST', path: '/bookings' }, openapi: { method: 'GET', path: '/openapi.json' },
13
15
  // push notifications (only when the site enables push; 404 otherwise)
@@ -28,7 +30,7 @@ export const endpoints = {
28
30
  guestBookings: { method: 'GET', path: '/guest/bookings' }, guestSignOut: { method: 'POST', path: '/guest/revoke' },
29
31
  };
30
32
  // site: its legal links; pushTopics: topic names
31
- const localized = new Set(['home', 'page', 'content', 'contentById', 'search', 'events', 'site', 'pushTopics', 'announcements', 'announcement']);
33
+ const localized = new Set(['home', 'page', 'content', 'contentById', 'search', 'events', 'rsvpStatus', 'site', 'pushTopics', 'announcements', 'announcement']);
32
34
  // localized since core 0.3.32; older sites answer ?locale= with a 400, so ask them again without it
33
35
  const lateLocalized = new Set(['site', 'pushTopics']);
34
36
  function uuid() {
@@ -112,8 +114,8 @@ export function createClient(options) {
112
114
  opts.signal?.addEventListener('abort', abort, { once: true });
113
115
  const timer = setTimeout(() => controller.abort(new AcmsError(0, 'timeout', 'Request timed out.', undefined, undefined, key)), timeoutMs);
114
116
  const signal = controller.signal;
115
- // availability and live status are never served from the cache
116
- const cacheable = endpoint.method === 'GET' && name !== 'availability' && name !== 'live' && !extra.headers && cache;
117
+ // availability, live status and RSVP seats are never served from the cache
118
+ const cacheable = endpoint.method === 'GET' && name !== 'availability' && name !== 'live' && name !== 'rsvpStatus' && !extra.headers && cache;
117
119
  // only replay what the server treats as repeatable: a lost reply to a code
118
120
  // exchange or a move must not run it twice
119
121
  const replayable = endpoint.method !== 'POST' || !!key || name === 'registerDevice';
@@ -277,6 +279,7 @@ export function createClient(options) {
277
279
  },
278
280
  calendars: (o = {}) => request('calendars', o),
279
281
  availability: (o) => request('availability', o, { resource: o.resource, link: o.link, from: o.from, days: o.days, minutes: o.minutes }),
282
+ rsvpStatus: (slug, o) => request('rsvpStatus', o, { start: o.start }, slug),
280
283
  book: (o) => {
281
284
  const { locale, signal, idempotencyKey, ...body } = o;
282
285
  const key = idempotencyKey ?? defaultHeaders.get('Idempotency-Key') ?? uuid();
package/dist/types.d.ts CHANGED
@@ -29,6 +29,7 @@ export type Site = {
29
29
  }>;
30
30
  "calendar": boolean;
31
31
  "bookings": boolean;
32
+ "rsvp": boolean;
32
33
  "search": boolean;
33
34
  "bookingChanges": boolean;
34
35
  "guestSignIn": boolean;
@@ -591,6 +592,40 @@ export type FormReceipt = {
591
592
  "received": true;
592
593
  "confirmation": string;
593
594
  };
595
+ export type RsvpStatus = {
596
+ "event": {
597
+ "id": string;
598
+ "slug": string;
599
+ "title": string;
600
+ };
601
+ "start": string;
602
+ "end": string;
603
+ "open": boolean;
604
+ "reason": ("disabled" | "ended" | "cancelled" | "full") | (null);
605
+ "going": number;
606
+ "left": (number) | (null);
607
+ "capacity": number;
608
+ "maxSeats": number;
609
+ "autoConfirm": boolean;
610
+ "form": ({
611
+ "version": string;
612
+ "title": string;
613
+ "intro": string;
614
+ "submitLabel": string;
615
+ "confirmation": string;
616
+ "fields": Array<{
617
+ "id": string;
618
+ "type": "text" | "textarea" | "email" | "phone" | "number" | "select" | "checkbox" | "date" | "radio" | "checkboxes" | "url" | "consent" | "heading";
619
+ "label": string;
620
+ "required": boolean;
621
+ "help": string;
622
+ "options": Array<string>;
623
+ "placeholder": string;
624
+ "width": "full" | "half";
625
+ "allowOther": boolean;
626
+ }>;
627
+ }) | (null);
628
+ };
594
629
  export type ManagedBooking = {
595
630
  "booking": {
596
631
  "id": string;
@@ -634,6 +669,7 @@ export type ManagedBooking = {
634
669
  "cutoffHours": number;
635
670
  "closed": boolean;
636
671
  };
672
+ "confirmation": string | null;
637
673
  };
638
674
  export type GuestCodeSent = {
639
675
  "sent": true;
@@ -774,7 +810,7 @@ export type BookingResult = {
774
810
  [key: string]: string;
775
811
  };
776
812
  };
777
- "source": "web" | "admin" | "api" | "agent";
813
+ "source": "web" | "app" | "admin" | "api" | "agent";
778
814
  "start": string;
779
815
  "end": string;
780
816
  "resourceId": string | null;
@@ -1332,7 +1368,7 @@ export type EditorBooking = {
1332
1368
  [key: string]: string;
1333
1369
  };
1334
1370
  };
1335
- "source": "web" | "admin" | "api" | "agent";
1371
+ "source": "web" | "app" | "admin" | "api" | "agent";
1336
1372
  "notes": string;
1337
1373
  "start": string;
1338
1374
  "end": string;
@@ -1379,7 +1415,7 @@ export type EditorBookingList = {
1379
1415
  [key: string]: string;
1380
1416
  };
1381
1417
  };
1382
- "source": "web" | "admin" | "api" | "agent";
1418
+ "source": "web" | "app" | "admin" | "api" | "agent";
1383
1419
  "notes": string;
1384
1420
  "start": string;
1385
1421
  "end": string;
@@ -1924,22 +1960,22 @@ export type BookingInput = ({
1924
1960
  "name": string;
1925
1961
  "email": string;
1926
1962
  "phone"?: string;
1963
+ "answers"?: {
1964
+ [key: string]: (string) | (Array<string>);
1965
+ };
1927
1966
  "resource": string;
1928
1967
  "link": string;
1929
1968
  "minutes"?: number;
1930
- "answers"?: {
1931
- [key: string]: string;
1932
- };
1933
1969
  }) | ({
1934
1970
  "start": string;
1935
1971
  "name": string;
1936
1972
  "email": string;
1937
1973
  "phone"?: string;
1938
- "event": string;
1939
- "seats"?: number;
1940
1974
  "answers"?: {
1941
- [key: string]: string;
1975
+ [key: string]: (string) | (Array<string>);
1942
1976
  };
1977
+ "event": string;
1978
+ "seats"?: number;
1943
1979
  "formVersion"?: string;
1944
1980
  });
1945
1981
  export type DeviceInput = {
@@ -2429,4 +2465,8 @@ export type SearchEventHit = SearchResults["events"][number];
2429
2465
  export type LinkItem = Links["items"][number];
2430
2466
  export type FormField = Form["fields"][number];
2431
2467
  export type GuestBooking = GuestBookings["items"][number];
2468
+ export type RsvpInput = Extract<BookingInput, {
2469
+ "event": string;
2470
+ }>;
2471
+ export type RsvpForm = NonNullable<RsvpStatus["form"]>;
2432
2472
  export type LiveDisplay = LiveStatus["display"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acms-subir/client",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Typed, dependency-free client for the ACMS public mobile API.",
5
5
  "type": "module",
6
6
  "license": "MIT",