@botpress/adk-cli 1.19.0-beta.4 → 1.19.0-beta.6

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.
Files changed (87) hide show
  1. package/{dist/index-a3ZL0_Jr-q145h35p.js → assets/ui-dist/assets/index-BC_iUF2d.js} +2 -2
  2. package/assets/ui-dist/assets/index-BxpvDT_K.css +1 -0
  3. package/{dist/index-BESZeTO5-2rpmcefa.js → assets/ui-dist/assets/index-C7gAokRv.js} +2 -2
  4. package/assets/ui-dist/assets/{index-B3b7PPgV.js → index-exyr_Djl.js} +424 -424
  5. package/assets/ui-dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  6. package/assets/ui-dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  7. package/assets/ui-dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  8. package/assets/ui-dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  9. package/assets/ui-dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  10. package/assets/ui-dist/index.html +2 -2
  11. package/dist/{README-wjb7z7f0.md → README-824wa4jj.md} +1 -1
  12. package/dist/SKILL-75ym6tcj.md +257 -0
  13. package/dist/SKILL-chnwzmt1.md +548 -0
  14. package/dist/SKILL-d4bs53y3.md +244 -0
  15. package/dist/SKILL-h4b2f1n9.md +114 -0
  16. package/dist/SKILL-hbw5nt95.md +111 -0
  17. package/dist/actions-h7hxxvxr.md +823 -0
  18. package/dist/adk-build-emhwd0tg.md +39 -0
  19. package/dist/adk-debug-yrrdtpmb.md +23 -0
  20. package/dist/adk-dev-console-7z9rtjpy.md +17 -0
  21. package/dist/adk-document-ras6zzzh.md +68 -0
  22. package/dist/adk-eval-vx76dwq0.md +17 -0
  23. package/dist/adk-explain-fcsaw852.md +22 -0
  24. package/dist/adk-init-9kmxmbbe.md +30 -0
  25. package/dist/adk-ship-dybzn884.md +29 -0
  26. package/dist/adk-test-xq67savs.md +27 -0
  27. package/dist/adk-validate-1p4skv4m.md +31 -0
  28. package/dist/advanced-patterns-z86m9h1a.md +669 -0
  29. package/dist/agent-config-1aqvkrgf.md +450 -0
  30. package/dist/agent-steps-zj4c1n9t.md +115 -0
  31. package/dist/assets-q7jt8r2q.md +237 -0
  32. package/dist/autonomous-execution-fymc12e5.md +747 -0
  33. package/dist/cli-c31e06yr.md +1334 -0
  34. package/dist/cli.js +4241 -6449
  35. package/dist/commands.json +25 -4
  36. package/dist/common-failures-ht29w2db.md +231 -0
  37. package/dist/component-registry-bbpw0see.md +43 -0
  38. package/dist/context-api-760cvh0h.md +461 -0
  39. package/dist/conversation-analysis-8ka4nrm4.md +420 -0
  40. package/dist/conversation-lifecycle-4seqqjc1.md +311 -0
  41. package/dist/conversations-gwfvxaee.md +1111 -0
  42. package/dist/custom-components-y5wdgpey.md +167 -0
  43. package/dist/debug-workflow-vz84q80v.md +239 -0
  44. package/dist/desk-k54ktvsd.md +852 -0
  45. package/dist/doc-standards-ck6ax2jd.md +124 -0
  46. package/dist/eval-format-7n9brja4.md +354 -0
  47. package/dist/evals-y1zcr1mc.md +11 -0
  48. package/dist/explain-config-1yaq3zzd.md +424 -0
  49. package/dist/files-dzt0wxn3.md +918 -0
  50. package/dist/{index-3t354nbw.html → index-0qts7wfk.html} +2 -2
  51. package/{assets/ui-dist/assets/index-a3ZL0_Jr.js → dist/index-BC_iUF2d-3h9mqazs.js} +2 -2
  52. package/dist/index-BxpvDT_K-62864175.css +1 -0
  53. package/{assets/ui-dist/assets/index-BESZeTO5.js → dist/index-C7gAokRv-zr7dzkba.js} +2 -2
  54. package/dist/{index-B3b7PPgV-zetkb01w.js → index-exyr_Djl-ksys08wc.js} +424 -424
  55. package/dist/integration-actions-xang7tma.md +659 -0
  56. package/dist/integrations-k5ss07f4.md +266 -0
  57. package/dist/interfaces-6t0bpxs4.md +97 -0
  58. package/dist/jetbrains-mono-cyrillic-wght-normal-D73BlboJ-6vpq1jfh.woff2 +0 -0
  59. package/dist/jetbrains-mono-greek-wght-normal-Bw9x6K1M-jv5dqe09.woff2 +0 -0
  60. package/dist/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a-h2ysfc3f.woff2 +0 -0
  61. package/dist/jetbrains-mono-latin-wght-normal-B9CIFXIH-hj2e42wt.woff2 +0 -0
  62. package/dist/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq-wttk1pwp.woff2 +0 -0
  63. package/dist/knowledge-bases-20rg8p5c.md +456 -0
  64. package/dist/llm-debugging-76ng8rzm.md +209 -0
  65. package/dist/mcp-server-n1asyzhv.md +112 -0
  66. package/dist/messages-gkkg0b54.md +769 -0
  67. package/dist/model-configuration-f273kgd0.md +201 -0
  68. package/dist/multi-agent-dashboard-1mdfjdts.md +70 -0
  69. package/dist/pages-xn18xn73.md +273 -0
  70. package/dist/patterns-mistakes-5vb99dhf.md +469 -0
  71. package/dist/plugins-ttnx8f49.md +280 -0
  72. package/dist/tables-x45t36dd.md +1136 -0
  73. package/dist/tags-46td3qrg.md +516 -0
  74. package/dist/test-patterns-hj47w4nc.md +355 -0
  75. package/dist/testing-workflow-2vkp932f.md +225 -0
  76. package/dist/tools-wk7bq24z.md +581 -0
  77. package/dist/trace-summarization-hdyx1c2e.md +313 -0
  78. package/dist/traces-and-logs-f2kjfh19.md +203 -0
  79. package/dist/triggers-4j2cc86x.md +719 -0
  80. package/dist/ui-server-entry.js +1839 -791
  81. package/dist/workflow-steps-mrdrf1c5.md +851 -0
  82. package/dist/workflows-gadx3s69.md +1408 -0
  83. package/dist/zai-agent-reference-e6644aad.md +454 -0
  84. package/dist/zai-complete-guide-mnna5d6f.md +1408 -0
  85. package/package.json +4 -4
  86. package/assets/ui-dist/assets/index-DIFGtVmI.css +0 -1
  87. package/dist/index-DIFGtVmI-d4dhjg97.css +0 -1
@@ -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