@awesomate/sdk 0.24.0 → 0.25.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 +346 -10
- package/dist/index.js +222 -9
- 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.25.0";
|
|
28
28
|
/** Augmented by the generated awesomate.d.ts, so each kind's rows are typed. */
|
|
29
29
|
export interface Kinds {
|
|
30
30
|
}
|
|
@@ -205,9 +205,13 @@ export interface TaskCard {
|
|
|
205
205
|
/** The Tasks board, as the account's token sees it (the owner, so `scope: 'all'` works). */
|
|
206
206
|
export interface TaskBoard {
|
|
207
207
|
scope: 'mine' | 'all';
|
|
208
|
+
/** Whether this person may ask for `scope: 'all'`: the owner only. */
|
|
209
|
+
canChooseScope: boolean;
|
|
208
210
|
me: TaskPerson & {
|
|
209
211
|
role: string;
|
|
210
212
|
};
|
|
213
|
+
/** Whether this person may give tasks (everyone but view-only people). */
|
|
214
|
+
canGive: boolean;
|
|
211
215
|
/** Who a task can be given to. */
|
|
212
216
|
people: Array<TaskPerson & {
|
|
213
217
|
me: boolean;
|
|
@@ -218,7 +222,10 @@ export interface TaskBoard {
|
|
|
218
222
|
later: TaskCard[];
|
|
219
223
|
/** With someone else. */
|
|
220
224
|
waiting: TaskCard[];
|
|
221
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* Recently decided, for the record. `canBringBack` is true for a given task this person may open
|
|
227
|
+
* again with bringBack(): the number after `task:` in its key is the task's id.
|
|
228
|
+
*/
|
|
222
229
|
decided: Array<{
|
|
223
230
|
key: string;
|
|
224
231
|
kind: string;
|
|
@@ -226,7 +233,49 @@ export interface TaskBoard {
|
|
|
226
233
|
text: string;
|
|
227
234
|
by: TaskPerson | null;
|
|
228
235
|
at: string;
|
|
236
|
+
canBringBack: boolean;
|
|
229
237
|
}>;
|
|
238
|
+
/**
|
|
239
|
+
* Places the board could not read just now, in the owner's words. When it is not empty the
|
|
240
|
+
* board may be missing cards from them: say so rather than treating the board as complete.
|
|
241
|
+
*/
|
|
242
|
+
unavailable: string[];
|
|
243
|
+
/** Whether anyone besides the owner is on the account. */
|
|
244
|
+
hasTeam: boolean;
|
|
245
|
+
}
|
|
246
|
+
/** One step in a task's record, in order. */
|
|
247
|
+
export interface TaskRecordStep {
|
|
248
|
+
at: string;
|
|
249
|
+
/** What happened, in a sentence. */
|
|
250
|
+
text: string;
|
|
251
|
+
/** Where it happened (email, Telegram), when that was not the hub; otherwise null. */
|
|
252
|
+
place: string | null;
|
|
253
|
+
/** How long since the step before, when it is worth showing ("29 min later"). */
|
|
254
|
+
gap: string | null;
|
|
255
|
+
tone: 'start' | 'move' | 'seen' | 'answer' | 'end';
|
|
256
|
+
}
|
|
257
|
+
/** What happened to a given task, from tasks.record(): who gave it, who passed it on, who closed it, and how long each person had it. */
|
|
258
|
+
export interface TaskRecord {
|
|
259
|
+
kind: 'task';
|
|
260
|
+
title: string;
|
|
261
|
+
state: string;
|
|
262
|
+
steps: TaskRecordStep[];
|
|
263
|
+
timings: {
|
|
264
|
+
/** From given to done or dropped; null while it is open. */
|
|
265
|
+
totalMs: number | null;
|
|
266
|
+
total: string | null;
|
|
267
|
+
/** From reaching its first person to that person's first look; null when they have not looked. */
|
|
268
|
+
untilFirstSeenMs: number | null;
|
|
269
|
+
untilFirstSeen: string | null;
|
|
270
|
+
/** Time with each person, in order of first holding it. */
|
|
271
|
+
withPeople: Array<{
|
|
272
|
+
name: string;
|
|
273
|
+
ms: number;
|
|
274
|
+
text: string;
|
|
275
|
+
}>;
|
|
276
|
+
/** How many times it moved to someone else after it was first given. */
|
|
277
|
+
handoffs: number;
|
|
278
|
+
};
|
|
230
279
|
}
|
|
231
280
|
/** A task to give: to someone on the account by email, or to the owner when `forEmail` is left out. */
|
|
232
281
|
export interface GiveTask {
|
|
@@ -251,6 +300,17 @@ export interface FileEntry {
|
|
|
251
300
|
/** For a file in public/: the address anyone with it can open, with no login. Otherwise null. */
|
|
252
301
|
public_url: string | null;
|
|
253
302
|
}
|
|
303
|
+
/** Something in the Files trash, from files.trash(). */
|
|
304
|
+
export interface TrashEntry {
|
|
305
|
+
/** Where it sits in the trash. */
|
|
306
|
+
path: string;
|
|
307
|
+
/** What it was called before it was removed. */
|
|
308
|
+
name: string;
|
|
309
|
+
type: 'file' | 'dir';
|
|
310
|
+
bytes: number;
|
|
311
|
+
/** When it was removed, or null when the trash name does not say. */
|
|
312
|
+
deleted_at: string | null;
|
|
313
|
+
}
|
|
254
314
|
/** How much of the plan's Files space is used. */
|
|
255
315
|
export interface FilesUsage {
|
|
256
316
|
used_bytes: number;
|
|
@@ -393,6 +453,23 @@ export declare class AwesomateClient {
|
|
|
393
453
|
remove: (path: string) => Promise<{
|
|
394
454
|
trashPath: string;
|
|
395
455
|
}>;
|
|
456
|
+
/** What is in the trash, and how many bytes it holds. Needs files:read. */
|
|
457
|
+
trash: () => Promise<{
|
|
458
|
+
entries: TrashEntry[];
|
|
459
|
+
total_bytes: number;
|
|
460
|
+
}>;
|
|
461
|
+
/**
|
|
462
|
+
* Erase what is in the trash, for good: the only call that deletes a file outright. With
|
|
463
|
+
* `olderThanDays` (1 to 3650), only what was removed longer ago than that. Needs files:write.
|
|
464
|
+
* An automation account that cannot empty its trash yet answers serverCode trash_empty_unavailable.
|
|
465
|
+
*/
|
|
466
|
+
emptyTrash: (options?: {
|
|
467
|
+
olderThanDays?: number;
|
|
468
|
+
}) => Promise<{
|
|
469
|
+
removed: number;
|
|
470
|
+
bytes: number;
|
|
471
|
+
files: number;
|
|
472
|
+
}>;
|
|
396
473
|
};
|
|
397
474
|
private tasksCall;
|
|
398
475
|
/**
|
|
@@ -422,16 +499,122 @@ export declare class AwesomateClient {
|
|
|
422
499
|
done: (id: number | string) => Promise<void>;
|
|
423
500
|
/** Take a given task off the board without doing it: whoever wrote it, or the owner. */
|
|
424
501
|
drop: (id: number | string) => Promise<void>;
|
|
502
|
+
/**
|
|
503
|
+
* Open a done or dropped task again, with the person who had it. They get an email unless
|
|
504
|
+
* they are the one bringing it back. Who may: see `decided[].canBringBack` on the board.
|
|
505
|
+
*/
|
|
506
|
+
bringBack: (id: number | string) => Promise<{
|
|
507
|
+
emailed: boolean;
|
|
508
|
+
}>;
|
|
509
|
+
/**
|
|
510
|
+
* What happened to a given task, in order: who gave it, who passed it on, who closed it and
|
|
511
|
+
* brought it back, and how long each person had it. The owner reads any task's record; anyone
|
|
512
|
+
* else only one they were part of.
|
|
513
|
+
*/
|
|
514
|
+
record: (id: number | string) => Promise<TaskRecord>;
|
|
425
515
|
/** Put a card off until a time (at most 90 days), for this person only. */
|
|
426
516
|
notNow: (key: string, until: Date | string) => Promise<{
|
|
427
517
|
until: string;
|
|
428
518
|
}>;
|
|
519
|
+
/** Bring a card put off with notNow() back now, for this person. */
|
|
520
|
+
cancelNotNow: (key: string) => Promise<void>;
|
|
429
521
|
/** Hand a card to someone on the account who can act on it. */
|
|
430
522
|
passOn: (key: string, toEmail: string, reason?: string) => Promise<{
|
|
431
523
|
holder: TaskPerson;
|
|
432
524
|
emailed: boolean;
|
|
433
525
|
}>;
|
|
434
526
|
};
|
|
527
|
+
/**
|
|
528
|
+
* The account's bookings, for the business's own staff screens: the list with each customer,
|
|
529
|
+
* one booking, the open times, and booking, cancelling, moving and recording an outcome on
|
|
530
|
+
* anyone's behalf. Needs the account's token (never an app key) and Bookings on the account.
|
|
531
|
+
* Reading needs crm:read, on every plan; changing needs crm:write, Support Plus and above.
|
|
532
|
+
* Booking, moving and cancelling email the customer and the calendar's notice address, unless
|
|
533
|
+
* `notify: false` (say a booking taken by phone that you confirm yourself). For visitors who are
|
|
534
|
+
* not signed in, use createBookingsClient() instead.
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* const today = await db.bookings.list({ from: '2026-10-08T00:00:00+11:00', to: '2026-10-09T00:00:00+11:00', status: 'confirmed' });
|
|
538
|
+
*/
|
|
539
|
+
readonly bookings: {
|
|
540
|
+
/**
|
|
541
|
+
* Bookings that start in a window, soonest first, each with its customer. The window is from a
|
|
542
|
+
* day ago for 31 days unless told otherwise; `limit` is 1 to 500 (default 200).
|
|
543
|
+
*/
|
|
544
|
+
list: (options?: {
|
|
545
|
+
from?: Date | string;
|
|
546
|
+
to?: Date | string;
|
|
547
|
+
status?: StaffBooking["status"];
|
|
548
|
+
calendar?: string;
|
|
549
|
+
limit?: number;
|
|
550
|
+
}) => Promise<StaffBooking[]>;
|
|
551
|
+
/** One booking by its id, or null when there is none (an id that is not a booking id never reaches the hub). */
|
|
552
|
+
get: (id: string) => Promise<StaffBooking | null>;
|
|
553
|
+
/**
|
|
554
|
+
* The times a service can be booked, from now for two weeks unless told otherwise (at most 62
|
|
555
|
+
* days at once). The same open times customers see; book() with `outsideHours` goes beyond them.
|
|
556
|
+
*/
|
|
557
|
+
openTimes: (service: string, options?: {
|
|
558
|
+
from?: Date | string;
|
|
559
|
+
to?: Date | string;
|
|
560
|
+
calendar?: string;
|
|
561
|
+
seats?: number;
|
|
562
|
+
}) => Promise<OpenTime[]>;
|
|
563
|
+
/**
|
|
564
|
+
* Book a time for a customer named by email (found in Contacts, or added; consent is never
|
|
565
|
+
* changed). Switches Bookings on if it was not. A time that is not open is refused with code
|
|
566
|
+
* `conflict` and `field` saying why: not_open, slot_taken, too_many_seats, session_full or
|
|
567
|
+
* day_full. Pass `idempotencyKey` so a retry returns the same booking.
|
|
568
|
+
*/
|
|
569
|
+
book: (request: StaffBookingRequest) => Promise<{
|
|
570
|
+
bookingId: string;
|
|
571
|
+
created: boolean;
|
|
572
|
+
startsAt: string;
|
|
573
|
+
endsAt: string;
|
|
574
|
+
}>;
|
|
575
|
+
/** Cancel a booking. A booking already cancelled answers `cancelled: false`. */
|
|
576
|
+
cancel: (id: string, options?: {
|
|
577
|
+
reason?: string;
|
|
578
|
+
notify?: boolean;
|
|
579
|
+
}) => Promise<{
|
|
580
|
+
bookingId: string;
|
|
581
|
+
cancelled: boolean;
|
|
582
|
+
status: string | null;
|
|
583
|
+
}>;
|
|
584
|
+
/**
|
|
585
|
+
* Move a confirmed booking to a new start, on the same calendar or another (`calendar`). A time
|
|
586
|
+
* that is not open is refused with code `conflict` (field not_open, slot_taken, session_full or
|
|
587
|
+
* day_full) unless `outsideHours`; a booking that is not confirmed, or a time that has passed,
|
|
588
|
+
* with code `validation`.
|
|
589
|
+
*/
|
|
590
|
+
move: (id: string, startsAt: Date | string, options?: {
|
|
591
|
+
calendar?: string;
|
|
592
|
+
outsideHours?: boolean;
|
|
593
|
+
notify?: boolean;
|
|
594
|
+
}) => Promise<{
|
|
595
|
+
bookingId: string;
|
|
596
|
+
startsAt: string;
|
|
597
|
+
endsAt: string;
|
|
598
|
+
calendar: string;
|
|
599
|
+
}>;
|
|
600
|
+
/** Record how a booking went: the customer came (`completed`) or did not (`no_show`). Sends no email. */
|
|
601
|
+
outcome: (id: string, status: "completed" | "no_show") => Promise<void>;
|
|
602
|
+
};
|
|
603
|
+
/**
|
|
604
|
+
* Tell Contacts something happened to someone ("job_paid", "quote_sent"), so any email series
|
|
605
|
+
* waiting for that event starts or stops for them. The person is found by email, or added; it
|
|
606
|
+
* never signs anyone up for email or changes their consent. Sending the same `key` twice records
|
|
607
|
+
* the event once (`duplicate: true`), so pass your own id for it. `record` names the quote, job or
|
|
608
|
+
* booking it is about. An event more than two days old is kept as a fact a series reads, never
|
|
609
|
+
* a start. Needs the account's token with crm:write (Support Plus and above), never an app key.
|
|
610
|
+
* The answer is the same whether or not the address belongs to someone erased from Contacts.
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* await db.recordEvent({ event: 'job_paid', email: 'sam@example.com', record: 'job-1042', key: 'xero:INV-1042' });
|
|
614
|
+
*/
|
|
615
|
+
recordEvent(event: SeriesEvent): Promise<{
|
|
616
|
+
duplicate: boolean;
|
|
617
|
+
}>;
|
|
435
618
|
/** The kinds this token can read, their columns, operators and examples. */
|
|
436
619
|
schema(): Promise<{
|
|
437
620
|
kinds: Array<{
|
|
@@ -466,7 +649,10 @@ export declare class AwesomateClient {
|
|
|
466
649
|
queryAll<K extends KindName, S extends keyof RowOf<K> = keyof RowOf<K>>(kind: K, options?: Omit<QueryOptions<RowOf<K>, S>, 'after'> & {
|
|
467
650
|
maxRows?: number;
|
|
468
651
|
}): AsyncGenerator<Pick<RowOf<K>, S>>;
|
|
469
|
-
/**
|
|
652
|
+
/**
|
|
653
|
+
* One row by id, or null when there is none this token can read. Ids are UUIDs: anything else
|
|
654
|
+
* names no row, so it answers null without asking the hub.
|
|
655
|
+
*/
|
|
470
656
|
get<K extends KindName>(kind: K, id: string, options?: {
|
|
471
657
|
tz?: string;
|
|
472
658
|
}): Promise<RowOf<K> | null>;
|
|
@@ -537,6 +723,18 @@ export declare class AwesomateClient {
|
|
|
537
723
|
* gone 30 days unused, and not before 2026-11-06.
|
|
538
724
|
*/
|
|
539
725
|
businessMapV1(): Promise<BusinessMapV1>;
|
|
726
|
+
/**
|
|
727
|
+
* The business's long documents that exist (brand guide, voice guide, brand from the website,
|
|
728
|
+
* business summary): kind, version, length and where each came from, without the text. Needs
|
|
729
|
+
* the account's token (hosting:read, every plan), never an app key.
|
|
730
|
+
*/
|
|
731
|
+
businessDocuments(): Promise<BusinessDocumentSummary[]>;
|
|
732
|
+
/**
|
|
733
|
+
* One of the business's documents with its text (the current version), or null when the
|
|
734
|
+
* business has none of that kind yet. The text is the owner's own: use it for their business
|
|
735
|
+
* (agent instructions, site copy in their voice), never show it to anyone else.
|
|
736
|
+
*/
|
|
737
|
+
businessDocument(kind: BusinessDocumentKind): Promise<BusinessDocument | null>;
|
|
540
738
|
/** Details waiting for the owner's yes on Your business: from the website, the account, a team member or a file. */
|
|
541
739
|
businessSuggestions(): Promise<BusinessSuggestion[]>;
|
|
542
740
|
/** Grouped numbers (counts, sums) in the Business Data API's shape. */
|
|
@@ -1021,6 +1219,44 @@ export interface BusinessSuggestion {
|
|
|
1021
1219
|
sourceRef: string | null;
|
|
1022
1220
|
recordedAt: string;
|
|
1023
1221
|
}
|
|
1222
|
+
/** The business's long documents, by kind. */
|
|
1223
|
+
export type BusinessDocumentKind = 'brand_guide' | 'voice_guide' | 'website_brand' | 'business_summary';
|
|
1224
|
+
/** One of the business's documents, without its text (businessDocuments()). */
|
|
1225
|
+
export interface BusinessDocumentSummary {
|
|
1226
|
+
kind: BusinessDocumentKind;
|
|
1227
|
+
/** What the owner sees it called in the hub ("Voice and style guide"). */
|
|
1228
|
+
label: string;
|
|
1229
|
+
/** Goes up by one each time it is saved again. */
|
|
1230
|
+
version: number;
|
|
1231
|
+
/** How long the text is, in characters. */
|
|
1232
|
+
chars: number;
|
|
1233
|
+
/** Where this version came from: the owner, the onboarding agent, a chat brought in, a file. */
|
|
1234
|
+
sourceKind: string;
|
|
1235
|
+
recordedAt: string;
|
|
1236
|
+
}
|
|
1237
|
+
/** One of the business's documents with its text (businessDocument()). */
|
|
1238
|
+
export interface BusinessDocument extends BusinessDocumentSummary {
|
|
1239
|
+
/** The document itself, usually Markdown. The owner's own words. */
|
|
1240
|
+
body: string;
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* What happened to someone, for recordEvent(). `event` is lower case: letters, digits and
|
|
1244
|
+
* `_ . : -`, up to 64 characters ("job_paid", "quote_sent").
|
|
1245
|
+
*/
|
|
1246
|
+
export interface SeriesEvent {
|
|
1247
|
+
event: string;
|
|
1248
|
+
/** Whose event it is. Found in Contacts, or added. */
|
|
1249
|
+
email: string;
|
|
1250
|
+
/** The quote, job or booking it is about (1 to 120 characters). A series that runs per record keys on it. */
|
|
1251
|
+
record?: string;
|
|
1252
|
+
/** When it happened. Default now. */
|
|
1253
|
+
occurredAt?: Date | string;
|
|
1254
|
+
/** Your own id for the event (1 to 200 characters). Sending the same key again records it once. */
|
|
1255
|
+
key?: string;
|
|
1256
|
+
/** Used only when the person is added to Contacts by this event. */
|
|
1257
|
+
firstName?: string;
|
|
1258
|
+
lastName?: string;
|
|
1259
|
+
}
|
|
1024
1260
|
/** One attribute (column) of a kind. */
|
|
1025
1261
|
export interface AttributeSpec {
|
|
1026
1262
|
key: string;
|
|
@@ -1031,6 +1267,20 @@ export interface AttributeSpec {
|
|
|
1031
1267
|
sensitivity?: 'ordinary' | 'personal' | 'sensitive';
|
|
1032
1268
|
/** Default false. Only readable attributes reach query(), get() and the generated types. */
|
|
1033
1269
|
readable_by_ai?: boolean;
|
|
1270
|
+
/**
|
|
1271
|
+
* Which of the app's roles see this attribute (up to 10, lower case). Left out or null: everyone
|
|
1272
|
+
* who can read the record. An empty list: none of the app's people.
|
|
1273
|
+
*/
|
|
1274
|
+
visible_to?: string[] | null;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Who among an app's signed-in people may read and change a kind's records, by role. Each rule is
|
|
1278
|
+
* `all`, `none`, `own` (records they created) or `linked:<link>` (records linked to them, such as
|
|
1279
|
+
* `linked:customer` or `linked:job.customer`); join several with `|`. A role left out gets none.
|
|
1280
|
+
*/
|
|
1281
|
+
export interface KindAccess {
|
|
1282
|
+
read?: Record<string, string>;
|
|
1283
|
+
write?: Record<string, string>;
|
|
1034
1284
|
}
|
|
1035
1285
|
/** A kind to define: a table an app keeps. */
|
|
1036
1286
|
export interface KindSpec {
|
|
@@ -1046,16 +1296,22 @@ export interface KindSpec {
|
|
|
1046
1296
|
to_label?: string;
|
|
1047
1297
|
required?: boolean;
|
|
1048
1298
|
}>;
|
|
1299
|
+
/** The app's access rules, set with the kind. Until a kind has rules, owner and staff can do everything with it and members nothing. */
|
|
1300
|
+
access?: KindAccess;
|
|
1049
1301
|
}
|
|
1050
1302
|
/** A kind as the account has defined it. */
|
|
1051
|
-
export interface KindDescription extends Omit<KindSpec, 'attributes'> {
|
|
1303
|
+
export interface KindDescription extends Omit<KindSpec, 'attributes' | 'access'> {
|
|
1052
1304
|
storage: 'plain' | 'tracked';
|
|
1053
1305
|
attributes: Array<Required<Pick<AttributeSpec, 'key' | 'label' | 'type' | 'required' | 'sensitivity' | 'readable_by_ai'>> & {
|
|
1054
1306
|
choices: string[] | null;
|
|
1307
|
+
visible_to: string[] | null;
|
|
1055
1308
|
}>;
|
|
1309
|
+
/** The kind's access rules. A kind with none set reads owner and staff `all`, for reading and writing. */
|
|
1310
|
+
access: KindAccess;
|
|
1056
1311
|
views: {
|
|
1057
1312
|
all: string;
|
|
1058
1313
|
for_agents: string;
|
|
1314
|
+
for_app: string;
|
|
1059
1315
|
};
|
|
1060
1316
|
}
|
|
1061
1317
|
/** Options for write(). */
|
|
@@ -1130,6 +1386,8 @@ export interface RecipeSpec {
|
|
|
1130
1386
|
export interface RecipeDescription extends Required<Omit<RecipeSpec, 'description' | 'params'>> {
|
|
1131
1387
|
description: string | null;
|
|
1132
1388
|
params: ParamSpec[];
|
|
1389
|
+
/** The app roles that may run it from the app (empty: only your server, with the account's token or an app key). The owner sets them in the hub or with Claude Code. */
|
|
1390
|
+
run_by: string[];
|
|
1133
1391
|
updated_at: string;
|
|
1134
1392
|
}
|
|
1135
1393
|
/** What a recipe run wrote. */
|
|
@@ -1336,7 +1594,10 @@ export declare class AwesomateAppClient {
|
|
|
1336
1594
|
queryAll<K extends KindName, S extends keyof RowOf<K> = keyof RowOf<K>>(kind: K, options?: Omit<QueryOptions<RowOf<K>, S>, 'after'> & {
|
|
1337
1595
|
maxRows?: number;
|
|
1338
1596
|
}): AsyncGenerator<Pick<RowOf<K>, S>>;
|
|
1339
|
-
/**
|
|
1597
|
+
/**
|
|
1598
|
+
* One record by id, or null when there is none this user may read. Ids are UUIDs: anything
|
|
1599
|
+
* else names no record, so it answers null without asking the hub.
|
|
1600
|
+
*/
|
|
1340
1601
|
get<K extends KindName>(kind: K, id: string): Promise<RowOf<K> | null>;
|
|
1341
1602
|
/** Create a record (returns its id) or change one with options.id, inside the kind's write rule for this user. */
|
|
1342
1603
|
write<K extends KindName>(kind: K, data: Partial<RowOf<K>> | Record<string, unknown>, options?: WriteOptions): Promise<string>;
|
|
@@ -1474,6 +1735,8 @@ export interface BookableService {
|
|
|
1474
1735
|
/** The price as the business wrote it. Shown, never charged. */
|
|
1475
1736
|
price_text: string;
|
|
1476
1737
|
location: string;
|
|
1738
|
+
/** How many hours before the start a customer can still cancel or move online. */
|
|
1739
|
+
cancel_cutoff_hours: number;
|
|
1477
1740
|
intake: BookingQuestion[];
|
|
1478
1741
|
calendars: Array<{
|
|
1479
1742
|
key: string;
|
|
@@ -1538,6 +1801,66 @@ export interface ManagedBooking {
|
|
|
1538
1801
|
canCancel: boolean;
|
|
1539
1802
|
canMove: boolean;
|
|
1540
1803
|
changesCloseAt: string;
|
|
1804
|
+
/** The price as the business wrote it. Shown, never charged. */
|
|
1805
|
+
priceText: string;
|
|
1806
|
+
}
|
|
1807
|
+
/** How the business looks, for a booking page in its colours. Each is null when the business has not set it. */
|
|
1808
|
+
export interface BookingPageLook {
|
|
1809
|
+
/** The business's website (https). */
|
|
1810
|
+
website: string | null;
|
|
1811
|
+
/** Its logo's address (https). */
|
|
1812
|
+
logo: string | null;
|
|
1813
|
+
/** Its main colour, as #rrggbb. */
|
|
1814
|
+
colour: string | null;
|
|
1815
|
+
}
|
|
1816
|
+
/** A booking as the business's staff see it (db.bookings), with its customer. */
|
|
1817
|
+
export interface StaffBooking {
|
|
1818
|
+
bookingId: string;
|
|
1819
|
+
status: 'confirmed' | 'cancelled' | 'completed' | 'no_show';
|
|
1820
|
+
/** The service's key, and its name when the booking was made. */
|
|
1821
|
+
service: string;
|
|
1822
|
+
serviceName: string | null;
|
|
1823
|
+
/** The calendar's key, and its name when the booking was made. */
|
|
1824
|
+
calendar: string;
|
|
1825
|
+
calendarName: string | null;
|
|
1826
|
+
startsAt: string;
|
|
1827
|
+
endsAt: string;
|
|
1828
|
+
seats: number;
|
|
1829
|
+
/** How it was made: website, hub, phone, agent or api. */
|
|
1830
|
+
source: string | null;
|
|
1831
|
+
/** The customer's answers to the service's questions, by question key. */
|
|
1832
|
+
answers: Record<string, string | string[]> | null;
|
|
1833
|
+
/** The customer's contact id in Contacts. */
|
|
1834
|
+
contactId: string;
|
|
1835
|
+
/** The customer, or null when they have since been removed from Contacts. */
|
|
1836
|
+
customer: {
|
|
1837
|
+
name: string | null;
|
|
1838
|
+
email: string | null;
|
|
1839
|
+
phone: string | null;
|
|
1840
|
+
} | null;
|
|
1841
|
+
createdAt: string;
|
|
1842
|
+
cancelledAt: string | null;
|
|
1843
|
+
}
|
|
1844
|
+
/** What db.bookings.book() needs. Take `startsAt` and `calendar` from an OpenTime. */
|
|
1845
|
+
export interface StaffBookingRequest {
|
|
1846
|
+
service: string;
|
|
1847
|
+
calendar: string;
|
|
1848
|
+
startsAt: string;
|
|
1849
|
+
/** The customer, found in Contacts by email or added. */
|
|
1850
|
+
email: string;
|
|
1851
|
+
firstName?: string;
|
|
1852
|
+
lastName?: string;
|
|
1853
|
+
phone?: string;
|
|
1854
|
+
/** Places, for a class. Default 1. */
|
|
1855
|
+
seats?: number;
|
|
1856
|
+
/** Answers to the service's questions, by question key. */
|
|
1857
|
+
answers?: Record<string, string | string[]>;
|
|
1858
|
+
/** One per booking, kept across retries, so a retry returns the same booking. */
|
|
1859
|
+
idempotencyKey?: string;
|
|
1860
|
+
/** Book outside the calendar's hours and notice. Overlap, seats and the daily limit still apply. */
|
|
1861
|
+
outsideHours?: boolean;
|
|
1862
|
+
/** false: no email to the customer (the calendar's notice address still hears). Default true. */
|
|
1863
|
+
notify?: boolean;
|
|
1541
1864
|
}
|
|
1542
1865
|
/** Options for createBookingsClient(). */
|
|
1543
1866
|
export interface BookingsClientOptions {
|
|
@@ -1579,13 +1902,19 @@ export declare class AwesomateBookingsClient {
|
|
|
1579
1902
|
}): Promise<OpenTime[]>;
|
|
1580
1903
|
/**
|
|
1581
1904
|
* Book a time for a visitor. They get an email with an invite and a link to change or cancel;
|
|
1582
|
-
* the business gets a notice. A
|
|
1583
|
-
*
|
|
1905
|
+
* the business gets a notice. A refusal has code `conflict` and `field` saying why:
|
|
1906
|
+
* - `not_open`, `slot_taken`, `too_many_seats`, `session_full`, `day_full`: the time is not
|
|
1907
|
+
* free (taken since you listed it, or not enough places left): list the times again;
|
|
1908
|
+
* - `too_many_open`: this email already has three bookings coming up;
|
|
1909
|
+
* - `monthly_limit`: the business has taken its plan's online bookings for the month.
|
|
1910
|
+
*
|
|
1911
|
+
* Show `personMessage` to the visitor for the last two.
|
|
1584
1912
|
*/
|
|
1585
1913
|
book(request: BookingRequest): Promise<BookingResult>;
|
|
1586
|
-
/** One booking, from the token in its manage link (manageTokenFrom()). */
|
|
1914
|
+
/** One booking, from the token in its manage link (manageTokenFrom()), with how the business looks so the page can match it. */
|
|
1587
1915
|
booking(manageToken: string): Promise<{
|
|
1588
1916
|
business: string;
|
|
1917
|
+
look: BookingPageLook;
|
|
1589
1918
|
booking: ManagedBooking;
|
|
1590
1919
|
}>;
|
|
1591
1920
|
/** The times a booking could move to, leaving its own time out. */
|
|
@@ -1593,11 +1922,18 @@ export declare class AwesomateBookingsClient {
|
|
|
1593
1922
|
from?: Date | string;
|
|
1594
1923
|
to?: Date | string;
|
|
1595
1924
|
}): Promise<OpenTime[]>;
|
|
1596
|
-
/**
|
|
1925
|
+
/**
|
|
1926
|
+
* Cancel a booking from its manage link. A second cancel answers cancelled: false. Once changes
|
|
1927
|
+
* have closed (`canCancel` false) it is refused with code `conflict`, field `too_late`.
|
|
1928
|
+
*/
|
|
1597
1929
|
cancel(manageToken: string, reason?: string): Promise<{
|
|
1598
1930
|
cancelled: boolean;
|
|
1599
1931
|
}>;
|
|
1600
|
-
/**
|
|
1932
|
+
/**
|
|
1933
|
+
* Move a booking to a time from openTimesToMove(). Refused with code `conflict` and field
|
|
1934
|
+
* `too_late` once changes have closed, or `not_open` (or another of book()'s reasons) when the
|
|
1935
|
+
* time is no longer free.
|
|
1936
|
+
*/
|
|
1601
1937
|
move(manageToken: string, startsAt: string): Promise<{
|
|
1602
1938
|
startsAt: string;
|
|
1603
1939
|
endsAt: string;
|
package/dist/index.js
CHANGED
|
@@ -24,8 +24,10 @@
|
|
|
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.25.0';
|
|
28
28
|
const DEFAULT_BASE = 'https://hub.awesomate.ai';
|
|
29
|
+
/** Every record and contact id is a UUID; anything else can name no row. */
|
|
30
|
+
const RECORD_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
29
31
|
const ERROR_CODES = ['unauthenticated', 'forbidden', 'not_found', 'validation', 'consent_blocked', 'rate_limited', 'conflict', 'unavailable'];
|
|
30
32
|
/**
|
|
31
33
|
* Every failure from the hub. `message` is for you, the developer; `personMessage`, when the hub
|
|
@@ -229,6 +231,24 @@ export class AwesomateClient {
|
|
|
229
231
|
},
|
|
230
232
|
/** Move to the trash. Not erased, and no longer counted toward the Files space. */
|
|
231
233
|
remove: async (path) => this.filesCall('DELETE', `/api/files?path=${encodeURIComponent(path)}`),
|
|
234
|
+
/** What is in the trash, and how many bytes it holds. Needs files:read. */
|
|
235
|
+
trash: async () => {
|
|
236
|
+
const r = await this.filesCall('GET', '/api/files/trash');
|
|
237
|
+
return { entries: r.entries, total_bytes: r.total_bytes };
|
|
238
|
+
},
|
|
239
|
+
/**
|
|
240
|
+
* Erase what is in the trash, for good: the only call that deletes a file outright. With
|
|
241
|
+
* `olderThanDays` (1 to 3650), only what was removed longer ago than that. Needs files:write.
|
|
242
|
+
* An automation account that cannot empty its trash yet answers serverCode trash_empty_unavailable.
|
|
243
|
+
*/
|
|
244
|
+
emptyTrash: async (options = {}) => {
|
|
245
|
+
const days = options.olderThanDays;
|
|
246
|
+
if (days !== undefined && (!Number.isInteger(days) || days < 1 || days > 3650)) {
|
|
247
|
+
throw new AwesomateError('validation', 'olderThanDays is a whole number of days from 1 to 3650, or leave it out to empty everything.', 0, 'olderThanDays');
|
|
248
|
+
}
|
|
249
|
+
const r = await this.filesCall('POST', '/api/files/trash/empty', days === undefined ? {} : { older_than_days: days });
|
|
250
|
+
return { removed: r.removed, bytes: r.bytes, files: r.files };
|
|
251
|
+
},
|
|
232
252
|
};
|
|
233
253
|
tasksCall(method, path, body) {
|
|
234
254
|
if (this.appKey) {
|
|
@@ -265,6 +285,20 @@ export class AwesomateClient {
|
|
|
265
285
|
drop: async (id) => {
|
|
266
286
|
await this.tasksCall('POST', `/api/my-tasks/tasks/${encodeURIComponent(String(id))}/drop`);
|
|
267
287
|
},
|
|
288
|
+
/**
|
|
289
|
+
* Open a done or dropped task again, with the person who had it. They get an email unless
|
|
290
|
+
* they are the one bringing it back. Who may: see `decided[].canBringBack` on the board.
|
|
291
|
+
*/
|
|
292
|
+
bringBack: async (id) => {
|
|
293
|
+
const r = await this.tasksCall('POST', `/api/my-tasks/tasks/${encodeURIComponent(String(id))}/reopen`);
|
|
294
|
+
return { emailed: r.emailed };
|
|
295
|
+
},
|
|
296
|
+
/**
|
|
297
|
+
* What happened to a given task, in order: who gave it, who passed it on, who closed it and
|
|
298
|
+
* brought it back, and how long each person had it. The owner reads any task's record; anyone
|
|
299
|
+
* else only one they were part of.
|
|
300
|
+
*/
|
|
301
|
+
record: (id) => this.tasksCall('GET', `/api/my-tasks/tasks/${encodeURIComponent(String(id))}/record`),
|
|
268
302
|
/** Put a card off until a time (at most 90 days), for this person only. */
|
|
269
303
|
notNow: async (key, until) => {
|
|
270
304
|
const r = await this.tasksCall('POST', `/api/my-tasks/items/${encodeURIComponent(key)}/not-now`, {
|
|
@@ -272,6 +306,10 @@ export class AwesomateClient {
|
|
|
272
306
|
});
|
|
273
307
|
return { until: r.until };
|
|
274
308
|
},
|
|
309
|
+
/** Bring a card put off with notNow() back now, for this person. */
|
|
310
|
+
cancelNotNow: async (key) => {
|
|
311
|
+
await this.tasksCall('DELETE', `/api/my-tasks/items/${encodeURIComponent(key)}/not-now`);
|
|
312
|
+
},
|
|
275
313
|
/** Hand a card to someone on the account who can act on it. */
|
|
276
314
|
passOn: async (key, toEmail, reason) => {
|
|
277
315
|
const r = await this.tasksCall('POST', `/api/my-tasks/items/${encodeURIComponent(key)}/pass-on`, {
|
|
@@ -280,6 +318,126 @@ export class AwesomateClient {
|
|
|
280
318
|
return { holder: r.holder, emailed: r.emailed };
|
|
281
319
|
},
|
|
282
320
|
};
|
|
321
|
+
/**
|
|
322
|
+
* The account's bookings, for the business's own staff screens: the list with each customer,
|
|
323
|
+
* one booking, the open times, and booking, cancelling, moving and recording an outcome on
|
|
324
|
+
* anyone's behalf. Needs the account's token (never an app key) and Bookings on the account.
|
|
325
|
+
* Reading needs crm:read, on every plan; changing needs crm:write, Support Plus and above.
|
|
326
|
+
* Booking, moving and cancelling email the customer and the calendar's notice address, unless
|
|
327
|
+
* `notify: false` (say a booking taken by phone that you confirm yourself). For visitors who are
|
|
328
|
+
* not signed in, use createBookingsClient() instead.
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* const today = await db.bookings.list({ from: '2026-10-08T00:00:00+11:00', to: '2026-10-09T00:00:00+11:00', status: 'confirmed' });
|
|
332
|
+
*/
|
|
333
|
+
bookings = {
|
|
334
|
+
/**
|
|
335
|
+
* Bookings that start in a window, soonest first, each with its customer. The window is from a
|
|
336
|
+
* day ago for 31 days unless told otherwise; `limit` is 1 to 500 (default 200).
|
|
337
|
+
*/
|
|
338
|
+
list: async (options = {}) => {
|
|
339
|
+
const q = new URLSearchParams();
|
|
340
|
+
const from = iso(options.from);
|
|
341
|
+
const to = iso(options.to);
|
|
342
|
+
if (from)
|
|
343
|
+
q.set('from', from);
|
|
344
|
+
if (to)
|
|
345
|
+
q.set('to', to);
|
|
346
|
+
if (options.status)
|
|
347
|
+
q.set('status', options.status);
|
|
348
|
+
if (options.calendar)
|
|
349
|
+
q.set('calendar', options.calendar);
|
|
350
|
+
if (options.limit)
|
|
351
|
+
q.set('limit', String(options.limit));
|
|
352
|
+
const qs = q.toString();
|
|
353
|
+
const r = await this.request('GET', `/api/my-crm/v1/bookings${qs ? `?${qs}` : ''}`, undefined, false);
|
|
354
|
+
return r.bookings.map(staffBooking);
|
|
355
|
+
},
|
|
356
|
+
/** One booking by its id, or null when there is none (an id that is not a booking id never reaches the hub). */
|
|
357
|
+
get: async (id) => {
|
|
358
|
+
if (!RECORD_ID.test(String(id)))
|
|
359
|
+
return null;
|
|
360
|
+
try {
|
|
361
|
+
const r = await this.request('GET', `/api/my-crm/v1/bookings/${encodeURIComponent(String(id))}`, undefined, false);
|
|
362
|
+
return staffBooking(r.booking);
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
if (err instanceof AwesomateError && err.code === 'not_found')
|
|
366
|
+
return null;
|
|
367
|
+
throw err;
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
/**
|
|
371
|
+
* The times a service can be booked, from now for two weeks unless told otherwise (at most 62
|
|
372
|
+
* days at once). The same open times customers see; book() with `outsideHours` goes beyond them.
|
|
373
|
+
*/
|
|
374
|
+
openTimes: async (service, options = {}) => {
|
|
375
|
+
const q = new URLSearchParams({ service });
|
|
376
|
+
const from = iso(options.from);
|
|
377
|
+
const to = iso(options.to);
|
|
378
|
+
if (from)
|
|
379
|
+
q.set('from', from);
|
|
380
|
+
if (to)
|
|
381
|
+
q.set('to', to);
|
|
382
|
+
if (options.calendar)
|
|
383
|
+
q.set('calendar', options.calendar);
|
|
384
|
+
if (options.seats)
|
|
385
|
+
q.set('seats', String(options.seats));
|
|
386
|
+
return (await this.request('GET', `/api/my-crm/v1/bookings/open-times?${q}`, undefined, false)).times;
|
|
387
|
+
},
|
|
388
|
+
/**
|
|
389
|
+
* Book a time for a customer named by email (found in Contacts, or added; consent is never
|
|
390
|
+
* changed). Switches Bookings on if it was not. A time that is not open is refused with code
|
|
391
|
+
* `conflict` and `field` saying why: not_open, slot_taken, too_many_seats, session_full or
|
|
392
|
+
* day_full. Pass `idempotencyKey` so a retry returns the same booking.
|
|
393
|
+
*/
|
|
394
|
+
book: async (request) => {
|
|
395
|
+
const r = await this.request('POST', '/api/my-crm/v1/bookings', {
|
|
396
|
+
service: request.service, calendar: request.calendar, starts_at: request.startsAt, email: request.email,
|
|
397
|
+
first_name: request.firstName, last_name: request.lastName, phone: request.phone, seats: request.seats, answers: request.answers,
|
|
398
|
+
idempotency_key: request.idempotencyKey, outside_hours: request.outsideHours, notify: request.notify,
|
|
399
|
+
}, false);
|
|
400
|
+
return { bookingId: r.booking_id, created: r.created, startsAt: r.starts_at, endsAt: r.ends_at };
|
|
401
|
+
},
|
|
402
|
+
/** Cancel a booking. A booking already cancelled answers `cancelled: false`. */
|
|
403
|
+
cancel: async (id, options = {}) => {
|
|
404
|
+
const r = await this.request('POST', `/api/my-crm/v1/bookings/${encodeURIComponent(String(id))}/cancel`, { reason: options.reason, notify: options.notify }, false);
|
|
405
|
+
return { bookingId: r.booking_id, cancelled: r.cancelled, status: r.status };
|
|
406
|
+
},
|
|
407
|
+
/**
|
|
408
|
+
* Move a confirmed booking to a new start, on the same calendar or another (`calendar`). A time
|
|
409
|
+
* that is not open is refused with code `conflict` (field not_open, slot_taken, session_full or
|
|
410
|
+
* day_full) unless `outsideHours`; a booking that is not confirmed, or a time that has passed,
|
|
411
|
+
* with code `validation`.
|
|
412
|
+
*/
|
|
413
|
+
move: async (id, startsAt, options = {}) => {
|
|
414
|
+
const r = await this.request('POST', `/api/my-crm/v1/bookings/${encodeURIComponent(String(id))}/move`, { starts_at: iso(startsAt), calendar: options.calendar, outside_hours: options.outsideHours, notify: options.notify }, false);
|
|
415
|
+
return { bookingId: r.booking_id, startsAt: r.starts_at, endsAt: r.ends_at, calendar: r.calendar };
|
|
416
|
+
},
|
|
417
|
+
/** Record how a booking went: the customer came (`completed`) or did not (`no_show`). Sends no email. */
|
|
418
|
+
outcome: async (id, status) => {
|
|
419
|
+
await this.request('POST', `/api/my-crm/v1/bookings/${encodeURIComponent(String(id))}/outcome`, { status }, false);
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* Tell Contacts something happened to someone ("job_paid", "quote_sent"), so any email series
|
|
424
|
+
* waiting for that event starts or stops for them. The person is found by email, or added; it
|
|
425
|
+
* never signs anyone up for email or changes their consent. Sending the same `key` twice records
|
|
426
|
+
* the event once (`duplicate: true`), so pass your own id for it. `record` names the quote, job or
|
|
427
|
+
* booking it is about. An event more than two days old is kept as a fact a series reads, never
|
|
428
|
+
* a start. Needs the account's token with crm:write (Support Plus and above), never an app key.
|
|
429
|
+
* The answer is the same whether or not the address belongs to someone erased from Contacts.
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* await db.recordEvent({ event: 'job_paid', email: 'sam@example.com', record: 'job-1042', key: 'xero:INV-1042' });
|
|
433
|
+
*/
|
|
434
|
+
async recordEvent(event) {
|
|
435
|
+
const r = await this.request('POST', '/api/my-crm/v1/events', {
|
|
436
|
+
event: event.event, email: event.email, record: event.record, occurred_at: iso(event.occurredAt), key: event.key,
|
|
437
|
+
first_name: event.firstName, last_name: event.lastName,
|
|
438
|
+
}, false);
|
|
439
|
+
return { duplicate: !!r.duplicate };
|
|
440
|
+
}
|
|
283
441
|
/** The kinds this token can read, their columns, operators and examples. */
|
|
284
442
|
schema() {
|
|
285
443
|
return this.request('GET', '/api/my-crm/v1/rows/schema');
|
|
@@ -317,8 +475,13 @@ export class AwesomateClient {
|
|
|
317
475
|
after = page.next ?? undefined;
|
|
318
476
|
} while (after);
|
|
319
477
|
}
|
|
320
|
-
/**
|
|
478
|
+
/**
|
|
479
|
+
* One row by id, or null when there is none this token can read. Ids are UUIDs: anything else
|
|
480
|
+
* names no row, so it answers null without asking the hub.
|
|
481
|
+
*/
|
|
321
482
|
async get(kind, id, options = {}) {
|
|
483
|
+
if (!RECORD_ID.test(String(id)))
|
|
484
|
+
return null;
|
|
322
485
|
try {
|
|
323
486
|
const qs = options.tz ? `?tz=${encodeURIComponent(options.tz)}` : '';
|
|
324
487
|
const r = await this.request('GET', `/api/my-crm/v1/rows/${encodeURIComponent(kind)}/${encodeURIComponent(String(id))}${qs}`);
|
|
@@ -431,6 +594,29 @@ export class AwesomateClient {
|
|
|
431
594
|
async businessMapV1() {
|
|
432
595
|
return (await this.request('GET', '/api/my-business/v1/map?format=json')).map;
|
|
433
596
|
}
|
|
597
|
+
/**
|
|
598
|
+
* The business's long documents that exist (brand guide, voice guide, brand from the website,
|
|
599
|
+
* business summary): kind, version, length and where each came from, without the text. Needs
|
|
600
|
+
* the account's token (hosting:read, every plan), never an app key.
|
|
601
|
+
*/
|
|
602
|
+
async businessDocuments() {
|
|
603
|
+
return (await this.request('GET', '/api/my-business/v1/documents')).documents;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* One of the business's documents with its text (the current version), or null when the
|
|
607
|
+
* business has none of that kind yet. The text is the owner's own: use it for their business
|
|
608
|
+
* (agent instructions, site copy in their voice), never show it to anyone else.
|
|
609
|
+
*/
|
|
610
|
+
async businessDocument(kind) {
|
|
611
|
+
try {
|
|
612
|
+
return await this.request('GET', `/api/my-business/v1/documents/${encodeURIComponent(kind)}`);
|
|
613
|
+
}
|
|
614
|
+
catch (err) {
|
|
615
|
+
if (err instanceof AwesomateError && err.code === 'not_found')
|
|
616
|
+
return null;
|
|
617
|
+
throw err;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
434
620
|
/** Details waiting for the owner's yes on Your business: from the website, the account, a team member or a file. */
|
|
435
621
|
async businessSuggestions() {
|
|
436
622
|
return (await this.request('GET', '/api/my-business/v1/proposals')).proposals;
|
|
@@ -726,8 +912,13 @@ export class AwesomateAppClient {
|
|
|
726
912
|
after = page.next ?? undefined;
|
|
727
913
|
} while (after);
|
|
728
914
|
}
|
|
729
|
-
/**
|
|
915
|
+
/**
|
|
916
|
+
* One record by id, or null when there is none this user may read. Ids are UUIDs: anything
|
|
917
|
+
* else names no record, so it answers null without asking the hub.
|
|
918
|
+
*/
|
|
730
919
|
async get(kind, id) {
|
|
920
|
+
if (!RECORD_ID.test(String(id)))
|
|
921
|
+
return null;
|
|
731
922
|
try {
|
|
732
923
|
return (await this.data('GET', `/rows/${encodeURIComponent(kind)}/${encodeURIComponent(String(id))}`)).row;
|
|
733
924
|
}
|
|
@@ -1238,6 +1429,15 @@ export function createAppClient(options) {
|
|
|
1238
1429
|
export function createClient(options) {
|
|
1239
1430
|
return new AwesomateClient(options);
|
|
1240
1431
|
}
|
|
1432
|
+
function staffBooking(b) {
|
|
1433
|
+
return {
|
|
1434
|
+
bookingId: b.booking_id, status: b.status, service: b.service,
|
|
1435
|
+
serviceName: b.service_name ?? null, calendar: b.calendar, calendarName: b.calendar_name ?? null,
|
|
1436
|
+
startsAt: b.starts_at, endsAt: b.ends_at, seats: b.seats, source: b.source ?? null,
|
|
1437
|
+
answers: b.answers ?? null, contactId: b.contact_id,
|
|
1438
|
+
customer: b.customer ?? null, createdAt: b.created_at, cancelledAt: b.cancelled_at ?? null,
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1241
1441
|
/** The token in a booking's manage link (`/booking?t=...`), or null. */
|
|
1242
1442
|
export function manageTokenFrom(url) {
|
|
1243
1443
|
try {
|
|
@@ -1312,8 +1512,13 @@ export class AwesomateBookingsClient {
|
|
|
1312
1512
|
}
|
|
1313
1513
|
/**
|
|
1314
1514
|
* Book a time for a visitor. They get an email with an invite and a link to change or cancel;
|
|
1315
|
-
* the business gets a notice. A
|
|
1316
|
-
*
|
|
1515
|
+
* the business gets a notice. A refusal has code `conflict` and `field` saying why:
|
|
1516
|
+
* - `not_open`, `slot_taken`, `too_many_seats`, `session_full`, `day_full`: the time is not
|
|
1517
|
+
* free (taken since you listed it, or not enough places left): list the times again;
|
|
1518
|
+
* - `too_many_open`: this email already has three bookings coming up;
|
|
1519
|
+
* - `monthly_limit`: the business has taken its plan's online bookings for the month.
|
|
1520
|
+
*
|
|
1521
|
+
* Show `personMessage` to the visitor for the last two.
|
|
1317
1522
|
*/
|
|
1318
1523
|
async book(request) {
|
|
1319
1524
|
const r = await this.request('POST', '/book', {
|
|
@@ -1322,17 +1527,18 @@ export class AwesomateBookingsClient {
|
|
|
1322
1527
|
});
|
|
1323
1528
|
return { bookingId: r.booking_id, created: r.created, startsAt: r.starts_at, endsAt: r.ends_at, manageUrl: r.manage_url };
|
|
1324
1529
|
}
|
|
1325
|
-
/** One booking, from the token in its manage link (manageTokenFrom()). */
|
|
1530
|
+
/** One booking, from the token in its manage link (manageTokenFrom()), with how the business looks so the page can match it. */
|
|
1326
1531
|
async booking(manageToken) {
|
|
1327
1532
|
const r = await this.request('GET', `/manage?t=${encodeURIComponent(manageToken)}`, undefined, false);
|
|
1328
1533
|
const b = r.booking;
|
|
1329
1534
|
return {
|
|
1330
1535
|
business: r.business,
|
|
1536
|
+
look: { website: r.look?.website ?? null, logo: r.look?.logo ?? null, colour: r.look?.colour ?? null },
|
|
1331
1537
|
booking: {
|
|
1332
1538
|
bookingId: b.booking_id, status: b.status, service: b.service ?? null,
|
|
1333
1539
|
with: b.with ?? null, startsAt: b.starts_at, endsAt: b.ends_at, timezone: b.timezone,
|
|
1334
1540
|
location: b.location ?? '', seats: b.seats, canCancel: !!b.can_cancel, canMove: !!b.can_move,
|
|
1335
|
-
changesCloseAt: b.changes_close_at,
|
|
1541
|
+
changesCloseAt: b.changes_close_at, priceText: b.price_text ?? '',
|
|
1336
1542
|
},
|
|
1337
1543
|
};
|
|
1338
1544
|
}
|
|
@@ -1347,11 +1553,18 @@ export class AwesomateBookingsClient {
|
|
|
1347
1553
|
q.set('to', to);
|
|
1348
1554
|
return (await this.request('GET', `/manage/open-times?${q}`, undefined, false)).times;
|
|
1349
1555
|
}
|
|
1350
|
-
/**
|
|
1556
|
+
/**
|
|
1557
|
+
* Cancel a booking from its manage link. A second cancel answers cancelled: false. Once changes
|
|
1558
|
+
* have closed (`canCancel` false) it is refused with code `conflict`, field `too_late`.
|
|
1559
|
+
*/
|
|
1351
1560
|
cancel(manageToken, reason) {
|
|
1352
1561
|
return this.request('POST', '/manage/cancel', { t: manageToken, reason }, false);
|
|
1353
1562
|
}
|
|
1354
|
-
/**
|
|
1563
|
+
/**
|
|
1564
|
+
* Move a booking to a time from openTimesToMove(). Refused with code `conflict` and field
|
|
1565
|
+
* `too_late` once changes have closed, or `not_open` (or another of book()'s reasons) when the
|
|
1566
|
+
* time is no longer free.
|
|
1567
|
+
*/
|
|
1355
1568
|
async move(manageToken, startsAt) {
|
|
1356
1569
|
const r = await this.request('POST', '/manage/move', { t: manageToken, starts_at: startsAt }, false);
|
|
1357
1570
|
return { startsAt: r.starts_at, endsAt: r.ends_at };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awesomate/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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",
|