@awesomate/sdk 0.16.0 → 0.18.0
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/dist/index.d.ts +166 -2
- package/dist/index.js +137 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* Docs: https://hub.awesomate.ai/docs/sdk/
|
|
25
25
|
*/
|
|
26
26
|
/** This package's version, sent to the hub with every server call. */
|
|
27
|
-
export declare const VERSION = "0.
|
|
27
|
+
export declare const VERSION = "0.18.0";
|
|
28
28
|
/** Augmented by the generated awesomate.d.ts, so each kind's rows are typed. */
|
|
29
29
|
export interface Kinds {
|
|
30
30
|
}
|
|
@@ -290,7 +290,7 @@ export declare class AwesomateClient {
|
|
|
290
290
|
move: (from: string, to: string) => Promise<FileEntry>;
|
|
291
291
|
/** Move a file from private/ or temp/ to the same place under public/, and answer its public_url. Anyone with the address can open it. */
|
|
292
292
|
makePublic: (path: string) => Promise<FileEntry>;
|
|
293
|
-
/** Move a file out of public/ into private/. Its public address stops serving it
|
|
293
|
+
/** Move a file out of public/ into private/. Its public address stops serving it within seconds (a browser that already opened it may keep its own copy for a while). */
|
|
294
294
|
makePrivate: (path: string) => Promise<FileEntry>;
|
|
295
295
|
/** Move to the trash. Not erased, and no longer counted toward the Files space. */
|
|
296
296
|
remove: (path: string) => Promise<{
|
|
@@ -1030,4 +1030,168 @@ export declare function createAppClient(options: AppClientOptions): AwesomateApp
|
|
|
1030
1030
|
* const { rows } = await db.query('contact', { limit: 10 });
|
|
1031
1031
|
*/
|
|
1032
1032
|
export declare function createClient(options: ClientOptions): AwesomateClient;
|
|
1033
|
+
/** One question a service asks when someone books. */
|
|
1034
|
+
export interface BookingQuestion {
|
|
1035
|
+
key: string;
|
|
1036
|
+
label: string;
|
|
1037
|
+
type: 'text' | 'textarea' | 'select' | 'multiselect' | 'url';
|
|
1038
|
+
required?: boolean;
|
|
1039
|
+
/** The choices, for select and multiselect. */
|
|
1040
|
+
options?: string[];
|
|
1041
|
+
}
|
|
1042
|
+
/** Something the business offers to book, with the calendars (people, rooms) it is booked with. */
|
|
1043
|
+
export interface BookableService {
|
|
1044
|
+
key: string;
|
|
1045
|
+
name: string;
|
|
1046
|
+
description: string;
|
|
1047
|
+
/** How long it lasts. */
|
|
1048
|
+
minutes: number;
|
|
1049
|
+
/** 1 for one person at a time; more for a class several people join at one start. */
|
|
1050
|
+
capacity: number;
|
|
1051
|
+
/** The price as the business wrote it. Shown, never charged. */
|
|
1052
|
+
price_text: string;
|
|
1053
|
+
location: string;
|
|
1054
|
+
intake: BookingQuestion[];
|
|
1055
|
+
calendars: Array<{
|
|
1056
|
+
key: string;
|
|
1057
|
+
name: string;
|
|
1058
|
+
timezone: string;
|
|
1059
|
+
}>;
|
|
1060
|
+
}
|
|
1061
|
+
/** A time that can be booked. */
|
|
1062
|
+
export interface OpenTime {
|
|
1063
|
+
start: string;
|
|
1064
|
+
end: string;
|
|
1065
|
+
/** The calendar it is with. */
|
|
1066
|
+
calendar: string;
|
|
1067
|
+
calendarName: string;
|
|
1068
|
+
/** Places left: 1 for a one-person service, more while a class has room. */
|
|
1069
|
+
seatsLeft: number;
|
|
1070
|
+
/** True when this start joins a class someone has already booked. */
|
|
1071
|
+
joins: boolean;
|
|
1072
|
+
}
|
|
1073
|
+
/** What book() needs. Take `startsAt` and `calendar` from an OpenTime. */
|
|
1074
|
+
export interface BookingRequest {
|
|
1075
|
+
service: string;
|
|
1076
|
+
calendar: string;
|
|
1077
|
+
startsAt: string;
|
|
1078
|
+
name: string;
|
|
1079
|
+
email: string;
|
|
1080
|
+
phone?: string;
|
|
1081
|
+
/** Places, for a class. Default 1. */
|
|
1082
|
+
seats?: number;
|
|
1083
|
+
/** Answers to the service's questions, by question key. */
|
|
1084
|
+
answers?: Record<string, string | string[]>;
|
|
1085
|
+
/**
|
|
1086
|
+
* One per attempt, so a retry after a dropped connection returns the same booking instead of
|
|
1087
|
+
* making a second. Make a new one for each new booking.
|
|
1088
|
+
*/
|
|
1089
|
+
idempotencyKey?: string;
|
|
1090
|
+
}
|
|
1091
|
+
/** A booking just made. */
|
|
1092
|
+
export interface BookingResult {
|
|
1093
|
+
bookingId: string;
|
|
1094
|
+
/** False when idempotencyKey matched a booking already made. */
|
|
1095
|
+
created: boolean;
|
|
1096
|
+
startsAt: string;
|
|
1097
|
+
endsAt: string;
|
|
1098
|
+
/** The customer's own page to change or cancel, the same link their email carries. */
|
|
1099
|
+
manageUrl: string;
|
|
1100
|
+
}
|
|
1101
|
+
/** One booking, as its customer's link shows it. Names no person. */
|
|
1102
|
+
export interface ManagedBooking {
|
|
1103
|
+
bookingId: string;
|
|
1104
|
+
status: 'confirmed' | 'cancelled' | 'completed' | 'no_show';
|
|
1105
|
+
service: string | null;
|
|
1106
|
+
/** The calendar's name: who or what it is with. */
|
|
1107
|
+
with: string | null;
|
|
1108
|
+
startsAt: string;
|
|
1109
|
+
endsAt: string;
|
|
1110
|
+
/** The calendar's IANA zone, for showing the times. */
|
|
1111
|
+
timezone: string;
|
|
1112
|
+
location: string;
|
|
1113
|
+
seats: number;
|
|
1114
|
+
/** Whether it can still be cancelled or moved online (the service's cutoff has not passed). */
|
|
1115
|
+
canCancel: boolean;
|
|
1116
|
+
canMove: boolean;
|
|
1117
|
+
changesCloseAt: string;
|
|
1118
|
+
}
|
|
1119
|
+
/** Options for createBookingsClient(). */
|
|
1120
|
+
export interface BookingsClientOptions {
|
|
1121
|
+
/** The booking key (bk_...) from Contacts, Bookings, On your website. Public: it works only on the sites it lists. */
|
|
1122
|
+
key: string;
|
|
1123
|
+
/** Default https://hub.awesomate.ai. */
|
|
1124
|
+
baseUrl?: string;
|
|
1125
|
+
/** Your own fetch. Default: the global one. */
|
|
1126
|
+
fetch?: typeof fetch;
|
|
1127
|
+
}
|
|
1128
|
+
/** The token in a booking's manage link (`/booking?t=...`), or null. */
|
|
1129
|
+
export declare function manageTokenFrom(url: string): string | null;
|
|
1130
|
+
/**
|
|
1131
|
+
* The bookings client, from createBookingsClient(): a business's own booking page, for visitors
|
|
1132
|
+
* who are not signed in. Lists what can be booked and when, books, and lets a customer see,
|
|
1133
|
+
* cancel or move their booking from the link in its emails. Safe in a browser: the booking key is
|
|
1134
|
+
* public and locked to the business's own sites.
|
|
1135
|
+
*/
|
|
1136
|
+
export declare class AwesomateBookingsClient {
|
|
1137
|
+
private readonly opts;
|
|
1138
|
+
private readonly base;
|
|
1139
|
+
private readonly doFetch;
|
|
1140
|
+
constructor(opts: BookingsClientOptions);
|
|
1141
|
+
private request;
|
|
1142
|
+
/** The business's name and what it offers to book. */
|
|
1143
|
+
services(): Promise<{
|
|
1144
|
+
business: string;
|
|
1145
|
+
services: BookableService[];
|
|
1146
|
+
}>;
|
|
1147
|
+
/**
|
|
1148
|
+
* The times a service can be booked, soonest first, from now for two weeks unless told
|
|
1149
|
+
* otherwise (at most 62 days at once). Times are UTC; show them in the calendar's own zone.
|
|
1150
|
+
*/
|
|
1151
|
+
openTimes(service: string, options?: {
|
|
1152
|
+
from?: Date | string;
|
|
1153
|
+
to?: Date | string;
|
|
1154
|
+
calendar?: string;
|
|
1155
|
+
seats?: number;
|
|
1156
|
+
}): Promise<OpenTime[]>;
|
|
1157
|
+
/**
|
|
1158
|
+
* Book a time for a visitor. They get an email with an invite and a link to change or cancel;
|
|
1159
|
+
* the business gets a notice. A time taken since you listed it is refused with code `conflict`
|
|
1160
|
+
* and `field` saying why (not_open, slot_taken, session_full, day_full): list the times again.
|
|
1161
|
+
*/
|
|
1162
|
+
book(request: BookingRequest): Promise<BookingResult>;
|
|
1163
|
+
/** One booking, from the token in its manage link (manageTokenFrom()). */
|
|
1164
|
+
booking(manageToken: string): Promise<{
|
|
1165
|
+
business: string;
|
|
1166
|
+
booking: ManagedBooking;
|
|
1167
|
+
}>;
|
|
1168
|
+
/** The times a booking could move to, leaving its own time out. */
|
|
1169
|
+
openTimesToMove(manageToken: string, options?: {
|
|
1170
|
+
from?: Date | string;
|
|
1171
|
+
to?: Date | string;
|
|
1172
|
+
}): Promise<OpenTime[]>;
|
|
1173
|
+
/** Cancel a booking from its manage link. A second cancel answers cancelled: false. */
|
|
1174
|
+
cancel(manageToken: string, reason?: string): Promise<{
|
|
1175
|
+
cancelled: boolean;
|
|
1176
|
+
}>;
|
|
1177
|
+
/** Move a booking to a time from openTimesToMove(). */
|
|
1178
|
+
move(manageToken: string, startsAt: string): Promise<{
|
|
1179
|
+
startsAt: string;
|
|
1180
|
+
endsAt: string;
|
|
1181
|
+
}>;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* The client for a business's own booking page. Use the booking key from Contacts, Bookings, On
|
|
1185
|
+
* your website; it works on every plan, from the sites the key lists.
|
|
1186
|
+
*
|
|
1187
|
+
* @example
|
|
1188
|
+
* ```ts
|
|
1189
|
+
* import { createBookingsClient } from '@awesomate/sdk';
|
|
1190
|
+
*
|
|
1191
|
+
* const bookings = createBookingsClient({ key: 'bk_your_booking_key' });
|
|
1192
|
+
* const { services } = await bookings.services();
|
|
1193
|
+
* const times = await bookings.openTimes(services[0].key);
|
|
1194
|
+
* ```
|
|
1195
|
+
*/
|
|
1196
|
+
export declare function createBookingsClient(options: BookingsClientOptions): AwesomateBookingsClient;
|
|
1033
1197
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* Docs: https://hub.awesomate.ai/docs/sdk/
|
|
25
25
|
*/
|
|
26
26
|
/** This package's version, sent to the hub with every server call. */
|
|
27
|
-
export const VERSION = '0.
|
|
27
|
+
export const VERSION = '0.18.0';
|
|
28
28
|
const DEFAULT_BASE = 'https://hub.awesomate.ai';
|
|
29
29
|
const ERROR_CODES = ['unauthenticated', 'forbidden', 'not_found', 'validation', 'consent_blocked', 'rate_limited', 'conflict', 'unavailable'];
|
|
30
30
|
/**
|
|
@@ -220,7 +220,7 @@ export class AwesomateClient {
|
|
|
220
220
|
throw new AwesomateError('validation', 'Name a file inside the folder.', 0, 'path');
|
|
221
221
|
return this.files.move(path, `public/${rest.join('/')}`);
|
|
222
222
|
},
|
|
223
|
-
/** Move a file out of public/ into private/. Its public address stops serving it
|
|
223
|
+
/** Move a file out of public/ into private/. Its public address stops serving it within seconds (a browser that already opened it may keep its own copy for a while). */
|
|
224
224
|
makePrivate: async (path) => {
|
|
225
225
|
const [top, ...rest] = path.replace(/^\/+/, '').split('/');
|
|
226
226
|
if (top !== 'public' || !rest.length)
|
|
@@ -1110,3 +1110,138 @@ export function createAppClient(options) {
|
|
|
1110
1110
|
export function createClient(options) {
|
|
1111
1111
|
return new AwesomateClient(options);
|
|
1112
1112
|
}
|
|
1113
|
+
/** The token in a booking's manage link (`/booking?t=...`), or null. */
|
|
1114
|
+
export function manageTokenFrom(url) {
|
|
1115
|
+
try {
|
|
1116
|
+
return new URL(url).searchParams.get('t');
|
|
1117
|
+
}
|
|
1118
|
+
catch {
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const iso = (d) => (d === undefined ? undefined : typeof d === 'string' ? d : d.toISOString());
|
|
1123
|
+
/**
|
|
1124
|
+
* The bookings client, from createBookingsClient(): a business's own booking page, for visitors
|
|
1125
|
+
* who are not signed in. Lists what can be booked and when, books, and lets a customer see,
|
|
1126
|
+
* cancel or move their booking from the link in its emails. Safe in a browser: the booking key is
|
|
1127
|
+
* public and locked to the business's own sites.
|
|
1128
|
+
*/
|
|
1129
|
+
export class AwesomateBookingsClient {
|
|
1130
|
+
opts;
|
|
1131
|
+
base;
|
|
1132
|
+
doFetch;
|
|
1133
|
+
constructor(opts) {
|
|
1134
|
+
this.opts = opts;
|
|
1135
|
+
if (!opts?.key?.startsWith('bk_'))
|
|
1136
|
+
throw new Error('createBookingsClient needs the booking key (bk_...) from Contacts, Bookings, On your website.');
|
|
1137
|
+
this.base = `${(opts.baseUrl ?? DEFAULT_BASE).replace(/\/+$/, '')}/api/sdk/v1/bookings`;
|
|
1138
|
+
const f = opts.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
1139
|
+
if (!f)
|
|
1140
|
+
throw new Error('No fetch available.');
|
|
1141
|
+
this.doFetch = f;
|
|
1142
|
+
}
|
|
1143
|
+
async request(method, path, body, withKey = true) {
|
|
1144
|
+
let res;
|
|
1145
|
+
try {
|
|
1146
|
+
res = await this.doFetch(`${this.base}${path}`, {
|
|
1147
|
+
method,
|
|
1148
|
+
headers: { 'content-type': 'application/json', ...(withKey ? { 'x-awesomate-key': this.opts.key } : {}) },
|
|
1149
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
catch (err) {
|
|
1153
|
+
throw new AwesomateError('unavailable', `The hub could not be reached: ${err.message}`, 0);
|
|
1154
|
+
}
|
|
1155
|
+
const data = (await res.json().catch(() => ({})));
|
|
1156
|
+
if (!res.ok) {
|
|
1157
|
+
const server = typeof data.code === 'string' ? data.code : undefined;
|
|
1158
|
+
const message = typeof data.error === 'string' ? data.error : `Request failed (${res.status})`;
|
|
1159
|
+
throw new AwesomateError(errorCode(res.status, server), message, res.status, typeof data.field === 'string' ? data.field : undefined, server, message);
|
|
1160
|
+
}
|
|
1161
|
+
return data;
|
|
1162
|
+
}
|
|
1163
|
+
/** The business's name and what it offers to book. */
|
|
1164
|
+
services() {
|
|
1165
|
+
return this.request('GET', '/services');
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* The times a service can be booked, soonest first, from now for two weeks unless told
|
|
1169
|
+
* otherwise (at most 62 days at once). Times are UTC; show them in the calendar's own zone.
|
|
1170
|
+
*/
|
|
1171
|
+
async openTimes(service, options = {}) {
|
|
1172
|
+
const q = new URLSearchParams({ service });
|
|
1173
|
+
const from = iso(options.from);
|
|
1174
|
+
const to = iso(options.to);
|
|
1175
|
+
if (from)
|
|
1176
|
+
q.set('from', from);
|
|
1177
|
+
if (to)
|
|
1178
|
+
q.set('to', to);
|
|
1179
|
+
if (options.calendar)
|
|
1180
|
+
q.set('calendar', options.calendar);
|
|
1181
|
+
if (options.seats)
|
|
1182
|
+
q.set('seats', String(options.seats));
|
|
1183
|
+
return (await this.request('GET', `/open-times?${q}`)).times;
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Book a time for a visitor. They get an email with an invite and a link to change or cancel;
|
|
1187
|
+
* the business gets a notice. A time taken since you listed it is refused with code `conflict`
|
|
1188
|
+
* and `field` saying why (not_open, slot_taken, session_full, day_full): list the times again.
|
|
1189
|
+
*/
|
|
1190
|
+
async book(request) {
|
|
1191
|
+
const r = await this.request('POST', '/book', {
|
|
1192
|
+
service: request.service, calendar: request.calendar, starts_at: request.startsAt, name: request.name, email: request.email,
|
|
1193
|
+
phone: request.phone, seats: request.seats, answers: request.answers, idempotency_key: request.idempotencyKey,
|
|
1194
|
+
});
|
|
1195
|
+
return { bookingId: r.booking_id, created: r.created, startsAt: r.starts_at, endsAt: r.ends_at, manageUrl: r.manage_url };
|
|
1196
|
+
}
|
|
1197
|
+
/** One booking, from the token in its manage link (manageTokenFrom()). */
|
|
1198
|
+
async booking(manageToken) {
|
|
1199
|
+
const r = await this.request('GET', `/manage?t=${encodeURIComponent(manageToken)}`, undefined, false);
|
|
1200
|
+
const b = r.booking;
|
|
1201
|
+
return {
|
|
1202
|
+
business: r.business,
|
|
1203
|
+
booking: {
|
|
1204
|
+
bookingId: b.booking_id, status: b.status, service: b.service ?? null,
|
|
1205
|
+
with: b.with ?? null, startsAt: b.starts_at, endsAt: b.ends_at, timezone: b.timezone,
|
|
1206
|
+
location: b.location ?? '', seats: b.seats, canCancel: !!b.can_cancel, canMove: !!b.can_move,
|
|
1207
|
+
changesCloseAt: b.changes_close_at,
|
|
1208
|
+
},
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
/** The times a booking could move to, leaving its own time out. */
|
|
1212
|
+
async openTimesToMove(manageToken, options = {}) {
|
|
1213
|
+
const q = new URLSearchParams({ t: manageToken });
|
|
1214
|
+
const from = iso(options.from);
|
|
1215
|
+
const to = iso(options.to);
|
|
1216
|
+
if (from)
|
|
1217
|
+
q.set('from', from);
|
|
1218
|
+
if (to)
|
|
1219
|
+
q.set('to', to);
|
|
1220
|
+
return (await this.request('GET', `/manage/open-times?${q}`, undefined, false)).times;
|
|
1221
|
+
}
|
|
1222
|
+
/** Cancel a booking from its manage link. A second cancel answers cancelled: false. */
|
|
1223
|
+
cancel(manageToken, reason) {
|
|
1224
|
+
return this.request('POST', '/manage/cancel', { t: manageToken, reason }, false);
|
|
1225
|
+
}
|
|
1226
|
+
/** Move a booking to a time from openTimesToMove(). */
|
|
1227
|
+
async move(manageToken, startsAt) {
|
|
1228
|
+
const r = await this.request('POST', '/manage/move', { t: manageToken, starts_at: startsAt }, false);
|
|
1229
|
+
return { startsAt: r.starts_at, endsAt: r.ends_at };
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* The client for a business's own booking page. Use the booking key from Contacts, Bookings, On
|
|
1234
|
+
* your website; it works on every plan, from the sites the key lists.
|
|
1235
|
+
*
|
|
1236
|
+
* @example
|
|
1237
|
+
* ```ts
|
|
1238
|
+
* import { createBookingsClient } from '@awesomate/sdk';
|
|
1239
|
+
*
|
|
1240
|
+
* const bookings = createBookingsClient({ key: 'bk_your_booking_key' });
|
|
1241
|
+
* const { services } = await bookings.services();
|
|
1242
|
+
* const times = await bookings.openTimes(services[0].key);
|
|
1243
|
+
* ```
|
|
1244
|
+
*/
|
|
1245
|
+
export function createBookingsClient(options) {
|
|
1246
|
+
return new AwesomateBookingsClient(options);
|
|
1247
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awesomate/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Your own Awesomate data from Node and the browser: query contacts and app data with generated types, and sign your app's own users in",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|