@awesomate/sdk 0.23.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 +626 -53
- package/dist/index.js +247 -13
- 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>;
|
|
@@ -516,12 +702,39 @@ export declare class AwesomateClient {
|
|
|
516
702
|
*/
|
|
517
703
|
business(): Promise<BusinessIdentity>;
|
|
518
704
|
/**
|
|
519
|
-
* The business map: the seven
|
|
520
|
-
* which agents and automations help which
|
|
521
|
-
* important first. Read only: the owner changes the map in the hub.
|
|
522
|
-
* (hosting:read, every plan), never an app key, and the account must
|
|
705
|
+
* The business map: the seven departments every business has, their sub-departments, the roles in
|
|
706
|
+
* each and who holds them, which agents and automations help which role and how far each may go,
|
|
707
|
+
* and what is missing, most important first. Read only: the owner changes the map in the hub.
|
|
708
|
+
* Needs the account's token (hosting:read, every plan), never an app key, and the account must
|
|
709
|
+
* have the business map. Since 0.24.0 in the words the owner sees; businessMapV1() is the old shape.
|
|
523
710
|
*/
|
|
524
711
|
businessMap(): Promise<BusinessMap>;
|
|
712
|
+
/** Every role on the map, one line each: slug, title, department and who holds it. */
|
|
713
|
+
businessMapRoles(): Promise<BusinessMapRoleSummary[]>;
|
|
714
|
+
/**
|
|
715
|
+
* One role by its slug, with the line each helper's instructions carry (`helpers[].instructionLine`):
|
|
716
|
+
* which role it helps, for whom, how far it may go and where its procedures are.
|
|
717
|
+
*/
|
|
718
|
+
businessMapRole(slug: string): Promise<BusinessMapRoleDetail>;
|
|
719
|
+
/**
|
|
720
|
+
* The business map in its first shape, where a department is a `division`, a sub-department a
|
|
721
|
+
* `department` and a role a `job`.
|
|
722
|
+
* @deprecated Use businessMap(), which uses the words the owner sees. v1 is removed once it has
|
|
723
|
+
* gone 30 days unused, and not before 2026-11-06.
|
|
724
|
+
*/
|
|
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>;
|
|
525
738
|
/** Details waiting for the owner's yes on Your business: from the website, the account, a team member or a file. */
|
|
526
739
|
businessSuggestions(): Promise<BusinessSuggestion[]>;
|
|
527
740
|
/** Grouped numbers (counts, sums) in the Business Data API's shape. */
|
|
@@ -592,20 +805,20 @@ export interface BusinessMapHelper {
|
|
|
592
805
|
ref: string;
|
|
593
806
|
label: string;
|
|
594
807
|
}
|
|
595
|
-
/** A
|
|
596
|
-
export interface
|
|
808
|
+
/** A role on the map. `slug` is its stable name for routing work: "whoever holds quotes". */
|
|
809
|
+
export interface BusinessMapRole {
|
|
597
810
|
id: number;
|
|
598
811
|
slug: string;
|
|
599
812
|
title: string;
|
|
600
813
|
mission: string | null;
|
|
601
814
|
/** active: someone holds it. open: nobody does. hire_next: the owner's next hire. */
|
|
602
815
|
state: 'active' | 'open' | 'hire_next';
|
|
603
|
-
|
|
604
|
-
/** Runs its
|
|
605
|
-
headsDivision: boolean;
|
|
606
|
-
/** Runs its department: the person responsible for it, and for its department-level KPIs. */
|
|
816
|
+
isOwnerRole: boolean;
|
|
817
|
+
/** Runs its department: above its sub-departments, so subDepartmentNo is null (the owner's role excepted). */
|
|
607
818
|
headsDepartment: boolean;
|
|
608
|
-
|
|
819
|
+
/** Runs its sub-department: the person responsible for it, and for its KPIs. */
|
|
820
|
+
headsSubDepartment: boolean;
|
|
821
|
+
subDepartmentNo: number | null;
|
|
609
822
|
reportsTo: {
|
|
610
823
|
id: number;
|
|
611
824
|
title: string;
|
|
@@ -628,12 +841,12 @@ export interface BusinessMapJob {
|
|
|
628
841
|
exceptions: string | null;
|
|
629
842
|
minutesSavedPerRun: number | null;
|
|
630
843
|
}>;
|
|
631
|
-
/** The tag this
|
|
844
|
+
/** The tag this role's procedures carry in 1Brain, such as 'job:quotes'. The map keeps no procedure text. */
|
|
632
845
|
proceduresTag: string;
|
|
633
|
-
/** Quarterly priorities on this
|
|
846
|
+
/** Quarterly priorities on this role, every quarter. */
|
|
634
847
|
priorities: BusinessMapPriority[];
|
|
635
848
|
}
|
|
636
|
-
/** A quarterly priority on a
|
|
849
|
+
/** A quarterly priority on a role: one of the few things it must move this quarter. */
|
|
637
850
|
export interface BusinessMapPriority {
|
|
638
851
|
id: number;
|
|
639
852
|
/** '2026-Q4'. */
|
|
@@ -649,15 +862,15 @@ export interface BusinessMapPriority {
|
|
|
649
862
|
export interface BusinessMapPathStep {
|
|
650
863
|
position: number;
|
|
651
864
|
label: string;
|
|
652
|
-
|
|
865
|
+
departmentNo: 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
653
866
|
verb: string;
|
|
654
|
-
/** The
|
|
655
|
-
|
|
867
|
+
/** The role that looks after the step, when the owner named one. */
|
|
868
|
+
role: {
|
|
656
869
|
id: number;
|
|
657
870
|
slug: string;
|
|
658
871
|
title: string;
|
|
659
872
|
} | null;
|
|
660
|
-
/** Who looks after it: the
|
|
873
|
+
/** Who looks after it: the role's holder, else whoever runs the department (byDefault). */
|
|
661
874
|
owner: {
|
|
662
875
|
name: string;
|
|
663
876
|
byDefault: boolean;
|
|
@@ -665,8 +878,23 @@ export interface BusinessMapPathStep {
|
|
|
665
878
|
/** Where this step hands over to the next, in the owner's words. */
|
|
666
879
|
handoffRule: string | null;
|
|
667
880
|
}
|
|
668
|
-
/**
|
|
669
|
-
export interface
|
|
881
|
+
/** A sub-department and who runs it: the holder of the role that heads it, else whoever runs the department (byDefault). */
|
|
882
|
+
export interface BusinessMapSubDepartment {
|
|
883
|
+
no: number;
|
|
884
|
+
name: string;
|
|
885
|
+
gloss: string;
|
|
886
|
+
runBy: {
|
|
887
|
+
name: string;
|
|
888
|
+
byDefault: boolean;
|
|
889
|
+
};
|
|
890
|
+
headRole: {
|
|
891
|
+
id: number;
|
|
892
|
+
slug: string;
|
|
893
|
+
title: string;
|
|
894
|
+
} | null;
|
|
895
|
+
}
|
|
896
|
+
/** One of the seven departments, in board order (7 first). */
|
|
897
|
+
export interface BusinessMapDepartment {
|
|
670
898
|
no: 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
671
899
|
/** Envision, Form, Promise, Balance, Fulfil, Refine, Share. */
|
|
672
900
|
verb: string;
|
|
@@ -678,12 +906,230 @@ export interface BusinessMapDivision {
|
|
|
678
906
|
name: string;
|
|
679
907
|
byDefault: boolean;
|
|
680
908
|
};
|
|
909
|
+
roles: BusinessMapRole[];
|
|
910
|
+
/** Helping here but not yet put on a role, with how they were placed, in words. */
|
|
911
|
+
helpers: Array<BusinessMapHelper & {
|
|
912
|
+
placedBy: string;
|
|
913
|
+
}>;
|
|
914
|
+
subDepartments: BusinessMapSubDepartment[];
|
|
915
|
+
ideas: Array<{
|
|
916
|
+
label: string;
|
|
917
|
+
what: string;
|
|
918
|
+
status: 'live' | 'library' | 'coming' | 'building' | 'planned';
|
|
919
|
+
path?: string;
|
|
920
|
+
}>;
|
|
921
|
+
numbers: Array<{
|
|
922
|
+
label: string;
|
|
923
|
+
kind: 'lead' | 'result';
|
|
924
|
+
}>;
|
|
925
|
+
question: string;
|
|
926
|
+
/** 1Brain Departments whose procedures belong in this department. */
|
|
927
|
+
oneBrainDepartments: string[];
|
|
928
|
+
/** People on the map who hold no role yet. They sit in Form (department 1) for display; empty for every other department. */
|
|
929
|
+
noRoleYet: Array<{
|
|
930
|
+
id: number;
|
|
931
|
+
name: string;
|
|
932
|
+
}>;
|
|
933
|
+
}
|
|
934
|
+
/** The business map, as businessMap() returns it. No email addresses. */
|
|
935
|
+
export interface BusinessMap {
|
|
936
|
+
version: 2;
|
|
937
|
+
business: {
|
|
938
|
+
name: string | null;
|
|
939
|
+
ownerName: string;
|
|
940
|
+
};
|
|
941
|
+
/** false: the owner has not started the map, so it is worked out from what the account runs. */
|
|
942
|
+
stored: boolean;
|
|
943
|
+
/** access: what the person may do in the hub (owner, full or view). */
|
|
944
|
+
people: Array<{
|
|
945
|
+
id: number | null;
|
|
946
|
+
name: string;
|
|
947
|
+
kind: 'owner' | 'staff' | 'contractor' | 'adviser' | null;
|
|
948
|
+
access: 'owner' | 'full' | 'view' | null;
|
|
949
|
+
location: string | null;
|
|
950
|
+
fromTeamAccess: boolean;
|
|
951
|
+
}>;
|
|
952
|
+
departments: BusinessMapDepartment[];
|
|
953
|
+
/** Helpers we could not place on a department. */
|
|
954
|
+
unplaced: Array<BusinessMapHelper & {
|
|
955
|
+
placedBy: string;
|
|
956
|
+
}>;
|
|
957
|
+
/** How a customer moves through the business. stored false: suggested for its kind of business, not set by the owner yet. */
|
|
958
|
+
path: {
|
|
959
|
+
stored: boolean;
|
|
960
|
+
template: {
|
|
961
|
+
key: string;
|
|
962
|
+
label: string;
|
|
963
|
+
} | null;
|
|
964
|
+
steps: BusinessMapPathStep[];
|
|
965
|
+
};
|
|
966
|
+
/** This quarter, '2026-Q4' (UTC): the one the map shows priorities for. */
|
|
967
|
+
quarter: string;
|
|
968
|
+
/** What is missing, most important first. */
|
|
969
|
+
gaps: Array<{
|
|
970
|
+
kind: 'procedure_first' | 'no_helper' | 'role_open' | 'path_missing' | 'path_unowned' | 'owner_everywhere' | 'unplaced';
|
|
971
|
+
department: number | null;
|
|
972
|
+
title: string;
|
|
973
|
+
detail: string;
|
|
974
|
+
action: {
|
|
975
|
+
label: string;
|
|
976
|
+
path: string;
|
|
977
|
+
} | null;
|
|
978
|
+
}>;
|
|
979
|
+
/** oneBrainCategory: the 1Brain category this business is, once linked. */
|
|
980
|
+
settings: {
|
|
981
|
+
adviser: string | null;
|
|
982
|
+
runsWeek: string | null;
|
|
983
|
+
oneBrainCategory: string | null;
|
|
984
|
+
};
|
|
985
|
+
counts: {
|
|
986
|
+
people: number;
|
|
987
|
+
helpers: number;
|
|
988
|
+
placed: number;
|
|
989
|
+
departmentsWithHelpers: number;
|
|
990
|
+
roles: number;
|
|
991
|
+
};
|
|
992
|
+
/** Sources that could not be read just now: a missing helper may simply not have been read. */
|
|
993
|
+
unavailable: string[];
|
|
994
|
+
}
|
|
995
|
+
/** One role in businessMapRoles(). */
|
|
996
|
+
export interface BusinessMapRoleSummary {
|
|
997
|
+
slug: string;
|
|
998
|
+
title: string;
|
|
999
|
+
state: BusinessMapRole['state'];
|
|
1000
|
+
department: {
|
|
1001
|
+
no: number;
|
|
1002
|
+
verb: string;
|
|
1003
|
+
name: string;
|
|
1004
|
+
};
|
|
1005
|
+
/** The accountable holder, else the first; null when nobody holds it. */
|
|
1006
|
+
holder: string | null;
|
|
1007
|
+
}
|
|
1008
|
+
/** One role, as businessMapRole() returns it. */
|
|
1009
|
+
export interface BusinessMapRoleDetail {
|
|
1010
|
+
slug: string;
|
|
1011
|
+
title: string;
|
|
1012
|
+
mission: string | null;
|
|
1013
|
+
state: BusinessMapRole['state'];
|
|
1014
|
+
department: {
|
|
1015
|
+
no: number;
|
|
1016
|
+
verb: string;
|
|
1017
|
+
name: string;
|
|
1018
|
+
};
|
|
1019
|
+
headsDepartment: boolean;
|
|
1020
|
+
headsSubDepartment: boolean;
|
|
1021
|
+
reportsTo: {
|
|
1022
|
+
title: string;
|
|
1023
|
+
slug: string | null;
|
|
1024
|
+
} | null;
|
|
1025
|
+
holder: string | null;
|
|
1026
|
+
procedures: {
|
|
1027
|
+
where: '1Brain';
|
|
1028
|
+
tag: string;
|
|
1029
|
+
};
|
|
1030
|
+
/** This quarter's and next quarter's. */
|
|
1031
|
+
priorities: Array<{
|
|
1032
|
+
quarter: string;
|
|
1033
|
+
title: string;
|
|
1034
|
+
status: BusinessMapPriority['status'];
|
|
1035
|
+
owner: string | null;
|
|
1036
|
+
dueOn: string | null;
|
|
1037
|
+
}>;
|
|
1038
|
+
pathSteps: Array<{
|
|
1039
|
+
position: number;
|
|
1040
|
+
label: string;
|
|
1041
|
+
handoffRule: string | null;
|
|
1042
|
+
}>;
|
|
1043
|
+
holders: Array<{
|
|
1044
|
+
name: string;
|
|
1045
|
+
accountable: boolean;
|
|
1046
|
+
timeSharePct: number | null;
|
|
1047
|
+
}>;
|
|
1048
|
+
responsibilities: Array<{
|
|
1049
|
+
text: string;
|
|
1050
|
+
level: BusinessMapLevel;
|
|
1051
|
+
levelLabel: string;
|
|
1052
|
+
exceptions: string | null;
|
|
1053
|
+
}>;
|
|
1054
|
+
helpers: Array<{
|
|
1055
|
+
kind: BusinessMapHelper['kind'];
|
|
1056
|
+
ref: string;
|
|
1057
|
+
label: string;
|
|
1058
|
+
supervisor: string;
|
|
1059
|
+
level: BusinessMapLevel | null;
|
|
1060
|
+
levelLabel: string | null;
|
|
1061
|
+
exceptions: string | null;
|
|
1062
|
+
instructionLine: string;
|
|
1063
|
+
}>;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* A job on the v1 map (a role).
|
|
1067
|
+
* @deprecated v1 shape, from businessMapV1(). Use BusinessMapRole.
|
|
1068
|
+
*/
|
|
1069
|
+
export interface BusinessMapJob {
|
|
1070
|
+
id: number;
|
|
1071
|
+
slug: string;
|
|
1072
|
+
title: string;
|
|
1073
|
+
mission: string | null;
|
|
1074
|
+
state: 'active' | 'open' | 'hire_next';
|
|
1075
|
+
isOwnerJob: boolean;
|
|
1076
|
+
/** Runs its division (a department). */
|
|
1077
|
+
headsDivision: boolean;
|
|
1078
|
+
/** Runs its department (a sub-department). */
|
|
1079
|
+
headsDepartment: boolean;
|
|
1080
|
+
/** The sub-department. */
|
|
1081
|
+
departmentNo: number | null;
|
|
1082
|
+
reportsTo: {
|
|
1083
|
+
id: number;
|
|
1084
|
+
title: string;
|
|
1085
|
+
} | null;
|
|
1086
|
+
holders: BusinessMapRole['holders'];
|
|
1087
|
+
responsibilities: BusinessMapRole['responsibilities'];
|
|
1088
|
+
helpers: BusinessMapRole['helpers'];
|
|
1089
|
+
proceduresTag: string;
|
|
1090
|
+
priorities: BusinessMapPriority[];
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* One step of the customer's path on the v1 map.
|
|
1094
|
+
* @deprecated v1 shape, from businessMapV1(). Use BusinessMapPathStep.
|
|
1095
|
+
*/
|
|
1096
|
+
export interface BusinessMapPathStepV1 {
|
|
1097
|
+
position: number;
|
|
1098
|
+
label: string;
|
|
1099
|
+
/** The department. */
|
|
1100
|
+
divisionNo: 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
1101
|
+
verb: string;
|
|
1102
|
+
/** The role. */
|
|
1103
|
+
job: {
|
|
1104
|
+
id: number;
|
|
1105
|
+
slug: string;
|
|
1106
|
+
title: string;
|
|
1107
|
+
} | null;
|
|
1108
|
+
owner: {
|
|
1109
|
+
name: string;
|
|
1110
|
+
byDefault: boolean;
|
|
1111
|
+
};
|
|
1112
|
+
handoffRule: string | null;
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* A division on the v1 map (a department).
|
|
1116
|
+
* @deprecated v1 shape, from businessMapV1(). Use BusinessMapDepartment.
|
|
1117
|
+
*/
|
|
1118
|
+
export interface BusinessMapDivision {
|
|
1119
|
+
no: 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
1120
|
+
verb: string;
|
|
1121
|
+
name: string;
|
|
1122
|
+
purpose: string;
|
|
1123
|
+
stage: 'survive' | 'grow' | 'scale';
|
|
1124
|
+
runBy: {
|
|
1125
|
+
name: string;
|
|
1126
|
+
byDefault: boolean;
|
|
1127
|
+
};
|
|
681
1128
|
jobs: BusinessMapJob[];
|
|
682
|
-
/** Helping here but not yet put on a job, with how they were placed, in words. */
|
|
683
1129
|
helpers: Array<BusinessMapHelper & {
|
|
684
1130
|
placedBy: string;
|
|
685
1131
|
}>;
|
|
686
|
-
/**
|
|
1132
|
+
/** The sub-departments. */
|
|
687
1133
|
departments: Array<{
|
|
688
1134
|
no: number;
|
|
689
1135
|
name: string;
|
|
@@ -698,33 +1144,26 @@ export interface BusinessMapDivision {
|
|
|
698
1144
|
title: string;
|
|
699
1145
|
} | null;
|
|
700
1146
|
}>;
|
|
701
|
-
ideas:
|
|
702
|
-
|
|
703
|
-
what: string;
|
|
704
|
-
status: 'live' | 'library' | 'coming' | 'building' | 'planned';
|
|
705
|
-
path?: string;
|
|
706
|
-
}>;
|
|
707
|
-
numbers: Array<{
|
|
708
|
-
label: string;
|
|
709
|
-
kind: 'lead' | 'result';
|
|
710
|
-
}>;
|
|
1147
|
+
ideas: BusinessMapDepartment['ideas'];
|
|
1148
|
+
numbers: BusinessMapDepartment['numbers'];
|
|
711
1149
|
question: string;
|
|
712
|
-
/** 1Brain departments whose procedures belong in this division. */
|
|
713
1150
|
oneBrainDepartments: string[];
|
|
714
|
-
/** People on the map who wear no hat yet. They sit in Form (division 1) for display; empty for every other division. */
|
|
715
1151
|
noHatYet: Array<{
|
|
716
1152
|
id: number;
|
|
717
1153
|
name: string;
|
|
718
1154
|
}>;
|
|
719
1155
|
}
|
|
720
|
-
/**
|
|
721
|
-
|
|
1156
|
+
/**
|
|
1157
|
+
* The business map in its first shape, as businessMapV1() returns it.
|
|
1158
|
+
* @deprecated Use BusinessMap, from businessMap().
|
|
1159
|
+
*/
|
|
1160
|
+
export interface BusinessMapV1 {
|
|
722
1161
|
business: {
|
|
723
1162
|
name: string | null;
|
|
724
1163
|
ownerName: string;
|
|
725
1164
|
};
|
|
726
|
-
/** false: the owner has not started the map, so it is worked out from what the account runs. */
|
|
727
1165
|
stored: boolean;
|
|
1166
|
+
/** role: the person's access. */
|
|
728
1167
|
people: Array<{
|
|
729
1168
|
id: number | null;
|
|
730
1169
|
name: string;
|
|
@@ -734,22 +1173,18 @@ export interface BusinessMap {
|
|
|
734
1173
|
fromTeamAccess: boolean;
|
|
735
1174
|
}>;
|
|
736
1175
|
divisions: BusinessMapDivision[];
|
|
737
|
-
/** Helpers we could not place on a division. */
|
|
738
1176
|
unplaced: Array<BusinessMapHelper & {
|
|
739
1177
|
placedBy: string;
|
|
740
1178
|
}>;
|
|
741
|
-
/** How a customer moves through the business. stored false: suggested for its kind of business, not set by the owner yet. */
|
|
742
1179
|
path: {
|
|
743
1180
|
stored: boolean;
|
|
744
1181
|
template: {
|
|
745
1182
|
key: string;
|
|
746
1183
|
label: string;
|
|
747
1184
|
} | null;
|
|
748
|
-
steps:
|
|
1185
|
+
steps: BusinessMapPathStepV1[];
|
|
749
1186
|
};
|
|
750
|
-
/** This quarter, '2026-Q4' (UTC): the one the map shows priorities for. */
|
|
751
1187
|
quarter: string;
|
|
752
|
-
/** What is missing, most important first. */
|
|
753
1188
|
gaps: Array<{
|
|
754
1189
|
kind: 'procedure_first' | 'no_helper' | 'job_open' | 'path_missing' | 'path_unowned' | 'owner_everywhere' | 'unplaced';
|
|
755
1190
|
division: number | null;
|
|
@@ -763,6 +1198,7 @@ export interface BusinessMap {
|
|
|
763
1198
|
settings: {
|
|
764
1199
|
adviser: string | null;
|
|
765
1200
|
runsWeek: string | null;
|
|
1201
|
+
oneBrainCategory: string | null;
|
|
766
1202
|
};
|
|
767
1203
|
counts: {
|
|
768
1204
|
people: number;
|
|
@@ -771,7 +1207,6 @@ export interface BusinessMap {
|
|
|
771
1207
|
divisionsWithHelpers: number;
|
|
772
1208
|
jobs: number;
|
|
773
1209
|
};
|
|
774
|
-
/** Sources that could not be read just now: a missing helper may simply not have been read. */
|
|
775
1210
|
unavailable: string[];
|
|
776
1211
|
}
|
|
777
1212
|
/** A detail waiting for the owner's yes. */
|
|
@@ -784,6 +1219,44 @@ export interface BusinessSuggestion {
|
|
|
784
1219
|
sourceRef: string | null;
|
|
785
1220
|
recordedAt: string;
|
|
786
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
|
+
}
|
|
787
1260
|
/** One attribute (column) of a kind. */
|
|
788
1261
|
export interface AttributeSpec {
|
|
789
1262
|
key: string;
|
|
@@ -794,6 +1267,20 @@ export interface AttributeSpec {
|
|
|
794
1267
|
sensitivity?: 'ordinary' | 'personal' | 'sensitive';
|
|
795
1268
|
/** Default false. Only readable attributes reach query(), get() and the generated types. */
|
|
796
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>;
|
|
797
1284
|
}
|
|
798
1285
|
/** A kind to define: a table an app keeps. */
|
|
799
1286
|
export interface KindSpec {
|
|
@@ -809,16 +1296,22 @@ export interface KindSpec {
|
|
|
809
1296
|
to_label?: string;
|
|
810
1297
|
required?: boolean;
|
|
811
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;
|
|
812
1301
|
}
|
|
813
1302
|
/** A kind as the account has defined it. */
|
|
814
|
-
export interface KindDescription extends Omit<KindSpec, 'attributes'> {
|
|
1303
|
+
export interface KindDescription extends Omit<KindSpec, 'attributes' | 'access'> {
|
|
815
1304
|
storage: 'plain' | 'tracked';
|
|
816
1305
|
attributes: Array<Required<Pick<AttributeSpec, 'key' | 'label' | 'type' | 'required' | 'sensitivity' | 'readable_by_ai'>> & {
|
|
817
1306
|
choices: string[] | null;
|
|
1307
|
+
visible_to: string[] | null;
|
|
818
1308
|
}>;
|
|
1309
|
+
/** The kind's access rules. A kind with none set reads owner and staff `all`, for reading and writing. */
|
|
1310
|
+
access: KindAccess;
|
|
819
1311
|
views: {
|
|
820
1312
|
all: string;
|
|
821
1313
|
for_agents: string;
|
|
1314
|
+
for_app: string;
|
|
822
1315
|
};
|
|
823
1316
|
}
|
|
824
1317
|
/** Options for write(). */
|
|
@@ -893,6 +1386,8 @@ export interface RecipeSpec {
|
|
|
893
1386
|
export interface RecipeDescription extends Required<Omit<RecipeSpec, 'description' | 'params'>> {
|
|
894
1387
|
description: string | null;
|
|
895
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[];
|
|
896
1391
|
updated_at: string;
|
|
897
1392
|
}
|
|
898
1393
|
/** What a recipe run wrote. */
|
|
@@ -1099,7 +1594,10 @@ export declare class AwesomateAppClient {
|
|
|
1099
1594
|
queryAll<K extends KindName, S extends keyof RowOf<K> = keyof RowOf<K>>(kind: K, options?: Omit<QueryOptions<RowOf<K>, S>, 'after'> & {
|
|
1100
1595
|
maxRows?: number;
|
|
1101
1596
|
}): AsyncGenerator<Pick<RowOf<K>, S>>;
|
|
1102
|
-
/**
|
|
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
|
+
*/
|
|
1103
1601
|
get<K extends KindName>(kind: K, id: string): Promise<RowOf<K> | null>;
|
|
1104
1602
|
/** Create a record (returns its id) or change one with options.id, inside the kind's write rule for this user. */
|
|
1105
1603
|
write<K extends KindName>(kind: K, data: Partial<RowOf<K>> | Record<string, unknown>, options?: WriteOptions): Promise<string>;
|
|
@@ -1237,6 +1735,8 @@ export interface BookableService {
|
|
|
1237
1735
|
/** The price as the business wrote it. Shown, never charged. */
|
|
1238
1736
|
price_text: string;
|
|
1239
1737
|
location: string;
|
|
1738
|
+
/** How many hours before the start a customer can still cancel or move online. */
|
|
1739
|
+
cancel_cutoff_hours: number;
|
|
1240
1740
|
intake: BookingQuestion[];
|
|
1241
1741
|
calendars: Array<{
|
|
1242
1742
|
key: string;
|
|
@@ -1301,6 +1801,66 @@ export interface ManagedBooking {
|
|
|
1301
1801
|
canCancel: boolean;
|
|
1302
1802
|
canMove: boolean;
|
|
1303
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;
|
|
1304
1864
|
}
|
|
1305
1865
|
/** Options for createBookingsClient(). */
|
|
1306
1866
|
export interface BookingsClientOptions {
|
|
@@ -1342,13 +1902,19 @@ export declare class AwesomateBookingsClient {
|
|
|
1342
1902
|
}): Promise<OpenTime[]>;
|
|
1343
1903
|
/**
|
|
1344
1904
|
* Book a time for a visitor. They get an email with an invite and a link to change or cancel;
|
|
1345
|
-
* the business gets a notice. A
|
|
1346
|
-
*
|
|
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.
|
|
1347
1912
|
*/
|
|
1348
1913
|
book(request: BookingRequest): Promise<BookingResult>;
|
|
1349
|
-
/** 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. */
|
|
1350
1915
|
booking(manageToken: string): Promise<{
|
|
1351
1916
|
business: string;
|
|
1917
|
+
look: BookingPageLook;
|
|
1352
1918
|
booking: ManagedBooking;
|
|
1353
1919
|
}>;
|
|
1354
1920
|
/** The times a booking could move to, leaving its own time out. */
|
|
@@ -1356,11 +1922,18 @@ export declare class AwesomateBookingsClient {
|
|
|
1356
1922
|
from?: Date | string;
|
|
1357
1923
|
to?: Date | string;
|
|
1358
1924
|
}): Promise<OpenTime[]>;
|
|
1359
|
-
/**
|
|
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
|
+
*/
|
|
1360
1929
|
cancel(manageToken: string, reason?: string): Promise<{
|
|
1361
1930
|
cancelled: boolean;
|
|
1362
1931
|
}>;
|
|
1363
|
-
/**
|
|
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
|
+
*/
|
|
1364
1937
|
move(manageToken: string, startsAt: string): Promise<{
|
|
1365
1938
|
startsAt: string;
|
|
1366
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}`);
|
|
@@ -402,14 +565,58 @@ export class AwesomateClient {
|
|
|
402
565
|
return this.request('GET', '/api/my-business/v1/identity?format=json');
|
|
403
566
|
}
|
|
404
567
|
/**
|
|
405
|
-
* The business map: the seven
|
|
406
|
-
* which agents and automations help which
|
|
407
|
-
* important first. Read only: the owner changes the map in the hub.
|
|
408
|
-
* (hosting:read, every plan), never an app key, and the account must
|
|
568
|
+
* The business map: the seven departments every business has, their sub-departments, the roles in
|
|
569
|
+
* each and who holds them, which agents and automations help which role and how far each may go,
|
|
570
|
+
* and what is missing, most important first. Read only: the owner changes the map in the hub.
|
|
571
|
+
* Needs the account's token (hosting:read, every plan), never an app key, and the account must
|
|
572
|
+
* have the business map. Since 0.24.0 in the words the owner sees; businessMapV1() is the old shape.
|
|
409
573
|
*/
|
|
410
574
|
async businessMap() {
|
|
575
|
+
return (await this.request('GET', '/api/my-business/v2/map?format=json')).map;
|
|
576
|
+
}
|
|
577
|
+
/** Every role on the map, one line each: slug, title, department and who holds it. */
|
|
578
|
+
async businessMapRoles() {
|
|
579
|
+
return (await this.request('GET', '/api/my-business/v2/map/roles')).roles;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* One role by its slug, with the line each helper's instructions carry (`helpers[].instructionLine`):
|
|
583
|
+
* which role it helps, for whom, how far it may go and where its procedures are.
|
|
584
|
+
*/
|
|
585
|
+
async businessMapRole(slug) {
|
|
586
|
+
return (await this.request('GET', `/api/my-business/v2/map/roles/${encodeURIComponent(slug)}`)).role;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* The business map in its first shape, where a department is a `division`, a sub-department a
|
|
590
|
+
* `department` and a role a `job`.
|
|
591
|
+
* @deprecated Use businessMap(), which uses the words the owner sees. v1 is removed once it has
|
|
592
|
+
* gone 30 days unused, and not before 2026-11-06.
|
|
593
|
+
*/
|
|
594
|
+
async businessMapV1() {
|
|
411
595
|
return (await this.request('GET', '/api/my-business/v1/map?format=json')).map;
|
|
412
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
|
+
}
|
|
413
620
|
/** Details waiting for the owner's yes on Your business: from the website, the account, a team member or a file. */
|
|
414
621
|
async businessSuggestions() {
|
|
415
622
|
return (await this.request('GET', '/api/my-business/v1/proposals')).proposals;
|
|
@@ -705,8 +912,13 @@ export class AwesomateAppClient {
|
|
|
705
912
|
after = page.next ?? undefined;
|
|
706
913
|
} while (after);
|
|
707
914
|
}
|
|
708
|
-
/**
|
|
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
|
+
*/
|
|
709
919
|
async get(kind, id) {
|
|
920
|
+
if (!RECORD_ID.test(String(id)))
|
|
921
|
+
return null;
|
|
710
922
|
try {
|
|
711
923
|
return (await this.data('GET', `/rows/${encodeURIComponent(kind)}/${encodeURIComponent(String(id))}`)).row;
|
|
712
924
|
}
|
|
@@ -1217,6 +1429,15 @@ export function createAppClient(options) {
|
|
|
1217
1429
|
export function createClient(options) {
|
|
1218
1430
|
return new AwesomateClient(options);
|
|
1219
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
|
+
}
|
|
1220
1441
|
/** The token in a booking's manage link (`/booking?t=...`), or null. */
|
|
1221
1442
|
export function manageTokenFrom(url) {
|
|
1222
1443
|
try {
|
|
@@ -1291,8 +1512,13 @@ export class AwesomateBookingsClient {
|
|
|
1291
1512
|
}
|
|
1292
1513
|
/**
|
|
1293
1514
|
* Book a time for a visitor. They get an email with an invite and a link to change or cancel;
|
|
1294
|
-
* the business gets a notice. A
|
|
1295
|
-
*
|
|
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.
|
|
1296
1522
|
*/
|
|
1297
1523
|
async book(request) {
|
|
1298
1524
|
const r = await this.request('POST', '/book', {
|
|
@@ -1301,17 +1527,18 @@ export class AwesomateBookingsClient {
|
|
|
1301
1527
|
});
|
|
1302
1528
|
return { bookingId: r.booking_id, created: r.created, startsAt: r.starts_at, endsAt: r.ends_at, manageUrl: r.manage_url };
|
|
1303
1529
|
}
|
|
1304
|
-
/** 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. */
|
|
1305
1531
|
async booking(manageToken) {
|
|
1306
1532
|
const r = await this.request('GET', `/manage?t=${encodeURIComponent(manageToken)}`, undefined, false);
|
|
1307
1533
|
const b = r.booking;
|
|
1308
1534
|
return {
|
|
1309
1535
|
business: r.business,
|
|
1536
|
+
look: { website: r.look?.website ?? null, logo: r.look?.logo ?? null, colour: r.look?.colour ?? null },
|
|
1310
1537
|
booking: {
|
|
1311
1538
|
bookingId: b.booking_id, status: b.status, service: b.service ?? null,
|
|
1312
1539
|
with: b.with ?? null, startsAt: b.starts_at, endsAt: b.ends_at, timezone: b.timezone,
|
|
1313
1540
|
location: b.location ?? '', seats: b.seats, canCancel: !!b.can_cancel, canMove: !!b.can_move,
|
|
1314
|
-
changesCloseAt: b.changes_close_at,
|
|
1541
|
+
changesCloseAt: b.changes_close_at, priceText: b.price_text ?? '',
|
|
1315
1542
|
},
|
|
1316
1543
|
};
|
|
1317
1544
|
}
|
|
@@ -1326,11 +1553,18 @@ export class AwesomateBookingsClient {
|
|
|
1326
1553
|
q.set('to', to);
|
|
1327
1554
|
return (await this.request('GET', `/manage/open-times?${q}`, undefined, false)).times;
|
|
1328
1555
|
}
|
|
1329
|
-
/**
|
|
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
|
+
*/
|
|
1330
1560
|
cancel(manageToken, reason) {
|
|
1331
1561
|
return this.request('POST', '/manage/cancel', { t: manageToken, reason }, false);
|
|
1332
1562
|
}
|
|
1333
|
-
/**
|
|
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
|
+
*/
|
|
1334
1568
|
async move(manageToken, startsAt) {
|
|
1335
1569
|
const r = await this.request('POST', '/manage/move', { t: manageToken, starts_at: startsAt }, false);
|
|
1336
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",
|