@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 +24 -2
- package/dist/index.d.ts +15 -1
- package/dist/index.js +6 -3
- package/dist/types.d.ts +49 -9
- package/package.json +1 -1
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
|
-
| `
|
|
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
|
|
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"];
|