@awesomate/sdk 0.6.0 → 0.8.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 +34 -0
- package/dist/index.d.ts +25 -2
- package/dist/index.js +27 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,6 +79,26 @@ Every failure is an `AwesomateError` with a `code`: `unauthenticated`, `forbidde
|
|
|
79
79
|
The token is your account's hosting token. Use it in a server, a script or a scheduled job, never
|
|
80
80
|
in a browser. For a browser, sign your app's own users in (below).
|
|
81
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
|
+
|
|
82
102
|
## Your app's users sign in (Pro and above)
|
|
83
103
|
|
|
84
104
|
Set the app up first (Claude Code: `awesomate_crm_apps`): its name, the origins it runs on, and
|
|
@@ -126,6 +146,20 @@ and takes a fresh copy when it does. Up to 200 rows a list and 20 lists a connec
|
|
|
126
146
|
`{ onRows, onError }` to hear about a list the server refused (a column that does not exist).
|
|
127
147
|
Outside a browser, give it a WebSocket: `createAppClient({ publishableKey, WebSocket })`.
|
|
128
148
|
|
|
149
|
+
### Staff find a customer
|
|
150
|
+
|
|
151
|
+
People in an app read no contacts but their own, so by default staff cannot put a job under a
|
|
152
|
+
customer. Switch it on per app for the roles that need it (`awesomate_crm_apps`,
|
|
153
|
+
`customer_lookup`, never the role customers sign in with). Those roles can then search by name or
|
|
154
|
+
email and point a record at any customer:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const [jo] = await app.lookupCustomers({ q: 'jo' }); // or { ids: [...] }, up to 50
|
|
158
|
+
await app.write('job', { title: 'Gutters' }, { links: { customer: jo.id } });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Name, email and phone only, and nothing the owner marked sensitive. Anyone else is refused.
|
|
162
|
+
|
|
129
163
|
The rules are kept in your own database, which applies them to every read and write, so a mistake
|
|
130
164
|
in the app cannot show anyone more than their role allows. A disabled person is refused on their
|
|
131
165
|
next call. Sessions are kept in `localStorage`; in a Capacitor app pass `storage` (Preferences) and
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* queries works on every plan; writing app data (kinds, records, saved queries, recipes) needs
|
|
23
23
|
* crm:write, Support Plus and above.
|
|
24
24
|
*/
|
|
25
|
-
export declare const VERSION = "0.
|
|
25
|
+
export declare const VERSION = "0.7.0";
|
|
26
26
|
/** Augmented by the generated awesomate.d.ts, so each kind's rows are typed. */
|
|
27
27
|
export interface Kinds {
|
|
28
28
|
}
|
|
@@ -143,7 +143,11 @@ export declare class AwesomateError extends Error {
|
|
|
143
143
|
serverCode?: string | undefined);
|
|
144
144
|
}
|
|
145
145
|
export interface ClientOptions {
|
|
146
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* The account's hosting token (amt_pat_...) with crm:read, or an app's server key (ak_...) from
|
|
148
|
+
* the account's apps. A key reads and writes records and runs saved queries and recipes, for the
|
|
149
|
+
* kinds it was made for; changing kinds, queries or recipes needs the account's token.
|
|
150
|
+
*/
|
|
147
151
|
token: string;
|
|
148
152
|
baseUrl?: string;
|
|
149
153
|
fetch?: typeof fetch;
|
|
@@ -152,6 +156,8 @@ export declare class AwesomateClient {
|
|
|
152
156
|
private readonly opts;
|
|
153
157
|
private readonly base;
|
|
154
158
|
private readonly doFetch;
|
|
159
|
+
/** An app key talks to its own routes, which carry only what a key may do. */
|
|
160
|
+
private readonly appKey;
|
|
155
161
|
constructor(opts: ClientOptions);
|
|
156
162
|
private request;
|
|
157
163
|
/** The kinds this token can read, their columns, operators and examples. */
|
|
@@ -364,6 +370,13 @@ export interface AppUser {
|
|
|
364
370
|
role: string;
|
|
365
371
|
contact_id: string | null;
|
|
366
372
|
}
|
|
373
|
+
/** A customer as lookupCustomers() returns them. A field the owner marked sensitive is null. */
|
|
374
|
+
export interface Customer {
|
|
375
|
+
id: string;
|
|
376
|
+
name: string | null;
|
|
377
|
+
email: string | null;
|
|
378
|
+
phone: string | null;
|
|
379
|
+
}
|
|
367
380
|
export interface AppClientOptions {
|
|
368
381
|
/** The app's publishable key (pk_...). Not a secret: it belongs in browser code. */
|
|
369
382
|
publishableKey: string;
|
|
@@ -479,6 +492,16 @@ export declare class AwesomateAppClient {
|
|
|
479
492
|
/** Create a record (returns its id) or change one with options.id, inside the kind's write rule for this user. */
|
|
480
493
|
write<K extends KindName>(kind: K, data: Partial<RowOf<K>> | Record<string, unknown>, options?: WriteOptions): Promise<string>;
|
|
481
494
|
archive<K extends KindName>(kind: K, id: string): Promise<void>;
|
|
495
|
+
/**
|
|
496
|
+
* Customers by name or email (two characters or more), or by id, so staff can find one and put
|
|
497
|
+
* a record under them. Only for a role the account lets look customers up in this app; anyone
|
|
498
|
+
* else gets a forbidden error. Name, email and phone only, at most 50.
|
|
499
|
+
*/
|
|
500
|
+
lookupCustomers(options: {
|
|
501
|
+
q?: string;
|
|
502
|
+
ids?: string[];
|
|
503
|
+
limit?: number;
|
|
504
|
+
}): Promise<Customer[]>;
|
|
482
505
|
/**
|
|
483
506
|
* A list kept current: onRows gets the whole list (up to 200 rows, the query's order) at first
|
|
484
507
|
* and again whenever a row in it is added, changed, removed, or stops being one this person may
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* queries works on every plan; writing app data (kinds, records, saved queries, recipes) needs
|
|
23
23
|
* crm:write, Support Plus and above.
|
|
24
24
|
*/
|
|
25
|
-
export const VERSION = '0.
|
|
25
|
+
export const VERSION = '0.7.0';
|
|
26
26
|
const DEFAULT_BASE = 'https://hub.awesomate.ai';
|
|
27
27
|
const ERROR_CODES = ['unauthenticated', 'forbidden', 'not_found', 'validation', 'consent_blocked', 'rate_limited', 'conflict', 'unavailable'];
|
|
28
28
|
export class AwesomateError extends Error {
|
|
@@ -58,20 +58,37 @@ function errorCode(status, server) {
|
|
|
58
58
|
return 'not_found';
|
|
59
59
|
return 'unavailable';
|
|
60
60
|
}
|
|
61
|
+
const ACCOUNT = '/api/my-crm/v1';
|
|
62
|
+
const KEY_ROUTES = [
|
|
63
|
+
['GET', /^\/rows\/schema$/], ['POST', /^\/rows\/query$/], ['GET', /^\/rows\/[^/]+\/[^/?]+(\?.*)?$/], ['POST', /^\/call$/],
|
|
64
|
+
['GET', /^\/queries$/], ['POST', /^\/queries\/[^/]+\/run$/], ['GET', /^\/recipes$/],
|
|
65
|
+
];
|
|
66
|
+
/** The same call on an app key's routes, or a clear refusal for what only the account may do. */
|
|
67
|
+
function keyPath(method, path) {
|
|
68
|
+
const rest = path.startsWith(ACCOUNT) ? path.slice(ACCOUNT.length) : path;
|
|
69
|
+
if (KEY_ROUTES.some(([m, re]) => m === method && re.test(rest)))
|
|
70
|
+
return `/api/sdk/v1/server${rest}`;
|
|
71
|
+
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
|
+
}
|
|
61
73
|
export class AwesomateClient {
|
|
62
74
|
opts;
|
|
63
75
|
base;
|
|
64
76
|
doFetch;
|
|
77
|
+
/** An app key talks to its own routes, which carry only what a key may do. */
|
|
78
|
+
appKey;
|
|
65
79
|
constructor(opts) {
|
|
66
80
|
this.opts = opts;
|
|
67
81
|
if (!opts?.token)
|
|
68
|
-
throw new Error('createClient needs a token
|
|
82
|
+
throw new Error('createClient needs a token: the account\'s hosting token, or an app\'s server key (ak_...).');
|
|
83
|
+
this.appKey = opts.token.startsWith('ak_');
|
|
69
84
|
this.base = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/+$/, '');
|
|
70
85
|
this.doFetch = opts.fetch ?? globalThis.fetch;
|
|
71
86
|
if (!this.doFetch)
|
|
72
87
|
throw new Error('No fetch available: use Node 18 or later, or pass fetch.');
|
|
73
88
|
}
|
|
74
89
|
async request(method, path, body, retry = true) {
|
|
90
|
+
if (this.appKey)
|
|
91
|
+
path = keyPath(method, path);
|
|
75
92
|
const res = await this.doFetch(`${this.base}${path}`, {
|
|
76
93
|
method,
|
|
77
94
|
headers: {
|
|
@@ -497,6 +514,14 @@ export class AwesomateAppClient {
|
|
|
497
514
|
async archive(kind, id) {
|
|
498
515
|
await this.data('POST', '/call', { fn: 'archive_record', args: { kind, id } });
|
|
499
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Customers by name or email (two characters or more), or by id, so staff can find one and put
|
|
519
|
+
* a record under them. Only for a role the account lets look customers up in this app; anyone
|
|
520
|
+
* else gets a forbidden error. Name, email and phone only, at most 50.
|
|
521
|
+
*/
|
|
522
|
+
async lookupCustomers(options) {
|
|
523
|
+
return (await this.data('POST', '/customers/lookup', options)).customers;
|
|
524
|
+
}
|
|
500
525
|
/**
|
|
501
526
|
* A list kept current: onRows gets the whole list (up to 200 rows, the query's order) at first
|
|
502
527
|
* and again whenever a row in it is added, changed, removed, or stops being one this person may
|
package/package.json
CHANGED