@awesomate/sdk 0.11.0 → 0.13.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/README.md +20 -175
- package/dist/index.d.ts +221 -12
- package/dist/index.js +98 -7
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,203 +1,48 @@
|
|
|
1
1
|
# @awesomate/sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Build apps on your own Awesomate data. Sign your customers in, show them their records as they change, and let them act, with the rules about who sees what kept in your own database.
|
|
4
|
+
|
|
5
|
+
**Docs: https://hub.awesomate.ai/docs/sdk/** (a quickstart, a tutorial that builds a customer portal, guides, and a reference generated from this package's code). For AI tools: [llms.txt](https://hub.awesomate.ai/docs/sdk/llms.txt).
|
|
5
6
|
|
|
6
7
|
```bash
|
|
7
8
|
npm install @awesomate/sdk
|
|
8
|
-
npx @awesomate/sdk types --out awesomate.d.ts # uses your Awesomate MCP connection, or AWESOMATE_TOKEN
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
import { createClient } from '@awesomate/sdk';
|
|
13
|
-
import './awesomate'; // the generated file: every query below is typed
|
|
14
|
-
|
|
15
|
-
const db = createClient({ token: process.env.AWESOMATE_TOKEN! });
|
|
16
|
-
|
|
17
|
-
// People who joined this year, newest first
|
|
18
|
-
const { rows, next, applied } = await db.query('contact', {
|
|
19
|
-
where: { created_at: { gte: '$YEAR_BEGIN' } },
|
|
20
|
-
orderBy: ['created_at', 'desc'],
|
|
21
|
-
limit: 50,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// Everyone tagged VIP in one suburb, a page at a time
|
|
25
|
-
for await (const person of db.queryAll('contact', { where: { tags: 'VIP', suburb: 'Carindale' }, select: ['first_name', 'email'] })) {
|
|
26
|
-
console.log(person.first_name, person.email);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const one = await db.get('contact', rows[0].id); // null if there is none
|
|
30
|
-
const byPlan = await db.aggregate({ measures: [{ column: 'people', agg: 'count' }], dimensions: ['plan'] });
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Writing app data (Support Plus and above)
|
|
34
|
-
|
|
35
|
-
Define the tables an app keeps, then write to them. There's no migration and no SQL; every value is
|
|
36
|
-
checked against the kind (types, choices, required attributes and links).
|
|
37
|
-
|
|
38
|
-
```ts
|
|
39
|
-
await db.defineKind({
|
|
40
|
-
key: 'job', label: 'Job', label_plural: 'Jobs',
|
|
41
|
-
attributes: [
|
|
42
|
-
{ key: 'title', type: 'text', required: true, readable_by_ai: true },
|
|
43
|
-
{ key: 'status', type: 'choice', choices: ['open', 'booked', 'done'], readable_by_ai: true },
|
|
44
|
-
],
|
|
45
|
-
links: [{ key: 'customer', to: 'contact' }],
|
|
46
|
-
});
|
|
47
|
-
const id = await db.write('job', { title: 'Possum in the roof', status: 'open' }, { links: { customer: contactId } });
|
|
48
|
-
await db.write('job', { status: 'booked' }, { id }); // values not given stay
|
|
49
|
-
const jobs = await db.query('job', { where: { customer_id: contactId } });
|
|
50
|
-
await db.archive('job', id);
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Only attributes marked `readable_by_ai` come back from `query` and `get`; a `sensitive` attribute
|
|
54
|
-
can never be readable. Contacts themselves are not written this way: the contact list keeps its
|
|
55
|
-
own consent rules.
|
|
56
|
-
|
|
57
|
-
## What you can read
|
|
58
|
-
|
|
59
|
-
The built-in contact details (name, email, phone, company, tags, when they were added) and only
|
|
60
|
-
the fields you have marked **readable by AI** under Contacts, Your fields in the hub. A field
|
|
61
|
-
marked sensitive is never readable. `db.schema()` lists the columns and says how many are hidden.
|
|
62
|
-
|
|
63
|
-
## The where grammar
|
|
64
|
-
|
|
65
|
-
`{ column: value }` means equals; `{ column: { op: value } }` uses an operator: `eq`, `neq`, `gt`,
|
|
66
|
-
`gte`, `lt`, `lte`, `in`, `nin`, `contains`, `startsWith`, `like`, `ilike`, `isNull`, and for lists
|
|
67
|
-
(tags) `has`, `hasAny`, `hasAll`, `isEmpty`. Combine with `and: [...]`, `or: [...]`, `not: {...}`.
|
|
68
|
-
A date (`'2026-09-30'`) against a timestamp means that whole day in your time zone; time
|
|
69
|
-
variables `$TODAY`, `$WEEK_BEGIN`, `$MONTH_BEGIN`, `$QUARTER_BEGIN`, `$YEAR_BEGIN` and `$FY_BEGIN`
|
|
70
|
-
(1 July) take an offset in their own unit (`$MONTH_BEGIN-1` is the start of last month).
|
|
71
|
-
|
|
72
|
-
## Errors
|
|
73
|
-
|
|
74
|
-
Every failure is an `AwesomateError` with a `code`: `unauthenticated`, `forbidden`, `not_found`,
|
|
75
|
-
`validation` (with `field`), `consent_blocked`, `rate_limited`, `conflict` or `unavailable`.
|
|
76
|
-
|
|
77
|
-
## Keep the token on a server
|
|
78
|
-
|
|
79
|
-
The token is your account's hosting token. Use it in a server, a script or a scheduled job, never
|
|
80
|
-
in a browser. For a browser, sign your app's own users in (below).
|
|
81
|
-
|
|
82
|
-
## Your app's server: a key of its own
|
|
83
|
-
|
|
84
|
-
An app's own server, or an n8n workflow, should not carry the account's token. Give it a server
|
|
85
|
-
key instead: Claude makes one with `awesomate_crm_apps` (`create_key`), read or write, for every
|
|
86
|
-
kind or only the ones it names. The key is shown once; put it in the server's environment.
|
|
87
|
-
|
|
88
|
-
```ts
|
|
89
|
-
const db = createClient({ token: process.env.AWESOMATE_APP_KEY! }); // ak_...
|
|
90
|
-
const { rows } = await db.query('job', { where: { status: 'booked' } });
|
|
91
|
-
await db.write('job', { status: 'done' }, { id: rows[0].id });
|
|
92
|
-
await db.call('book_job', { customer, title: 'Gutter clean', status: 'booked' });
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
A key reads and writes records and runs saved queries and recipes, only for its kinds (a recipe
|
|
96
|
-
that writes anything else is refused). It reads every attribute of an app kind, and the contact
|
|
97
|
-
list only as Claude does. Writes need Support Plus and above. It cannot change kinds, rules,
|
|
98
|
-
queries or recipes, and it never works from a browser. Revoking a key stops it at once; switching
|
|
99
|
-
the app off stops all of them. From n8n, send it as `Authorization: Bearer ak_...` to
|
|
100
|
-
`https://hub.awesomate.ai/api/sdk/v1/server/rows/query` (and `/call`, `/queries/<key>/run`).
|
|
101
|
-
|
|
102
|
-
## Your app's users sign in (Pro and above)
|
|
103
|
-
|
|
104
|
-
Set the app up first (Claude Code: `awesomate_crm_apps`): its name, the origins it runs on, and
|
|
105
|
-
whether anyone may sign up or only people you add. That gives you a publishable key, which is not a
|
|
106
|
-
secret. Then say who may read and change each kind (`awesomate_crm_kinds`, `set_access`), for
|
|
107
|
-
example members read jobs whose customer is them and the memos on those jobs.
|
|
11
|
+
## In a web page: your app's people (Pro and above)
|
|
108
12
|
|
|
109
13
|
```ts
|
|
110
14
|
import { createAppClient } from '@awesomate/sdk';
|
|
111
15
|
|
|
112
|
-
const app = createAppClient({ publishableKey: 'pk_...' });
|
|
113
|
-
|
|
114
|
-
// Render on every change of person: signed in, signed out, a new role.
|
|
115
|
-
app.auth.onChange((u) => render(u));
|
|
116
|
-
app.auth.onSignInError((err) => showMessage(err.message));
|
|
16
|
+
const app = createAppClient({ publishableKey: 'pk_...' }); // not a secret
|
|
117
17
|
|
|
118
|
-
|
|
18
|
+
app.auth.onChange((user) => render(user));
|
|
19
|
+
render(await app.auth.user()); // someone already signed in on this device
|
|
119
20
|
await app.auth.signInWithLink(email, { redirectTo: location.href });
|
|
120
21
|
|
|
121
|
-
//
|
|
122
|
-
const
|
|
123
|
-
await app.
|
|
124
|
-
await app.auth.signOut();
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
The link signs them in by itself. In a browser the client takes the link from the address bar,
|
|
128
|
-
on load or when it arrives in a tab that is already open (only the part after `#` changes, so
|
|
129
|
-
the page does not reload), clears it at once and tells `onChange`. A link that has expired or
|
|
130
|
-
was already used goes to `onSignInError`. `await app.auth.completeSignIn()` still works and
|
|
131
|
-
returns the same sign-in, never a second exchange. Pass `handleSignInLinks: false` to take links
|
|
132
|
-
yourself. `onChange` hears about people, not tokens: the routine token refresh is silent.
|
|
133
|
-
|
|
134
|
-
### Lists that keep themselves current
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
const stop = app.live('job', { orderBy: ['created_at', 'desc'], limit: 50 }, (rows) => render(rows));
|
|
138
|
-
// later: stop();
|
|
22
|
+
// As the signed-in person: the database applies the account's rules.
|
|
23
|
+
const stop = app.live('job', { orderBy: ['created_at', 'desc'] }, (rows) => render(rows));
|
|
24
|
+
await app.call('accept_quote', { job: job.id });
|
|
139
25
|
```
|
|
140
26
|
|
|
141
|
-
|
|
142
|
-
removed, or stops being one this person may read (a job moved to another customer leaves their
|
|
143
|
-
list, with its memos). Changes made anywhere reach the list: in this app, by your team in Claude
|
|
144
|
-
Code, by an n8n workflow. One connection serves every list on the client; it reconnects by itself
|
|
145
|
-
and takes a fresh copy when it does. Up to 200 rows a list and 20 lists a connection. Pass
|
|
146
|
-
`{ onRows, onError }` to hear about a list the server refused (a column that does not exist).
|
|
147
|
-
Outside a browser, give it a WebSocket: `createAppClient({ publishableKey, WebSocket })`.
|
|
148
|
-
|
|
149
|
-
While a list is open in a browser, the connection also tells the hub whether the tab is in front.
|
|
150
|
-
If the account switched on reply emails, someone looking at the app isn't emailed about a reply,
|
|
151
|
-
but a tab left open in the background doesn't count as looking, and closing the last one counts as
|
|
152
|
-
leaving straight away.
|
|
153
|
-
|
|
154
|
-
A conversation can also show who else has it open and who is typing:
|
|
27
|
+
## On a server: your account's data
|
|
155
28
|
|
|
156
29
|
```ts
|
|
157
|
-
|
|
158
|
-
messageBox.oninput = () => room.typing(true); // sent at most every few seconds
|
|
159
|
-
messageBox.onblur = () => room.typing(false);
|
|
160
|
-
// later: room.leave();
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Everyone else with that record open and their tab in front is listed once, and the app's
|
|
164
|
-
assistant appears while it writes a reply. The hub checks this person may read the record, and
|
|
165
|
-
keeps checking; a refusal goes to the optional third argument, `onError`. Nothing is stored.
|
|
166
|
-
|
|
167
|
-
### Staff find a customer
|
|
30
|
+
import { createClient } from '@awesomate/sdk';
|
|
168
31
|
|
|
169
|
-
|
|
170
|
-
customer. Switch it on per app for the roles that need it (`awesomate_crm_apps`,
|
|
171
|
-
`customer_lookup`, never the role customers sign in with). Those roles can then search by name or
|
|
172
|
-
email and point a record at any customer:
|
|
32
|
+
const db = createClient({ token: process.env.AWESOMATE_TOKEN! }); // a secret: never in a browser
|
|
173
33
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
await app.write('job', { title: 'Gutters' }, { links: { customer: jo.id } });
|
|
34
|
+
const { rows, next } = await db.query('contact', { where: { created_at: { gte: '$YEAR_BEGIN' } }, limit: 50 });
|
|
35
|
+
await db.write('job', { title: 'Possum in the roof', status: 'open' }, { links: { customer: rows[0].id } });
|
|
177
36
|
```
|
|
178
37
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
An attribute can also be visible to some roles only (`awesomate_crm_kinds`, `set_visibility`, for
|
|
182
|
-
example a cost only `staff` see). For everyone else it comes back `null` and a write that sets it
|
|
183
|
-
is refused, whatever the app asks for.
|
|
184
|
-
|
|
185
|
-
Some things a customer should do without being able to edit the record: accept their quote, say.
|
|
186
|
-
Save it as a write recipe and open it to their role (`awesomate_crm_recipes`, `run_by`, Pro and
|
|
187
|
-
above), then:
|
|
38
|
+
Types for your own account's kinds, saved queries and recipes:
|
|
188
39
|
|
|
189
|
-
```
|
|
190
|
-
|
|
40
|
+
```bash
|
|
41
|
+
npx @awesomate/sdk types --out awesomate.d.ts
|
|
191
42
|
```
|
|
192
43
|
|
|
193
|
-
|
|
194
|
-
other value is fixed, and an existing record must be one they can already read. A recipe not
|
|
195
|
-
opened to their role is "not found".
|
|
44
|
+
## What changed
|
|
196
45
|
|
|
197
|
-
|
|
198
|
-
in the app cannot show anyone more than their role allows. A disabled person is refused on their
|
|
199
|
-
next call. Sessions are kept in `localStorage`; in a Capacitor app pass `storage` (Preferences) and
|
|
200
|
-
call `completeSignIn(url)` with the deep link that opened the app. Your own server can verify
|
|
201
|
-
`await app.auth.accessToken()` against `https://hub.awesomate.ai/api/sdk/v1/jwks.json`.
|
|
46
|
+
See the [changelog](https://hub.awesomate.ai/docs/sdk/changelog/).
|
|
202
47
|
|
|
203
48
|
MIT licence.
|
package/dist/index.d.ts
CHANGED
|
@@ -18,15 +18,19 @@
|
|
|
18
18
|
* const stop = app.live('job', {}, (rows) => render(rows)); // and kept current as they change
|
|
19
19
|
*
|
|
20
20
|
* The account's token (a hosting PAT with crm:read) is a secret: use it on a server, never in a
|
|
21
|
-
* browser.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* browser. Reading and running saved queries works on every plan; writing app data (kinds,
|
|
22
|
+
* records, saved queries, recipes) needs crm:write, Support Plus and above.
|
|
23
|
+
*
|
|
24
|
+
* Docs: https://hub.awesomate.ai/docs/sdk/
|
|
24
25
|
*/
|
|
25
|
-
|
|
26
|
+
/** This package's version, sent to the hub with every server call. */
|
|
27
|
+
export declare const VERSION = "0.13.0";
|
|
26
28
|
/** Augmented by the generated awesomate.d.ts, so each kind's rows are typed. */
|
|
27
29
|
export interface Kinds {
|
|
28
30
|
}
|
|
31
|
+
/** A kind's name: one of yours once the generated types are imported, any string before then. */
|
|
29
32
|
export type KindName = [keyof Kinds] extends [never] ? string : Extract<keyof Kinds, string>;
|
|
33
|
+
/** A row of kind K: typed from the generated file, or a plain record before then. */
|
|
30
34
|
export type RowOf<K> = K extends keyof Kinds ? Kinds[K] : Record<string, unknown>;
|
|
31
35
|
/** Augmented by the generated awesomate.d.ts: each saved query's params and row. */
|
|
32
36
|
export interface Queries {
|
|
@@ -34,18 +38,24 @@ export interface Queries {
|
|
|
34
38
|
/** Augmented by the generated awesomate.d.ts: each write recipe's args. */
|
|
35
39
|
export interface Recipes {
|
|
36
40
|
}
|
|
41
|
+
/** A saved query's key: one of yours once the generated types are imported, any string before then. */
|
|
37
42
|
export type QueryName = [keyof Queries] extends [never] ? string : Extract<keyof Queries, string>;
|
|
43
|
+
/** The parameters saved query Q takes. */
|
|
38
44
|
export type ParamsOf<Q> = Q extends keyof Queries ? (Queries[Q] extends {
|
|
39
45
|
params: infer P;
|
|
40
46
|
} ? P : never) : Record<string, unknown>;
|
|
47
|
+
/** The row saved query Q returns. */
|
|
41
48
|
export type QueryRowOf<Q> = Q extends keyof Queries ? (Queries[Q] extends {
|
|
42
49
|
row: infer R;
|
|
43
50
|
} ? R : never) : Record<string, unknown>;
|
|
51
|
+
/** A write recipe's key: one of yours once the generated types are imported, any string before then. */
|
|
44
52
|
export type RecipeName = [keyof Recipes] extends [never] ? string : Extract<keyof Recipes, string>;
|
|
53
|
+
/** The arguments recipe R takes. */
|
|
45
54
|
export type ArgsOf<R> = R extends keyof Recipes ? (Recipes[R] extends {
|
|
46
55
|
args: infer A;
|
|
47
56
|
} ? A : never) : Record<string, unknown>;
|
|
48
|
-
|
|
57
|
+
/** Filters on a text column. gt/gte/lt/lte also take time variables such as `$YEAR_BEGIN` on dates. */
|
|
58
|
+
export type TextOps<V extends string> = {
|
|
49
59
|
eq?: V | null;
|
|
50
60
|
neq?: V | null;
|
|
51
61
|
in?: V[];
|
|
@@ -60,7 +70,8 @@ type TextOps<V extends string> = {
|
|
|
60
70
|
lte?: string;
|
|
61
71
|
isNull?: boolean;
|
|
62
72
|
};
|
|
63
|
-
|
|
73
|
+
/** Filters on a number column. */
|
|
74
|
+
export type NumberOps = {
|
|
64
75
|
eq?: number | null;
|
|
65
76
|
neq?: number | null;
|
|
66
77
|
gt?: number;
|
|
@@ -71,20 +82,29 @@ type NumberOps = {
|
|
|
71
82
|
nin?: number[];
|
|
72
83
|
isNull?: boolean;
|
|
73
84
|
};
|
|
74
|
-
|
|
85
|
+
/** Filters on a yes/no column. */
|
|
86
|
+
export type BoolOps = {
|
|
75
87
|
eq?: boolean | null;
|
|
76
88
|
neq?: boolean | null;
|
|
77
89
|
isNull?: boolean;
|
|
78
90
|
};
|
|
79
|
-
|
|
91
|
+
/** Filters on a list column (tags, choices): has one, any or all, or is empty. */
|
|
92
|
+
export type ListOps<E> = {
|
|
80
93
|
has?: E;
|
|
81
94
|
hasAny?: E[];
|
|
82
95
|
hasAll?: E[];
|
|
83
96
|
isEmpty?: boolean;
|
|
84
97
|
isNull?: boolean;
|
|
85
98
|
};
|
|
86
|
-
/**
|
|
87
|
-
|
|
99
|
+
/**
|
|
100
|
+
* What one column accepts in a where clause: a bare value means equals (a list means in, or has
|
|
101
|
+
* all for a list column). The checks are wrapped in [ ] so a choice column ('open' | 'booked')
|
|
102
|
+
* is taken whole: `{ in: ['open', 'booked'] }` is one filter, not one per choice.
|
|
103
|
+
*/
|
|
104
|
+
export type ColumnFilter<V> = [
|
|
105
|
+
V
|
|
106
|
+
] extends [Array<infer E>] ? E | E[] | ListOps<E> : [V] extends [number] ? V | V[] | null | NumberOps : [V] extends [boolean] ? V | null | BoolOps : [V] extends [string] ? V | V[] | null | TextOps<V> : unknown;
|
|
107
|
+
/** A where clause: a filter per column, combined with and, or and not. */
|
|
88
108
|
export type Where<T> = {
|
|
89
109
|
[C in keyof T]?: ColumnFilter<NonNullable<T[C]>>;
|
|
90
110
|
} & {
|
|
@@ -96,7 +116,9 @@ export type Where<T> = {
|
|
|
96
116
|
export type SortableColumn<T> = Extract<{
|
|
97
117
|
[C in keyof T]: NonNullable<T[C]> extends unknown[] ? never : C;
|
|
98
118
|
}[keyof T], string>;
|
|
119
|
+
/** How a query picks, orders, pages and narrows rows. */
|
|
99
120
|
export interface QueryOptions<T, S extends keyof T = keyof T> {
|
|
121
|
+
/** Which rows: a filter per column. */
|
|
100
122
|
where?: Where<T>;
|
|
101
123
|
/** One sort column; id breaks ties. Default: created_at, newest first. */
|
|
102
124
|
orderBy?: [SortableColumn<T>, 'asc' | 'desc'];
|
|
@@ -104,10 +126,12 @@ export interface QueryOptions<T, S extends keyof T = keyof T> {
|
|
|
104
126
|
limit?: number;
|
|
105
127
|
/** The previous page's next. Repeat the same orderBy. */
|
|
106
128
|
after?: string;
|
|
129
|
+
/** Only these columns. Default: every column this caller may read. */
|
|
107
130
|
select?: S[];
|
|
108
131
|
/** IANA zone for dates and time variables. Default Australia/Sydney. */
|
|
109
132
|
tz?: string;
|
|
110
133
|
}
|
|
134
|
+
/** What the hub assumed for a query (order, limit, time zone, what each time variable meant), so it can be said back. */
|
|
111
135
|
export interface Applied {
|
|
112
136
|
order_by: [string, 'asc' | 'desc'];
|
|
113
137
|
limit: number;
|
|
@@ -115,33 +139,58 @@ export interface Applied {
|
|
|
115
139
|
time_variables: Record<string, string>;
|
|
116
140
|
defaults: string[];
|
|
117
141
|
}
|
|
142
|
+
/** One page of rows. Pass `next` as `after` for the following page. */
|
|
118
143
|
export interface Page<R> {
|
|
144
|
+
/** The rows on this page. */
|
|
119
145
|
rows: R[];
|
|
146
|
+
/** Where the next page starts, or null on the last page. */
|
|
120
147
|
next: string | null;
|
|
148
|
+
/** Rows on this page. */
|
|
121
149
|
count: number;
|
|
122
150
|
/** What the hub assumed; repeat it to whoever asked. */
|
|
123
151
|
applied: Applied;
|
|
152
|
+
/** Columns left out: not marked readable by AI (to the account's token), or sensitive. */
|
|
124
153
|
hidden: {
|
|
125
154
|
not_readable_by_ai: number;
|
|
126
155
|
sensitive: number;
|
|
127
156
|
};
|
|
157
|
+
/** When the hub read the rows. */
|
|
128
158
|
asAt: string;
|
|
129
159
|
}
|
|
130
160
|
declare const ERROR_CODES: readonly ["unauthenticated", "forbidden", "not_found", "validation", "consent_blocked", "rate_limited", "conflict", "unavailable"];
|
|
161
|
+
/**
|
|
162
|
+
* Why a call failed, one of a fixed list: unauthenticated, forbidden, not_found, validation,
|
|
163
|
+
* consent_blocked, rate_limited, conflict, unavailable. The hub's own detail is in serverCode.
|
|
164
|
+
*/
|
|
131
165
|
export type ErrorCode = (typeof ERROR_CODES)[number];
|
|
166
|
+
/**
|
|
167
|
+
* Every failure from the hub. `message` is for you, the developer; `personMessage`, when the hub
|
|
168
|
+
* sent one, is the sentence to show the person using your app.
|
|
169
|
+
*/
|
|
132
170
|
export declare class AwesomateError extends Error {
|
|
171
|
+
/** Why it failed. */
|
|
133
172
|
readonly code: ErrorCode;
|
|
173
|
+
/** The HTTP status, or 0 when the SDK refused before sending. */
|
|
134
174
|
readonly status: number;
|
|
135
175
|
/** The column or parameter a validation error is about. */
|
|
136
176
|
readonly field?: string | undefined;
|
|
137
177
|
/** The hub's own code, unmapped. */
|
|
138
178
|
readonly serverCode?: string | undefined;
|
|
139
|
-
|
|
179
|
+
/** A sentence for the person using the app, when the hub sent one (a refused voice call: "You've used today's voice time"). */
|
|
180
|
+
readonly personMessage?: string | undefined;
|
|
181
|
+
constructor(
|
|
182
|
+
/** Why it failed. */
|
|
183
|
+
code: ErrorCode, message: string,
|
|
184
|
+
/** The HTTP status, or 0 when the SDK refused before sending. */
|
|
185
|
+
status: number,
|
|
140
186
|
/** The column or parameter a validation error is about. */
|
|
141
187
|
field?: string | undefined,
|
|
142
188
|
/** The hub's own code, unmapped. */
|
|
143
|
-
serverCode?: string | undefined
|
|
189
|
+
serverCode?: string | undefined,
|
|
190
|
+
/** A sentence for the person using the app, when the hub sent one (a refused voice call: "You've used today's voice time"). */
|
|
191
|
+
personMessage?: string | undefined);
|
|
144
192
|
}
|
|
193
|
+
/** Options for createClient(). */
|
|
145
194
|
export interface ClientOptions {
|
|
146
195
|
/**
|
|
147
196
|
* The account's hosting token (amt_pat_...) with crm:read, or an app's server key (ak_...) from
|
|
@@ -149,9 +198,15 @@ export interface ClientOptions {
|
|
|
149
198
|
* kinds it was made for; changing kinds, queries or recipes needs the account's token.
|
|
150
199
|
*/
|
|
151
200
|
token: string;
|
|
201
|
+
/** Default https://hub.awesomate.ai. */
|
|
152
202
|
baseUrl?: string;
|
|
203
|
+
/** Your own fetch. Default: the global one (Node 18 and later). */
|
|
153
204
|
fetch?: typeof fetch;
|
|
154
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* The server client, from createClient(): reads and writes the account's data with its token or
|
|
208
|
+
* an app's server key. Keep it on a server; it never works in a browser.
|
|
209
|
+
*/
|
|
155
210
|
export declare class AwesomateClient {
|
|
156
211
|
private readonly opts;
|
|
157
212
|
private readonly base;
|
|
@@ -179,6 +234,16 @@ export declare class AwesomateClient {
|
|
|
179
234
|
content: string;
|
|
180
235
|
as_at: string;
|
|
181
236
|
}>;
|
|
237
|
+
/**
|
|
238
|
+
* Rows of one kind that this token can read, a page at a time.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* const { rows, next } = await db.query('contact', {
|
|
242
|
+
* where: { created_at: { gte: '$YEAR_BEGIN' } },
|
|
243
|
+
* orderBy: ['created_at', 'desc'],
|
|
244
|
+
* limit: 50,
|
|
245
|
+
* });
|
|
246
|
+
*/
|
|
182
247
|
query<K extends KindName, S extends keyof RowOf<K> = keyof RowOf<K>>(kind: K, options?: QueryOptions<RowOf<K>, S>): Promise<Page<Pick<RowOf<K>, S>>>;
|
|
183
248
|
/** Every matching row, a page at a time. maxRows guards against reading a whole list by accident. */
|
|
184
249
|
queryAll<K extends KindName, S extends keyof RowOf<K> = keyof RowOf<K>>(kind: K, options?: Omit<QueryOptions<RowOf<K>, S>, 'after'> & {
|
|
@@ -204,6 +269,7 @@ export declare class AwesomateClient {
|
|
|
204
269
|
* stored, so a column it cannot read is refused now rather than when it runs.
|
|
205
270
|
*/
|
|
206
271
|
saveQuery(spec: SavedQuerySpec): Promise<SavedQueryDescription>;
|
|
272
|
+
/** Archive a saved query by its key. */
|
|
207
273
|
archiveQuery(key: string): Promise<void>;
|
|
208
274
|
/** Run a saved query with its params. An optional param left out drops its condition. */
|
|
209
275
|
run<Q extends QueryName>(query: Q, params?: ParamsOf<Q>, options?: {
|
|
@@ -218,12 +284,22 @@ export declare class AwesomateClient {
|
|
|
218
284
|
* {"$param": "<name>"} takes a caller's value, {"$step": "<as>"} the id an earlier step wrote.
|
|
219
285
|
*/
|
|
220
286
|
saveRecipe(spec: RecipeSpec): Promise<RecipeDescription>;
|
|
287
|
+
/** Archive a write recipe by its key. */
|
|
221
288
|
archiveRecipe(key: string): Promise<void>;
|
|
222
289
|
/**
|
|
223
290
|
* Run a write recipe. Every step commits or none does; a refusal names the step and the field.
|
|
224
291
|
* Never retried: a write that may have landed is not safe to send twice.
|
|
225
292
|
*/
|
|
226
293
|
call<R extends RecipeName>(recipe: R, args?: ArgsOf<R>): Promise<RecipeResult>;
|
|
294
|
+
/**
|
|
295
|
+
* The business itself: the details Awesomate keeps about it (name, what it does, voice, colours,
|
|
296
|
+
* how customers reach it), grouped, each with where it came from. `confirmed` details are the
|
|
297
|
+
* ones agents and automations use; `suggestion` ones wait for the owner. Needs the account's
|
|
298
|
+
* token (hosting:read, every plan), never an app key.
|
|
299
|
+
*/
|
|
300
|
+
business(): Promise<BusinessIdentity>;
|
|
301
|
+
/** Details waiting for the owner's yes on Your business: from the website, the account, a team member or a file. */
|
|
302
|
+
businessSuggestions(): Promise<BusinessSuggestion[]>;
|
|
227
303
|
/** Grouped numbers (counts, sums) in the Business Data API's shape. */
|
|
228
304
|
aggregate(request: {
|
|
229
305
|
measures: Array<{
|
|
@@ -251,6 +327,50 @@ export declare class AwesomateClient {
|
|
|
251
327
|
limit?: number;
|
|
252
328
|
}): Promise<Record<string, unknown>>;
|
|
253
329
|
}
|
|
330
|
+
/** One detail about the business. */
|
|
331
|
+
export interface BusinessFact {
|
|
332
|
+
key: string;
|
|
333
|
+
label: string;
|
|
334
|
+
value: string;
|
|
335
|
+
/** confirmed: in use by agents and automations. suggestion: waiting for the owner. */
|
|
336
|
+
status: 'confirmed' | 'suggestion';
|
|
337
|
+
/** Where it came from, in words: "confirmed by the owner", "added by a team member", "website research"... */
|
|
338
|
+
source: string;
|
|
339
|
+
}
|
|
340
|
+
/** The business, as business() returns it. */
|
|
341
|
+
export interface BusinessIdentity {
|
|
342
|
+
/** The account's slug. */
|
|
343
|
+
account: string;
|
|
344
|
+
/** The business's name, when known. */
|
|
345
|
+
name: string | null;
|
|
346
|
+
/** The details, grouped for reading. */
|
|
347
|
+
groups: Array<{
|
|
348
|
+
id: string;
|
|
349
|
+
title: string;
|
|
350
|
+
facts: BusinessFact[];
|
|
351
|
+
}>;
|
|
352
|
+
/** The eight core details every business should have, and which are missing. */
|
|
353
|
+
completeness: {
|
|
354
|
+
core_set: number;
|
|
355
|
+
core_total: number;
|
|
356
|
+
missing_core: string[];
|
|
357
|
+
};
|
|
358
|
+
/** Whether each source could be read: 'read', 'empty' or 'unavailable'. */
|
|
359
|
+
sources: Record<'confirmed_details' | 'saved_details' | 'account_details' | 'website_research', string>;
|
|
360
|
+
/** Where the owner changes these details, in words. */
|
|
361
|
+
how_to_change: string;
|
|
362
|
+
}
|
|
363
|
+
/** A detail waiting for the owner's yes. */
|
|
364
|
+
export interface BusinessSuggestion {
|
|
365
|
+
id: number;
|
|
366
|
+
key: string;
|
|
367
|
+
value: string;
|
|
368
|
+
/** website, research, import, team_member, staff, upload, legacy_variables... */
|
|
369
|
+
sourceKind: string;
|
|
370
|
+
sourceRef: string | null;
|
|
371
|
+
recordedAt: string;
|
|
372
|
+
}
|
|
373
|
+
/** One attribute (column) of a kind. */
|
|
254
374
|
export interface AttributeSpec {
|
|
255
375
|
key: string;
|
|
256
376
|
label?: string;
|
|
@@ -261,6 +381,7 @@ export interface AttributeSpec {
|
|
|
261
381
|
/** Default false. Only readable attributes reach query(), get() and the generated types. */
|
|
262
382
|
readable_by_ai?: boolean;
|
|
263
383
|
}
|
|
384
|
+
/** A kind to define: a table an app keeps. */
|
|
264
385
|
export interface KindSpec {
|
|
265
386
|
key: string;
|
|
266
387
|
label?: string;
|
|
@@ -275,6 +396,7 @@ export interface KindSpec {
|
|
|
275
396
|
required?: boolean;
|
|
276
397
|
}>;
|
|
277
398
|
}
|
|
399
|
+
/** A kind as the account has defined it. */
|
|
278
400
|
export interface KindDescription extends Omit<KindSpec, 'attributes'> {
|
|
279
401
|
storage: 'plain' | 'tracked';
|
|
280
402
|
attributes: Array<Required<Pick<AttributeSpec, 'key' | 'label' | 'type' | 'required' | 'sensitivity' | 'readable_by_ai'>> & {
|
|
@@ -285,13 +407,16 @@ export interface KindDescription extends Omit<KindSpec, 'attributes'> {
|
|
|
285
407
|
for_agents: string;
|
|
286
408
|
};
|
|
287
409
|
}
|
|
410
|
+
/** Options for write(). */
|
|
288
411
|
export interface WriteOptions {
|
|
289
412
|
/** Change this record instead of creating one; values not given stay. */
|
|
290
413
|
id?: string;
|
|
291
414
|
/** Set a link by its key ('<id>'), or end it (null). */
|
|
292
415
|
links?: Record<string, string | null>;
|
|
293
416
|
}
|
|
417
|
+
/** The type of a saved query's or recipe's parameter. */
|
|
294
418
|
export type ParamType = 'text' | 'number' | 'date' | 'datetime' | 'boolean' | 'uuid' | 'text_list' | 'number_list';
|
|
419
|
+
/** One parameter of a saved query or recipe. */
|
|
295
420
|
export interface ParamSpec {
|
|
296
421
|
name: string;
|
|
297
422
|
/** Default text. */
|
|
@@ -304,6 +429,7 @@ export interface ParamSpec {
|
|
|
304
429
|
export type Param = {
|
|
305
430
|
$param: string;
|
|
306
431
|
};
|
|
432
|
+
/** A saved query to store with saveQuery(). */
|
|
307
433
|
export interface SavedQuerySpec {
|
|
308
434
|
key: string;
|
|
309
435
|
label: string;
|
|
@@ -318,11 +444,13 @@ export interface SavedQuerySpec {
|
|
|
318
444
|
};
|
|
319
445
|
params?: ParamSpec[];
|
|
320
446
|
}
|
|
447
|
+
/** A saved query as stored. */
|
|
321
448
|
export interface SavedQueryDescription extends Required<Omit<SavedQuerySpec, 'description' | 'params'>> {
|
|
322
449
|
description: string | null;
|
|
323
450
|
params: ParamSpec[];
|
|
324
451
|
updated_at: string;
|
|
325
452
|
}
|
|
453
|
+
/** One step of a write recipe: write a record, or archive one. */
|
|
326
454
|
export type RecipeStep = {
|
|
327
455
|
op: 'write_record';
|
|
328
456
|
kind: string;
|
|
@@ -339,6 +467,7 @@ export type RecipeStep = {
|
|
|
339
467
|
$step: string;
|
|
340
468
|
};
|
|
341
469
|
};
|
|
470
|
+
/** A write recipe to store with saveRecipe(). */
|
|
342
471
|
export interface RecipeSpec {
|
|
343
472
|
key: string;
|
|
344
473
|
label: string;
|
|
@@ -346,11 +475,13 @@ export interface RecipeSpec {
|
|
|
346
475
|
params?: ParamSpec[];
|
|
347
476
|
steps: RecipeStep[];
|
|
348
477
|
}
|
|
478
|
+
/** A write recipe as stored. */
|
|
349
479
|
export interface RecipeDescription extends Required<Omit<RecipeSpec, 'description' | 'params'>> {
|
|
350
480
|
description: string | null;
|
|
351
481
|
params: ParamSpec[];
|
|
352
482
|
updated_at: string;
|
|
353
483
|
}
|
|
484
|
+
/** What a recipe run wrote. */
|
|
354
485
|
export interface RecipeResult {
|
|
355
486
|
/** The id each step named with `as` wrote. */
|
|
356
487
|
ids: Record<string, string>;
|
|
@@ -359,15 +490,22 @@ export interface RecipeResult {
|
|
|
359
490
|
}
|
|
360
491
|
/** Where a session is kept: localStorage, Capacitor Preferences, or anything with these three. */
|
|
361
492
|
export interface SessionStorageLike {
|
|
493
|
+
/** The stored value, or null. */
|
|
362
494
|
getItem(key: string): string | null | Promise<string | null>;
|
|
495
|
+
/** Store a value. */
|
|
363
496
|
setItem(key: string, value: string): void | Promise<void>;
|
|
497
|
+
/** Forget a value. */
|
|
364
498
|
removeItem(key: string): void | Promise<void>;
|
|
365
499
|
}
|
|
500
|
+
/** The signed-in person. */
|
|
366
501
|
export interface AppUser {
|
|
502
|
+
/** Their app user id. */
|
|
367
503
|
id: string;
|
|
504
|
+
/** The address they sign in with. */
|
|
368
505
|
email: string;
|
|
369
506
|
/** owner, staff, member or one of the account's own; the database re-reads it on every call */
|
|
370
507
|
role: string;
|
|
508
|
+
/** Their record in the account's Contacts, when linked. */
|
|
371
509
|
contact_id: string | null;
|
|
372
510
|
}
|
|
373
511
|
/** A customer as lookupCustomers() returns them. A field the owner marked sensitive is null. */
|
|
@@ -377,10 +515,13 @@ export interface Customer {
|
|
|
377
515
|
email: string | null;
|
|
378
516
|
phone: string | null;
|
|
379
517
|
}
|
|
518
|
+
/** Options for createAppClient(). */
|
|
380
519
|
export interface AppClientOptions {
|
|
381
520
|
/** The app's publishable key (pk_...). Not a secret: it belongs in browser code. */
|
|
382
521
|
publishableKey: string;
|
|
522
|
+
/** Default https://hub.awesomate.ai. */
|
|
383
523
|
baseUrl?: string;
|
|
524
|
+
/** Your own fetch. Default: the global one. */
|
|
384
525
|
fetch?: typeof fetch;
|
|
385
526
|
/** Default: localStorage in a browser, memory elsewhere. In Capacitor, pass Preferences. */
|
|
386
527
|
storage?: SessionStorageLike;
|
|
@@ -398,20 +539,28 @@ export interface AppClientOptions {
|
|
|
398
539
|
/** The part of the WebSocket API live() uses: the browser's, Node 22's, or the ws package's. */
|
|
399
540
|
export interface WebSocketLike {
|
|
400
541
|
new (url: string): {
|
|
542
|
+
/** As the browser's WebSocket. */
|
|
401
543
|
readyState: number;
|
|
544
|
+
/** As the browser's WebSocket. */
|
|
402
545
|
send(data: string): void;
|
|
546
|
+
/** As the browser's WebSocket. */
|
|
403
547
|
close(code?: number, reason?: string): void;
|
|
548
|
+
/** As the browser's WebSocket. */
|
|
404
549
|
onopen: ((ev: unknown) => void) | null;
|
|
550
|
+
/** As the browser's WebSocket. */
|
|
405
551
|
onmessage: ((ev: {
|
|
406
552
|
data: unknown;
|
|
407
553
|
}) => void) | null;
|
|
554
|
+
/** As the browser's WebSocket. */
|
|
408
555
|
onclose: ((ev: {
|
|
409
556
|
code: number;
|
|
410
557
|
reason: string;
|
|
411
558
|
}) => void) | null;
|
|
559
|
+
/** As the browser's WebSocket. */
|
|
412
560
|
onerror: ((ev: unknown) => void) | null;
|
|
413
561
|
};
|
|
414
562
|
}
|
|
563
|
+
/** What changed since onRows was last called. */
|
|
415
564
|
export interface LiveChange<R> {
|
|
416
565
|
/** Rows that are new or changed since the last call. */
|
|
417
566
|
upserts: R[];
|
|
@@ -429,6 +578,7 @@ export interface HerePerson {
|
|
|
429
578
|
/** The app's AI assistant, writing a reply. */
|
|
430
579
|
assistant?: true;
|
|
431
580
|
}
|
|
581
|
+
/** From here(): say this person is typing, or close the record. */
|
|
432
582
|
export interface HereHandle {
|
|
433
583
|
/**
|
|
434
584
|
* Say this person is typing (call it on each keystroke: it sends at most every few seconds) or has
|
|
@@ -438,6 +588,7 @@ export interface HereHandle {
|
|
|
438
588
|
/** Close the record: the others stop seeing this person on it. */
|
|
439
589
|
leave(): void;
|
|
440
590
|
}
|
|
591
|
+
/** What live() calls. */
|
|
441
592
|
export interface LiveHandlers<R> {
|
|
442
593
|
/** Called with the whole list, in the query's order, at first and after every change. */
|
|
443
594
|
onRows: (rows: R[], change: LiveChange<R> | null) => void;
|
|
@@ -446,6 +597,24 @@ export interface LiveHandlers<R> {
|
|
|
446
597
|
}
|
|
447
598
|
/** The sign-in token a link carries, from a URL's fragment (#awesomate_token=...), or null. */
|
|
448
599
|
export declare function signInTokenFrom(url: string): string | null;
|
|
600
|
+
/** What AwesomateAgent.mount's session() returns: pass it through unchanged. */
|
|
601
|
+
export interface VoiceSession {
|
|
602
|
+
session_id: string;
|
|
603
|
+
url: string;
|
|
604
|
+
token: string;
|
|
605
|
+
expires_at: string;
|
|
606
|
+
max_seconds: number;
|
|
607
|
+
agent_name: string;
|
|
608
|
+
pages: Array<{
|
|
609
|
+
label: string;
|
|
610
|
+
path: string;
|
|
611
|
+
}>;
|
|
612
|
+
say_as: Record<string, string>;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* The browser client for one of the account's apps, from createAppClient(): sign-in is under
|
|
616
|
+
* `auth`, and every data call runs as the signed-in person, inside the account's access rules.
|
|
617
|
+
*/
|
|
449
618
|
export declare class AwesomateAppClient {
|
|
450
619
|
private readonly opts;
|
|
451
620
|
private readonly base;
|
|
@@ -487,6 +656,7 @@ export declare class AwesomateAppClient {
|
|
|
487
656
|
onChange: (listener: (user: AppUser | null) => void) => (() => void);
|
|
488
657
|
/** Called when a sign-in link could not be used (expired, already used, not this app's). */
|
|
489
658
|
onSignInError: (listener: (err: AwesomateError) => void) => (() => void);
|
|
659
|
+
/** Sign out here and end the session at the hub. */
|
|
490
660
|
signOut: () => Promise<void>;
|
|
491
661
|
/** A current access token, for the app's own server to verify against the JWKS. */
|
|
492
662
|
accessToken: () => Promise<string | null>;
|
|
@@ -503,7 +673,15 @@ export declare class AwesomateAppClient {
|
|
|
503
673
|
private refresh;
|
|
504
674
|
/** A data call as the signed-in user: refused once (a disable, a key change), it refreshes and tries again; refused twice, it signs out. */
|
|
505
675
|
private data;
|
|
676
|
+
/**
|
|
677
|
+
* Rows of one kind that this person may read, a page at a time. The database applies the
|
|
678
|
+
* kind's read rule for their role: a customer gets their own jobs, staff whatever theirs allows.
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* const { rows } = await app.query('job', { orderBy: ['created_at', 'desc'], limit: 20 });
|
|
682
|
+
*/
|
|
506
683
|
query<K extends KindName, S extends keyof RowOf<K> = keyof RowOf<K>>(kind: K, options?: QueryOptions<RowOf<K>, S>): Promise<Omit<Page<Pick<RowOf<K>, S>>, 'hidden'>>;
|
|
684
|
+
/** Every matching row this person may read, a page at a time. maxRows (default 10,000) guards against reading a whole list by accident. */
|
|
507
685
|
queryAll<K extends KindName, S extends keyof RowOf<K> = keyof RowOf<K>>(kind: K, options?: Omit<QueryOptions<RowOf<K>, S>, 'after'> & {
|
|
508
686
|
maxRows?: number;
|
|
509
687
|
}): AsyncGenerator<Pick<RowOf<K>, S>>;
|
|
@@ -511,6 +689,7 @@ export declare class AwesomateAppClient {
|
|
|
511
689
|
get<K extends KindName>(kind: K, id: string): Promise<RowOf<K> | null>;
|
|
512
690
|
/** Create a record (returns its id) or change one with options.id, inside the kind's write rule for this user. */
|
|
513
691
|
write<K extends KindName>(kind: K, data: Partial<RowOf<K>> | Record<string, unknown>, options?: WriteOptions): Promise<string>;
|
|
692
|
+
/** Archive a record, inside the kind's write rule for this person. */
|
|
514
693
|
archive<K extends KindName>(kind: K, id: string): Promise<void>;
|
|
515
694
|
/**
|
|
516
695
|
* Run a recipe the account opened to this person's role: "accept this quote", something their
|
|
@@ -529,6 +708,19 @@ export declare class AwesomateAppClient {
|
|
|
529
708
|
ids?: string[];
|
|
530
709
|
limit?: number;
|
|
531
710
|
}): Promise<Customer[]>;
|
|
711
|
+
/**
|
|
712
|
+
* The agent this app talks with by voice, or null when the account has not picked one. Pass it
|
|
713
|
+
* to AwesomateAgent.mount as agentId; the hub decides which agent answers, never the page.
|
|
714
|
+
*/
|
|
715
|
+
voiceAgent(): Promise<string | null>;
|
|
716
|
+
/**
|
|
717
|
+
* A voice session with the app's agent, as this signed-in person:
|
|
718
|
+
* `AwesomateAgent.mount({ agentId, session: () => app.voiceSession() })`. The agent greets them
|
|
719
|
+
* by name and, when the app's assistant is set up, knows the recent conversations they can see, and nothing more. A refusal's serverCode says why:
|
|
720
|
+
* no_voice_agent (the app has none), plan_required, consent_required, person_daily, agent_daily;
|
|
721
|
+
* its personMessage is the sentence to show the person, which the AwesomateAgent widget shows.
|
|
722
|
+
*/
|
|
723
|
+
voiceSession(): Promise<VoiceSession>;
|
|
532
724
|
/**
|
|
533
725
|
* A list kept current: onRows gets the whole list (up to 200 rows, the query's order) at first
|
|
534
726
|
* and again whenever a row in it is added, changed, removed, or stops being one this person may
|
|
@@ -546,6 +738,23 @@ export declare class AwesomateAppClient {
|
|
|
546
738
|
*/
|
|
547
739
|
here(recordId: string, onPeople: (people: HerePerson[]) => void, onError?: (err: AwesomateError) => void): HereHandle;
|
|
548
740
|
}
|
|
741
|
+
/**
|
|
742
|
+
* The browser client for one of the account's apps (Pro and above). Give it the app's publishable
|
|
743
|
+
* key, which is not a secret; never the account's token.
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* const app = createAppClient({ publishableKey: 'pk_...' });
|
|
747
|
+
* app.auth.onChange((user) => render(user));
|
|
748
|
+
* await app.auth.signInWithLink(email, { redirectTo: location.href });
|
|
749
|
+
*/
|
|
549
750
|
export declare function createAppClient(options: AppClientOptions): AwesomateAppClient;
|
|
751
|
+
/**
|
|
752
|
+
* The server client: the account's token (amt_pat_... with crm:read) or an app's server key
|
|
753
|
+
* (ak_...). Both are secrets: keep them on a server, never in a browser.
|
|
754
|
+
*
|
|
755
|
+
* @example
|
|
756
|
+
* const db = createClient({ token: process.env.AWESOMATE_TOKEN! });
|
|
757
|
+
* const { rows } = await db.query('contact', { limit: 10 });
|
|
758
|
+
*/
|
|
550
759
|
export declare function createClient(options: ClientOptions): AwesomateClient;
|
|
551
760
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -18,28 +18,42 @@
|
|
|
18
18
|
* const stop = app.live('job', {}, (rows) => render(rows)); // and kept current as they change
|
|
19
19
|
*
|
|
20
20
|
* The account's token (a hosting PAT with crm:read) is a secret: use it on a server, never in a
|
|
21
|
-
* browser.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* browser. Reading and running saved queries works on every plan; writing app data (kinds,
|
|
22
|
+
* records, saved queries, recipes) needs crm:write, Support Plus and above.
|
|
23
|
+
*
|
|
24
|
+
* Docs: https://hub.awesomate.ai/docs/sdk/
|
|
24
25
|
*/
|
|
25
|
-
|
|
26
|
+
/** This package's version, sent to the hub with every server call. */
|
|
27
|
+
export const VERSION = '0.13.0';
|
|
26
28
|
const DEFAULT_BASE = 'https://hub.awesomate.ai';
|
|
27
29
|
const ERROR_CODES = ['unauthenticated', 'forbidden', 'not_found', 'validation', 'consent_blocked', 'rate_limited', 'conflict', 'unavailable'];
|
|
30
|
+
/**
|
|
31
|
+
* Every failure from the hub. `message` is for you, the developer; `personMessage`, when the hub
|
|
32
|
+
* sent one, is the sentence to show the person using your app.
|
|
33
|
+
*/
|
|
28
34
|
export class AwesomateError extends Error {
|
|
29
35
|
code;
|
|
30
36
|
status;
|
|
31
37
|
field;
|
|
32
38
|
serverCode;
|
|
33
|
-
|
|
39
|
+
personMessage;
|
|
40
|
+
constructor(
|
|
41
|
+
/** Why it failed. */
|
|
42
|
+
code, message,
|
|
43
|
+
/** The HTTP status, or 0 when the SDK refused before sending. */
|
|
44
|
+
status,
|
|
34
45
|
/** The column or parameter a validation error is about. */
|
|
35
46
|
field,
|
|
36
47
|
/** The hub's own code, unmapped. */
|
|
37
|
-
serverCode
|
|
48
|
+
serverCode,
|
|
49
|
+
/** A sentence for the person using the app, when the hub sent one (a refused voice call: "You've used today's voice time"). */
|
|
50
|
+
personMessage) {
|
|
38
51
|
super(message);
|
|
39
52
|
this.code = code;
|
|
40
53
|
this.status = status;
|
|
41
54
|
this.field = field;
|
|
42
55
|
this.serverCode = serverCode;
|
|
56
|
+
this.personMessage = personMessage;
|
|
43
57
|
this.name = 'AwesomateError';
|
|
44
58
|
}
|
|
45
59
|
}
|
|
@@ -70,6 +84,10 @@ function keyPath(method, path) {
|
|
|
70
84
|
return `/api/sdk/v1/server${rest}`;
|
|
71
85
|
throw new AwesomateError('forbidden', 'An app key reads and writes records and runs saved queries and recipes. This needs the account\'s token.', 0);
|
|
72
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* The server client, from createClient(): reads and writes the account's data with its token or
|
|
89
|
+
* an app's server key. Keep it on a server; it never works in a browser.
|
|
90
|
+
*/
|
|
73
91
|
export class AwesomateClient {
|
|
74
92
|
opts;
|
|
75
93
|
base;
|
|
@@ -123,6 +141,16 @@ export class AwesomateClient {
|
|
|
123
141
|
types() {
|
|
124
142
|
return this.request('GET', '/api/my-crm/v1/rows/types');
|
|
125
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Rows of one kind that this token can read, a page at a time.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* const { rows, next } = await db.query('contact', {
|
|
149
|
+
* where: { created_at: { gte: '$YEAR_BEGIN' } },
|
|
150
|
+
* orderBy: ['created_at', 'desc'],
|
|
151
|
+
* limit: 50,
|
|
152
|
+
* });
|
|
153
|
+
*/
|
|
126
154
|
async query(kind, options = {}) {
|
|
127
155
|
const r = await this.request('POST', '/api/my-crm/v1/rows/query', { kind, ...options });
|
|
128
156
|
return { rows: r.rows, next: r.next, count: r.count, applied: r.applied, hidden: r.hidden, asAt: r.as_at };
|
|
@@ -186,6 +214,7 @@ export class AwesomateClient {
|
|
|
186
214
|
async saveQuery(spec) {
|
|
187
215
|
return (await this.request('POST', '/api/my-crm/v1/queries', spec, false)).query;
|
|
188
216
|
}
|
|
217
|
+
/** Archive a saved query by its key. */
|
|
189
218
|
async archiveQuery(key) {
|
|
190
219
|
await this.request('DELETE', `/api/my-crm/v1/queries/${encodeURIComponent(key)}`, undefined, false);
|
|
191
220
|
}
|
|
@@ -205,6 +234,7 @@ export class AwesomateClient {
|
|
|
205
234
|
async saveRecipe(spec) {
|
|
206
235
|
return (await this.request('POST', '/api/my-crm/v1/recipes', spec, false)).recipe;
|
|
207
236
|
}
|
|
237
|
+
/** Archive a write recipe by its key. */
|
|
208
238
|
async archiveRecipe(key) {
|
|
209
239
|
await this.request('DELETE', `/api/my-crm/v1/recipes/${encodeURIComponent(key)}`, undefined, false);
|
|
210
240
|
}
|
|
@@ -215,6 +245,19 @@ export class AwesomateClient {
|
|
|
215
245
|
async call(recipe, args = {}) {
|
|
216
246
|
return (await this.request('POST', '/api/my-crm/v1/call', { fn: recipe, args }, false)).result;
|
|
217
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* The business itself: the details Awesomate keeps about it (name, what it does, voice, colours,
|
|
250
|
+
* how customers reach it), grouped, each with where it came from. `confirmed` details are the
|
|
251
|
+
* ones agents and automations use; `suggestion` ones wait for the owner. Needs the account's
|
|
252
|
+
* token (hosting:read, every plan), never an app key.
|
|
253
|
+
*/
|
|
254
|
+
business() {
|
|
255
|
+
return this.request('GET', '/api/my-business/v1/identity?format=json');
|
|
256
|
+
}
|
|
257
|
+
/** Details waiting for the owner's yes on Your business: from the website, the account, a team member or a file. */
|
|
258
|
+
async businessSuggestions() {
|
|
259
|
+
return (await this.request('GET', '/api/my-business/v1/proposals')).proposals;
|
|
260
|
+
}
|
|
218
261
|
/** Grouped numbers (counts, sums) in the Business Data API's shape. */
|
|
219
262
|
aggregate(request) {
|
|
220
263
|
return this.request('POST', '/api/my-crm/v1/data/query', request);
|
|
@@ -241,6 +284,10 @@ export function signInTokenFrom(url) {
|
|
|
241
284
|
return v && v.length >= 20 ? v : null;
|
|
242
285
|
}
|
|
243
286
|
const REFRESH_EARLY_MS = 30_000;
|
|
287
|
+
/**
|
|
288
|
+
* The browser client for one of the account's apps, from createAppClient(): sign-in is under
|
|
289
|
+
* `auth`, and every data call runs as the signed-in person, inside the account's access rules.
|
|
290
|
+
*/
|
|
244
291
|
export class AwesomateAppClient {
|
|
245
292
|
opts;
|
|
246
293
|
base;
|
|
@@ -338,6 +385,7 @@ export class AwesomateAppClient {
|
|
|
338
385
|
this.linkErrorListeners.add(listener);
|
|
339
386
|
return () => { this.linkErrorListeners.delete(listener); };
|
|
340
387
|
},
|
|
388
|
+
/** Sign out here and end the session at the hub. */
|
|
341
389
|
signOut: async () => {
|
|
342
390
|
const s = await this.load();
|
|
343
391
|
await this.clear();
|
|
@@ -369,7 +417,7 @@ export class AwesomateAppClient {
|
|
|
369
417
|
return json;
|
|
370
418
|
const server = typeof json.code === 'string' ? json.code : undefined;
|
|
371
419
|
const message = typeof json.error === 'string' ? json.error : `The hub answered ${res.status}.`;
|
|
372
|
-
throw new AwesomateError(errorCode(res.status, server), message, res.status, typeof json.field === 'string' ? json.field : undefined, server);
|
|
420
|
+
throw new AwesomateError(errorCode(res.status, server), message, res.status, typeof json.field === 'string' ? json.field : undefined, server, typeof json.message === 'string' ? json.message : undefined);
|
|
373
421
|
}
|
|
374
422
|
async read() {
|
|
375
423
|
try {
|
|
@@ -475,10 +523,18 @@ export class AwesomateAppClient {
|
|
|
475
523
|
return this.request(method, path, body, s.access_token);
|
|
476
524
|
}
|
|
477
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* Rows of one kind that this person may read, a page at a time. The database applies the
|
|
528
|
+
* kind's read rule for their role: a customer gets their own jobs, staff whatever theirs allows.
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* const { rows } = await app.query('job', { orderBy: ['created_at', 'desc'], limit: 20 });
|
|
532
|
+
*/
|
|
478
533
|
async query(kind, options = {}) {
|
|
479
534
|
const r = await this.data('POST', '/rows/query', { kind, ...options });
|
|
480
535
|
return { rows: r.rows, next: r.next, count: r.count, applied: r.applied, asAt: r.as_at };
|
|
481
536
|
}
|
|
537
|
+
/** Every matching row this person may read, a page at a time. maxRows (default 10,000) guards against reading a whole list by accident. */
|
|
482
538
|
async *queryAll(kind, options = {}) {
|
|
483
539
|
const { maxRows = 10_000, ...rest } = options;
|
|
484
540
|
let after;
|
|
@@ -511,6 +567,7 @@ export class AwesomateAppClient {
|
|
|
511
567
|
});
|
|
512
568
|
return r.result.id;
|
|
513
569
|
}
|
|
570
|
+
/** Archive a record, inside the kind's write rule for this person. */
|
|
514
571
|
async archive(kind, id) {
|
|
515
572
|
await this.data('POST', '/call', { fn: 'archive_record', args: { kind, id } });
|
|
516
573
|
}
|
|
@@ -531,6 +588,23 @@ export class AwesomateAppClient {
|
|
|
531
588
|
async lookupCustomers(options) {
|
|
532
589
|
return (await this.data('POST', '/customers/lookup', options)).customers;
|
|
533
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* The agent this app talks with by voice, or null when the account has not picked one. Pass it
|
|
593
|
+
* to AwesomateAgent.mount as agentId; the hub decides which agent answers, never the page.
|
|
594
|
+
*/
|
|
595
|
+
async voiceAgent() {
|
|
596
|
+
return (await this.data('GET', '/me')).app.voice_agent_id ?? null;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* A voice session with the app's agent, as this signed-in person:
|
|
600
|
+
* `AwesomateAgent.mount({ agentId, session: () => app.voiceSession() })`. The agent greets them
|
|
601
|
+
* by name and, when the app's assistant is set up, knows the recent conversations they can see, and nothing more. A refusal's serverCode says why:
|
|
602
|
+
* no_voice_agent (the app has none), plan_required, consent_required, person_daily, agent_daily;
|
|
603
|
+
* its personMessage is the sentence to show the person, which the AwesomateAgent widget shows.
|
|
604
|
+
*/
|
|
605
|
+
async voiceSession() {
|
|
606
|
+
return this.data('POST', '/agents/sessions');
|
|
607
|
+
}
|
|
534
608
|
/**
|
|
535
609
|
* A list kept current: onRows gets the whole list (up to 200 rows, the query's order) at first
|
|
536
610
|
* and again whenever a row in it is added, changed, removed, or stops being one this person may
|
|
@@ -842,9 +916,26 @@ class LiveSocket {
|
|
|
842
916
|
this.failAll(new AwesomateError('unauthenticated', 'Signed out.', 401));
|
|
843
917
|
}
|
|
844
918
|
}
|
|
919
|
+
/**
|
|
920
|
+
* The browser client for one of the account's apps (Pro and above). Give it the app's publishable
|
|
921
|
+
* key, which is not a secret; never the account's token.
|
|
922
|
+
*
|
|
923
|
+
* @example
|
|
924
|
+
* const app = createAppClient({ publishableKey: 'pk_...' });
|
|
925
|
+
* app.auth.onChange((user) => render(user));
|
|
926
|
+
* await app.auth.signInWithLink(email, { redirectTo: location.href });
|
|
927
|
+
*/
|
|
845
928
|
export function createAppClient(options) {
|
|
846
929
|
return new AwesomateAppClient(options);
|
|
847
930
|
}
|
|
931
|
+
/**
|
|
932
|
+
* The server client: the account's token (amt_pat_... with crm:read) or an app's server key
|
|
933
|
+
* (ak_...). Both are secrets: keep them on a server, never in a browser.
|
|
934
|
+
*
|
|
935
|
+
* @example
|
|
936
|
+
* const db = createClient({ token: process.env.AWESOMATE_TOKEN! });
|
|
937
|
+
* const { rows } = await db.query('contact', { limit: 10 });
|
|
938
|
+
*/
|
|
848
939
|
export function createClient(options) {
|
|
849
940
|
return new AwesomateClient(options);
|
|
850
941
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awesomate/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|
|
@@ -28,11 +28,16 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc -p tsconfig.json",
|
|
30
30
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p test/typing/tsconfig.json",
|
|
31
|
-
"test": "npm run build && node --test test/*.test.mjs && tsc -p test/typing/tsconfig.json",
|
|
31
|
+
"test": "npm run build && node --test test/*.test.mjs && tsc -p test/typing/tsconfig.json && npm run docs:check",
|
|
32
|
+
"docs": "node scripts/build-docs.mjs",
|
|
33
|
+
"docs:check": "node scripts/build-docs.mjs --check",
|
|
32
34
|
"prepublishOnly": "npm run typecheck && npm test"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/node": "^20.17.0",
|
|
38
|
+
"marked": "^18.0.14",
|
|
39
|
+
"shiki": "^4.5.0",
|
|
40
|
+
"typedoc": "^0.28.20",
|
|
36
41
|
"typescript": "^5.7.2"
|
|
37
42
|
}
|
|
38
43
|
}
|