@mindstudio-ai/remy 0.1.156 → 0.1.157
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.
|
@@ -145,18 +145,26 @@ export async function listVendors(input: {
|
|
|
145
145
|
status?: string;
|
|
146
146
|
search?: string;
|
|
147
147
|
}) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
let q = Vendors.sortBy(v => v.name);
|
|
149
|
+
if (input.status) {
|
|
150
|
+
q = q.filter(
|
|
151
|
+
(v, $) => v.status === $.status,
|
|
152
|
+
{ status: input.status }, // bindings: lifts closure var so filter compiles to SQL
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (input.search) {
|
|
156
|
+
q = q.filter(
|
|
157
|
+
(v, $) => v.name.includes($.search),
|
|
158
|
+
{ search: input.search }, // bindings (LIKE): lifts closure var so filter compiles to SQL
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const vendors = await q;
|
|
156
162
|
return { vendors };
|
|
157
163
|
}
|
|
158
164
|
```
|
|
159
165
|
|
|
166
|
+
Building the query progressively (one `.filter` per optional input) is the canonical shape — each clause compiles to SQL independently and the bindings form keeps `input.*` references out of closures.
|
|
167
|
+
|
|
160
168
|
### Role-Gated Operation
|
|
161
169
|
|
|
162
170
|
```typescript
|
|
@@ -195,25 +195,89 @@ const cleared = await Vendors.clear(); // number (
|
|
|
195
195
|
|
|
196
196
|
### Filter Predicates
|
|
197
197
|
|
|
198
|
-
Predicates are arrow functions compiled to SQL WHERE clauses:
|
|
198
|
+
Predicates are arrow functions compiled to SQL WHERE clauses. Two forms — closure (literal-only) and bindings (when the predicate references an outer-scope value):
|
|
199
199
|
|
|
200
200
|
```typescript
|
|
201
|
-
|
|
202
|
-
Vendors.filter(v => v.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
Vendors.filter(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
201
|
+
// Closure form — works for literals only:
|
|
202
|
+
Vendors.filter(v => v.status === 'approved');
|
|
203
|
+
|
|
204
|
+
// Bindings form — required when the predicate references an outer-scope
|
|
205
|
+
// value (input.*, auth.*, foreign keys collected earlier, etc.). Without
|
|
206
|
+
// bindings, the predicate falls back to fetching every row and filtering
|
|
207
|
+
// in JS — a warning is logged when this happens.
|
|
208
|
+
Vendors.filter(
|
|
209
|
+
(v, $) => v.companyId === $.companyId,
|
|
210
|
+
{ companyId: input.companyId }, // bindings: lifts closure var so filter compiles to SQL
|
|
211
|
+
);
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
Both produce identical results. The bindings form is faster on tables of any size and dramatically faster on large tables.
|
|
215
|
+
|
|
216
|
+
#### Patterns
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// Equality / inequality
|
|
220
|
+
Companies.filter((c, $) => c.ownerId === $.ownerId, { ownerId: auth.userId }); // bindings: lifts closure var so filter compiles to SQL
|
|
221
|
+
|
|
222
|
+
// Comparison / range
|
|
223
|
+
Investments.filter((i, $) => i.amountInvested >= $.minAmount, { minAmount: 10000 }); // bindings: lifts closure var so filter compiles to SQL
|
|
224
|
+
|
|
225
|
+
// Mixed bindings + literal — freely combinable
|
|
226
|
+
Investments.filter(
|
|
227
|
+
(i, $) => i.companyId === $.companyId && i.status === 'active',
|
|
228
|
+
{ companyId: input.companyId }, // bindings: lifts closure var so filter compiles to SQL
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
// IN clause — array binding
|
|
232
|
+
ContactRelationships.filter(
|
|
233
|
+
(r, $) => $.contactIds.includes(r.contactId),
|
|
234
|
+
{ contactIds: ['a', 'b', 'c'] }, // bindings: lifts closure var so filter compiles to SQL
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// LIKE clause — string binding
|
|
238
|
+
SimpleRecords.filter(
|
|
239
|
+
(r, $) => r.slug.includes($.prefix),
|
|
240
|
+
{ prefix: 'lat-' }, // bindings: lifts closure var so filter compiles to SQL
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Nested keys
|
|
244
|
+
Orders.filter(
|
|
245
|
+
(o, $) => o.companyId === $.user.companyId,
|
|
246
|
+
{ user: { companyId: input.companyId } }, // bindings: lifts closure var so filter compiles to SQL
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// removeAll with bindings — single DELETE WHERE … instead of fetch-all-then-delete-by-id
|
|
250
|
+
SimpleRecords.removeAll(
|
|
251
|
+
(r, $) => r.slug.includes($.prefix),
|
|
252
|
+
{ prefix: 'lat-' }, // bindings: lifts closure var so removeAll compiles to SQL
|
|
253
|
+
);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### What compiles to SQL
|
|
257
|
+
|
|
258
|
+
- Literals: equality, comparisons, range, null checks, `&&`/`||`/`!`, `.includes()` for arrays and strings, nested JSON access (`v.address.city === 'New York'`).
|
|
259
|
+
- Outer-scope values when passed via bindings (same shapes as above).
|
|
260
|
+
|
|
261
|
+
#### What falls back to JS
|
|
262
|
+
|
|
263
|
+
- Closure references without a bindings argument: `o => o.x === input.x`.
|
|
264
|
+
- Two-param predicate without a bindings argument: `(o, $) => …` but no `{…}` second arg.
|
|
265
|
+
- Mixed predicate where some closure refs aren't lifted (e.g. `(o, $) => o.companyId === $.companyId && o.status === stale` where `stale` is from outer scope). Falls back for the *whole* predicate — lift everything outer-scope into bindings.
|
|
266
|
+
- Bindings keys missing or `undefined` (predicate references `$.foo` but bindings has no `foo`). The SDK does not silently substitute NULL.
|
|
267
|
+
- Bindings value is a non-scalar/non-array (e.g. `$.user` is `{...}`). Read scalar leaves only.
|
|
268
|
+
- `.startsWith()`, regex, computed expressions like `o.a + o.b > 100`.
|
|
269
|
+
|
|
270
|
+
A warning is logged whenever a predicate falls back to JS. Avoid these patterns on large tables.
|
|
271
|
+
|
|
272
|
+
#### When to use bindings — and when to skip
|
|
273
|
+
|
|
274
|
+
Use bindings whenever a predicate compares against `input.*`, `auth.*`, request params, foreign keys collected earlier, or any other function-scope value, and the table can grow beyond a few hundred rows. Always use them inside `db.batch(...)` where table size could grow over time.
|
|
275
|
+
|
|
276
|
+
Skip bindings when the predicate only references the row plus string/number literals — those already compile to SQL with no help. Also fine to skip on small fixed-size tables (under ~100 rows) where the JS fallback is fast enough not to matter.
|
|
277
|
+
|
|
278
|
+
#### Inline-comment requirement
|
|
215
279
|
|
|
216
|
-
|
|
280
|
+
When you write a bindings argument, include a brief inline comment on the bindings object explaining its purpose. Without the comment, downstream coding agents read the bindings object as boilerplate and "simplify" it back to closure form — silently regressing performance. The canonical phrasing is `// bindings: lifts closure var so filter compiles to SQL`. Exact wording isn't critical; what matters is naming the failure mode.
|
|
217
281
|
|
|
218
282
|
### Time Helpers
|
|
219
283
|
|
|
@@ -251,11 +315,15 @@ Write methods throw `MindStudioError` with specific codes:
|
|
|
251
315
|
`db.batch()` combines multiple operations into a single HTTP round-trip. Every `await` on a table operation is a network call, so batching is critical for performance. Use it whenever you have multiple reads, writes, or a mix of both. `upsert()` works in batches just like `push()` and `update()`:
|
|
252
316
|
|
|
253
317
|
```typescript
|
|
254
|
-
// Reads: fetch related data in one call instead of sequential awaits
|
|
318
|
+
// Reads: fetch related data in one call instead of sequential awaits.
|
|
319
|
+
// Note the bindings on PurchaseOrders — without it, that filter would
|
|
320
|
+
// fall back to JS because vendorId is a closure capture. Hoisting a
|
|
321
|
+
// shared `$` lets every filter in the batch reuse it.
|
|
322
|
+
const $ = { vendorId }; // bindings: lifts closure var so filters compile to SQL
|
|
255
323
|
const [vendors, orders, invoiceCount] = await db.batch(
|
|
256
|
-
Vendors.filter(v => v.status === 'approved'),
|
|
257
|
-
PurchaseOrders.filter(po => po.vendorId === vendorId),
|
|
258
|
-
Invoices.count(i => i.status === 'pending'),
|
|
324
|
+
Vendors.filter(v => v.status === 'approved'), // literal — no bindings needed
|
|
325
|
+
PurchaseOrders.filter((po, $) => po.vendorId === $.vendorId, $), // bindings
|
|
326
|
+
Invoices.count(i => i.status === 'pending'), // literal — no bindings needed
|
|
259
327
|
);
|
|
260
328
|
|
|
261
329
|
// Writes: batch multiple updates instead of awaiting each one in a loop
|
|
@@ -35,6 +35,8 @@ For multi-step tasks with branching logic (research, enrichment, content pipelin
|
|
|
35
35
|
|
|
36
36
|
For methods that take more than a few seconds, use `stream()` from `@mindstudio-ai/agent` to push real-time progress to the frontend. Pipe `onLog` from SDK actions through `stream()` so users see what's happening. The frontend calls the method with `stream: true` and gets updates via `onToken`. See the methods reference for the full pattern.
|
|
37
37
|
|
|
38
|
+
When writing `db` filter predicates that reference outer-scope values (`input.*`, `auth.*`, foreign keys collected earlier, etc.), use the bindings form so the filter compiles to SQL — see `tables.md` "Filter Predicates" for patterns and the inline-comment convention.
|
|
39
|
+
|
|
38
40
|
### Auth
|
|
39
41
|
- Not every app needs auth, and even for apps that do need auth, not every screen needs auth. Think intentionally about places where auth is required. Don't make auth be the first thing a user sees - that's jarring. Only show auth at intuitive and natural moments in the user's journey - be thoughtful about how to implement auth in the UI.
|
|
40
42
|
- Frontend interfaces are always untrusted. Always enforce auth in backend methods. Use frontend auth and role information as a hint to conditionally show/hide UI to make the experience pleasant and seamless for users depending on their state, but remember to always use backend methods for gating data that is conditional on auth.
|