@botpress/adk-cli 1.19.0-beta.4 → 1.19.0-beta.5
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/dist/{README-wjb7z7f0.md → README-824wa4jj.md} +1 -1
- package/dist/SKILL-75ym6tcj.md +257 -0
- package/dist/SKILL-chnwzmt1.md +548 -0
- package/dist/SKILL-d4bs53y3.md +244 -0
- package/dist/SKILL-h4b2f1n9.md +114 -0
- package/dist/SKILL-hbw5nt95.md +111 -0
- package/dist/actions-h7hxxvxr.md +823 -0
- package/dist/adk-build-emhwd0tg.md +39 -0
- package/dist/adk-debug-yrrdtpmb.md +23 -0
- package/dist/adk-dev-console-7z9rtjpy.md +17 -0
- package/dist/adk-document-ras6zzzh.md +68 -0
- package/dist/adk-eval-vx76dwq0.md +17 -0
- package/dist/adk-explain-fcsaw852.md +22 -0
- package/dist/adk-init-9kmxmbbe.md +30 -0
- package/dist/adk-ship-dybzn884.md +29 -0
- package/dist/adk-test-xq67savs.md +27 -0
- package/dist/adk-validate-1p4skv4m.md +31 -0
- package/dist/advanced-patterns-z86m9h1a.md +669 -0
- package/dist/agent-config-1aqvkrgf.md +450 -0
- package/dist/agent-steps-zj4c1n9t.md +115 -0
- package/dist/assets-q7jt8r2q.md +237 -0
- package/dist/autonomous-execution-fymc12e5.md +747 -0
- package/dist/cli-c31e06yr.md +1334 -0
- package/dist/cli.js +970 -566
- package/dist/commands.json +24 -3
- package/dist/common-failures-ht29w2db.md +231 -0
- package/dist/component-registry-bbpw0see.md +43 -0
- package/dist/context-api-760cvh0h.md +461 -0
- package/dist/conversation-analysis-8ka4nrm4.md +420 -0
- package/dist/conversation-lifecycle-4seqqjc1.md +311 -0
- package/dist/conversations-gwfvxaee.md +1111 -0
- package/dist/custom-components-y5wdgpey.md +167 -0
- package/dist/debug-workflow-vz84q80v.md +239 -0
- package/dist/desk-k54ktvsd.md +852 -0
- package/dist/doc-standards-ck6ax2jd.md +124 -0
- package/dist/eval-format-7n9brja4.md +354 -0
- package/dist/evals-y1zcr1mc.md +11 -0
- package/dist/explain-config-1yaq3zzd.md +424 -0
- package/dist/files-dzt0wxn3.md +918 -0
- package/dist/integration-actions-xang7tma.md +659 -0
- package/dist/integrations-k5ss07f4.md +266 -0
- package/dist/interfaces-6t0bpxs4.md +97 -0
- package/dist/knowledge-bases-20rg8p5c.md +456 -0
- package/dist/llm-debugging-76ng8rzm.md +209 -0
- package/dist/mcp-server-n1asyzhv.md +112 -0
- package/dist/messages-gkkg0b54.md +769 -0
- package/dist/model-configuration-f273kgd0.md +201 -0
- package/dist/multi-agent-dashboard-1mdfjdts.md +70 -0
- package/dist/pages-xn18xn73.md +273 -0
- package/dist/patterns-mistakes-5vb99dhf.md +469 -0
- package/dist/plugins-ttnx8f49.md +280 -0
- package/dist/tables-x45t36dd.md +1136 -0
- package/dist/tags-46td3qrg.md +516 -0
- package/dist/test-patterns-hj47w4nc.md +355 -0
- package/dist/testing-workflow-2vkp932f.md +225 -0
- package/dist/tools-wk7bq24z.md +581 -0
- package/dist/trace-summarization-hdyx1c2e.md +313 -0
- package/dist/traces-and-logs-f2kjfh19.md +203 -0
- package/dist/triggers-4j2cc86x.md +719 -0
- package/dist/ui-server-entry.js +952 -137
- package/dist/workflow-steps-mrdrf1c5.md +851 -0
- package/dist/workflows-gadx3s69.md +1408 -0
- package/dist/zai-agent-reference-e6644aad.md +454 -0
- package/dist/zai-complete-guide-mnna5d6f.md +1408 -0
- package/package.json +4 -4
|
@@ -0,0 +1,1136 @@
|
|
|
1
|
+
# Tables
|
|
2
|
+
|
|
3
|
+
Tables provide structured data storage with automatic schema management, CRUD operations, and optional semantic search capabilities.
|
|
4
|
+
|
|
5
|
+
## Basic Concepts
|
|
6
|
+
|
|
7
|
+
### What are Tables?
|
|
8
|
+
|
|
9
|
+
- **Structured storage**: Define schemas with Zod validation
|
|
10
|
+
- **Auto-created**: Tables are automatically created from your schema
|
|
11
|
+
- **Searchable**: Optional semantic search on specified columns
|
|
12
|
+
- **Type-safe**: Full TypeScript support for all operations
|
|
13
|
+
|
|
14
|
+
### File Location
|
|
15
|
+
|
|
16
|
+
- **Location**: `src/tables/*.ts`
|
|
17
|
+
- **Auto-registration**: Files automatically create database tables
|
|
18
|
+
|
|
19
|
+
## Creating Tables
|
|
20
|
+
|
|
21
|
+
### ⚠️ CRITICAL REQUIREMENTS
|
|
22
|
+
|
|
23
|
+
**Before creating any table, you MUST follow these two rules or table creation will fail:**
|
|
24
|
+
|
|
25
|
+
1. **DO NOT define `id` column in schema** - The `id` field is automatically created by the system and is RESERVED. Including it in your `columns` definition will cause an error.
|
|
26
|
+
- ❌ Don't define `id` in `columns: { id: z.string(), ... }`
|
|
27
|
+
- ✅ The `id` field is automatically assigned by the server as a `number` when creating rows
|
|
28
|
+
- ✅ You can optionally provide `id` (as `number`) when using `upsertRows()` for updates
|
|
29
|
+
|
|
30
|
+
2. **Table names MUST end with "Table" suffix** - The `name` property passed to `new Table()` must end with "Table" (e.g., `"UsersTable"`, `"OrdersTable"`). This is enforced by runtime validation.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// ✅ CORRECT
|
|
34
|
+
export const UsersTable = new Table({
|
|
35
|
+
name: "UsersTable",
|
|
36
|
+
columns: {
|
|
37
|
+
// No id field - it's automatic!
|
|
38
|
+
name: z.string(),
|
|
39
|
+
email: z.string(),
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ❌ WRONG - id is defined (will fail!)
|
|
44
|
+
export const Users = new Table({
|
|
45
|
+
// Also missing "Table" suffix on variable name
|
|
46
|
+
name: "UsersTable",
|
|
47
|
+
columns: {
|
|
48
|
+
id: z.string(), // ❌ Don't define id!
|
|
49
|
+
name: z.string(),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Basic Table Structure
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { Table, z } from "@botpress/runtime";
|
|
58
|
+
|
|
59
|
+
export const UsersTable = new Table({
|
|
60
|
+
name: "UsersTable",
|
|
61
|
+
description: "Stores user information",
|
|
62
|
+
|
|
63
|
+
columns: {
|
|
64
|
+
// Note: id is NOT defined - it's automatic!
|
|
65
|
+
name: z.string(),
|
|
66
|
+
email: z.string().email(),
|
|
67
|
+
role: z.enum(["admin", "user", "guest"]),
|
|
68
|
+
createdAt: z.date(),
|
|
69
|
+
metadata: z.object({}).passthrough(), // Flexible object
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Table with Key Column and Tags
|
|
75
|
+
|
|
76
|
+
Use `keyColumn` to document the logical key for the table and `tags` for table metadata:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { Table, z } from "@botpress/runtime";
|
|
80
|
+
|
|
81
|
+
export const ExternalUsersTable = new Table({
|
|
82
|
+
name: "ExternalUsersTable",
|
|
83
|
+
description: "Users synced from external system",
|
|
84
|
+
|
|
85
|
+
// Key column for upsert operations - must be a column name
|
|
86
|
+
keyColumn: "externalId",
|
|
87
|
+
|
|
88
|
+
// Tags for table metadata (key-value pairs)
|
|
89
|
+
tags: {
|
|
90
|
+
source: "salesforce",
|
|
91
|
+
syncType: "incremental",
|
|
92
|
+
version: "2.0"
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
columns: {
|
|
96
|
+
externalId: z.string(), // This is the key column
|
|
97
|
+
name: z.string(),
|
|
98
|
+
email: z.string().email(),
|
|
99
|
+
lastSyncedAt: z.date(),
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Key Column Notes:**
|
|
105
|
+
- `keyColumn` documents the intended unique key for the table.
|
|
106
|
+
- Current runtime upsert helpers may still require `keyColumn` to be passed explicitly per `upsertRows()` call.
|
|
107
|
+
- Do not assume table-level `keyColumn` is automatically applied everywhere.
|
|
108
|
+
|
|
109
|
+
**Tags Use Cases:**
|
|
110
|
+
- Track data source/origin
|
|
111
|
+
- Version control for schemas
|
|
112
|
+
- Environment markers (staging, production)
|
|
113
|
+
- Sync metadata (last sync, sync type)
|
|
114
|
+
|
|
115
|
+
### Complex Schema Patterns
|
|
116
|
+
|
|
117
|
+
Tables support complex nested structures and flexible schemas:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
export const ConversationsTable = new Table({
|
|
121
|
+
name: "ConversationsTable",
|
|
122
|
+
description: "Store conversation data with complex metadata",
|
|
123
|
+
|
|
124
|
+
columns: {
|
|
125
|
+
// ISO date strings (common pattern for API data)
|
|
126
|
+
createdAt: z.string().describe("ISO 8601 date string"),
|
|
127
|
+
updatedAt: z.string().describe("ISO 8601 date string"),
|
|
128
|
+
|
|
129
|
+
// Optional fields
|
|
130
|
+
waitingSince: z.string().optional(),
|
|
131
|
+
snoozedUntil: z.string().optional(),
|
|
132
|
+
|
|
133
|
+
// Nested objects with specific structure
|
|
134
|
+
source: z
|
|
135
|
+
.object({
|
|
136
|
+
type: z.string(),
|
|
137
|
+
id: z.string().optional(),
|
|
138
|
+
deliveredAs: z.string().optional(),
|
|
139
|
+
subject: z.string().optional(),
|
|
140
|
+
body: z.string().optional(),
|
|
141
|
+
author: z
|
|
142
|
+
.object({
|
|
143
|
+
type: z.string(),
|
|
144
|
+
id: z.string().optional(),
|
|
145
|
+
})
|
|
146
|
+
.optional(),
|
|
147
|
+
})
|
|
148
|
+
.optional(),
|
|
149
|
+
|
|
150
|
+
// Array fields
|
|
151
|
+
contactIds: z.array(z.string()).optional(),
|
|
152
|
+
admins: z
|
|
153
|
+
.array(
|
|
154
|
+
z.object({
|
|
155
|
+
type: z.string(),
|
|
156
|
+
id: z.string(),
|
|
157
|
+
name: z.string().optional(),
|
|
158
|
+
email: z.string().optional(),
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
161
|
+
.optional(),
|
|
162
|
+
|
|
163
|
+
// Flexible objects with passthrough
|
|
164
|
+
metadata: z.object({}).passthrough(),
|
|
165
|
+
|
|
166
|
+
// Discriminated unions for type safety
|
|
167
|
+
status: z.discriminatedUnion("type", [
|
|
168
|
+
z.object({ type: z.literal("active"), reason: z.string() }),
|
|
169
|
+
z.object({ type: z.literal("snoozed"), until: z.string() }),
|
|
170
|
+
z.object({ type: z.literal("closed"), closedAt: z.string() }),
|
|
171
|
+
]),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Searchable Columns
|
|
177
|
+
|
|
178
|
+
Enable semantic search on specific columns:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
export const DocumentsTable = new Table({
|
|
182
|
+
name: "DocumentsTable",
|
|
183
|
+
description: "Document storage with search",
|
|
184
|
+
|
|
185
|
+
columns: {
|
|
186
|
+
// Searchable field with shorthand syntax
|
|
187
|
+
title: {
|
|
188
|
+
searchable: true,
|
|
189
|
+
schema: z.string().describe("Document title"),
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
// Enable semantic search on content
|
|
193
|
+
content: {
|
|
194
|
+
schema: z.string(),
|
|
195
|
+
searchable: true,
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// Another searchable field
|
|
199
|
+
summary: {
|
|
200
|
+
schema: z.string(),
|
|
201
|
+
searchable: true,
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
tags: z.array(z.string()),
|
|
205
|
+
createdAt: z.date(),
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
// Optional: Search optimization factor
|
|
209
|
+
// factor controls the relative importance of this table in search results
|
|
210
|
+
// Higher values (e.g., 5, 10, 20) give this table higher priority in search
|
|
211
|
+
// Default is 1 if not specified
|
|
212
|
+
factor: 5,
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Important Column Syntax
|
|
217
|
+
|
|
218
|
+
For searchable columns, use object notation:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// ✅ CORRECT - Object with schema and searchable
|
|
222
|
+
columns: {
|
|
223
|
+
searchableField: {
|
|
224
|
+
schema: z.string(),
|
|
225
|
+
searchable: true
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ❌ WRONG - Chain notation doesn't work
|
|
230
|
+
columns: {
|
|
231
|
+
searchableField: z.string().searchable() // This doesn't exist!
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## CRUD Operations
|
|
236
|
+
|
|
237
|
+
### Creating Rows
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { UsersTable } from "./tables/Users";
|
|
241
|
+
|
|
242
|
+
// Create single row (id is auto-assigned)
|
|
243
|
+
await UsersTable.createRows({
|
|
244
|
+
rows: [
|
|
245
|
+
{
|
|
246
|
+
name: "John Doe",
|
|
247
|
+
email: "john@example.com",
|
|
248
|
+
role: "user",
|
|
249
|
+
createdAt: new Date(),
|
|
250
|
+
metadata: { source: "signup" },
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Create multiple rows (ids are auto-assigned)
|
|
256
|
+
await UsersTable.createRows({
|
|
257
|
+
rows: [
|
|
258
|
+
{
|
|
259
|
+
name: "Alice",
|
|
260
|
+
email: "alice@example.com",
|
|
261
|
+
role: "admin",
|
|
262
|
+
createdAt: new Date(),
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: "Bob",
|
|
266
|
+
email: "bob@example.com",
|
|
267
|
+
role: "user",
|
|
268
|
+
createdAt: new Date(),
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Finding Rows
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// Find by condition
|
|
278
|
+
const { rows } = await UsersTable.findRows({
|
|
279
|
+
filter: { role: "admin" },
|
|
280
|
+
orderBy: "createdAt",
|
|
281
|
+
orderDirection: "desc",
|
|
282
|
+
limit: 10,
|
|
283
|
+
offset: 0,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Find by multiple conditions
|
|
287
|
+
const { rows } = await UsersTable.findRows({
|
|
288
|
+
filter: {
|
|
289
|
+
role: "user",
|
|
290
|
+
email: "john@example.com",
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Find with complex conditions
|
|
295
|
+
const { rows } = await UsersTable.findRows({
|
|
296
|
+
filter: {
|
|
297
|
+
createdAt: { $gte: new Date("2024-01-01") },
|
|
298
|
+
},
|
|
299
|
+
orderBy: "name",
|
|
300
|
+
orderDirection: "asc",
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Complex filters with logical operators
|
|
304
|
+
const { rows } = await UsersTable.findRows({
|
|
305
|
+
filter: {
|
|
306
|
+
$and: [{ role: "user" }, { createdAt: { $gte: new Date("2024-01-01") } }],
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Getting Single Row
|
|
312
|
+
|
|
313
|
+
Get a specific row by its id:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Get row by id
|
|
317
|
+
const row = await UsersTable.getRow({ id: 123 });
|
|
318
|
+
|
|
319
|
+
console.log(row.id); // 123
|
|
320
|
+
console.log(row.name); // Access row properties
|
|
321
|
+
console.log(row.createdAt); // Auto-generated timestamp
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Semantic Search
|
|
325
|
+
|
|
326
|
+
Search across searchable columns using the `search` parameter:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
// Search documents
|
|
330
|
+
const { rows } = await DocumentsTable.findRows({
|
|
331
|
+
search: "machine learning algorithms",
|
|
332
|
+
limit: 5,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Results are ranked by semantic similarity
|
|
336
|
+
rows.forEach((doc) => {
|
|
337
|
+
console.log(doc.title, doc.content);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Combine search with filters
|
|
341
|
+
const { rows } = await DocumentsTable.findRows({
|
|
342
|
+
search: "machine learning",
|
|
343
|
+
filter: { tags: { $in: ["ai", "ml"] } },
|
|
344
|
+
limit: 10,
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Upsert Operations
|
|
349
|
+
|
|
350
|
+
Upsert allows inserting or updating rows based on a key column:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// Upsert based on a unique column
|
|
354
|
+
await MessagesTable.upsertRows({
|
|
355
|
+
rows: [
|
|
356
|
+
{
|
|
357
|
+
messageId: "msg-123", // Key column
|
|
358
|
+
content: "Updated content",
|
|
359
|
+
timestamp: new Date(),
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
keyColumn: "messageId", // Updates if messageId exists, inserts if not
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Upsert with id (optional, as number)
|
|
366
|
+
await UsersTable.upsertRows({
|
|
367
|
+
rows: [
|
|
368
|
+
{
|
|
369
|
+
id: 123, // Optional: provide id to update specific row
|
|
370
|
+
name: "Jane Doe",
|
|
371
|
+
email: "jane@example.com",
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
keyColumn: "id",
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Bulk upsert with multiple rows
|
|
378
|
+
await ContactsTable.upsertRows({
|
|
379
|
+
rows: contacts.map((contact) => ({
|
|
380
|
+
contactId: contact.id,
|
|
381
|
+
name: contact.name,
|
|
382
|
+
email: contact.email,
|
|
383
|
+
updatedAt: new Date(),
|
|
384
|
+
})),
|
|
385
|
+
keyColumn: "contactId",
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Updating Rows
|
|
390
|
+
|
|
391
|
+
Update existing rows by ID:
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
// Update specific row by ID
|
|
395
|
+
await UsersTable.updateRows({
|
|
396
|
+
rows: [
|
|
397
|
+
{
|
|
398
|
+
id: 123, // Row ID (required)
|
|
399
|
+
role: "admin",
|
|
400
|
+
metadata: { promoted: true },
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Update multiple rows
|
|
406
|
+
await UsersTable.updateRows({
|
|
407
|
+
rows: [
|
|
408
|
+
{ id: 123, role: "admin" },
|
|
409
|
+
{ id: 456, role: "user" },
|
|
410
|
+
],
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**Note:** For tables with computed columns, see "Computed Columns" section below for the `waitComputed` option.
|
|
415
|
+
|
|
416
|
+
### Deleting Rows
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
// Delete by filter condition
|
|
420
|
+
await UsersTable.deleteRows({
|
|
421
|
+
role: "guest",
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Delete with complex filter
|
|
425
|
+
await UsersTable.deleteRows({
|
|
426
|
+
$and: [{ role: "guest" }, { createdAt: { $lt: new Date("2024-01-01") } }],
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Delete by specific IDs
|
|
430
|
+
await UsersTable.deleteRowIds([123, 456, 789]);
|
|
431
|
+
|
|
432
|
+
// Delete all rows (use with caution!)
|
|
433
|
+
await UsersTable.deleteAllRows();
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Advanced: Direct Table Operations
|
|
437
|
+
|
|
438
|
+
In workflows, you can also use the client for low-level table operations:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import { Workflow } from "@botpress/runtime";
|
|
442
|
+
|
|
443
|
+
export const ResetTablesWorkflow = new Workflow({
|
|
444
|
+
name: "resetTables",
|
|
445
|
+
description: "Resets specific tables",
|
|
446
|
+
async handler({ client }) {
|
|
447
|
+
// Delete entire table (drops and recreates)
|
|
448
|
+
await client._inner.deleteTable({ table: "MyTable" });
|
|
449
|
+
|
|
450
|
+
// Other low-level operations available via client._inner
|
|
451
|
+
// Use with caution as these bypass type safety
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Complex Schemas
|
|
457
|
+
|
|
458
|
+
### Nested Objects
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
export const OrdersTable = new Table({
|
|
462
|
+
name: "OrdersTable",
|
|
463
|
+
|
|
464
|
+
columns: {
|
|
465
|
+
customer: z.object({
|
|
466
|
+
id: z.string(),
|
|
467
|
+
name: z.string(),
|
|
468
|
+
email: z.string().email(),
|
|
469
|
+
}),
|
|
470
|
+
items: z.array(
|
|
471
|
+
z.object({
|
|
472
|
+
productId: z.string(),
|
|
473
|
+
quantity: z.number().int().positive(),
|
|
474
|
+
price: z.number().positive(),
|
|
475
|
+
}),
|
|
476
|
+
),
|
|
477
|
+
shipping: z.object({
|
|
478
|
+
address: z.string(),
|
|
479
|
+
city: z.string(),
|
|
480
|
+
country: z.string(),
|
|
481
|
+
postalCode: z.string(),
|
|
482
|
+
}),
|
|
483
|
+
status: z.enum(["pending", "processing", "shipped", "delivered"]),
|
|
484
|
+
total: z.number().positive(),
|
|
485
|
+
createdAt: z.date(),
|
|
486
|
+
updatedAt: z.date(),
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Optional and Default Values
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
export const ProductsTable = new Table({
|
|
495
|
+
name: "ProductsTable",
|
|
496
|
+
|
|
497
|
+
columns: {
|
|
498
|
+
name: z.string(),
|
|
499
|
+
description: z.string().optional(), // Optional field
|
|
500
|
+
|
|
501
|
+
// With default value
|
|
502
|
+
price: z.number().positive().default(0),
|
|
503
|
+
inStock: z.boolean().default(true),
|
|
504
|
+
|
|
505
|
+
// Optional with default
|
|
506
|
+
category: z.string().optional().default("uncategorized"),
|
|
507
|
+
|
|
508
|
+
// Nullable field
|
|
509
|
+
discountPrice: z.number().positive().nullable(),
|
|
510
|
+
|
|
511
|
+
metadata: z.object({}).passthrough().optional(),
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Computed Columns
|
|
517
|
+
|
|
518
|
+
Computed columns are automatically calculated based on other column values. They support dependencies and are recomputed when their dependencies change.
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
import { Table, z } from "@botpress/runtime";
|
|
522
|
+
|
|
523
|
+
export const ProfilesTable = new Table({
|
|
524
|
+
name: "ProfilesTable",
|
|
525
|
+
|
|
526
|
+
columns: {
|
|
527
|
+
firstName: z.string(),
|
|
528
|
+
lastName: z.string(),
|
|
529
|
+
|
|
530
|
+
// Computed column - automatically generated
|
|
531
|
+
fullName: {
|
|
532
|
+
computed: true,
|
|
533
|
+
schema: z.string(),
|
|
534
|
+
dependencies: ["firstName", "lastName"],
|
|
535
|
+
value: async (row) => {
|
|
536
|
+
return `${row.firstName} ${row.lastName}`;
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
age: z.number(),
|
|
541
|
+
|
|
542
|
+
// Computed with single dependency
|
|
543
|
+
ageInMonths: {
|
|
544
|
+
computed: true,
|
|
545
|
+
schema: z.number(),
|
|
546
|
+
dependencies: ["age"],
|
|
547
|
+
value: async (row) => {
|
|
548
|
+
return row.age * 12;
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
#### Using Computed Columns
|
|
556
|
+
|
|
557
|
+
Computed columns are calculated automatically when rows are created or updated:
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
// Create row - computed columns calculated automatically
|
|
561
|
+
await ProfilesTable.createRows({
|
|
562
|
+
rows: [
|
|
563
|
+
{
|
|
564
|
+
firstName: "John",
|
|
565
|
+
lastName: "Doe",
|
|
566
|
+
age: 30,
|
|
567
|
+
// fullName and ageInMonths will be computed automatically
|
|
568
|
+
// id is auto-assigned by server
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// The created row will have: id (number), fullName = "John Doe", ageInMonths = 360
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
#### Waiting for Computed Values
|
|
577
|
+
|
|
578
|
+
By default, computed columns run asynchronously. Use `waitComputed: true` to ensure computed values finish processing before the operation returns:
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
// Update with waitComputed - blocks until computed columns finish
|
|
582
|
+
await ProfilesTable.updateRows({
|
|
583
|
+
rows: [
|
|
584
|
+
{
|
|
585
|
+
id: 123, // Row id as number
|
|
586
|
+
firstName: "Jane", // fullName will be recomputed
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
waitComputed: true, // Wait for fullName to finish computing
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// Without waitComputed, computed columns update eventually
|
|
593
|
+
await ProfilesTable.updateRows({
|
|
594
|
+
rows: [{ id: 123, firstName: "Jane" }],
|
|
595
|
+
// Returns immediately, computed columns update in background
|
|
596
|
+
});
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
#### Complex Dependencies
|
|
600
|
+
|
|
601
|
+
Computed columns can have multiple dependencies and form dependency chains:
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
export const OrdersTable = new Table({
|
|
605
|
+
name: "OrdersTable",
|
|
606
|
+
|
|
607
|
+
columns: {
|
|
608
|
+
price: z.number(),
|
|
609
|
+
quantity: z.number(),
|
|
610
|
+
taxRate: z.number(),
|
|
611
|
+
|
|
612
|
+
// First level: subtotal
|
|
613
|
+
subtotal: {
|
|
614
|
+
computed: true,
|
|
615
|
+
schema: z.number(),
|
|
616
|
+
dependencies: ["price", "quantity"],
|
|
617
|
+
value: async (row) => row.price * row.quantity,
|
|
618
|
+
},
|
|
619
|
+
|
|
620
|
+
// Second level: tax (depends on computed column)
|
|
621
|
+
tax: {
|
|
622
|
+
computed: true,
|
|
623
|
+
schema: z.number(),
|
|
624
|
+
dependencies: ["subtotal", "taxRate"],
|
|
625
|
+
value: async (row) => row.subtotal * row.taxRate,
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
// Third level: total (depends on two computed columns)
|
|
629
|
+
total: {
|
|
630
|
+
computed: true,
|
|
631
|
+
schema: z.number(),
|
|
632
|
+
dependencies: ["subtotal", "tax"],
|
|
633
|
+
value: async (row) => row.subtotal + row.tax,
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
#### Computed Column Use Cases
|
|
640
|
+
|
|
641
|
+
**Dynamic Summaries:**
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
export const UsersTable = new Table({
|
|
645
|
+
name: "UsersTable",
|
|
646
|
+
columns: {
|
|
647
|
+
firstName: z.string(),
|
|
648
|
+
lastName: z.string(),
|
|
649
|
+
email: z.string(),
|
|
650
|
+
age: z.number(),
|
|
651
|
+
|
|
652
|
+
// Generate user bio automatically
|
|
653
|
+
bio: {
|
|
654
|
+
computed: true,
|
|
655
|
+
schema: z.string(),
|
|
656
|
+
dependencies: ["firstName", "lastName", "age"],
|
|
657
|
+
value: async (row) => {
|
|
658
|
+
return `Name: ${row.firstName} ${row.lastName}, Age: ${row.age}`;
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
**Calculations:**
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
export const ProductsTable = new Table({
|
|
669
|
+
name: "ProductsTable",
|
|
670
|
+
columns: {
|
|
671
|
+
price: z.number(),
|
|
672
|
+
cost: z.number(),
|
|
673
|
+
|
|
674
|
+
// Calculate profit margin
|
|
675
|
+
profitMargin: {
|
|
676
|
+
computed: true,
|
|
677
|
+
schema: z.number(),
|
|
678
|
+
dependencies: ["price", "cost"],
|
|
679
|
+
value: async (row) => {
|
|
680
|
+
return ((row.price - row.cost) / row.price) * 100;
|
|
681
|
+
},
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
**Data Transformations:**
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
export const ContactsTable = new Table({
|
|
691
|
+
name: "ContactsTable",
|
|
692
|
+
columns: {
|
|
693
|
+
phone: z.string(),
|
|
694
|
+
|
|
695
|
+
// Normalize phone format
|
|
696
|
+
phoneNormalized: {
|
|
697
|
+
computed: true,
|
|
698
|
+
schema: z.string(),
|
|
699
|
+
dependencies: ["phone"],
|
|
700
|
+
value: async (row) => {
|
|
701
|
+
// Remove all non-digit characters
|
|
702
|
+
return row.phone.replace(/\D/g, "");
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
},
|
|
706
|
+
});
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
#### Important Notes
|
|
710
|
+
|
|
711
|
+
- **Not Stored in Schema**: Computed columns are calculated, not stored in the base schema definition
|
|
712
|
+
- **Automatic Resolution**: Dependencies are automatically resolved in the correct order
|
|
713
|
+
- **No Circular Dependencies**: The system prevents circular dependency chains
|
|
714
|
+
- **Async Support**: The `value` function is async, allowing API calls or complex computations
|
|
715
|
+
- **Use `waitComputed`**: When you need computed values immediately after an operation
|
|
716
|
+
- **Performance**: Computed columns add processing time - use sparingly for expensive operations
|
|
717
|
+
|
|
718
|
+
## Usage in Actions/Workflows
|
|
719
|
+
|
|
720
|
+
### In Actions
|
|
721
|
+
|
|
722
|
+
```typescript
|
|
723
|
+
import { Action, z } from "@botpress/runtime";
|
|
724
|
+
import { UsersTable } from "../tables/Users";
|
|
725
|
+
|
|
726
|
+
export const createUser = new Action({
|
|
727
|
+
name: "createUser",
|
|
728
|
+
input: z.object({
|
|
729
|
+
name: z.string(),
|
|
730
|
+
email: z.string().email(),
|
|
731
|
+
role: z.enum(["admin", "user", "guest"]),
|
|
732
|
+
}),
|
|
733
|
+
output: z.object({
|
|
734
|
+
userId: z.string(),
|
|
735
|
+
created: z.boolean(),
|
|
736
|
+
}),
|
|
737
|
+
|
|
738
|
+
async handler({ input }) {
|
|
739
|
+
// Check if user exists
|
|
740
|
+
const existing = await UsersTable.findRows({
|
|
741
|
+
filter: { email: input.email },
|
|
742
|
+
limit: 1,
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
if (existing.rows.length > 0) {
|
|
746
|
+
return {
|
|
747
|
+
userId: existing.rows[0].id,
|
|
748
|
+
created: false,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Create new user
|
|
753
|
+
const result = await UsersTable.createRows({
|
|
754
|
+
rows: [
|
|
755
|
+
{
|
|
756
|
+
name: input.name,
|
|
757
|
+
email: input.email,
|
|
758
|
+
role: input.role,
|
|
759
|
+
createdAt: new Date(),
|
|
760
|
+
metadata: {},
|
|
761
|
+
},
|
|
762
|
+
],
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
return {
|
|
766
|
+
userId: result.rows[0].id.toString(),
|
|
767
|
+
created: true,
|
|
768
|
+
};
|
|
769
|
+
},
|
|
770
|
+
});
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
### In Workflows
|
|
774
|
+
|
|
775
|
+
```typescript
|
|
776
|
+
import { Workflow, z } from "@botpress/runtime";
|
|
777
|
+
import { AuditLogTable } from "../tables/AuditLog";
|
|
778
|
+
|
|
779
|
+
export const ProcessingWorkflow = new Workflow({
|
|
780
|
+
name: "processing",
|
|
781
|
+
input: z.object({ data: z.string() }),
|
|
782
|
+
|
|
783
|
+
handler: async ({ input, step }) => {
|
|
784
|
+
// Log workflow start
|
|
785
|
+
await step("log-start", async () => {
|
|
786
|
+
await AuditLogTable.createRows({
|
|
787
|
+
rows: [
|
|
788
|
+
{
|
|
789
|
+
action: "workflow_started",
|
|
790
|
+
details: { workflow: "processing", input },
|
|
791
|
+
timestamp: new Date(),
|
|
792
|
+
},
|
|
793
|
+
],
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
// Process data
|
|
798
|
+
const result = await step("process", async () => {
|
|
799
|
+
return await processData(input.data);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
// Log completion
|
|
803
|
+
await step("log-complete", async () => {
|
|
804
|
+
await AuditLogTable.createRows({
|
|
805
|
+
rows: [
|
|
806
|
+
{
|
|
807
|
+
action: "workflow_completed",
|
|
808
|
+
details: { workflow: "processing", result },
|
|
809
|
+
timestamp: new Date(),
|
|
810
|
+
},
|
|
811
|
+
],
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
return { success: true };
|
|
816
|
+
},
|
|
817
|
+
});
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### In Conversations
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
import { Conversation, user } from "@botpress/runtime";
|
|
824
|
+
import { ConversationHistoryTable } from "../tables/ConversationHistory";
|
|
825
|
+
|
|
826
|
+
export const Chat = new Conversation({
|
|
827
|
+
channel: "chat.channel",
|
|
828
|
+
|
|
829
|
+
async handler({ message, conversation }) {
|
|
830
|
+
// Store message in history
|
|
831
|
+
if (message?.type === "text") {
|
|
832
|
+
await ConversationHistoryTable.createRows({
|
|
833
|
+
rows: [
|
|
834
|
+
{
|
|
835
|
+
conversationId: conversation.id,
|
|
836
|
+
userId: user.id,
|
|
837
|
+
message: message.payload.text,
|
|
838
|
+
timestamp: new Date(),
|
|
839
|
+
},
|
|
840
|
+
],
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Search previous conversations
|
|
845
|
+
if (message?.payload.text?.startsWith("/search")) {
|
|
846
|
+
const query = message.payload.text.substring(7).trim();
|
|
847
|
+
|
|
848
|
+
const { rows } = await ConversationHistoryTable.findRows({
|
|
849
|
+
search: query,
|
|
850
|
+
limit: 5,
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
await conversation.send({
|
|
854
|
+
type: "text",
|
|
855
|
+
payload: {
|
|
856
|
+
text: `Found ${rows.length} matching conversations`,
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
});
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
## Using Tables with Knowledge Bases
|
|
865
|
+
|
|
866
|
+
Tables can be used as data sources for Knowledge Bases to enable RAG (Retrieval Augmented Generation) from your structured data.
|
|
867
|
+
|
|
868
|
+
For complete documentation on using tables with knowledge bases, including `DataSource.Table.fromTable()`, transform functions, and filtering options, see:
|
|
869
|
+
|
|
870
|
+
**→ [knowledge-bases.md - Table Data Sources](./knowledge-bases.md)**
|
|
871
|
+
|
|
872
|
+
## Best Practices
|
|
873
|
+
|
|
874
|
+
### 1. Use Meaningful Table Names with "Table" Suffix
|
|
875
|
+
|
|
876
|
+
```typescript
|
|
877
|
+
// ✅ CORRECT - Clear, descriptive names
|
|
878
|
+
export const UsersTable = new Table({ name: "UsersTable", columns: { ... } });
|
|
879
|
+
export const OrdersTable = new Table({ name: "OrdersTable", columns: { ... } });
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### 2. Add Timestamps
|
|
883
|
+
|
|
884
|
+
```typescript
|
|
885
|
+
columns: {
|
|
886
|
+
// Always include these for audit trails
|
|
887
|
+
createdAt: z.date(),
|
|
888
|
+
updatedAt: z.date(),
|
|
889
|
+
|
|
890
|
+
// Optional: soft delete
|
|
891
|
+
deletedAt: z.date().nullable().optional()
|
|
892
|
+
}
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
### 3. Handle Pagination
|
|
896
|
+
|
|
897
|
+
```typescript
|
|
898
|
+
export const fetchPaginatedData = new Action({
|
|
899
|
+
input: z.object({
|
|
900
|
+
page: z.number().int().positive().default(1),
|
|
901
|
+
pageSize: z.number().int().positive().max(100).default(20),
|
|
902
|
+
}),
|
|
903
|
+
|
|
904
|
+
async handler({ input }) {
|
|
905
|
+
const offset = (input.page - 1) * input.pageSize;
|
|
906
|
+
|
|
907
|
+
const { rows } = await MyTable.findRows({
|
|
908
|
+
limit: input.pageSize,
|
|
909
|
+
offset,
|
|
910
|
+
orderBy: "createdAt",
|
|
911
|
+
orderDirection: "desc",
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
// Get total row count
|
|
915
|
+
const { rows: totalRows } = await MyTable.getTable();
|
|
916
|
+
|
|
917
|
+
return {
|
|
918
|
+
rows,
|
|
919
|
+
pagination: {
|
|
920
|
+
page: input.page,
|
|
921
|
+
pageSize: input.pageSize,
|
|
922
|
+
total: totalRows,
|
|
923
|
+
totalPages: Math.ceil(totalRows / input.pageSize),
|
|
924
|
+
},
|
|
925
|
+
};
|
|
926
|
+
},
|
|
927
|
+
});
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
### 4. Optimize Searchable Content
|
|
931
|
+
|
|
932
|
+
```typescript
|
|
933
|
+
export const DocumentsTable = new Table({
|
|
934
|
+
columns: {
|
|
935
|
+
// Store both original and optimized search content
|
|
936
|
+
content: z.string(), // Original content
|
|
937
|
+
|
|
938
|
+
searchContent: {
|
|
939
|
+
schema: z.string(),
|
|
940
|
+
searchable: true, // Optimized for search
|
|
941
|
+
},
|
|
942
|
+
},
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
// When creating rows
|
|
946
|
+
await DocumentsTable.createRows({
|
|
947
|
+
rows: [
|
|
948
|
+
{
|
|
949
|
+
content: originalContent,
|
|
950
|
+
searchContent: stripHtml(originalContent).toLowerCase(), // Optimized
|
|
951
|
+
},
|
|
952
|
+
],
|
|
953
|
+
});
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
## Common Patterns
|
|
957
|
+
|
|
958
|
+
### Audit Log Pattern
|
|
959
|
+
|
|
960
|
+
```typescript
|
|
961
|
+
export const AuditLogTable = new Table({
|
|
962
|
+
name: "AuditLogTable",
|
|
963
|
+
columns: {
|
|
964
|
+
userId: z.string(),
|
|
965
|
+
action: z.string(),
|
|
966
|
+
entityType: z.string().optional(),
|
|
967
|
+
entityId: z.string().optional(),
|
|
968
|
+
changes: z.object({}).passthrough().optional(),
|
|
969
|
+
metadata: z.object({}).passthrough(),
|
|
970
|
+
timestamp: z.date(),
|
|
971
|
+
},
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
// Helper function
|
|
975
|
+
export async function logAction(
|
|
976
|
+
userId: string,
|
|
977
|
+
action: string,
|
|
978
|
+
details: Record<string, any>,
|
|
979
|
+
) {
|
|
980
|
+
await AuditLogTable.createRows({
|
|
981
|
+
rows: [
|
|
982
|
+
{
|
|
983
|
+
userId,
|
|
984
|
+
action,
|
|
985
|
+
...details,
|
|
986
|
+
timestamp: new Date(),
|
|
987
|
+
},
|
|
988
|
+
],
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
### Cache Pattern
|
|
994
|
+
|
|
995
|
+
```typescript
|
|
996
|
+
export const CacheTable = new Table({
|
|
997
|
+
name: "CacheTable",
|
|
998
|
+
columns: {
|
|
999
|
+
key: z.string(),
|
|
1000
|
+
value: z.unknown(),
|
|
1001
|
+
expiresAt: z.date(),
|
|
1002
|
+
createdAt: z.date(),
|
|
1003
|
+
},
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
// Cache helper
|
|
1007
|
+
export async function getCached<T>(
|
|
1008
|
+
key: string,
|
|
1009
|
+
fetcher: () => Promise<T>,
|
|
1010
|
+
ttlMinutes: number = 60,
|
|
1011
|
+
): Promise<T> {
|
|
1012
|
+
// Check cache
|
|
1013
|
+
const { rows } = await CacheTable.findRows({
|
|
1014
|
+
filter: { key },
|
|
1015
|
+
limit: 1,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
const cached = rows[0];
|
|
1019
|
+
if (cached && cached.expiresAt > new Date()) {
|
|
1020
|
+
return cached.value as T;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// Fetch new value
|
|
1024
|
+
const value = await fetcher();
|
|
1025
|
+
|
|
1026
|
+
// Store in cache
|
|
1027
|
+
await CacheTable.upsertRows({
|
|
1028
|
+
rows: [
|
|
1029
|
+
{
|
|
1030
|
+
key,
|
|
1031
|
+
value,
|
|
1032
|
+
expiresAt: new Date(Date.now() + ttlMinutes * 60 * 1000),
|
|
1033
|
+
createdAt: new Date(),
|
|
1034
|
+
},
|
|
1035
|
+
],
|
|
1036
|
+
keyColumn: "key",
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
return value;
|
|
1040
|
+
}
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
## Syncing Tables
|
|
1044
|
+
|
|
1045
|
+
As of ADK 1.9+, tables are automatically synced using the CLI during development and deployment.
|
|
1046
|
+
|
|
1047
|
+
### Automatic Sync
|
|
1048
|
+
|
|
1049
|
+
Table sync runs automatically during:
|
|
1050
|
+
|
|
1051
|
+
- `adk dev` - Syncs tables on startup and detects schema changes
|
|
1052
|
+
- `adk deploy` - Syncs tables before deployment
|
|
1053
|
+
|
|
1054
|
+
### What Gets Synced
|
|
1055
|
+
|
|
1056
|
+
The table sync process:
|
|
1057
|
+
|
|
1058
|
+
1. **Detects all tables** in your `src/tables/` directory
|
|
1059
|
+
2. **Compares schemas** against the remote bot's tables
|
|
1060
|
+
3. **Creates new tables** that don't exist remotely
|
|
1061
|
+
4. **Updates table schemas** when columns are added or modified
|
|
1062
|
+
5. **Warns about breaking changes** (like column removal)
|
|
1063
|
+
|
|
1064
|
+
### Schema Migration
|
|
1065
|
+
|
|
1066
|
+
When table schemas change:
|
|
1067
|
+
|
|
1068
|
+
```typescript
|
|
1069
|
+
// Original table
|
|
1070
|
+
export const UsersTable = new Table({
|
|
1071
|
+
name: "UsersTable",
|
|
1072
|
+
columns: {
|
|
1073
|
+
name: z.string(),
|
|
1074
|
+
email: z.string(),
|
|
1075
|
+
},
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
// After adding a column - sync handles this automatically
|
|
1079
|
+
export const UsersTable = new Table({
|
|
1080
|
+
name: "UsersTable",
|
|
1081
|
+
columns: {
|
|
1082
|
+
name: z.string(),
|
|
1083
|
+
email: z.string(),
|
|
1084
|
+
role: z.string().default("user"), // New column with default
|
|
1085
|
+
},
|
|
1086
|
+
});
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
**Important:** Adding columns with defaults is safe. Removing columns or changing types may require manual migration.
|
|
1090
|
+
|
|
1091
|
+
### Manual Table Operations
|
|
1092
|
+
|
|
1093
|
+
For advanced scenarios, you can use the client directly:
|
|
1094
|
+
|
|
1095
|
+
```typescript
|
|
1096
|
+
import { Workflow } from "@botpress/runtime";
|
|
1097
|
+
|
|
1098
|
+
export const MigrateTablesWorkflow = new Workflow({
|
|
1099
|
+
name: "migrateTables",
|
|
1100
|
+
|
|
1101
|
+
async handler({ client }) {
|
|
1102
|
+
// Delete and recreate a table (use with caution!)
|
|
1103
|
+
await client._inner.deleteTable({ table: "MyTable" });
|
|
1104
|
+
|
|
1105
|
+
// Note: The table will be recreated on next sync
|
|
1106
|
+
},
|
|
1107
|
+
});
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
## Troubleshooting
|
|
1111
|
+
|
|
1112
|
+
### Common Issues
|
|
1113
|
+
|
|
1114
|
+
1. **Table creation fails**
|
|
1115
|
+
- ❌ **Did you define `id` in columns?** Remove it - `id` is automatic and reserved!
|
|
1116
|
+
- ❌ **Does your table name end with "Table"?** Example: `UsersTable`, not `Users`
|
|
1117
|
+
- Ensure file is in `src/tables/` directory
|
|
1118
|
+
- Check that table is exported with `export const`
|
|
1119
|
+
- Verify schema is valid
|
|
1120
|
+
|
|
1121
|
+
2. **Search not working**
|
|
1122
|
+
- Confirm column has `searchable: true`
|
|
1123
|
+
- Use object notation for searchable columns: `{ schema: z.string(), searchable: true }`
|
|
1124
|
+
- Check that content is text-based
|
|
1125
|
+
|
|
1126
|
+
3. **Type errors**
|
|
1127
|
+
- Ensure data matches schema exactly
|
|
1128
|
+
- Use `.optional()` for nullable fields
|
|
1129
|
+
- Check date formats
|
|
1130
|
+
- Remember: don't define `id` in schema - it's auto-assigned as a number by the server
|
|
1131
|
+
|
|
1132
|
+
4. **Performance issues**
|
|
1133
|
+
- Add indexes on frequently queried columns
|
|
1134
|
+
- Limit result sets with `limit`
|
|
1135
|
+
- Use pagination for large datasets
|
|
1136
|
+
- Consider caching for expensive queries
|