@acms-subir/client 0.1.14 → 0.1.16
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 +29 -4
- package/dist/index.d.ts +16 -1
- package/dist/index.js +21 -6
- package/dist/types.d.ts +60 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,8 +27,11 @@ raw document. Every method accepts `locale` and `signal` in its options object.
|
|
|
27
27
|
Locale is sent only for routes that support translations. `withLocale` shares
|
|
28
28
|
the cache and transport configuration with the original client. `site()` and
|
|
29
29
|
`pushTopics()` take a language since core 0.3.32; an older site rejects it, so
|
|
30
|
-
the client asks once more without it and gets the main language.
|
|
31
|
-
|
|
30
|
+
the client asks once more without it and gets the main language. `book()`
|
|
31
|
+
sends the client's `locale` in its body, so the booking's emails (and the links
|
|
32
|
+
in them) speak the app's language; a site before core 0.3.38 refuses that field,
|
|
33
|
+
so the client books once more without it, with the same key, in the site's main
|
|
34
|
+
language. Update the site before shipping an app build with a newer client.
|
|
32
35
|
|
|
33
36
|
| Method | Options / behavior |
|
|
34
37
|
| --- | --- |
|
|
@@ -45,7 +48,8 @@ site before shipping an app build with a newer client.
|
|
|
45
48
|
| `calendars()` | Public calendars |
|
|
46
49
|
| `resources(opts)`, `resource(slug, opts)` | Public resources; slug lookup scans list pages |
|
|
47
50
|
| `availability(opts)` | Required `resource`, `link`; optional `from`, `days`, `minutes` |
|
|
48
|
-
| `
|
|
51
|
+
| `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 |
|
|
52
|
+
| `book(opts)` | Resource booking or RSVP input (`RsvpInput` for an RSVP), optional `idempotencyKey`; sends the client's `locale` (or `opts.locale`) for its emails |
|
|
49
53
|
| `openapi()` | Raw OpenAPI document |
|
|
50
54
|
| `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
55
|
| `updateDevice(token, { topics?, locale?, channels? })` | Change a device's topics, language or channels |
|
|
@@ -87,6 +91,8 @@ server results or provide snapshot consistency; deduplicate IDs in your app.
|
|
|
87
91
|
## Bookings, errors and cancellation
|
|
88
92
|
|
|
89
93
|
```ts
|
|
94
|
+
import type { RsvpInput } from '@acms-subir/client';
|
|
95
|
+
|
|
90
96
|
// Persist a UUID v4 with this booking intent before submitting; reuse it until
|
|
91
97
|
// the outcome is known. Use your platform's secure UUID/storage facilities.
|
|
92
98
|
const idempotencyKey = crypto.randomUUID();
|
|
@@ -103,6 +109,25 @@ try {
|
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
|
|
112
|
+
// RSVP to one date of an event (needs only the calendar: site.features.rsvp)
|
|
113
|
+
const { occurrences } = await client.events({ q: 'supper' });
|
|
114
|
+
const date = occurrences[0];
|
|
115
|
+
const status = await client.rsvpStatus(date.event.slug, { start: date.start });
|
|
116
|
+
if (status.open) {
|
|
117
|
+
const rsvp: RsvpInput = {
|
|
118
|
+
event: date.event.slug, start: date.start, seats: 2, name: 'Example Guest', email: 'guest@example.com',
|
|
119
|
+
// field id → answer; checkbox groups send a list
|
|
120
|
+
answers: { meal: 'Vegetarian', help: ['Setup', 'Music'] },
|
|
121
|
+
// required when the event asks questions: a changed form answers 409, so read the status again
|
|
122
|
+
...(status.form ? { formVersion: status.form.version } : {}),
|
|
123
|
+
};
|
|
124
|
+
const result = await client.book({ ...rsvp, idempotencyKey: crypto.randomUUID() });
|
|
125
|
+
// pending when the organiser approves RSVPs; status.form?.confirmation is the thank-you text.
|
|
126
|
+
// result.manageToken changes or cancels it later (manageBooking, cancelBooking), like the email's link:
|
|
127
|
+
// keep it in secure storage only.
|
|
128
|
+
console.log(result.booking.status);
|
|
129
|
+
}
|
|
130
|
+
|
|
106
131
|
const controller = new AbortController();
|
|
107
132
|
const pending = client.home({ signal: controller.signal });
|
|
108
133
|
controller.abort();
|
|
@@ -172,7 +197,7 @@ if (current?.status === 'live') console.log(`Live: ${current.title} → ${curren
|
|
|
172
197
|
ETag revalidation defaults to an in-memory URL-keyed cache. Each read contacts
|
|
173
198
|
the server with `If-None-Match`; a 304 reuses the saved data. This is not an
|
|
174
199
|
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.
|
|
200
|
+
to disable it. Availability, live status, RSVP status, booking results and errors are never cached.
|
|
176
201
|
Responses without an ETag or with `no-store` invalidate an older entry.
|
|
177
202
|
|
|
178
203
|
```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,13 @@ 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>;
|
|
258
|
+
/** A room booking or an RSVP. Sends the client's `locale` (or `o.locale`), so its emails and links speak it. */
|
|
244
259
|
book(o: BookOptions): Promise<BookingResult>;
|
|
245
260
|
openapi(o?: RequestOptions): Promise<OpenApiDocument>;
|
|
246
261
|
/** 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,10 +279,23 @@ 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 }),
|
|
280
|
-
|
|
281
|
-
|
|
282
|
+
rsvpStatus: (slug, o) => request('rsvpStatus', o, { start: o.start }, slug),
|
|
283
|
+
async book(o) {
|
|
284
|
+
const { locale = options.locale, signal, idempotencyKey, ...body } = o;
|
|
282
285
|
const key = idempotencyKey ?? defaultHeaders.get('Idempotency-Key') ?? uuid();
|
|
283
|
-
|
|
286
|
+
// the client's language rides in the body: the booking's emails and links speak it
|
|
287
|
+
if (locale === undefined)
|
|
288
|
+
return request('book', { signal }, {}, undefined, body, key);
|
|
289
|
+
try {
|
|
290
|
+
return await request('book', { signal }, {}, undefined, { ...body, locale }, key);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
// sites before core 0.3.38 refuse the field (and one the site doesn't have): book without it, in
|
|
294
|
+
// the site's main language. A refused request doesn't use up the key.
|
|
295
|
+
if (!(error instanceof AcmsError) || error.status !== 400 || !/locale/i.test(JSON.stringify([error.message, error.issues ?? []])))
|
|
296
|
+
throw error;
|
|
297
|
+
return request('book', { signal }, {}, undefined, body, key);
|
|
298
|
+
}
|
|
284
299
|
},
|
|
285
300
|
openapi: (o = {}) => request('openapi', o),
|
|
286
301
|
registerDevice: (input, o = {}) => request('registerDevice', o, {}, undefined, input),
|
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;
|
|
@@ -250,6 +251,9 @@ export type Event = {
|
|
|
250
251
|
"name": string;
|
|
251
252
|
"color": string;
|
|
252
253
|
}>;
|
|
254
|
+
"notify": {
|
|
255
|
+
"changes": Array<string>;
|
|
256
|
+
};
|
|
253
257
|
};
|
|
254
258
|
export type Events = {
|
|
255
259
|
"locale": string | null;
|
|
@@ -337,6 +341,9 @@ export type Events = {
|
|
|
337
341
|
"name": string;
|
|
338
342
|
"color": string;
|
|
339
343
|
}>;
|
|
344
|
+
"notify": {
|
|
345
|
+
"changes": Array<string>;
|
|
346
|
+
};
|
|
340
347
|
};
|
|
341
348
|
}>;
|
|
342
349
|
};
|
|
@@ -473,6 +480,9 @@ export type SearchResults = {
|
|
|
473
480
|
"name": string;
|
|
474
481
|
"color": string;
|
|
475
482
|
}>;
|
|
483
|
+
"notify": {
|
|
484
|
+
"changes": Array<string>;
|
|
485
|
+
};
|
|
476
486
|
};
|
|
477
487
|
}>;
|
|
478
488
|
"more": {
|
|
@@ -591,6 +601,40 @@ export type FormReceipt = {
|
|
|
591
601
|
"received": true;
|
|
592
602
|
"confirmation": string;
|
|
593
603
|
};
|
|
604
|
+
export type RsvpStatus = {
|
|
605
|
+
"event": {
|
|
606
|
+
"id": string;
|
|
607
|
+
"slug": string;
|
|
608
|
+
"title": string;
|
|
609
|
+
};
|
|
610
|
+
"start": string;
|
|
611
|
+
"end": string;
|
|
612
|
+
"open": boolean;
|
|
613
|
+
"reason": ("disabled" | "ended" | "cancelled" | "full") | (null);
|
|
614
|
+
"going": number;
|
|
615
|
+
"left": (number) | (null);
|
|
616
|
+
"capacity": number;
|
|
617
|
+
"maxSeats": number;
|
|
618
|
+
"autoConfirm": boolean;
|
|
619
|
+
"form": ({
|
|
620
|
+
"version": string;
|
|
621
|
+
"title": string;
|
|
622
|
+
"intro": string;
|
|
623
|
+
"submitLabel": string;
|
|
624
|
+
"confirmation": string;
|
|
625
|
+
"fields": Array<{
|
|
626
|
+
"id": string;
|
|
627
|
+
"type": "text" | "textarea" | "email" | "phone" | "number" | "select" | "checkbox" | "date" | "radio" | "checkboxes" | "url" | "consent" | "heading";
|
|
628
|
+
"label": string;
|
|
629
|
+
"required": boolean;
|
|
630
|
+
"help": string;
|
|
631
|
+
"options": Array<string>;
|
|
632
|
+
"placeholder": string;
|
|
633
|
+
"width": "full" | "half";
|
|
634
|
+
"allowOther": boolean;
|
|
635
|
+
}>;
|
|
636
|
+
}) | (null);
|
|
637
|
+
};
|
|
594
638
|
export type ManagedBooking = {
|
|
595
639
|
"booking": {
|
|
596
640
|
"id": string;
|
|
@@ -634,6 +678,7 @@ export type ManagedBooking = {
|
|
|
634
678
|
"cutoffHours": number;
|
|
635
679
|
"closed": boolean;
|
|
636
680
|
};
|
|
681
|
+
"confirmation": string | null;
|
|
637
682
|
};
|
|
638
683
|
export type GuestCodeSent = {
|
|
639
684
|
"sent": true;
|
|
@@ -774,7 +819,7 @@ export type BookingResult = {
|
|
|
774
819
|
[key: string]: string;
|
|
775
820
|
};
|
|
776
821
|
};
|
|
777
|
-
"source": "web" | "admin" | "api" | "agent";
|
|
822
|
+
"source": "web" | "app" | "admin" | "api" | "agent";
|
|
778
823
|
"start": string;
|
|
779
824
|
"end": string;
|
|
780
825
|
"resourceId": string | null;
|
|
@@ -1332,7 +1377,7 @@ export type EditorBooking = {
|
|
|
1332
1377
|
[key: string]: string;
|
|
1333
1378
|
};
|
|
1334
1379
|
};
|
|
1335
|
-
"source": "web" | "admin" | "api" | "agent";
|
|
1380
|
+
"source": "web" | "app" | "admin" | "api" | "agent";
|
|
1336
1381
|
"notes": string;
|
|
1337
1382
|
"start": string;
|
|
1338
1383
|
"end": string;
|
|
@@ -1379,7 +1424,7 @@ export type EditorBookingList = {
|
|
|
1379
1424
|
[key: string]: string;
|
|
1380
1425
|
};
|
|
1381
1426
|
};
|
|
1382
|
-
"source": "web" | "admin" | "api" | "agent";
|
|
1427
|
+
"source": "web" | "app" | "admin" | "api" | "agent";
|
|
1383
1428
|
"notes": string;
|
|
1384
1429
|
"start": string;
|
|
1385
1430
|
"end": string;
|
|
@@ -1924,22 +1969,24 @@ export type BookingInput = ({
|
|
|
1924
1969
|
"name": string;
|
|
1925
1970
|
"email": string;
|
|
1926
1971
|
"phone"?: string;
|
|
1972
|
+
"answers"?: {
|
|
1973
|
+
[key: string]: (string) | (Array<string>);
|
|
1974
|
+
};
|
|
1975
|
+
"locale"?: string;
|
|
1927
1976
|
"resource": string;
|
|
1928
1977
|
"link": string;
|
|
1929
1978
|
"minutes"?: number;
|
|
1930
|
-
"answers"?: {
|
|
1931
|
-
[key: string]: string;
|
|
1932
|
-
};
|
|
1933
1979
|
}) | ({
|
|
1934
1980
|
"start": string;
|
|
1935
1981
|
"name": string;
|
|
1936
1982
|
"email": string;
|
|
1937
1983
|
"phone"?: string;
|
|
1938
|
-
"event": string;
|
|
1939
|
-
"seats"?: number;
|
|
1940
1984
|
"answers"?: {
|
|
1941
|
-
[key: string]: string;
|
|
1985
|
+
[key: string]: (string) | (Array<string>);
|
|
1942
1986
|
};
|
|
1987
|
+
"locale"?: string;
|
|
1988
|
+
"event": string;
|
|
1989
|
+
"seats"?: number;
|
|
1943
1990
|
"formVersion"?: string;
|
|
1944
1991
|
});
|
|
1945
1992
|
export type DeviceInput = {
|
|
@@ -2429,4 +2476,8 @@ export type SearchEventHit = SearchResults["events"][number];
|
|
|
2429
2476
|
export type LinkItem = Links["items"][number];
|
|
2430
2477
|
export type FormField = Form["fields"][number];
|
|
2431
2478
|
export type GuestBooking = GuestBookings["items"][number];
|
|
2479
|
+
export type RsvpInput = Extract<BookingInput, {
|
|
2480
|
+
"event": string;
|
|
2481
|
+
}>;
|
|
2482
|
+
export type RsvpForm = NonNullable<RsvpStatus["form"]>;
|
|
2432
2483
|
export type LiveDisplay = LiveStatus["display"];
|