@butterbase/sdk 2.10.0 → 2.12.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,353 +1,378 @@
1
- # @butterbase/sdk
2
-
3
- Universal TypeScript SDK for Butterbase - works in browser, Node.js, and Deno environments.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @butterbase/sdk
9
- ```
10
-
11
- ## Quick Start
12
-
13
- ```typescript
14
- import { createClient } from '@butterbase/sdk';
15
-
16
- // Initialize client.
17
- // apiUrl is the same regardless of which region your app lives in —
18
- // requests are routed to the right region automatically.
19
- const butterbase = createClient({
20
- appId: 'app_abc123',
21
- apiUrl: 'https://api.butterbase.ai',
22
- anonKey: 'your-anon-key' // Optional, for public access
23
- });
24
-
25
- // Query data
26
- const { data, error } = await butterbase
27
- .from('posts')
28
- .select('*')
29
- .eq('status', 'published')
30
- .order('created_at', { ascending: false })
31
- .limit(10);
32
-
33
- // Insert data
34
- const { data, error } = await butterbase
35
- .from('posts')
36
- .insert({ title: 'Hello World', content: 'My first post' });
37
-
38
- // Update data
39
- const { data, error } = await butterbase
40
- .from('posts')
41
- .update({ status: 'archived' })
42
- .eq('id', '123');
43
-
44
- // Delete data
45
- const { data, error } = await butterbase
46
- .from('posts')
47
- .delete()
48
- .eq('id', '123');
49
- ```
50
-
51
- ## Migration from 0.x to 1.0
52
-
53
- **Breaking Change:** The `authUrl` parameter has been removed. All auth endpoints now run on the same URL as the API.
54
-
55
- **Before (0.x):**
56
- ```typescript
57
- const butterbase = createClient({
58
- appId: 'app_abc123',
59
- apiUrl: 'https://api.butterbase.ai',
60
- authUrl: 'https://auth.butterbase.com', // ❌ No longer needed
61
- });
62
- ```
63
-
64
- **After (1.0):**
65
- ```typescript
66
- const butterbase = createClient({
67
- appId: 'app_abc123',
68
- apiUrl: 'https://api.butterbase.ai', // ✅ All endpoints use this URL
69
- });
70
- ```
71
-
72
- ## Authentication
73
-
74
- Sessions are automatically persisted to `localStorage` and restored on page refresh. Access tokens are automatically refreshed before they expire.
75
-
76
- ```typescript
77
- // Sign up
78
- const { data, error } = await butterbase.auth.signUp({
79
- email: 'user@example.com',
80
- password: 'secure123'
81
- });
82
-
83
- // Sign in (session is automatically persisted)
84
- const { data, error } = await butterbase.auth.signIn({
85
- email: 'user@example.com',
86
- password: 'secure123'
87
- });
88
-
89
- // Get current user (works after page refresh)
90
- const { data: user } = await butterbase.auth.getUser();
91
-
92
- // Sign out (clears persisted session)
93
- await butterbase.auth.signOut();
94
-
95
- // Refresh session manually (uses stored refresh token if none provided)
96
- const { data } = await butterbase.auth.refreshSession();
97
-
98
- // OAuth
99
- const { url } = butterbase.auth.signInWithOAuth({
100
- provider: 'google',
101
- redirectTo: 'http://localhost:3000/callback'
102
- });
103
- window.location.href = url;
104
- ```
105
-
106
- ### Auth State Changes
107
-
108
- Subscribe to authentication events to react to sign-ins, sign-outs, and token refreshes:
109
-
110
- ```typescript
111
- const { unsubscribe } = butterbase.onAuthStateChange((event, session) => {
112
- // event: 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'SESSION_RESTORED'
113
- console.log(event, session?.user);
114
- });
115
-
116
- // Stop listening
117
- unsubscribe();
118
- ```
119
-
120
- ### React Usage
121
-
122
- ```typescript
123
- import { useEffect, useState } from 'react';
124
- import { butterbase } from './lib';
125
-
126
- function App() {
127
- const [user, setUser] = useState(null);
128
-
129
- useEffect(() => {
130
- // Session is auto-restored — check current state
131
- const session = butterbase.sessionManager.getSession();
132
- if (session) setUser(session.user);
133
-
134
- // React to future auth changes
135
- const { unsubscribe } = butterbase.onAuthStateChange((event, session) => {
136
- setUser(session?.user ?? null);
137
- });
138
- return unsubscribe;
139
- }, []);
140
- }
141
- ```
142
-
143
- ### Custom Storage
144
-
145
- By default, the SDK uses `localStorage` with an in-memory fallback for SSR/Node.js environments. You can provide a custom storage adapter:
146
-
147
- ```typescript
148
- import { createClient, MemorySessionStorage } from '@butterbase/sdk';
149
-
150
- // Memory-only (no persistence)
151
- const butterbase = createClient({
152
- appId: 'app_abc123',
153
- apiUrl: 'https://api.butterbase.ai',
154
- persistSession: false,
155
- });
156
-
157
- // Custom storage adapter (e.g., for React Native)
158
- const butterbase = createClient({
159
- appId: 'app_abc123',
160
- apiUrl: 'https://api.butterbase.ai',
161
- sessionStorage: myCustomStorage, // implements { getItem, setItem, removeItem }
162
- });
163
- ```
164
-
165
- ## Storage
166
-
167
- ```typescript
168
- // Upload file
169
- const { data, error } = await butterbase.storage.upload(file);
170
-
171
- // Get download URL
172
- const { data } = await butterbase.storage.getDownloadUrl(objectId);
173
-
174
- // List files
175
- const { data: objects } = await butterbase.storage.list();
176
-
177
- // Delete file
178
- await butterbase.storage.delete(objectId);
179
- ```
180
-
181
- ## Functions
182
-
183
- ```typescript
184
- // Invoke serverless function
185
- const { data, error } = await butterbase.functions.invoke('my-function', {
186
- body: { key: 'value' },
187
- method: 'POST'
188
- });
189
- ```
190
-
191
- > **Inside a function's handler** (server-side, not this client SDK), your
192
- > `ctx` gives you the same primitives for calling siblings without plumbing
193
- > a bearer:
194
- >
195
- > ```typescript
196
- > export async function handler(req, ctx) {
197
- > const r1 = await ctx.invoke('sibling-fn', { hello: 'world' });
198
- > const r2 = await ctx.invokeDO('room', 'room-42', { cmd: 'join' });
199
- > return new Response('ok');
200
- > }
201
- > ```
202
- >
203
- > See the platform docs: [Functions → Server-to-server](https://docs.butterbase.ai/core-concepts/functions#server-to-server-function-calls) and [Durable Objects → Server-to-server](https://docs.butterbase.ai/core-concepts/durable-objects#server-to-server-calls-into-durable-objects).
204
-
205
- ## Query Builder
206
-
207
- ### Operators
208
-
209
- - `eq(column, value)` - Equal to
210
- - `neq(column, value)` - Not equal to
211
- - `gt(column, value)` - Greater than
212
- - `gte(column, value)` - Greater than or equal to
213
- - `lt(column, value)` - Less than
214
- - `lte(column, value)` - Less than or equal to
215
- - `like(column, pattern)` - Pattern matching (case-sensitive)
216
- - `ilike(column, pattern)` - Pattern matching (case-insensitive)
217
- - `in(column, values)` - Value in array
218
- - `is(column, value)` - Is null/true/false
219
-
220
- ### Modifiers
221
-
222
- - `select(columns)` - Select specific columns
223
- - `order(column, options)` - Order results
224
- - `limit(count)` - Limit results
225
- - `offset(count)` - Skip results
226
-
227
- ## Usage in Deno
228
-
229
- ```typescript
230
- import { createClient } from 'npm:@butterbase/sdk';
231
-
232
- const butterbase = createClient({
233
- appId: Deno.env.get('BUTTERBASE_APP_ID')!,
234
- apiUrl: Deno.env.get('BUTTERBASE_API_URL')!,
235
- });
236
-
237
- // Use in serverless functions
238
- export async function handler(req: Request, ctx: any) {
239
- const { data } = await butterbase.from('posts').select('*');
240
- return Response.json({ data });
241
- }
242
- ```
243
-
244
- ## TypeScript Support
245
-
246
- The SDK is fully typed with TypeScript. All methods return `ButterbaseResponse<T>` with proper type inference:
247
-
248
- ```typescript
249
- interface Post {
250
- id: string;
251
- title: string;
252
- content: string;
253
- status: 'draft' | 'published';
254
- }
255
-
256
- const { data, error } = await butterbase
257
- .from<Post>('posts')
258
- .select('*')
259
- .eq('status', 'published');
260
-
261
- // data is typed as Post[] | null
262
- // error is typed as Error | null
263
- ```
264
-
265
- ## Integrations
266
-
267
- Connect third-party services to your app and execute actions on behalf of your users.
268
-
269
- ### Admin configuration (API key)
270
-
271
- ```typescript
272
- // Enable a toolkit
273
- await bb.integrations.configure('gmail', { displayName: 'Gmail' });
274
-
275
- // List enabled integrations
276
- const { data } = await bb.integrations.getConfig();
277
-
278
- // Disable a toolkit
279
- await bb.integrations.disable('gmail');
280
-
281
- // Search available toolkits
282
- const { data } = await bb.integrations.listAvailable({ search: 'salesforce' });
283
- ```
284
-
285
- ### End-user OAuth flow (user JWT)
286
-
287
- ```typescript
288
- // Generate connect URL — redirect the user to authUrl
289
- const { data } = await bb.integrations.connect('gmail', {
290
- redirectUrl: 'https://yourapp.com/settings',
291
- });
292
- window.location.href = data.authUrl;
293
-
294
- // List user's connected accounts
295
- const { data } = await bb.integrations.listConnections();
296
-
297
- // Disconnect an account
298
- await bb.integrations.disconnect(connectionId);
299
- ```
300
-
301
- ### Executing tools
302
-
303
- ```typescript
304
- // List tools for a toolkit
305
- const { data } = await bb.integrations.getTools('gmail');
306
- // data[0] = { name: 'GMAIL_SEND_EMAIL', description: '...', parameters: { ... } }
307
-
308
- // Execute a tool (user JWT auth)
309
- const { data } = await bb.integrations.execute('GMAIL_SEND_EMAIL', {
310
- to: 'user@example.com',
311
- subject: 'Hello',
312
- body: 'Sent via Butterbase integrations.',
313
- });
314
-
315
- // Execute on behalf of a user (API key + userId, e.g. in a cron)
316
- const { data } = await bb.integrations
317
- .asUser('user-uuid')
318
- .execute('GOOGLECALENDAR_EVENTS_LIST', { timeMin: new Date().toISOString() });
319
- ```
320
-
321
- ## Error handling
322
-
323
- All client methods return `{ data, error }`. When the backend returned a
324
- recognizable agent-friendly error, `error` is a typed `ButterbaseError`
325
- subclass (`AuthError`, `ValidationError`, `NotFoundError`, `QuotaError`,
326
- `NetworkError`) carrying `code`, `status`, `remediation`, and `details`.
327
-
328
- ```typescript
329
- import {
330
- ButterbaseClient, ErrorCodes,
331
- AuthError, NotFoundError, QuotaError,
332
- } from '@butterbase/sdk';
333
-
334
- const r = await client.from('posts').select('*').execute();
335
-
336
- if (r.error instanceof NotFoundError) {
337
- console.error('Missing table:', r.error.remediation);
338
- }
339
- if (r.error?.code === ErrorCodes.AUTH_INVALID_API_KEY) {
340
- // rotate the key
341
- }
342
- if (r.error instanceof QuotaError) {
343
- console.error(`Quota hit (${r.error.code}): ${r.error.remediation}`);
344
- }
345
- ```
346
-
347
- The full set of codes lives in `ErrorCodes` (re-exported from
348
- `@butterbase/shared`). Use `parseApiError(status, body)` if you're wrapping
349
- fetch yourself and want to dispatch to the same typed classes.
350
-
351
- ## License
352
-
353
- MIT
1
+ # @butterbase/sdk
2
+
3
+ Universal TypeScript SDK for Butterbase - works in browser, Node.js, and Deno environments.
4
+
5
+ ## Office
6
+
7
+ ```ts
8
+ import { OfficeClient } from '@butterbase/sdk';
9
+
10
+ const office = new OfficeClient({
11
+ apiUrl: 'https://api.butterbase.ai',
12
+ organizationId: '<organization UUID>',
13
+ getToken: () => process.env.BUTTERBASE_API_KEY!,
14
+ });
15
+ const { data, error } = await office.ask('What needs follow-up?', {
16
+ idempotencyKey: '<unique request ID>',
17
+ });
18
+ ```
19
+
20
+ Office uses a platform credential and no app ID. Its 22 actions include lifecycle controls and `decisions`, `conversations`, `memory`, `principles` namespaces. Enable/pause/resume/approve return browser confirmation receipts; the initiating owner confirms one saved action. `getConfirmation` and `waitForConfirmation` observe it. A wait timeout may retain the last receipt in `data` alongside `error`.
21
+
22
+ `askStream(message)` submits once. To observe an existing turn, use `askStream('', {conversationId, turnId, afterSequence})`. Abort ends observation only. Typed stream errors retain replay pointers; failed writes retain their `idempotencyKey`. Reuse a request key for retries, and generate a new one for a new intended write. Principles use native policy approval; confirmation does not override conflicts.
23
+
24
+ See the [complete Office client reference](https://docs.butterbase.ai/core-concepts/office-clients/) and [HTTP contract](https://docs.butterbase.ai/api-reference/office-api/) for all methods, native payload types, membership/owner rules and expired replay.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install @butterbase/sdk
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```typescript
35
+ import { createClient } from '@butterbase/sdk';
36
+
37
+ // Initialize client.
38
+ // apiUrl is the same regardless of which region your app lives in —
39
+ // requests are routed to the right region automatically.
40
+ const butterbase = createClient({
41
+ appId: 'app_abc123',
42
+ apiUrl: 'https://api.butterbase.ai',
43
+ anonKey: 'your-anon-key' // Optional, for public access
44
+ });
45
+
46
+ // Query data
47
+ const { data, error } = await butterbase
48
+ .from('posts')
49
+ .select('*')
50
+ .eq('status', 'published')
51
+ .order('created_at', { ascending: false })
52
+ .limit(10);
53
+
54
+ // Insert data
55
+ const { data, error } = await butterbase
56
+ .from('posts')
57
+ .insert({ title: 'Hello World', content: 'My first post' });
58
+
59
+ // Update data
60
+ const { data, error } = await butterbase
61
+ .from('posts')
62
+ .update({ status: 'archived' })
63
+ .eq('id', '123');
64
+
65
+ // Delete data
66
+ const { data, error } = await butterbase
67
+ .from('posts')
68
+ .delete()
69
+ .eq('id', '123');
70
+ ```
71
+
72
+ ## Migration from 0.x to 1.0
73
+
74
+ **Breaking Change:** The `authUrl` parameter has been removed. All auth endpoints now run on the same URL as the API.
75
+
76
+ **Before (0.x):**
77
+ ```typescript
78
+ const butterbase = createClient({
79
+ appId: 'app_abc123',
80
+ apiUrl: 'https://api.butterbase.ai',
81
+ authUrl: 'https://auth.butterbase.com', // ❌ No longer needed
82
+ });
83
+ ```
84
+
85
+ **After (1.0):**
86
+ ```typescript
87
+ const butterbase = createClient({
88
+ appId: 'app_abc123',
89
+ apiUrl: 'https://api.butterbase.ai', // ✅ All endpoints use this URL
90
+ });
91
+ ```
92
+
93
+ ## Authentication
94
+
95
+ Sessions are automatically persisted to `localStorage` and restored on page refresh. Access tokens are automatically refreshed before they expire.
96
+
97
+ ```typescript
98
+ // Sign up
99
+ const { data, error } = await butterbase.auth.signUp({
100
+ email: 'user@example.com',
101
+ password: 'secure123'
102
+ });
103
+
104
+ // Sign in (session is automatically persisted)
105
+ const { data, error } = await butterbase.auth.signIn({
106
+ email: 'user@example.com',
107
+ password: 'secure123'
108
+ });
109
+
110
+ // Get current user (works after page refresh)
111
+ const { data: user } = await butterbase.auth.getUser();
112
+
113
+ // Sign out (clears persisted session)
114
+ await butterbase.auth.signOut();
115
+
116
+ // Refresh session manually (uses stored refresh token if none provided)
117
+ const { data } = await butterbase.auth.refreshSession();
118
+
119
+ // OAuth
120
+ const { url } = butterbase.auth.signInWithOAuth({
121
+ provider: 'google',
122
+ redirectTo: 'http://localhost:3000/callback'
123
+ });
124
+ window.location.href = url;
125
+ ```
126
+
127
+ ### Auth State Changes
128
+
129
+ Subscribe to authentication events to react to sign-ins, sign-outs, and token refreshes:
130
+
131
+ ```typescript
132
+ const { unsubscribe } = butterbase.onAuthStateChange((event, session) => {
133
+ // event: 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'SESSION_RESTORED'
134
+ console.log(event, session?.user);
135
+ });
136
+
137
+ // Stop listening
138
+ unsubscribe();
139
+ ```
140
+
141
+ ### React Usage
142
+
143
+ ```typescript
144
+ import { useEffect, useState } from 'react';
145
+ import { butterbase } from './lib';
146
+
147
+ function App() {
148
+ const [user, setUser] = useState(null);
149
+
150
+ useEffect(() => {
151
+ // Session is auto-restored — check current state
152
+ const session = butterbase.sessionManager.getSession();
153
+ if (session) setUser(session.user);
154
+
155
+ // React to future auth changes
156
+ const { unsubscribe } = butterbase.onAuthStateChange((event, session) => {
157
+ setUser(session?.user ?? null);
158
+ });
159
+ return unsubscribe;
160
+ }, []);
161
+ }
162
+ ```
163
+
164
+ ### Custom Storage
165
+
166
+ By default, the SDK uses `localStorage` with an in-memory fallback for SSR/Node.js environments. You can provide a custom storage adapter:
167
+
168
+ ```typescript
169
+ import { createClient, MemorySessionStorage } from '@butterbase/sdk';
170
+
171
+ // Memory-only (no persistence)
172
+ const butterbase = createClient({
173
+ appId: 'app_abc123',
174
+ apiUrl: 'https://api.butterbase.ai',
175
+ persistSession: false,
176
+ });
177
+
178
+ // Custom storage adapter (e.g., for React Native)
179
+ const butterbase = createClient({
180
+ appId: 'app_abc123',
181
+ apiUrl: 'https://api.butterbase.ai',
182
+ sessionStorage: myCustomStorage, // implements { getItem, setItem, removeItem }
183
+ });
184
+ ```
185
+
186
+ ## Storage
187
+
188
+ ```typescript
189
+ // Upload file
190
+ const { data, error } = await butterbase.storage.upload(file);
191
+
192
+ // Get download URL
193
+ const { data } = await butterbase.storage.getDownloadUrl(objectId);
194
+
195
+ // List files
196
+ const { data: objects } = await butterbase.storage.list();
197
+
198
+ // Delete file
199
+ await butterbase.storage.delete(objectId);
200
+ ```
201
+
202
+ ## Functions
203
+
204
+ ```typescript
205
+ // Invoke serverless function
206
+ const { data, error } = await butterbase.functions.invoke('my-function', {
207
+ body: { key: 'value' },
208
+ method: 'POST'
209
+ });
210
+ ```
211
+
212
+ > **Inside a function's handler** (server-side, not this client SDK), your
213
+ > `ctx` gives you the same primitives for calling siblings without plumbing
214
+ > a bearer:
215
+ >
216
+ > ```typescript
217
+ > export async function handler(req, ctx) {
218
+ > const r1 = await ctx.invoke('sibling-fn', { hello: 'world' });
219
+ > const r2 = await ctx.invokeDO('room', 'room-42', { cmd: 'join' });
220
+ > return new Response('ok');
221
+ > }
222
+ > ```
223
+ >
224
+ > See the platform docs: [Functions → Server-to-server](https://docs.butterbase.ai/core-concepts/functions#server-to-server-function-calls) and [Durable Objects → Server-to-server](https://docs.butterbase.ai/core-concepts/durable-objects#server-to-server-calls-into-durable-objects).
225
+
226
+ ## Query Builder
227
+
228
+ ### Operators
229
+
230
+ - `eq(column, value)` - Equal to
231
+ - `neq(column, value)` - Not equal to
232
+ - `gt(column, value)` - Greater than
233
+ - `gte(column, value)` - Greater than or equal to
234
+ - `lt(column, value)` - Less than
235
+ - `lte(column, value)` - Less than or equal to
236
+ - `like(column, pattern)` - Pattern matching (case-sensitive)
237
+ - `ilike(column, pattern)` - Pattern matching (case-insensitive)
238
+ - `in(column, values)` - Value in array
239
+ - `is(column, value)` - Is null/true/false
240
+
241
+ ### Modifiers
242
+
243
+ - `select(columns)` - Select specific columns
244
+ - `order(column, options)` - Order results
245
+ - `limit(count)` - Limit results
246
+ - `offset(count)` - Skip results
247
+
248
+ ## Usage in Deno
249
+
250
+ ```typescript
251
+ import { createClient } from 'npm:@butterbase/sdk';
252
+
253
+ const butterbase = createClient({
254
+ appId: Deno.env.get('BUTTERBASE_APP_ID')!,
255
+ apiUrl: Deno.env.get('BUTTERBASE_API_URL')!,
256
+ });
257
+
258
+ // Use in serverless functions
259
+ export async function handler(req: Request, ctx: any) {
260
+ const { data } = await butterbase.from('posts').select('*');
261
+ return Response.json({ data });
262
+ }
263
+ ```
264
+
265
+ ## TypeScript Support
266
+
267
+ The SDK is fully typed with TypeScript. All methods return `ButterbaseResponse<T>` with proper type inference:
268
+
269
+ ```typescript
270
+ interface Post {
271
+ id: string;
272
+ title: string;
273
+ content: string;
274
+ status: 'draft' | 'published';
275
+ }
276
+
277
+ const { data, error } = await butterbase
278
+ .from<Post>('posts')
279
+ .select('*')
280
+ .eq('status', 'published');
281
+
282
+ // data is typed as Post[] | null
283
+ // error is typed as Error | null
284
+ ```
285
+
286
+ ## Integrations
287
+
288
+ Connect third-party services to your app and execute actions on behalf of your users.
289
+
290
+ ### Admin configuration (API key)
291
+
292
+ ```typescript
293
+ // Enable a toolkit
294
+ await bb.integrations.configure('gmail', { displayName: 'Gmail' });
295
+
296
+ // List enabled integrations
297
+ const { data } = await bb.integrations.getConfig();
298
+
299
+ // Disable a toolkit
300
+ await bb.integrations.disable('gmail');
301
+
302
+ // Search available toolkits
303
+ const { data } = await bb.integrations.listAvailable({ search: 'salesforce' });
304
+ ```
305
+
306
+ ### End-user OAuth flow (user JWT)
307
+
308
+ ```typescript
309
+ // Generate connect URL — redirect the user to authUrl
310
+ const { data } = await bb.integrations.connect('gmail', {
311
+ redirectUrl: 'https://yourapp.com/settings',
312
+ });
313
+ window.location.href = data.authUrl;
314
+
315
+ // List user's connected accounts
316
+ const { data } = await bb.integrations.listConnections();
317
+
318
+ // Disconnect an account
319
+ await bb.integrations.disconnect(connectionId);
320
+ ```
321
+
322
+ ### Executing tools
323
+
324
+ ```typescript
325
+ // List tools for a toolkit
326
+ const { data } = await bb.integrations.getTools('gmail');
327
+ // data[0] = { name: 'GMAIL_SEND_EMAIL', description: '...', parameters: { ... } }
328
+
329
+ // Execute a tool (user JWT auth)
330
+ const { data } = await bb.integrations.execute('GMAIL_SEND_EMAIL', {
331
+ to: 'user@example.com',
332
+ subject: 'Hello',
333
+ body: 'Sent via Butterbase integrations.',
334
+ });
335
+
336
+ // Execute on behalf of a user (API key + userId, e.g. in a cron)
337
+ const { data } = await bb.integrations
338
+ .asUser('user-uuid')
339
+ .execute('GOOGLECALENDAR_EVENTS_LIST', { timeMin: new Date().toISOString() });
340
+ ```
341
+
342
+ ## Other clients
343
+
344
+ The client also exposes `butterbase.realtime` (table-change subscriptions and presence: `connect`, `on`, `trackPresence`, `onPresence`, `send`), `butterbase.ai` (`chat`, `chatStream`, `embed`, `decide`, `listModels`, `getConfig`, `getUsage`), `butterbase.rag` (collections, `ingest`, documents, `query`), `butterbase.billing` (app-level Stripe Connect plans, products, subscriptions, orders), `butterbase.partners`, `butterbase.people`, `butterbase.substrate`, and `butterbase.admin.*` (schema, RLS, OAuth, config, functions, API keys, audit logs, frontend, domains, Durable Objects, edge SSR, migrations). The package also exports `defineKvConfig` and the `Kv*` error classes.
345
+
346
+ ## Error handling
347
+
348
+ All client methods return `{ data, error }`. When the backend returned a
349
+ recognizable agent-friendly error, `error` is a typed `ButterbaseError`
350
+ subclass (`AuthError`, `ValidationError`, `NotFoundError`, `QuotaError`,
351
+ `NetworkError`) carrying `code`, `status`, `remediation`, and `details`.
352
+
353
+ ```typescript
354
+ import {
355
+ ButterbaseClient, ErrorCodes,
356
+ AuthError, NotFoundError, QuotaError,
357
+ } from '@butterbase/sdk';
358
+
359
+ const r = await client.from('posts').select('*').execute();
360
+
361
+ if (r.error instanceof NotFoundError) {
362
+ console.error('Missing table:', r.error.remediation);
363
+ }
364
+ if (r.error?.code === ErrorCodes.AUTH_INVALID_API_KEY) {
365
+ // rotate the key
366
+ }
367
+ if (r.error instanceof QuotaError) {
368
+ console.error(`Quota hit (${r.error.code}): ${r.error.remediation}`);
369
+ }
370
+ ```
371
+
372
+ The full set of codes lives in `ErrorCodes` (re-exported from
373
+ `@butterbase/shared`). Use `parseApiError(status, body)` if you're wrapping
374
+ fetch yourself and want to dispatch to the same typed classes.
375
+
376
+ ## License
377
+
378
+ Apache-2.0