@awesomate/sdk 0.12.0 → 0.14.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 -187
- package/dist/index.d.ts +305 -12
- package/dist/index.js +94 -7
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,215 +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
|
-
```
|
|
10
|
-
|
|
11
|
-
```ts
|
|
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
9
|
```
|
|
32
10
|
|
|
33
|
-
##
|
|
34
|
-
|
|
35
|
-
```js
|
|
36
|
-
const biz = await db.business();
|
|
37
|
-
biz.name; // 'Acme Plumbing'
|
|
38
|
-
for (const g of biz.groups) for (const f of g.facts) console.log(g.title, f.label, f.value, f.status, f.source);
|
|
39
|
-
biz.completeness; // { core_set: 7, core_total: 8, missing_core: ['logo'] }
|
|
40
|
-
const waiting = await db.businessSuggestions(); // details waiting for the owner's yes
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
The details Awesomate keeps about the business: name, what it does, voice, colours, how customers reach it. Each one says where it came from. `confirmed` details are the ones agents and automations use. A `suggestion` waits for the owner, who confirms it on Knowledge, Your business in the hub. This needs the account's token, on every plan, never an app key.
|
|
44
|
-
|
|
45
|
-
## Writing app data (Support Plus and above)
|
|
46
|
-
|
|
47
|
-
Define the tables an app keeps, then write to them. There's no migration and no SQL; every value is
|
|
48
|
-
checked against the kind (types, choices, required attributes and links).
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
await db.defineKind({
|
|
52
|
-
key: 'job', label: 'Job', label_plural: 'Jobs',
|
|
53
|
-
attributes: [
|
|
54
|
-
{ key: 'title', type: 'text', required: true, readable_by_ai: true },
|
|
55
|
-
{ key: 'status', type: 'choice', choices: ['open', 'booked', 'done'], readable_by_ai: true },
|
|
56
|
-
],
|
|
57
|
-
links: [{ key: 'customer', to: 'contact' }],
|
|
58
|
-
});
|
|
59
|
-
const id = await db.write('job', { title: 'Possum in the roof', status: 'open' }, { links: { customer: contactId } });
|
|
60
|
-
await db.write('job', { status: 'booked' }, { id }); // values not given stay
|
|
61
|
-
const jobs = await db.query('job', { where: { customer_id: contactId } });
|
|
62
|
-
await db.archive('job', id);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Only attributes marked `readable_by_ai` come back from `query` and `get`; a `sensitive` attribute
|
|
66
|
-
can never be readable. Contacts themselves are not written this way: the contact list keeps its
|
|
67
|
-
own consent rules.
|
|
68
|
-
|
|
69
|
-
## What you can read
|
|
70
|
-
|
|
71
|
-
The built-in contact details (name, email, phone, company, tags, when they were added) and only
|
|
72
|
-
the fields you have marked **readable by AI** under Contacts, Your fields in the hub. A field
|
|
73
|
-
marked sensitive is never readable. `db.schema()` lists the columns and says how many are hidden.
|
|
74
|
-
|
|
75
|
-
## The where grammar
|
|
76
|
-
|
|
77
|
-
`{ column: value }` means equals; `{ column: { op: value } }` uses an operator: `eq`, `neq`, `gt`,
|
|
78
|
-
`gte`, `lt`, `lte`, `in`, `nin`, `contains`, `startsWith`, `like`, `ilike`, `isNull`, and for lists
|
|
79
|
-
(tags) `has`, `hasAny`, `hasAll`, `isEmpty`. Combine with `and: [...]`, `or: [...]`, `not: {...}`.
|
|
80
|
-
A date (`'2026-09-30'`) against a timestamp means that whole day in your time zone; time
|
|
81
|
-
variables `$TODAY`, `$WEEK_BEGIN`, `$MONTH_BEGIN`, `$QUARTER_BEGIN`, `$YEAR_BEGIN` and `$FY_BEGIN`
|
|
82
|
-
(1 July) take an offset in their own unit (`$MONTH_BEGIN-1` is the start of last month).
|
|
83
|
-
|
|
84
|
-
## Errors
|
|
85
|
-
|
|
86
|
-
Every failure is an `AwesomateError` with a `code`: `unauthenticated`, `forbidden`, `not_found`,
|
|
87
|
-
`validation` (with `field`), `consent_blocked`, `rate_limited`, `conflict` or `unavailable`.
|
|
88
|
-
|
|
89
|
-
## Keep the token on a server
|
|
90
|
-
|
|
91
|
-
The token is your account's hosting token. Use it in a server, a script or a scheduled job, never
|
|
92
|
-
in a browser. For a browser, sign your app's own users in (below).
|
|
93
|
-
|
|
94
|
-
## Your app's server: a key of its own
|
|
95
|
-
|
|
96
|
-
An app's own server, or an n8n workflow, should not carry the account's token. Give it a server
|
|
97
|
-
key instead: Claude makes one with `awesomate_crm_apps` (`create_key`), read or write, for every
|
|
98
|
-
kind or only the ones it names. The key is shown once; put it in the server's environment.
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
const db = createClient({ token: process.env.AWESOMATE_APP_KEY! }); // ak_...
|
|
102
|
-
const { rows } = await db.query('job', { where: { status: 'booked' } });
|
|
103
|
-
await db.write('job', { status: 'done' }, { id: rows[0].id });
|
|
104
|
-
await db.call('book_job', { customer, title: 'Gutter clean', status: 'booked' });
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
A key reads and writes records and runs saved queries and recipes, only for its kinds (a recipe
|
|
108
|
-
that writes anything else is refused). It reads every attribute of an app kind, and the contact
|
|
109
|
-
list only as Claude does. Writes need Support Plus and above. It cannot change kinds, rules,
|
|
110
|
-
queries or recipes, and it never works from a browser. Revoking a key stops it at once; switching
|
|
111
|
-
the app off stops all of them. From n8n, send it as `Authorization: Bearer ak_...` to
|
|
112
|
-
`https://hub.awesomate.ai/api/sdk/v1/server/rows/query` (and `/call`, `/queries/<key>/run`).
|
|
113
|
-
|
|
114
|
-
## Your app's users sign in (Pro and above)
|
|
115
|
-
|
|
116
|
-
Set the app up first (Claude Code: `awesomate_crm_apps`): its name, the origins it runs on, and
|
|
117
|
-
whether anyone may sign up or only people you add. That gives you a publishable key, which is not a
|
|
118
|
-
secret. Then say who may read and change each kind (`awesomate_crm_kinds`, `set_access`), for
|
|
119
|
-
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)
|
|
120
12
|
|
|
121
13
|
```ts
|
|
122
14
|
import { createAppClient } from '@awesomate/sdk';
|
|
123
15
|
|
|
124
|
-
const app = createAppClient({ publishableKey: 'pk_...' });
|
|
16
|
+
const app = createAppClient({ publishableKey: 'pk_...' }); // not a secret
|
|
125
17
|
|
|
126
|
-
|
|
127
|
-
app.auth.
|
|
128
|
-
app.auth.onSignInError((err) => showMessage(err.message));
|
|
129
|
-
|
|
130
|
-
// The sign-in page: an email link, no password. The answer is the same for any address.
|
|
18
|
+
app.auth.onChange((user) => render(user));
|
|
19
|
+
render(await app.auth.user()); // someone already signed in on this device
|
|
131
20
|
await app.auth.signInWithLink(email, { redirectTo: location.href });
|
|
132
21
|
|
|
133
|
-
//
|
|
134
|
-
const
|
|
135
|
-
await app.
|
|
136
|
-
await app.auth.signOut();
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
The link signs them in by itself. In a browser the client takes the link from the address bar,
|
|
140
|
-
on load or when it arrives in a tab that is already open (only the part after `#` changes, so
|
|
141
|
-
the page does not reload), clears it at once and tells `onChange`. A link that has expired or
|
|
142
|
-
was already used goes to `onSignInError`. `await app.auth.completeSignIn()` still works and
|
|
143
|
-
returns the same sign-in, never a second exchange. Pass `handleSignInLinks: false` to take links
|
|
144
|
-
yourself. `onChange` hears about people, not tokens: the routine token refresh is silent.
|
|
145
|
-
|
|
146
|
-
### Lists that keep themselves current
|
|
147
|
-
|
|
148
|
-
```ts
|
|
149
|
-
const stop = app.live('job', { orderBy: ['created_at', 'desc'], limit: 50 }, (rows) => render(rows));
|
|
150
|
-
// 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 });
|
|
151
25
|
```
|
|
152
26
|
|
|
153
|
-
|
|
154
|
-
removed, or stops being one this person may read (a job moved to another customer leaves their
|
|
155
|
-
list, with its memos). Changes made anywhere reach the list: in this app, by your team in Claude
|
|
156
|
-
Code, by an n8n workflow. One connection serves every list on the client; it reconnects by itself
|
|
157
|
-
and takes a fresh copy when it does. Up to 200 rows a list and 20 lists a connection. Pass
|
|
158
|
-
`{ onRows, onError }` to hear about a list the server refused (a column that does not exist).
|
|
159
|
-
Outside a browser, give it a WebSocket: `createAppClient({ publishableKey, WebSocket })`.
|
|
160
|
-
|
|
161
|
-
While a list is open in a browser, the connection also tells the hub whether the tab is in front.
|
|
162
|
-
If the account switched on reply emails, someone looking at the app isn't emailed about a reply,
|
|
163
|
-
but a tab left open in the background doesn't count as looking, and closing the last one counts as
|
|
164
|
-
leaving straight away.
|
|
165
|
-
|
|
166
|
-
A conversation can also show who else has it open and who is typing:
|
|
27
|
+
## On a server: your account's data
|
|
167
28
|
|
|
168
29
|
```ts
|
|
169
|
-
|
|
170
|
-
messageBox.oninput = () => room.typing(true); // sent at most every few seconds
|
|
171
|
-
messageBox.onblur = () => room.typing(false);
|
|
172
|
-
// later: room.leave();
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
Everyone else with that record open and their tab in front is listed once, and the app's
|
|
176
|
-
assistant appears while it writes a reply. The hub checks this person may read the record, and
|
|
177
|
-
keeps checking; a refusal goes to the optional third argument, `onError`. Nothing is stored.
|
|
178
|
-
|
|
179
|
-
### Staff find a customer
|
|
30
|
+
import { createClient } from '@awesomate/sdk';
|
|
180
31
|
|
|
181
|
-
|
|
182
|
-
customer. Switch it on per app for the roles that need it (`awesomate_crm_apps`,
|
|
183
|
-
`customer_lookup`, never the role customers sign in with). Those roles can then search by name or
|
|
184
|
-
email and point a record at any customer:
|
|
32
|
+
const db = createClient({ token: process.env.AWESOMATE_TOKEN! }); // a secret: never in a browser
|
|
185
33
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
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 } });
|
|
189
36
|
```
|
|
190
37
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
An attribute can also be visible to some roles only (`awesomate_crm_kinds`, `set_visibility`, for
|
|
194
|
-
example a cost only `staff` see). For everyone else it comes back `null` and a write that sets it
|
|
195
|
-
is refused, whatever the app asks for.
|
|
196
|
-
|
|
197
|
-
Some things a customer should do without being able to edit the record: accept their quote, say.
|
|
198
|
-
Save it as a write recipe and open it to their role (`awesomate_crm_recipes`, `run_by`, Pro and
|
|
199
|
-
above), then:
|
|
38
|
+
Types for your own account's kinds, saved queries and recipes:
|
|
200
39
|
|
|
201
|
-
```
|
|
202
|
-
|
|
40
|
+
```bash
|
|
41
|
+
npx @awesomate/sdk types --out awesomate.d.ts
|
|
203
42
|
```
|
|
204
43
|
|
|
205
|
-
|
|
206
|
-
other value is fixed, and an existing record must be one they can already read. A recipe not
|
|
207
|
-
opened to their role is "not found".
|
|
44
|
+
## What changed
|
|
208
45
|
|
|
209
|
-
|
|
210
|
-
in the app cannot show anyone more than their role allows. A disabled person is refused on their
|
|
211
|
-
next call. Sessions are kept in `localStorage`; in a Capacitor app pass `storage` (Preferences) and
|
|
212
|
-
call `completeSignIn(url)` with the deep link that opened the app. Your own server can verify
|
|
213
|
-
`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/).
|
|
214
47
|
|
|
215
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.14.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,6 +284,7 @@ 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.
|
|
@@ -231,6 +298,13 @@ export declare class AwesomateClient {
|
|
|
231
298
|
* token (hosting:read, every plan), never an app key.
|
|
232
299
|
*/
|
|
233
300
|
business(): Promise<BusinessIdentity>;
|
|
301
|
+
/**
|
|
302
|
+
* The business map: the seven divisions every business has, the jobs in each and who holds them,
|
|
303
|
+
* which agents and automations help which job and how far each may go, and what is missing, most
|
|
304
|
+
* important first. Read only: the owner changes the map in the hub. Needs the account's token
|
|
305
|
+
* (hosting:read, every plan), never an app key, and the account must have the business map.
|
|
306
|
+
*/
|
|
307
|
+
businessMap(): Promise<BusinessMap>;
|
|
234
308
|
/** Details waiting for the owner's yes on Your business: from the website, the account, a team member or a file. */
|
|
235
309
|
businessSuggestions(): Promise<BusinessSuggestion[]>;
|
|
236
310
|
/** Grouped numbers (counts, sums) in the Business Data API's shape. */
|
|
@@ -270,9 +344,13 @@ export interface BusinessFact {
|
|
|
270
344
|
/** Where it came from, in words: "confirmed by the owner", "added by a team member", "website research"... */
|
|
271
345
|
source: string;
|
|
272
346
|
}
|
|
347
|
+
/** The business, as business() returns it. */
|
|
273
348
|
export interface BusinessIdentity {
|
|
349
|
+
/** The account's slug. */
|
|
274
350
|
account: string;
|
|
351
|
+
/** The business's name, when known. */
|
|
275
352
|
name: string | null;
|
|
353
|
+
/** The details, grouped for reading. */
|
|
276
354
|
groups: Array<{
|
|
277
355
|
id: string;
|
|
278
356
|
title: string;
|
|
@@ -286,8 +364,133 @@ export interface BusinessIdentity {
|
|
|
286
364
|
};
|
|
287
365
|
/** Whether each source could be read: 'read', 'empty' or 'unavailable'. */
|
|
288
366
|
sources: Record<'confirmed_details' | 'saved_details' | 'account_details' | 'website_research', string>;
|
|
367
|
+
/** Where the owner changes these details, in words. */
|
|
289
368
|
how_to_change: string;
|
|
290
369
|
}
|
|
370
|
+
/** How far someone, or an agent, decides alone: 1 find out, 2 suggest options, 3 recommend and wait, 4 do it then tell, 5 report only the exceptions. */
|
|
371
|
+
export type BusinessMapLevel = 1 | 2 | 3 | 4 | 5;
|
|
372
|
+
/** Something that helps: an agent, a library install, an automation Awesomate built, or a tool outside Awesomate. */
|
|
373
|
+
export interface BusinessMapHelper {
|
|
374
|
+
kind: 'agent' | 'bundle' | 'template' | 'build' | 'external';
|
|
375
|
+
ref: string;
|
|
376
|
+
label: string;
|
|
377
|
+
}
|
|
378
|
+
/** A job on the map. `slug` is its stable name for routing work: "whoever holds quotes". */
|
|
379
|
+
export interface BusinessMapJob {
|
|
380
|
+
id: number;
|
|
381
|
+
slug: string;
|
|
382
|
+
title: string;
|
|
383
|
+
mission: string | null;
|
|
384
|
+
/** active: someone holds it. open: nobody does. hire_next: the owner's next hire. */
|
|
385
|
+
state: 'active' | 'open' | 'hire_next';
|
|
386
|
+
isOwnerJob: boolean;
|
|
387
|
+
headsDivision: boolean;
|
|
388
|
+
departmentNo: number | null;
|
|
389
|
+
reportsTo: {
|
|
390
|
+
id: number;
|
|
391
|
+
title: string;
|
|
392
|
+
} | null;
|
|
393
|
+
holders: Array<{
|
|
394
|
+
membershipId: number;
|
|
395
|
+
name: string;
|
|
396
|
+
accountable: boolean;
|
|
397
|
+
timeSharePct: number | null;
|
|
398
|
+
}>;
|
|
399
|
+
responsibilities: Array<{
|
|
400
|
+
text: string;
|
|
401
|
+
level: BusinessMapLevel;
|
|
402
|
+
exceptions: string | null;
|
|
403
|
+
}>;
|
|
404
|
+
helpers: Array<BusinessMapHelper & {
|
|
405
|
+
id: number;
|
|
406
|
+
supervisor: string;
|
|
407
|
+
level: BusinessMapLevel | null;
|
|
408
|
+
exceptions: string | null;
|
|
409
|
+
minutesSavedPerRun: number | null;
|
|
410
|
+
}>;
|
|
411
|
+
}
|
|
412
|
+
/** One of the seven divisions, in board order (7 first). */
|
|
413
|
+
export interface BusinessMapDivision {
|
|
414
|
+
no: 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
415
|
+
/** Envision, Form, Promise, Balance, Fulfil, Refine, Share. */
|
|
416
|
+
verb: string;
|
|
417
|
+
name: string;
|
|
418
|
+
purpose: string;
|
|
419
|
+
stage: 'survive' | 'grow' | 'scale';
|
|
420
|
+
/** Who runs it. byDefault: nobody else has been given it, so the owner does. */
|
|
421
|
+
runBy: {
|
|
422
|
+
name: string;
|
|
423
|
+
byDefault: boolean;
|
|
424
|
+
};
|
|
425
|
+
jobs: BusinessMapJob[];
|
|
426
|
+
/** Helping here but not yet put on a job, with how they were placed, in words. */
|
|
427
|
+
helpers: Array<BusinessMapHelper & {
|
|
428
|
+
placedBy: string;
|
|
429
|
+
}>;
|
|
430
|
+
departments: Array<{
|
|
431
|
+
no: number;
|
|
432
|
+
name: string;
|
|
433
|
+
gloss: string;
|
|
434
|
+
}>;
|
|
435
|
+
ideas: Array<{
|
|
436
|
+
label: string;
|
|
437
|
+
what: string;
|
|
438
|
+
status: 'live' | 'library' | 'coming' | 'building' | 'planned';
|
|
439
|
+
path?: string;
|
|
440
|
+
}>;
|
|
441
|
+
numbers: Array<{
|
|
442
|
+
label: string;
|
|
443
|
+
kind: 'lead' | 'result';
|
|
444
|
+
}>;
|
|
445
|
+
question: string;
|
|
446
|
+
}
|
|
447
|
+
/** The business map, as businessMap() returns it. No email addresses. */
|
|
448
|
+
export interface BusinessMap {
|
|
449
|
+
business: {
|
|
450
|
+
name: string | null;
|
|
451
|
+
ownerName: string;
|
|
452
|
+
};
|
|
453
|
+
/** false: the owner has not started the map, so it is worked out from what the account runs. */
|
|
454
|
+
stored: boolean;
|
|
455
|
+
people: Array<{
|
|
456
|
+
id: number | null;
|
|
457
|
+
name: string;
|
|
458
|
+
kind: 'owner' | 'staff' | 'contractor' | 'adviser' | null;
|
|
459
|
+
role: 'owner' | 'full' | 'view' | null;
|
|
460
|
+
location: string | null;
|
|
461
|
+
fromTeamAccess: boolean;
|
|
462
|
+
}>;
|
|
463
|
+
divisions: BusinessMapDivision[];
|
|
464
|
+
/** Helpers we could not place on a division. */
|
|
465
|
+
unplaced: Array<BusinessMapHelper & {
|
|
466
|
+
placedBy: string;
|
|
467
|
+
}>;
|
|
468
|
+
/** What is missing, most important first. */
|
|
469
|
+
gaps: Array<{
|
|
470
|
+
kind: 'no_helper' | 'job_open' | 'owner_everywhere' | 'unplaced';
|
|
471
|
+
division: number | null;
|
|
472
|
+
title: string;
|
|
473
|
+
detail: string;
|
|
474
|
+
action: {
|
|
475
|
+
label: string;
|
|
476
|
+
path: string;
|
|
477
|
+
} | null;
|
|
478
|
+
}>;
|
|
479
|
+
settings: {
|
|
480
|
+
adviser: string | null;
|
|
481
|
+
runsWeek: string | null;
|
|
482
|
+
};
|
|
483
|
+
counts: {
|
|
484
|
+
people: number;
|
|
485
|
+
helpers: number;
|
|
486
|
+
placed: number;
|
|
487
|
+
divisionsWithHelpers: number;
|
|
488
|
+
jobs: number;
|
|
489
|
+
};
|
|
490
|
+
/** Sources that could not be read just now: a missing helper may simply not have been read. */
|
|
491
|
+
unavailable: string[];
|
|
492
|
+
}
|
|
493
|
+
/** A detail waiting for the owner's yes. */
|
|
291
494
|
export interface BusinessSuggestion {
|
|
292
495
|
id: number;
|
|
293
496
|
key: string;
|
|
@@ -297,6 +500,7 @@ export interface BusinessSuggestion {
|
|
|
297
500
|
sourceRef: string | null;
|
|
298
501
|
recordedAt: string;
|
|
299
502
|
}
|
|
503
|
+
/** One attribute (column) of a kind. */
|
|
300
504
|
export interface AttributeSpec {
|
|
301
505
|
key: string;
|
|
302
506
|
label?: string;
|
|
@@ -307,6 +511,7 @@ export interface AttributeSpec {
|
|
|
307
511
|
/** Default false. Only readable attributes reach query(), get() and the generated types. */
|
|
308
512
|
readable_by_ai?: boolean;
|
|
309
513
|
}
|
|
514
|
+
/** A kind to define: a table an app keeps. */
|
|
310
515
|
export interface KindSpec {
|
|
311
516
|
key: string;
|
|
312
517
|
label?: string;
|
|
@@ -321,6 +526,7 @@ export interface KindSpec {
|
|
|
321
526
|
required?: boolean;
|
|
322
527
|
}>;
|
|
323
528
|
}
|
|
529
|
+
/** A kind as the account has defined it. */
|
|
324
530
|
export interface KindDescription extends Omit<KindSpec, 'attributes'> {
|
|
325
531
|
storage: 'plain' | 'tracked';
|
|
326
532
|
attributes: Array<Required<Pick<AttributeSpec, 'key' | 'label' | 'type' | 'required' | 'sensitivity' | 'readable_by_ai'>> & {
|
|
@@ -331,13 +537,16 @@ export interface KindDescription extends Omit<KindSpec, 'attributes'> {
|
|
|
331
537
|
for_agents: string;
|
|
332
538
|
};
|
|
333
539
|
}
|
|
540
|
+
/** Options for write(). */
|
|
334
541
|
export interface WriteOptions {
|
|
335
542
|
/** Change this record instead of creating one; values not given stay. */
|
|
336
543
|
id?: string;
|
|
337
544
|
/** Set a link by its key ('<id>'), or end it (null). */
|
|
338
545
|
links?: Record<string, string | null>;
|
|
339
546
|
}
|
|
547
|
+
/** The type of a saved query's or recipe's parameter. */
|
|
340
548
|
export type ParamType = 'text' | 'number' | 'date' | 'datetime' | 'boolean' | 'uuid' | 'text_list' | 'number_list';
|
|
549
|
+
/** One parameter of a saved query or recipe. */
|
|
341
550
|
export interface ParamSpec {
|
|
342
551
|
name: string;
|
|
343
552
|
/** Default text. */
|
|
@@ -350,6 +559,7 @@ export interface ParamSpec {
|
|
|
350
559
|
export type Param = {
|
|
351
560
|
$param: string;
|
|
352
561
|
};
|
|
562
|
+
/** A saved query to store with saveQuery(). */
|
|
353
563
|
export interface SavedQuerySpec {
|
|
354
564
|
key: string;
|
|
355
565
|
label: string;
|
|
@@ -364,11 +574,13 @@ export interface SavedQuerySpec {
|
|
|
364
574
|
};
|
|
365
575
|
params?: ParamSpec[];
|
|
366
576
|
}
|
|
577
|
+
/** A saved query as stored. */
|
|
367
578
|
export interface SavedQueryDescription extends Required<Omit<SavedQuerySpec, 'description' | 'params'>> {
|
|
368
579
|
description: string | null;
|
|
369
580
|
params: ParamSpec[];
|
|
370
581
|
updated_at: string;
|
|
371
582
|
}
|
|
583
|
+
/** One step of a write recipe: write a record, or archive one. */
|
|
372
584
|
export type RecipeStep = {
|
|
373
585
|
op: 'write_record';
|
|
374
586
|
kind: string;
|
|
@@ -385,6 +597,7 @@ export type RecipeStep = {
|
|
|
385
597
|
$step: string;
|
|
386
598
|
};
|
|
387
599
|
};
|
|
600
|
+
/** A write recipe to store with saveRecipe(). */
|
|
388
601
|
export interface RecipeSpec {
|
|
389
602
|
key: string;
|
|
390
603
|
label: string;
|
|
@@ -392,11 +605,13 @@ export interface RecipeSpec {
|
|
|
392
605
|
params?: ParamSpec[];
|
|
393
606
|
steps: RecipeStep[];
|
|
394
607
|
}
|
|
608
|
+
/** A write recipe as stored. */
|
|
395
609
|
export interface RecipeDescription extends Required<Omit<RecipeSpec, 'description' | 'params'>> {
|
|
396
610
|
description: string | null;
|
|
397
611
|
params: ParamSpec[];
|
|
398
612
|
updated_at: string;
|
|
399
613
|
}
|
|
614
|
+
/** What a recipe run wrote. */
|
|
400
615
|
export interface RecipeResult {
|
|
401
616
|
/** The id each step named with `as` wrote. */
|
|
402
617
|
ids: Record<string, string>;
|
|
@@ -405,15 +620,22 @@ export interface RecipeResult {
|
|
|
405
620
|
}
|
|
406
621
|
/** Where a session is kept: localStorage, Capacitor Preferences, or anything with these three. */
|
|
407
622
|
export interface SessionStorageLike {
|
|
623
|
+
/** The stored value, or null. */
|
|
408
624
|
getItem(key: string): string | null | Promise<string | null>;
|
|
625
|
+
/** Store a value. */
|
|
409
626
|
setItem(key: string, value: string): void | Promise<void>;
|
|
627
|
+
/** Forget a value. */
|
|
410
628
|
removeItem(key: string): void | Promise<void>;
|
|
411
629
|
}
|
|
630
|
+
/** The signed-in person. */
|
|
412
631
|
export interface AppUser {
|
|
632
|
+
/** Their app user id. */
|
|
413
633
|
id: string;
|
|
634
|
+
/** The address they sign in with. */
|
|
414
635
|
email: string;
|
|
415
636
|
/** owner, staff, member or one of the account's own; the database re-reads it on every call */
|
|
416
637
|
role: string;
|
|
638
|
+
/** Their record in the account's Contacts, when linked. */
|
|
417
639
|
contact_id: string | null;
|
|
418
640
|
}
|
|
419
641
|
/** A customer as lookupCustomers() returns them. A field the owner marked sensitive is null. */
|
|
@@ -423,10 +645,13 @@ export interface Customer {
|
|
|
423
645
|
email: string | null;
|
|
424
646
|
phone: string | null;
|
|
425
647
|
}
|
|
648
|
+
/** Options for createAppClient(). */
|
|
426
649
|
export interface AppClientOptions {
|
|
427
650
|
/** The app's publishable key (pk_...). Not a secret: it belongs in browser code. */
|
|
428
651
|
publishableKey: string;
|
|
652
|
+
/** Default https://hub.awesomate.ai. */
|
|
429
653
|
baseUrl?: string;
|
|
654
|
+
/** Your own fetch. Default: the global one. */
|
|
430
655
|
fetch?: typeof fetch;
|
|
431
656
|
/** Default: localStorage in a browser, memory elsewhere. In Capacitor, pass Preferences. */
|
|
432
657
|
storage?: SessionStorageLike;
|
|
@@ -444,20 +669,28 @@ export interface AppClientOptions {
|
|
|
444
669
|
/** The part of the WebSocket API live() uses: the browser's, Node 22's, or the ws package's. */
|
|
445
670
|
export interface WebSocketLike {
|
|
446
671
|
new (url: string): {
|
|
672
|
+
/** As the browser's WebSocket. */
|
|
447
673
|
readyState: number;
|
|
674
|
+
/** As the browser's WebSocket. */
|
|
448
675
|
send(data: string): void;
|
|
676
|
+
/** As the browser's WebSocket. */
|
|
449
677
|
close(code?: number, reason?: string): void;
|
|
678
|
+
/** As the browser's WebSocket. */
|
|
450
679
|
onopen: ((ev: unknown) => void) | null;
|
|
680
|
+
/** As the browser's WebSocket. */
|
|
451
681
|
onmessage: ((ev: {
|
|
452
682
|
data: unknown;
|
|
453
683
|
}) => void) | null;
|
|
684
|
+
/** As the browser's WebSocket. */
|
|
454
685
|
onclose: ((ev: {
|
|
455
686
|
code: number;
|
|
456
687
|
reason: string;
|
|
457
688
|
}) => void) | null;
|
|
689
|
+
/** As the browser's WebSocket. */
|
|
458
690
|
onerror: ((ev: unknown) => void) | null;
|
|
459
691
|
};
|
|
460
692
|
}
|
|
693
|
+
/** What changed since onRows was last called. */
|
|
461
694
|
export interface LiveChange<R> {
|
|
462
695
|
/** Rows that are new or changed since the last call. */
|
|
463
696
|
upserts: R[];
|
|
@@ -475,6 +708,7 @@ export interface HerePerson {
|
|
|
475
708
|
/** The app's AI assistant, writing a reply. */
|
|
476
709
|
assistant?: true;
|
|
477
710
|
}
|
|
711
|
+
/** From here(): say this person is typing, or close the record. */
|
|
478
712
|
export interface HereHandle {
|
|
479
713
|
/**
|
|
480
714
|
* Say this person is typing (call it on each keystroke: it sends at most every few seconds) or has
|
|
@@ -484,6 +718,7 @@ export interface HereHandle {
|
|
|
484
718
|
/** Close the record: the others stop seeing this person on it. */
|
|
485
719
|
leave(): void;
|
|
486
720
|
}
|
|
721
|
+
/** What live() calls. */
|
|
487
722
|
export interface LiveHandlers<R> {
|
|
488
723
|
/** Called with the whole list, in the query's order, at first and after every change. */
|
|
489
724
|
onRows: (rows: R[], change: LiveChange<R> | null) => void;
|
|
@@ -492,6 +727,24 @@ export interface LiveHandlers<R> {
|
|
|
492
727
|
}
|
|
493
728
|
/** The sign-in token a link carries, from a URL's fragment (#awesomate_token=...), or null. */
|
|
494
729
|
export declare function signInTokenFrom(url: string): string | null;
|
|
730
|
+
/** What AwesomateAgent.mount's session() returns: pass it through unchanged. */
|
|
731
|
+
export interface VoiceSession {
|
|
732
|
+
session_id: string;
|
|
733
|
+
url: string;
|
|
734
|
+
token: string;
|
|
735
|
+
expires_at: string;
|
|
736
|
+
max_seconds: number;
|
|
737
|
+
agent_name: string;
|
|
738
|
+
pages: Array<{
|
|
739
|
+
label: string;
|
|
740
|
+
path: string;
|
|
741
|
+
}>;
|
|
742
|
+
say_as: Record<string, string>;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* The browser client for one of the account's apps, from createAppClient(): sign-in is under
|
|
746
|
+
* `auth`, and every data call runs as the signed-in person, inside the account's access rules.
|
|
747
|
+
*/
|
|
495
748
|
export declare class AwesomateAppClient {
|
|
496
749
|
private readonly opts;
|
|
497
750
|
private readonly base;
|
|
@@ -533,6 +786,7 @@ export declare class AwesomateAppClient {
|
|
|
533
786
|
onChange: (listener: (user: AppUser | null) => void) => (() => void);
|
|
534
787
|
/** Called when a sign-in link could not be used (expired, already used, not this app's). */
|
|
535
788
|
onSignInError: (listener: (err: AwesomateError) => void) => (() => void);
|
|
789
|
+
/** Sign out here and end the session at the hub. */
|
|
536
790
|
signOut: () => Promise<void>;
|
|
537
791
|
/** A current access token, for the app's own server to verify against the JWKS. */
|
|
538
792
|
accessToken: () => Promise<string | null>;
|
|
@@ -549,7 +803,15 @@ export declare class AwesomateAppClient {
|
|
|
549
803
|
private refresh;
|
|
550
804
|
/** 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. */
|
|
551
805
|
private data;
|
|
806
|
+
/**
|
|
807
|
+
* Rows of one kind that this person may read, a page at a time. The database applies the
|
|
808
|
+
* kind's read rule for their role: a customer gets their own jobs, staff whatever theirs allows.
|
|
809
|
+
*
|
|
810
|
+
* @example
|
|
811
|
+
* const { rows } = await app.query('job', { orderBy: ['created_at', 'desc'], limit: 20 });
|
|
812
|
+
*/
|
|
552
813
|
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'>>;
|
|
814
|
+
/** Every matching row this person may read, a page at a time. maxRows (default 10,000) guards against reading a whole list by accident. */
|
|
553
815
|
queryAll<K extends KindName, S extends keyof RowOf<K> = keyof RowOf<K>>(kind: K, options?: Omit<QueryOptions<RowOf<K>, S>, 'after'> & {
|
|
554
816
|
maxRows?: number;
|
|
555
817
|
}): AsyncGenerator<Pick<RowOf<K>, S>>;
|
|
@@ -557,6 +819,7 @@ export declare class AwesomateAppClient {
|
|
|
557
819
|
get<K extends KindName>(kind: K, id: string): Promise<RowOf<K> | null>;
|
|
558
820
|
/** Create a record (returns its id) or change one with options.id, inside the kind's write rule for this user. */
|
|
559
821
|
write<K extends KindName>(kind: K, data: Partial<RowOf<K>> | Record<string, unknown>, options?: WriteOptions): Promise<string>;
|
|
822
|
+
/** Archive a record, inside the kind's write rule for this person. */
|
|
560
823
|
archive<K extends KindName>(kind: K, id: string): Promise<void>;
|
|
561
824
|
/**
|
|
562
825
|
* Run a recipe the account opened to this person's role: "accept this quote", something their
|
|
@@ -575,6 +838,19 @@ export declare class AwesomateAppClient {
|
|
|
575
838
|
ids?: string[];
|
|
576
839
|
limit?: number;
|
|
577
840
|
}): Promise<Customer[]>;
|
|
841
|
+
/**
|
|
842
|
+
* The agent this app talks with by voice, or null when the account has not picked one. Pass it
|
|
843
|
+
* to AwesomateAgent.mount as agentId; the hub decides which agent answers, never the page.
|
|
844
|
+
*/
|
|
845
|
+
voiceAgent(): Promise<string | null>;
|
|
846
|
+
/**
|
|
847
|
+
* A voice session with the app's agent, as this signed-in person:
|
|
848
|
+
* `AwesomateAgent.mount({ agentId, session: () => app.voiceSession() })`. The agent greets them
|
|
849
|
+
* 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:
|
|
850
|
+
* no_voice_agent (the app has none), plan_required, consent_required, person_daily, agent_daily;
|
|
851
|
+
* its personMessage is the sentence to show the person, which the AwesomateAgent widget shows.
|
|
852
|
+
*/
|
|
853
|
+
voiceSession(): Promise<VoiceSession>;
|
|
578
854
|
/**
|
|
579
855
|
* A list kept current: onRows gets the whole list (up to 200 rows, the query's order) at first
|
|
580
856
|
* and again whenever a row in it is added, changed, removed, or stops being one this person may
|
|
@@ -592,6 +868,23 @@ export declare class AwesomateAppClient {
|
|
|
592
868
|
*/
|
|
593
869
|
here(recordId: string, onPeople: (people: HerePerson[]) => void, onError?: (err: AwesomateError) => void): HereHandle;
|
|
594
870
|
}
|
|
871
|
+
/**
|
|
872
|
+
* The browser client for one of the account's apps (Pro and above). Give it the app's publishable
|
|
873
|
+
* key, which is not a secret; never the account's token.
|
|
874
|
+
*
|
|
875
|
+
* @example
|
|
876
|
+
* const app = createAppClient({ publishableKey: 'pk_...' });
|
|
877
|
+
* app.auth.onChange((user) => render(user));
|
|
878
|
+
* await app.auth.signInWithLink(email, { redirectTo: location.href });
|
|
879
|
+
*/
|
|
595
880
|
export declare function createAppClient(options: AppClientOptions): AwesomateAppClient;
|
|
881
|
+
/**
|
|
882
|
+
* The server client: the account's token (amt_pat_... with crm:read) or an app's server key
|
|
883
|
+
* (ak_...). Both are secrets: keep them on a server, never in a browser.
|
|
884
|
+
*
|
|
885
|
+
* @example
|
|
886
|
+
* const db = createClient({ token: process.env.AWESOMATE_TOKEN! });
|
|
887
|
+
* const { rows } = await db.query('contact', { limit: 10 });
|
|
888
|
+
*/
|
|
596
889
|
export declare function createClient(options: ClientOptions): AwesomateClient;
|
|
597
890
|
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.14.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
|
}
|
|
@@ -224,6 +254,15 @@ export class AwesomateClient {
|
|
|
224
254
|
business() {
|
|
225
255
|
return this.request('GET', '/api/my-business/v1/identity?format=json');
|
|
226
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* The business map: the seven divisions every business has, the jobs in each and who holds them,
|
|
259
|
+
* which agents and automations help which job and how far each may go, and what is missing, most
|
|
260
|
+
* important first. Read only: the owner changes the map in the hub. Needs the account's token
|
|
261
|
+
* (hosting:read, every plan), never an app key, and the account must have the business map.
|
|
262
|
+
*/
|
|
263
|
+
async businessMap() {
|
|
264
|
+
return (await this.request('GET', '/api/my-business/v1/map?format=json')).map;
|
|
265
|
+
}
|
|
227
266
|
/** Details waiting for the owner's yes on Your business: from the website, the account, a team member or a file. */
|
|
228
267
|
async businessSuggestions() {
|
|
229
268
|
return (await this.request('GET', '/api/my-business/v1/proposals')).proposals;
|
|
@@ -254,6 +293,10 @@ export function signInTokenFrom(url) {
|
|
|
254
293
|
return v && v.length >= 20 ? v : null;
|
|
255
294
|
}
|
|
256
295
|
const REFRESH_EARLY_MS = 30_000;
|
|
296
|
+
/**
|
|
297
|
+
* The browser client for one of the account's apps, from createAppClient(): sign-in is under
|
|
298
|
+
* `auth`, and every data call runs as the signed-in person, inside the account's access rules.
|
|
299
|
+
*/
|
|
257
300
|
export class AwesomateAppClient {
|
|
258
301
|
opts;
|
|
259
302
|
base;
|
|
@@ -351,6 +394,7 @@ export class AwesomateAppClient {
|
|
|
351
394
|
this.linkErrorListeners.add(listener);
|
|
352
395
|
return () => { this.linkErrorListeners.delete(listener); };
|
|
353
396
|
},
|
|
397
|
+
/** Sign out here and end the session at the hub. */
|
|
354
398
|
signOut: async () => {
|
|
355
399
|
const s = await this.load();
|
|
356
400
|
await this.clear();
|
|
@@ -382,7 +426,7 @@ export class AwesomateAppClient {
|
|
|
382
426
|
return json;
|
|
383
427
|
const server = typeof json.code === 'string' ? json.code : undefined;
|
|
384
428
|
const message = typeof json.error === 'string' ? json.error : `The hub answered ${res.status}.`;
|
|
385
|
-
throw new AwesomateError(errorCode(res.status, server), message, res.status, typeof json.field === 'string' ? json.field : undefined, server);
|
|
429
|
+
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);
|
|
386
430
|
}
|
|
387
431
|
async read() {
|
|
388
432
|
try {
|
|
@@ -488,10 +532,18 @@ export class AwesomateAppClient {
|
|
|
488
532
|
return this.request(method, path, body, s.access_token);
|
|
489
533
|
}
|
|
490
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* Rows of one kind that this person may read, a page at a time. The database applies the
|
|
537
|
+
* kind's read rule for their role: a customer gets their own jobs, staff whatever theirs allows.
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* const { rows } = await app.query('job', { orderBy: ['created_at', 'desc'], limit: 20 });
|
|
541
|
+
*/
|
|
491
542
|
async query(kind, options = {}) {
|
|
492
543
|
const r = await this.data('POST', '/rows/query', { kind, ...options });
|
|
493
544
|
return { rows: r.rows, next: r.next, count: r.count, applied: r.applied, asAt: r.as_at };
|
|
494
545
|
}
|
|
546
|
+
/** Every matching row this person may read, a page at a time. maxRows (default 10,000) guards against reading a whole list by accident. */
|
|
495
547
|
async *queryAll(kind, options = {}) {
|
|
496
548
|
const { maxRows = 10_000, ...rest } = options;
|
|
497
549
|
let after;
|
|
@@ -524,6 +576,7 @@ export class AwesomateAppClient {
|
|
|
524
576
|
});
|
|
525
577
|
return r.result.id;
|
|
526
578
|
}
|
|
579
|
+
/** Archive a record, inside the kind's write rule for this person. */
|
|
527
580
|
async archive(kind, id) {
|
|
528
581
|
await this.data('POST', '/call', { fn: 'archive_record', args: { kind, id } });
|
|
529
582
|
}
|
|
@@ -544,6 +597,23 @@ export class AwesomateAppClient {
|
|
|
544
597
|
async lookupCustomers(options) {
|
|
545
598
|
return (await this.data('POST', '/customers/lookup', options)).customers;
|
|
546
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* The agent this app talks with by voice, or null when the account has not picked one. Pass it
|
|
602
|
+
* to AwesomateAgent.mount as agentId; the hub decides which agent answers, never the page.
|
|
603
|
+
*/
|
|
604
|
+
async voiceAgent() {
|
|
605
|
+
return (await this.data('GET', '/me')).app.voice_agent_id ?? null;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* A voice session with the app's agent, as this signed-in person:
|
|
609
|
+
* `AwesomateAgent.mount({ agentId, session: () => app.voiceSession() })`. The agent greets them
|
|
610
|
+
* 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:
|
|
611
|
+
* no_voice_agent (the app has none), plan_required, consent_required, person_daily, agent_daily;
|
|
612
|
+
* its personMessage is the sentence to show the person, which the AwesomateAgent widget shows.
|
|
613
|
+
*/
|
|
614
|
+
async voiceSession() {
|
|
615
|
+
return this.data('POST', '/agents/sessions');
|
|
616
|
+
}
|
|
547
617
|
/**
|
|
548
618
|
* A list kept current: onRows gets the whole list (up to 200 rows, the query's order) at first
|
|
549
619
|
* and again whenever a row in it is added, changed, removed, or stops being one this person may
|
|
@@ -855,9 +925,26 @@ class LiveSocket {
|
|
|
855
925
|
this.failAll(new AwesomateError('unauthenticated', 'Signed out.', 401));
|
|
856
926
|
}
|
|
857
927
|
}
|
|
928
|
+
/**
|
|
929
|
+
* The browser client for one of the account's apps (Pro and above). Give it the app's publishable
|
|
930
|
+
* key, which is not a secret; never the account's token.
|
|
931
|
+
*
|
|
932
|
+
* @example
|
|
933
|
+
* const app = createAppClient({ publishableKey: 'pk_...' });
|
|
934
|
+
* app.auth.onChange((user) => render(user));
|
|
935
|
+
* await app.auth.signInWithLink(email, { redirectTo: location.href });
|
|
936
|
+
*/
|
|
858
937
|
export function createAppClient(options) {
|
|
859
938
|
return new AwesomateAppClient(options);
|
|
860
939
|
}
|
|
940
|
+
/**
|
|
941
|
+
* The server client: the account's token (amt_pat_... with crm:read) or an app's server key
|
|
942
|
+
* (ak_...). Both are secrets: keep them on a server, never in a browser.
|
|
943
|
+
*
|
|
944
|
+
* @example
|
|
945
|
+
* const db = createClient({ token: process.env.AWESOMATE_TOKEN! });
|
|
946
|
+
* const { rows } = await db.query('contact', { limit: 10 });
|
|
947
|
+
*/
|
|
861
948
|
export function createClient(options) {
|
|
862
949
|
return new AwesomateClient(options);
|
|
863
950
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awesomate/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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,14 @@
|
|
|
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
|
+
"typedoc": "^0.28.20",
|
|
36
39
|
"typescript": "^5.7.2"
|
|
37
40
|
}
|
|
38
41
|
}
|