lua-cli 3.42.0-beta.1 → 3.42.0-beta.3
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/api-exports.d.ts +2458 -112
- package/dist/api-exports.js +18145 -11424
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +9696 -990
- package/dist/index.js.map +1 -1
- package/dist/voice/test/index.d.ts +54 -54
- package/dist/workflow-builder.d.ts +16 -2
- package/dist/workflow-builder.js +5788 -317
- package/dist/workflow-builder.js.map +1 -1
- package/docs/API_REFERENCE.md +7 -1
- package/docs/api/AI.md +3 -1
- package/docs/api/Agents.md +275 -12
- package/docs/api/Data.md +192 -61
- package/docs/api/Jobs.md +2 -0
- package/docs/api/LuaWebhook.md +168 -87
- package/docs/api/LuaWorkflow.md +15 -15
- package/docs/api/Orders.md +7 -5
- package/docs/api/Products.md +1 -1
- package/docs/api/Progress.md +57 -0
- package/docs/api/Threads.md +122 -0
- package/docs/api/User.md +2 -2
- package/docs/api/Workflows.md +22 -1
- package/docs/workflows/approvals.md +4 -0
- package/docs/workflows/coding-harness.md +27 -1
- package/docs/workflows/workspaces-and-long-steps.md +3 -1
- package/package.json +3 -3
- package/scripts/generate-catalog.lib.ts +813 -0
- package/scripts/generate-catalog.ts +94 -0
- package/template/package.json +1 -1
- package/template-web-app/web/src/lua/client.ts +11 -1
package/docs/api/Data.md
CHANGED
|
@@ -11,6 +11,7 @@ import { Data } from 'lua-cli';
|
|
|
11
11
|
## Overview
|
|
12
12
|
|
|
13
13
|
The Data API is perfect for:
|
|
14
|
+
|
|
14
15
|
- Custom data collections (movies, recipes, documents)
|
|
15
16
|
- Vector/semantic search
|
|
16
17
|
- User-specific data storage
|
|
@@ -24,6 +25,7 @@ The Data API is perfect for:
|
|
|
24
25
|
Retrieves entries from a collection with optional filtering and pagination.
|
|
25
26
|
|
|
26
27
|
**Parameters:**
|
|
28
|
+
|
|
27
29
|
- `collectionName` (string) - Name of the collection
|
|
28
30
|
- `filter` (object, optional) - MongoDB-style filter criteria
|
|
29
31
|
- `page` (number, optional) - Page number (default: 1)
|
|
@@ -48,6 +50,7 @@ Retrieves entries from a collection with optional filtering and pagination.
|
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
**Example:**
|
|
53
|
+
|
|
51
54
|
```typescript
|
|
52
55
|
// Get all (first page)
|
|
53
56
|
const result = await Data.get('movies');
|
|
@@ -69,6 +72,7 @@ const sciFi = await Data.get('movies', { genre: 'Sci-Fi' });
|
|
|
69
72
|
Performs semantic search on a collection using AI embeddings.
|
|
70
73
|
|
|
71
74
|
**Parameters:**
|
|
75
|
+
|
|
72
76
|
- `collectionName` (string) - Collection to search
|
|
73
77
|
- `query` (string) - Search query (uses AI embeddings)
|
|
74
78
|
- `limit` (number, optional) - Max results (default: 10)
|
|
@@ -79,6 +83,7 @@ Performs semantic search on a collection using AI embeddings.
|
|
|
79
83
|
Each result includes a `score` property indicating similarity (0-1).
|
|
80
84
|
|
|
81
85
|
**Example:**
|
|
86
|
+
|
|
82
87
|
```typescript
|
|
83
88
|
const results = await Data.search('movies', 'sci-fi action thriller', 5, 0.7);
|
|
84
89
|
|
|
@@ -93,6 +98,7 @@ for (const movie of results) {
|
|
|
93
98
|
Creates a new entry in a collection.
|
|
94
99
|
|
|
95
100
|
**Parameters:**
|
|
101
|
+
|
|
96
102
|
- `collectionName` (string) - Collection name
|
|
97
103
|
- `data` (object) - Data to store
|
|
98
104
|
- `searchText` (string, optional) - Text to use for vector search indexing
|
|
@@ -100,13 +106,18 @@ Creates a new entry in a collection.
|
|
|
100
106
|
**Returns:** `Promise<DataEntryInstance>`
|
|
101
107
|
|
|
102
108
|
**Example:**
|
|
109
|
+
|
|
103
110
|
```typescript
|
|
104
|
-
const movie = await Data.create(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
const movie = await Data.create(
|
|
112
|
+
'movies',
|
|
113
|
+
{
|
|
114
|
+
title: 'Inception',
|
|
115
|
+
year: 2010,
|
|
116
|
+
director: 'Christopher Nolan',
|
|
117
|
+
genre: ['Sci-Fi', 'Thriller'],
|
|
118
|
+
},
|
|
119
|
+
'Inception 2010 sci-fi thriller Christopher Nolan dream heist'
|
|
120
|
+
);
|
|
110
121
|
|
|
111
122
|
console.log('Created:', movie.id);
|
|
112
123
|
```
|
|
@@ -116,6 +127,7 @@ console.log('Created:', movie.id);
|
|
|
116
127
|
Updates an existing entry.
|
|
117
128
|
|
|
118
129
|
**Parameters:**
|
|
130
|
+
|
|
119
131
|
- `collectionName` (string) - Collection name
|
|
120
132
|
- `entryId` (string) - Entry ID to update
|
|
121
133
|
- `data` (object) - Updated data
|
|
@@ -124,30 +136,140 @@ Updates an existing entry.
|
|
|
124
136
|
**Returns:** `Promise<UpdateCustomDataResponse>`
|
|
125
137
|
|
|
126
138
|
**Example:**
|
|
139
|
+
|
|
127
140
|
```typescript
|
|
128
|
-
const result = await Data.update(
|
|
129
|
-
'movies',
|
|
130
|
-
'entry_123',
|
|
131
|
-
{ rating: 9.5, views: 1000 },
|
|
132
|
-
'Inception highly rated popular'
|
|
133
|
-
);
|
|
141
|
+
const result = await Data.update('movies', 'entry_123', { rating: 9.5, views: 1000 }, 'Inception highly rated popular');
|
|
134
142
|
|
|
135
143
|
if (result.status === 'success') {
|
|
136
144
|
console.log(result.message); // "Custom data entry updated"
|
|
137
145
|
}
|
|
138
146
|
```
|
|
139
147
|
|
|
148
|
+
### `Data.patch(collectionName, entryId, mutation, options?)`
|
|
149
|
+
|
|
150
|
+
Atomically sets and removes top-level fields in **one** write. Unlike `Data.update` (which reads the entry,
|
|
151
|
+
merges your fields and writes the whole `data` back, so concurrent updates can overwrite each other), `patch`
|
|
152
|
+
only touches the fields you name.
|
|
153
|
+
|
|
154
|
+
**Parameters:**
|
|
155
|
+
|
|
156
|
+
- `collectionName` (string) - Collection name
|
|
157
|
+
- `entryId` (string) - Entry ID
|
|
158
|
+
- `mutation` (object) - `{ set?: Record<string, any>; unset?: string[]; searchText?: string | null }`
|
|
159
|
+
(`searchText: null` clears the entry's search text and vector)
|
|
160
|
+
- `options` (object, optional) - `{ ifVersion?: number }` for compare-and-swap (below)
|
|
161
|
+
|
|
162
|
+
**Returns:** `Promise<UpdateCustomDataResponse>` - `{ status, message, version }`, where `version` is the entry's
|
|
163
|
+
new version
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
await Data.patch('tickets', ticketId, { set: { status: 'closed' }, unset: ['assignee'] });
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Compare-and-swap with `ifVersion`
|
|
170
|
+
|
|
171
|
+
Every entry carries a `version` number that **every** write (`create`, `update`, `patch`, `increment`)
|
|
172
|
+
bumps. Read it as `entry.version` on the entries `Data.get` returns, or `entry._version` on the
|
|
173
|
+
`DataEntryInstance` that `Data.getEntry` / `Data.create` return (underscored so it never hides a `version`
|
|
174
|
+
field of your own data). Entries not written since versioning was introduced report `0`.
|
|
175
|
+
|
|
176
|
+
Pass the version you read as `ifVersion` and the patch applies **only if nobody wrote the entry in between**.
|
|
177
|
+
Otherwise nothing is written and the call rejects with an error whose `code` is `'data_version_conflict'`
|
|
178
|
+
(`status` 409); `error.currentVersion` carries the version to re-read from and `error.expectedVersion` the
|
|
179
|
+
stale one you sent. This is the same in `lua test`, in deployed agents and in voice agents.
|
|
180
|
+
|
|
181
|
+
`ifVersion` must be a non-negative integer. If the `ifVersion` key is present with any other value,
|
|
182
|
+
including `undefined`, the call throws a `TypeError` and sends nothing. It is never quietly dropped, which
|
|
183
|
+
would turn the compare-and-swap into an unconditional write. Leave the key out entirely for an unconditional
|
|
184
|
+
patch, so build options with a conditional spread: `{ ...(v !== undefined ? { ifVersion: v } : {}) }`.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Claim a seat exactly once, even if two conversations race for it.
|
|
188
|
+
async function claimSeat(seatId: string, userId: string): Promise<boolean> {
|
|
189
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
190
|
+
const seat = await Data.getEntry('seats', seatId);
|
|
191
|
+
if (seat.data.heldBy) return false;
|
|
192
|
+
try {
|
|
193
|
+
await Data.patch('seats', seatId, { set: { heldBy: userId } }, { ifVersion: seat._version });
|
|
194
|
+
return true;
|
|
195
|
+
} catch (error: any) {
|
|
196
|
+
if (error?.code !== 'data_version_conflict') throw error;
|
|
197
|
+
// Someone else wrote the seat first: loop and re-read.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Compare `error.code` against the string `'data_version_conflict'` rather than using `instanceof`.
|
|
205
|
+
|
|
206
|
+
**Notes:**
|
|
207
|
+
|
|
208
|
+
- A patch with `ifVersion` is sent once and is not retried, because a retry of an attempt that landed would
|
|
209
|
+
find its own version bump and report a false conflict. So a network error leaves the outcome unknown,
|
|
210
|
+
just as with `increment`. After a network error, re-read the entry: if your change is there, your write
|
|
211
|
+
landed.
|
|
212
|
+
- Versions start at `1` when an entry is created, including by an `increment` upsert. If an entry is deleted
|
|
213
|
+
and an entry with the **same ID** is created again, its version restarts, and an `ifVersion` read from the
|
|
214
|
+
deleted entry can match the new one. Do not reuse the IDs of deleted entries for state you guard with
|
|
215
|
+
`ifVersion`.
|
|
216
|
+
|
|
217
|
+
### `Data.increment(collectionName, entryId, field, by?, options?)`
|
|
218
|
+
|
|
219
|
+
Atomically adds `by` to a top-level numeric field and resolves the field's **new value**. Simultaneous
|
|
220
|
+
increments never lose a count (each is a single database operation), which a `get` + `update` cannot
|
|
221
|
+
guarantee.
|
|
222
|
+
|
|
223
|
+
**Parameters:**
|
|
224
|
+
|
|
225
|
+
- `collectionName` (string) - Collection name
|
|
226
|
+
- `entryId` (string) - Entry ID (the ID to create under when upserting)
|
|
227
|
+
- `field` (string) - Top-level field name (no dots, no leading `$`)
|
|
228
|
+
- `by` (number, optional) - Amount to add; default `1`. Negative and fractional amounts are allowed
|
|
229
|
+
- `options` (object, optional) - `{ upsert?: boolean }`. With `upsert: true` a missing entry is created as
|
|
230
|
+
`{ [field]: by }` instead of failing
|
|
231
|
+
|
|
232
|
+
**Returns:** `Promise<number>` - the field's value after the increment
|
|
233
|
+
|
|
234
|
+
**Errors:** 404 when the entry does not exist and `upsert` is not set; 400 when the field (or the entry's
|
|
235
|
+
`data`) is not numeric. A missing field starts at `0`.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const views = await Data.increment('stats', 'homepage', 'views'); // 1, 2, 3, ...
|
|
239
|
+
await Data.increment('inventory', sku, 'available', -1); // decrement
|
|
240
|
+
|
|
241
|
+
// A daily counter: one long-lived entry, one FIELD per day. The entry is created once (see the note
|
|
242
|
+
// below); each new day's field simply starts at 0, so no conversation ever has to create anything.
|
|
243
|
+
await Data.increment('stats', 'calls', `day-${today}`); // `today` like '2026-10-01'
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Notes:**
|
|
247
|
+
|
|
248
|
+
- Upsert race: `upsert` is **not** safe for an entry that many conversations may create at the same moment.
|
|
249
|
+
Two first-time upserts of the same new ID that run concurrently can each create an entry. Later
|
|
250
|
+
increments then land on only one of them, and the count in the other is lost. So do not upsert a fresh
|
|
251
|
+
ID per day or per event under load (for example
|
|
252
|
+
``Data.increment('daily', `calls-${today}`, 'count', 1, { upsert: true })``). Create the entry once
|
|
253
|
+
from a path that does not run concurrently, such as a setup job or
|
|
254
|
+
`Data.increment('stats', 'calls', 'total', 0, { upsert: true })` before go-live. After that,
|
|
255
|
+
increment it, or add new fields to it, as above.
|
|
256
|
+
- The SDK sends the request once and does not retry it: an increment is not idempotent, so after a network
|
|
257
|
+
error the count may or may not have been applied. Do not blindly retry an increment that failed with a
|
|
258
|
+
network error if an exact count matters.
|
|
259
|
+
|
|
140
260
|
### `Data.delete(collectionName, entryId)`
|
|
141
261
|
|
|
142
262
|
Deletes an entry from a collection.
|
|
143
263
|
|
|
144
264
|
**Parameters:**
|
|
265
|
+
|
|
145
266
|
- `collectionName` (string) - Collection name
|
|
146
267
|
- `entryId` (string) - Entry ID to delete
|
|
147
268
|
|
|
148
269
|
**Returns:** `Promise<DeleteCustomDataResponse>`
|
|
149
270
|
|
|
150
271
|
**Example:**
|
|
272
|
+
|
|
151
273
|
```typescript
|
|
152
274
|
const result = await Data.delete('movies', 'entry_123');
|
|
153
275
|
|
|
@@ -165,13 +287,14 @@ Data entries have convenient methods:
|
|
|
165
287
|
Updates this entry.
|
|
166
288
|
|
|
167
289
|
**Example:**
|
|
290
|
+
|
|
168
291
|
```typescript
|
|
169
292
|
const movies = await Data.get('movies');
|
|
170
293
|
const movie = movies[0];
|
|
171
294
|
|
|
172
295
|
await movie.update({
|
|
173
296
|
rating: 9.0,
|
|
174
|
-
views: movie.views + 1
|
|
297
|
+
views: movie.views + 1,
|
|
175
298
|
});
|
|
176
299
|
```
|
|
177
300
|
|
|
@@ -180,9 +303,10 @@ await movie.update({
|
|
|
180
303
|
Deletes this entry.
|
|
181
304
|
|
|
182
305
|
**Example:**
|
|
306
|
+
|
|
183
307
|
```typescript
|
|
184
308
|
const movies = await Data.get('movies');
|
|
185
|
-
const oldMovie = movies.find(m => m.year < 1950);
|
|
309
|
+
const oldMovie = movies.find((m) => m.year < 1950);
|
|
186
310
|
|
|
187
311
|
if (oldMovie) {
|
|
188
312
|
await oldMovie.delete();
|
|
@@ -194,6 +318,7 @@ if (oldMovie) {
|
|
|
194
318
|
Saves modifications to this entry.
|
|
195
319
|
|
|
196
320
|
**Example:**
|
|
321
|
+
|
|
197
322
|
```typescript
|
|
198
323
|
const movies = await Data.get('movies');
|
|
199
324
|
const movie = movies[0];
|
|
@@ -236,7 +361,7 @@ import { z } from 'zod';
|
|
|
236
361
|
export default class MovieDatabaseTool implements LuaTool {
|
|
237
362
|
name = 'manage_movies';
|
|
238
363
|
description = 'Manage a movie database';
|
|
239
|
-
|
|
364
|
+
|
|
240
365
|
inputSchema = z.object({
|
|
241
366
|
action: z.enum(['add', 'search', 'update', 'delete']),
|
|
242
367
|
title: z.string().optional(),
|
|
@@ -245,78 +370,82 @@ export default class MovieDatabaseTool implements LuaTool {
|
|
|
245
370
|
genre: z.array(z.string()).optional(),
|
|
246
371
|
rating: z.number().min(0).max(10).optional(),
|
|
247
372
|
movieId: z.string().optional(),
|
|
248
|
-
query: z.string().optional()
|
|
373
|
+
query: z.string().optional(),
|
|
249
374
|
});
|
|
250
|
-
|
|
375
|
+
|
|
251
376
|
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
252
377
|
switch (input.action) {
|
|
253
378
|
case 'add':
|
|
254
379
|
if (!input.title || !input.year) {
|
|
255
380
|
return { success: false, error: 'Title and year required' };
|
|
256
381
|
}
|
|
257
|
-
|
|
382
|
+
|
|
258
383
|
const searchText = `${input.title} ${input.year} ${input.director || ''} ${input.genre?.join(' ') || ''}`;
|
|
259
|
-
|
|
260
|
-
const movie = await Data.create(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
384
|
+
|
|
385
|
+
const movie = await Data.create(
|
|
386
|
+
'movies',
|
|
387
|
+
{
|
|
388
|
+
title: input.title,
|
|
389
|
+
year: input.year,
|
|
390
|
+
director: input.director,
|
|
391
|
+
genre: input.genre || [],
|
|
392
|
+
rating: input.rating || 0,
|
|
393
|
+
createdAt: new Date().toISOString(),
|
|
394
|
+
},
|
|
395
|
+
searchText
|
|
396
|
+
);
|
|
397
|
+
|
|
269
398
|
return {
|
|
270
399
|
success: true,
|
|
271
400
|
message: `Added "${input.title}"`,
|
|
272
|
-
movieId: movie.id
|
|
401
|
+
movieId: movie.id,
|
|
273
402
|
};
|
|
274
|
-
|
|
403
|
+
|
|
275
404
|
case 'search':
|
|
276
405
|
if (!input.query) {
|
|
277
406
|
return { success: false, error: 'Query required' };
|
|
278
407
|
}
|
|
279
|
-
|
|
408
|
+
|
|
280
409
|
const results = await Data.search('movies', input.query, 10);
|
|
281
|
-
|
|
410
|
+
|
|
282
411
|
return {
|
|
283
412
|
success: true,
|
|
284
413
|
count: results.data.length,
|
|
285
|
-
movies: results.data.map(r => ({
|
|
414
|
+
movies: results.data.map((r) => ({
|
|
286
415
|
id: r.id,
|
|
287
416
|
title: r.data.title,
|
|
288
417
|
year: r.data.year,
|
|
289
418
|
director: r.data.director,
|
|
290
|
-
relevance: r.score
|
|
291
|
-
}))
|
|
419
|
+
relevance: r.score,
|
|
420
|
+
})),
|
|
292
421
|
};
|
|
293
|
-
|
|
422
|
+
|
|
294
423
|
case 'update':
|
|
295
424
|
if (!input.movieId) {
|
|
296
425
|
return { success: false, error: 'Movie ID required' };
|
|
297
426
|
}
|
|
298
|
-
|
|
427
|
+
|
|
299
428
|
const updates: any = {};
|
|
300
429
|
if (input.rating !== undefined) updates.rating = input.rating;
|
|
301
430
|
if (input.director) updates.director = input.director;
|
|
302
|
-
|
|
431
|
+
|
|
303
432
|
const updateResult = await Data.update('movies', input.movieId, updates);
|
|
304
|
-
|
|
433
|
+
|
|
305
434
|
return {
|
|
306
435
|
success: updateResult.status === 'success',
|
|
307
|
-
message: updateResult.message
|
|
436
|
+
message: updateResult.message,
|
|
308
437
|
};
|
|
309
|
-
|
|
438
|
+
|
|
310
439
|
case 'delete':
|
|
311
440
|
if (!input.movieId) {
|
|
312
441
|
return { success: false, error: 'Movie ID required' };
|
|
313
442
|
}
|
|
314
|
-
|
|
443
|
+
|
|
315
444
|
const deleteResult = await Data.delete('movies', input.movieId);
|
|
316
|
-
|
|
445
|
+
|
|
317
446
|
return {
|
|
318
447
|
success: deleteResult.status === 'success',
|
|
319
|
-
message: deleteResult.message
|
|
448
|
+
message: deleteResult.message,
|
|
320
449
|
};
|
|
321
450
|
}
|
|
322
451
|
}
|
|
@@ -332,33 +461,33 @@ import { z } from 'zod';
|
|
|
332
461
|
export default class KnowledgeBaseTool implements LuaTool {
|
|
333
462
|
name = 'search_knowledge_base';
|
|
334
463
|
description = 'Search company knowledge base for information';
|
|
335
|
-
|
|
464
|
+
|
|
336
465
|
inputSchema = z.object({
|
|
337
|
-
question: z.string().describe('User question')
|
|
466
|
+
question: z.string().describe('User question'),
|
|
338
467
|
});
|
|
339
|
-
|
|
468
|
+
|
|
340
469
|
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
341
470
|
// Semantic search finds relevant articles
|
|
342
471
|
const results = await Data.search('knowledge-base', input.question, 3);
|
|
343
|
-
|
|
472
|
+
|
|
344
473
|
if (results.data.length === 0) {
|
|
345
474
|
return {
|
|
346
475
|
success: true,
|
|
347
476
|
found: false,
|
|
348
|
-
message: 'No relevant articles found'
|
|
477
|
+
message: 'No relevant articles found',
|
|
349
478
|
};
|
|
350
479
|
}
|
|
351
|
-
|
|
480
|
+
|
|
352
481
|
// Return top results
|
|
353
482
|
return {
|
|
354
483
|
success: true,
|
|
355
484
|
found: true,
|
|
356
|
-
articles: results.data.map(r => ({
|
|
485
|
+
articles: results.data.map((r) => ({
|
|
357
486
|
title: r.data.title,
|
|
358
487
|
content: r.data.content,
|
|
359
488
|
relevance: Math.round(r.score * 100),
|
|
360
|
-
url: r.data.url
|
|
361
|
-
}))
|
|
489
|
+
url: r.data.url,
|
|
490
|
+
})),
|
|
362
491
|
};
|
|
363
492
|
}
|
|
364
493
|
}
|
|
@@ -373,27 +502,27 @@ import { z } from 'zod';
|
|
|
373
502
|
export default class RecipeTool implements LuaTool {
|
|
374
503
|
name = 'find_recipe';
|
|
375
504
|
description = 'Find recipes based on ingredients or cuisine';
|
|
376
|
-
|
|
505
|
+
|
|
377
506
|
inputSchema = z.object({
|
|
378
|
-
searchTerm: z.string().describe('Ingredients, cuisine, or dish name')
|
|
507
|
+
searchTerm: z.string().describe('Ingredients, cuisine, or dish name'),
|
|
379
508
|
});
|
|
380
|
-
|
|
509
|
+
|
|
381
510
|
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
382
511
|
// Search recipes
|
|
383
512
|
const results = await Data.search('recipes', input.searchTerm, 5);
|
|
384
|
-
|
|
513
|
+
|
|
385
514
|
return {
|
|
386
515
|
success: true,
|
|
387
516
|
count: results.data.length,
|
|
388
|
-
recipes: results.data.map(r => ({
|
|
517
|
+
recipes: results.data.map((r) => ({
|
|
389
518
|
name: r.data.name,
|
|
390
519
|
cuisine: r.data.cuisine,
|
|
391
520
|
ingredients: r.data.ingredients,
|
|
392
521
|
instructions: r.data.instructions,
|
|
393
522
|
prepTime: r.data.prepTime,
|
|
394
523
|
difficulty: r.data.difficulty,
|
|
395
|
-
matchScore: Math.round(r.score * 100)
|
|
396
|
-
}))
|
|
524
|
+
matchScore: Math.round(r.score * 100),
|
|
525
|
+
})),
|
|
397
526
|
};
|
|
398
527
|
}
|
|
399
528
|
}
|
|
@@ -413,6 +542,7 @@ await Data.search('movies', 'Star Wars type movies');
|
|
|
413
542
|
```
|
|
414
543
|
|
|
415
544
|
**Tips:**
|
|
545
|
+
|
|
416
546
|
- Use descriptive search text when creating entries
|
|
417
547
|
- Search queries can be natural language
|
|
418
548
|
- Results include similarity scores (0-1)
|
|
@@ -421,6 +551,7 @@ await Data.search('movies', 'Star Wars type movies');
|
|
|
421
551
|
## Best Practices
|
|
422
552
|
|
|
423
553
|
### ✅ DO
|
|
554
|
+
|
|
424
555
|
- Use descriptive collection names (`user-notes`, not `notes`)
|
|
425
556
|
- Provide searchText for better semantic search
|
|
426
557
|
- Include all relevant keywords in searchText
|
|
@@ -428,6 +559,7 @@ await Data.search('movies', 'Star Wars type movies');
|
|
|
428
559
|
- Use meaningful property names
|
|
429
560
|
|
|
430
561
|
### ❌ DON'T
|
|
562
|
+
|
|
431
563
|
- Don't store large binary data (use URLs instead)
|
|
432
564
|
- Don't use Data API for relational data (use Products/Orders)
|
|
433
565
|
- Don't skip searchText if you plan to search
|
|
@@ -443,4 +575,3 @@ await Data.search('movies', 'Star Wars type movies');
|
|
|
443
575
|
|
|
444
576
|
- [Custom Data Tool Example](../../template/src/skills/tools/CustomDataTool.ts)
|
|
445
577
|
- [Vector Search Guide](../guides/VectorSearch.md)
|
|
446
|
-
|
package/docs/api/Jobs.md
CHANGED