@awesomate/sdk 0.12.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 CHANGED
@@ -1,215 +1,48 @@
1
1
  # @awesomate/sdk
2
2
 
3
- Read your own Awesomate data from Node. Version 0.1 reads your **contact list**: query it,
4
- page through it, count it, with TypeScript types generated from your own fields.
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
- ## The business itself
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
- // Render on every change of person: signed in, signed out, a new role.
127
- app.auth.onChange((u) => render(u));
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
- // Everywhere else: the same calls as the server client, as this person.
134
- const { rows } = await app.query('job', { orderBy: ['created_at', 'desc'] });
135
- await app.write('forum_post', { body: 'Booked for Tuesday' }, { links: { job: rows[0].id } });
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
- `live()` calls you with the whole list at first and again whenever a row in it is added, changed,
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
- const room = app.here(job.id, (people) => showWhoIsHere(people)); // [{ user, role, name, typing, assistant? }]
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
- People in an app read no contacts but their own, so by default staff cannot put a job under a
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
- ```ts
187
- const [jo] = await app.lookupCustomers({ q: 'jo' }); // or { ids: [...] }, up to 50
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
- Name, email and phone only, and nothing the owner marked sensitive. Anyone else is refused.
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
- ```ts
202
- await app.call('accept_quote', { job: job.id });
40
+ ```bash
41
+ npx @awesomate/sdk types --out awesomate.d.ts
203
42
  ```
204
43
 
205
- The database lets them do exactly the recipe's steps: only its params take their values, every
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
- The rules are kept in your own database, which applies them to every read and write, so a mistake
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. End-user sign-in for browser apps comes in a later version. Reading and running saved
22
- * queries works on every plan; writing app data (kinds, records, saved queries, recipes) needs
23
- * crm:write, Support Plus and above.
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
- export declare const VERSION = "0.7.0";
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
- type TextOps<V extends string> = {
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
- type NumberOps = {
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
- type BoolOps = {
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
- type ListOps<E> = {
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
- /** What one column accepts in a where clause: a bare value means equals (a list means in, or has all for a list column). */
87
- export type ColumnFilter<V> = V 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;
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
- constructor(code: ErrorCode, message: string, status: number,
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.
@@ -270,9 +337,13 @@ export interface BusinessFact {
270
337
  /** Where it came from, in words: "confirmed by the owner", "added by a team member", "website research"... */
271
338
  source: string;
272
339
  }
340
+ /** The business, as business() returns it. */
273
341
  export interface BusinessIdentity {
342
+ /** The account's slug. */
274
343
  account: string;
344
+ /** The business's name, when known. */
275
345
  name: string | null;
346
+ /** The details, grouped for reading. */
276
347
  groups: Array<{
277
348
  id: string;
278
349
  title: string;
@@ -286,8 +357,10 @@ export interface BusinessIdentity {
286
357
  };
287
358
  /** Whether each source could be read: 'read', 'empty' or 'unavailable'. */
288
359
  sources: Record<'confirmed_details' | 'saved_details' | 'account_details' | 'website_research', string>;
360
+ /** Where the owner changes these details, in words. */
289
361
  how_to_change: string;
290
362
  }
363
+ /** A detail waiting for the owner's yes. */
291
364
  export interface BusinessSuggestion {
292
365
  id: number;
293
366
  key: string;
@@ -297,6 +370,7 @@ export interface BusinessSuggestion {
297
370
  sourceRef: string | null;
298
371
  recordedAt: string;
299
372
  }
373
+ /** One attribute (column) of a kind. */
300
374
  export interface AttributeSpec {
301
375
  key: string;
302
376
  label?: string;
@@ -307,6 +381,7 @@ export interface AttributeSpec {
307
381
  /** Default false. Only readable attributes reach query(), get() and the generated types. */
308
382
  readable_by_ai?: boolean;
309
383
  }
384
+ /** A kind to define: a table an app keeps. */
310
385
  export interface KindSpec {
311
386
  key: string;
312
387
  label?: string;
@@ -321,6 +396,7 @@ export interface KindSpec {
321
396
  required?: boolean;
322
397
  }>;
323
398
  }
399
+ /** A kind as the account has defined it. */
324
400
  export interface KindDescription extends Omit<KindSpec, 'attributes'> {
325
401
  storage: 'plain' | 'tracked';
326
402
  attributes: Array<Required<Pick<AttributeSpec, 'key' | 'label' | 'type' | 'required' | 'sensitivity' | 'readable_by_ai'>> & {
@@ -331,13 +407,16 @@ export interface KindDescription extends Omit<KindSpec, 'attributes'> {
331
407
  for_agents: string;
332
408
  };
333
409
  }
410
+ /** Options for write(). */
334
411
  export interface WriteOptions {
335
412
  /** Change this record instead of creating one; values not given stay. */
336
413
  id?: string;
337
414
  /** Set a link by its key ('<id>'), or end it (null). */
338
415
  links?: Record<string, string | null>;
339
416
  }
417
+ /** The type of a saved query's or recipe's parameter. */
340
418
  export type ParamType = 'text' | 'number' | 'date' | 'datetime' | 'boolean' | 'uuid' | 'text_list' | 'number_list';
419
+ /** One parameter of a saved query or recipe. */
341
420
  export interface ParamSpec {
342
421
  name: string;
343
422
  /** Default text. */
@@ -350,6 +429,7 @@ export interface ParamSpec {
350
429
  export type Param = {
351
430
  $param: string;
352
431
  };
432
+ /** A saved query to store with saveQuery(). */
353
433
  export interface SavedQuerySpec {
354
434
  key: string;
355
435
  label: string;
@@ -364,11 +444,13 @@ export interface SavedQuerySpec {
364
444
  };
365
445
  params?: ParamSpec[];
366
446
  }
447
+ /** A saved query as stored. */
367
448
  export interface SavedQueryDescription extends Required<Omit<SavedQuerySpec, 'description' | 'params'>> {
368
449
  description: string | null;
369
450
  params: ParamSpec[];
370
451
  updated_at: string;
371
452
  }
453
+ /** One step of a write recipe: write a record, or archive one. */
372
454
  export type RecipeStep = {
373
455
  op: 'write_record';
374
456
  kind: string;
@@ -385,6 +467,7 @@ export type RecipeStep = {
385
467
  $step: string;
386
468
  };
387
469
  };
470
+ /** A write recipe to store with saveRecipe(). */
388
471
  export interface RecipeSpec {
389
472
  key: string;
390
473
  label: string;
@@ -392,11 +475,13 @@ export interface RecipeSpec {
392
475
  params?: ParamSpec[];
393
476
  steps: RecipeStep[];
394
477
  }
478
+ /** A write recipe as stored. */
395
479
  export interface RecipeDescription extends Required<Omit<RecipeSpec, 'description' | 'params'>> {
396
480
  description: string | null;
397
481
  params: ParamSpec[];
398
482
  updated_at: string;
399
483
  }
484
+ /** What a recipe run wrote. */
400
485
  export interface RecipeResult {
401
486
  /** The id each step named with `as` wrote. */
402
487
  ids: Record<string, string>;
@@ -405,15 +490,22 @@ export interface RecipeResult {
405
490
  }
406
491
  /** Where a session is kept: localStorage, Capacitor Preferences, or anything with these three. */
407
492
  export interface SessionStorageLike {
493
+ /** The stored value, or null. */
408
494
  getItem(key: string): string | null | Promise<string | null>;
495
+ /** Store a value. */
409
496
  setItem(key: string, value: string): void | Promise<void>;
497
+ /** Forget a value. */
410
498
  removeItem(key: string): void | Promise<void>;
411
499
  }
500
+ /** The signed-in person. */
412
501
  export interface AppUser {
502
+ /** Their app user id. */
413
503
  id: string;
504
+ /** The address they sign in with. */
414
505
  email: string;
415
506
  /** owner, staff, member or one of the account's own; the database re-reads it on every call */
416
507
  role: string;
508
+ /** Their record in the account's Contacts, when linked. */
417
509
  contact_id: string | null;
418
510
  }
419
511
  /** A customer as lookupCustomers() returns them. A field the owner marked sensitive is null. */
@@ -423,10 +515,13 @@ export interface Customer {
423
515
  email: string | null;
424
516
  phone: string | null;
425
517
  }
518
+ /** Options for createAppClient(). */
426
519
  export interface AppClientOptions {
427
520
  /** The app's publishable key (pk_...). Not a secret: it belongs in browser code. */
428
521
  publishableKey: string;
522
+ /** Default https://hub.awesomate.ai. */
429
523
  baseUrl?: string;
524
+ /** Your own fetch. Default: the global one. */
430
525
  fetch?: typeof fetch;
431
526
  /** Default: localStorage in a browser, memory elsewhere. In Capacitor, pass Preferences. */
432
527
  storage?: SessionStorageLike;
@@ -444,20 +539,28 @@ export interface AppClientOptions {
444
539
  /** The part of the WebSocket API live() uses: the browser's, Node 22's, or the ws package's. */
445
540
  export interface WebSocketLike {
446
541
  new (url: string): {
542
+ /** As the browser's WebSocket. */
447
543
  readyState: number;
544
+ /** As the browser's WebSocket. */
448
545
  send(data: string): void;
546
+ /** As the browser's WebSocket. */
449
547
  close(code?: number, reason?: string): void;
548
+ /** As the browser's WebSocket. */
450
549
  onopen: ((ev: unknown) => void) | null;
550
+ /** As the browser's WebSocket. */
451
551
  onmessage: ((ev: {
452
552
  data: unknown;
453
553
  }) => void) | null;
554
+ /** As the browser's WebSocket. */
454
555
  onclose: ((ev: {
455
556
  code: number;
456
557
  reason: string;
457
558
  }) => void) | null;
559
+ /** As the browser's WebSocket. */
458
560
  onerror: ((ev: unknown) => void) | null;
459
561
  };
460
562
  }
563
+ /** What changed since onRows was last called. */
461
564
  export interface LiveChange<R> {
462
565
  /** Rows that are new or changed since the last call. */
463
566
  upserts: R[];
@@ -475,6 +578,7 @@ export interface HerePerson {
475
578
  /** The app's AI assistant, writing a reply. */
476
579
  assistant?: true;
477
580
  }
581
+ /** From here(): say this person is typing, or close the record. */
478
582
  export interface HereHandle {
479
583
  /**
480
584
  * Say this person is typing (call it on each keystroke: it sends at most every few seconds) or has
@@ -484,6 +588,7 @@ export interface HereHandle {
484
588
  /** Close the record: the others stop seeing this person on it. */
485
589
  leave(): void;
486
590
  }
591
+ /** What live() calls. */
487
592
  export interface LiveHandlers<R> {
488
593
  /** Called with the whole list, in the query's order, at first and after every change. */
489
594
  onRows: (rows: R[], change: LiveChange<R> | null) => void;
@@ -492,6 +597,24 @@ export interface LiveHandlers<R> {
492
597
  }
493
598
  /** The sign-in token a link carries, from a URL's fragment (#awesomate_token=...), or null. */
494
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
+ */
495
618
  export declare class AwesomateAppClient {
496
619
  private readonly opts;
497
620
  private readonly base;
@@ -533,6 +656,7 @@ export declare class AwesomateAppClient {
533
656
  onChange: (listener: (user: AppUser | null) => void) => (() => void);
534
657
  /** Called when a sign-in link could not be used (expired, already used, not this app's). */
535
658
  onSignInError: (listener: (err: AwesomateError) => void) => (() => void);
659
+ /** Sign out here and end the session at the hub. */
536
660
  signOut: () => Promise<void>;
537
661
  /** A current access token, for the app's own server to verify against the JWKS. */
538
662
  accessToken: () => Promise<string | null>;
@@ -549,7 +673,15 @@ export declare class AwesomateAppClient {
549
673
  private refresh;
550
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. */
551
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
+ */
552
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. */
553
685
  queryAll<K extends KindName, S extends keyof RowOf<K> = keyof RowOf<K>>(kind: K, options?: Omit<QueryOptions<RowOf<K>, S>, 'after'> & {
554
686
  maxRows?: number;
555
687
  }): AsyncGenerator<Pick<RowOf<K>, S>>;
@@ -557,6 +689,7 @@ export declare class AwesomateAppClient {
557
689
  get<K extends KindName>(kind: K, id: string): Promise<RowOf<K> | null>;
558
690
  /** Create a record (returns its id) or change one with options.id, inside the kind's write rule for this user. */
559
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. */
560
693
  archive<K extends KindName>(kind: K, id: string): Promise<void>;
561
694
  /**
562
695
  * Run a recipe the account opened to this person's role: "accept this quote", something their
@@ -575,6 +708,19 @@ export declare class AwesomateAppClient {
575
708
  ids?: string[];
576
709
  limit?: number;
577
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>;
578
724
  /**
579
725
  * A list kept current: onRows gets the whole list (up to 200 rows, the query's order) at first
580
726
  * and again whenever a row in it is added, changed, removed, or stops being one this person may
@@ -592,6 +738,23 @@ export declare class AwesomateAppClient {
592
738
  */
593
739
  here(recordId: string, onPeople: (people: HerePerson[]) => void, onError?: (err: AwesomateError) => void): HereHandle;
594
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
+ */
595
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
+ */
596
759
  export declare function createClient(options: ClientOptions): AwesomateClient;
597
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. End-user sign-in for browser apps comes in a later version. Reading and running saved
22
- * queries works on every plan; writing app data (kinds, records, saved queries, recipes) needs
23
- * crm:write, Support Plus and above.
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
- export const VERSION = '0.7.0';
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
- constructor(code, message, status,
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
  }
@@ -254,6 +284,10 @@ export function signInTokenFrom(url) {
254
284
  return v && v.length >= 20 ? v : null;
255
285
  }
256
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
+ */
257
291
  export class AwesomateAppClient {
258
292
  opts;
259
293
  base;
@@ -351,6 +385,7 @@ export class AwesomateAppClient {
351
385
  this.linkErrorListeners.add(listener);
352
386
  return () => { this.linkErrorListeners.delete(listener); };
353
387
  },
388
+ /** Sign out here and end the session at the hub. */
354
389
  signOut: async () => {
355
390
  const s = await this.load();
356
391
  await this.clear();
@@ -382,7 +417,7 @@ export class AwesomateAppClient {
382
417
  return json;
383
418
  const server = typeof json.code === 'string' ? json.code : undefined;
384
419
  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);
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);
386
421
  }
387
422
  async read() {
388
423
  try {
@@ -488,10 +523,18 @@ export class AwesomateAppClient {
488
523
  return this.request(method, path, body, s.access_token);
489
524
  }
490
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
+ */
491
533
  async query(kind, options = {}) {
492
534
  const r = await this.data('POST', '/rows/query', { kind, ...options });
493
535
  return { rows: r.rows, next: r.next, count: r.count, applied: r.applied, asAt: r.as_at };
494
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. */
495
538
  async *queryAll(kind, options = {}) {
496
539
  const { maxRows = 10_000, ...rest } = options;
497
540
  let after;
@@ -524,6 +567,7 @@ export class AwesomateAppClient {
524
567
  });
525
568
  return r.result.id;
526
569
  }
570
+ /** Archive a record, inside the kind's write rule for this person. */
527
571
  async archive(kind, id) {
528
572
  await this.data('POST', '/call', { fn: 'archive_record', args: { kind, id } });
529
573
  }
@@ -544,6 +588,23 @@ export class AwesomateAppClient {
544
588
  async lookupCustomers(options) {
545
589
  return (await this.data('POST', '/customers/lookup', options)).customers;
546
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
+ }
547
608
  /**
548
609
  * A list kept current: onRows gets the whole list (up to 200 rows, the query's order) at first
549
610
  * and again whenever a row in it is added, changed, removed, or stops being one this person may
@@ -855,9 +916,26 @@ class LiveSocket {
855
916
  this.failAll(new AwesomateError('unauthenticated', 'Signed out.', 401));
856
917
  }
857
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
+ */
858
928
  export function createAppClient(options) {
859
929
  return new AwesomateAppClient(options);
860
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
+ */
861
939
  export function createClient(options) {
862
940
  return new AwesomateClient(options);
863
941
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awesomate/sdk",
3
- "version": "0.12.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
  }