@awesomate/sdk 0.6.0 → 0.7.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 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +19 -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
|
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. */
|
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: {
|
package/package.json
CHANGED