@mindstudio-ai/agent 0.1.9 → 0.1.11

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/cli.js CHANGED
@@ -1419,6 +1419,19 @@ var init_auth = __esm({
1419
1419
  });
1420
1420
 
1421
1421
  // src/db/sql.ts
1422
+ function serializeParam(val) {
1423
+ if (val === null || val === void 0) return null;
1424
+ if (typeof val === "boolean") return val ? 1 : 0;
1425
+ if (typeof val === "number" || typeof val === "string") return val;
1426
+ return JSON.stringify(val);
1427
+ }
1428
+ function serializeColumnParam(val, columnName, columns) {
1429
+ const col = columns.find((c) => c.name === columnName);
1430
+ if (col?.type === "user" && typeof val === "string") {
1431
+ return `@@user@@${val}`;
1432
+ }
1433
+ return serializeParam(val);
1434
+ }
1422
1435
  function escapeValue(val) {
1423
1436
  if (val === null || val === void 0) return "NULL";
1424
1437
  if (typeof val === "boolean") return val ? "1" : "0";
@@ -1427,13 +1440,6 @@ function escapeValue(val) {
1427
1440
  const json = JSON.stringify(val);
1428
1441
  return `'${json.replace(/'/g, "''")}'`;
1429
1442
  }
1430
- function serializeValue(val, columnName, columns) {
1431
- const col = columns.find((c) => c.name === columnName);
1432
- if (col?.type === "user" && typeof val === "string") {
1433
- return escapeValue(`@@user@@${val}`);
1434
- }
1435
- return escapeValue(val);
1436
- }
1437
1443
  function deserializeRow(row, columns) {
1438
1444
  const result = {};
1439
1445
  for (const [key, value] of Object.entries(row)) {
@@ -1454,37 +1460,54 @@ function deserializeRow(row, columns) {
1454
1460
  }
1455
1461
  function buildSelect(table, options = {}) {
1456
1462
  let sql = `SELECT * FROM ${table}`;
1457
- if (options.where) sql += ` WHERE ${options.where}`;
1463
+ const params = [];
1464
+ if (options.where) {
1465
+ sql += ` WHERE ${options.where}`;
1466
+ if (options.whereParams) params.push(...options.whereParams);
1467
+ }
1458
1468
  if (options.orderBy) sql += ` ORDER BY ${options.orderBy}${options.desc ? " DESC" : " ASC"}`;
1459
1469
  if (options.limit != null) sql += ` LIMIT ${options.limit}`;
1460
1470
  if (options.offset != null) sql += ` OFFSET ${options.offset}`;
1461
- return sql;
1471
+ return { sql, params: params.length > 0 ? params : void 0 };
1462
1472
  }
1463
- function buildCount(table, where) {
1473
+ function buildCount(table, where, whereParams) {
1464
1474
  let sql = `SELECT COUNT(*) as count FROM ${table}`;
1465
1475
  if (where) sql += ` WHERE ${where}`;
1466
- return sql;
1476
+ return { sql, params: whereParams?.length ? whereParams : void 0 };
1467
1477
  }
1468
- function buildExists(table, where, negate) {
1478
+ function buildExists(table, where, whereParams, negate) {
1469
1479
  const inner = where ? `SELECT 1 FROM ${table} WHERE ${where}` : `SELECT 1 FROM ${table}`;
1470
1480
  const fn = negate ? "NOT EXISTS" : "EXISTS";
1471
- return `SELECT ${fn}(${inner}) as result`;
1481
+ return { sql: `SELECT ${fn}(${inner}) as result`, params: whereParams?.length ? whereParams : void 0 };
1472
1482
  }
1473
1483
  function buildInsert(table, data, columns) {
1474
1484
  const filtered = stripSystemColumns(data);
1475
1485
  const keys = Object.keys(filtered);
1476
- const vals = keys.map((k) => serializeValue(filtered[k], k, columns));
1477
- return `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${vals.join(", ")})`;
1486
+ const placeholders = keys.map(() => "?").join(", ");
1487
+ const params = keys.map((k) => serializeColumnParam(filtered[k], k, columns));
1488
+ return {
1489
+ sql: `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${placeholders}) RETURNING *`,
1490
+ params
1491
+ };
1478
1492
  }
1479
1493
  function buildUpdate(table, id, data, columns) {
1480
1494
  const filtered = stripSystemColumns(data);
1481
- const assignments = Object.entries(filtered).map(([k, v]) => `${k} = ${serializeValue(v, k, columns)}`).join(", ");
1482
- return `UPDATE ${table} SET ${assignments} WHERE id = ${escapeValue(id)}`;
1495
+ const keys = Object.keys(filtered);
1496
+ const assignments = keys.map((k) => `${k} = ?`).join(", ");
1497
+ const params = [
1498
+ ...keys.map((k) => serializeColumnParam(filtered[k], k, columns)),
1499
+ id
1500
+ // for WHERE id = ?
1501
+ ];
1502
+ return {
1503
+ sql: `UPDATE ${table} SET ${assignments} WHERE id = ? RETURNING *`,
1504
+ params
1505
+ };
1483
1506
  }
1484
- function buildDelete(table, where) {
1507
+ function buildDelete(table, where, whereParams) {
1485
1508
  let sql = `DELETE FROM ${table}`;
1486
1509
  if (where) sql += ` WHERE ${where}`;
1487
- return sql;
1510
+ return { sql, params: whereParams?.length ? whereParams : void 0 };
1488
1511
  }
1489
1512
  function stripSystemColumns(data) {
1490
1513
  const result = {};
@@ -1945,8 +1968,7 @@ var init_predicate = __esm({
1945
1968
  return PARSE_FAILED;
1946
1969
  }
1947
1970
  /**
1948
- * Attempt to resolve a closure variable by invoking the original function
1949
- * with a recording Proxy and inspecting what values it compares against.
1971
+ * Attempt to resolve a closure variable's value.
1950
1972
  *
1951
1973
  * This handles the common pattern:
1952
1974
  * ```ts
@@ -1954,40 +1976,28 @@ var init_predicate = __esm({
1954
1976
  * orders.filter(o => o.requestedBy === userId)
1955
1977
  * ```
1956
1978
  *
1957
- * The Proxy captures property accesses on the parameter and we can then
1958
- * extract the comparison value from the function's behavior. However,
1959
- * this approach has limitations if the function throws, has side effects,
1960
- * or uses the variable in a non-comparison context, we fall back to JS.
1979
+ * Closure variable resolution is fundamentally limited in JavaScript
1980
+ * we can't access another function's closure scope from outside without
1981
+ * `eval`. The `===` operator can't be overridden via Proxy or
1982
+ * Symbol.toPrimitive, so we can't intercept comparisons.
1983
+ *
1984
+ * For now, this falls back to JS execution. The predicate still works
1985
+ * correctly — it just scans all rows instead of generating SQL.
1986
+ * This is the most common reason for JS fallback in practice, since
1987
+ * almost every real-world filter references a variable like `userId`.
1988
+ *
1989
+ * A future improvement could accept an explicit `vars` argument:
1990
+ * ```ts
1991
+ * orders.filter(o => o.requestedBy === $userId, { $userId: auth.userId })
1992
+ * ```
1961
1993
  */
1962
1994
  resolveClosureVariable() {
1963
- const identToken = this.advance();
1964
- let closureExpr = identToken.value;
1995
+ this.advance();
1965
1996
  while (this.match("dot") && this.tokens[this.pos + 1]?.type === "identifier") {
1966
1997
  this.advance();
1967
- closureExpr += "." + this.advance().value;
1968
- }
1969
- try {
1970
- const MARKER = /* @__PURE__ */ Symbol("field_access_marker");
1971
- const accessed = [];
1972
- const proxy = new Proxy(
1973
- {},
1974
- {
1975
- get(_, prop) {
1976
- accessed.push(prop);
1977
- return new Proxy(() => MARKER, {
1978
- get(_2, nestedProp) {
1979
- accessed.push(nestedProp);
1980
- return MARKER;
1981
- }
1982
- });
1983
- }
1984
- }
1985
- );
1986
- void proxy;
1987
- return PARSE_FAILED;
1988
- } catch {
1989
- return PARSE_FAILED;
1998
+ this.advance();
1990
1999
  }
2000
+ return PARSE_FAILED;
1991
2001
  }
1992
2002
  /**
1993
2003
  * Look ahead to check if the next tokens form `.includes(`.
@@ -2026,17 +2036,11 @@ var init_query = __esm({
2026
2036
  init_predicate();
2027
2037
  init_sql();
2028
2038
  Query = class _Query {
2029
- /** @internal Accumulated predicate functions to filter by. */
2030
2039
  _predicates;
2031
- /** @internal The field accessor for sorting, if set. */
2032
2040
  _sortAccessor;
2033
- /** @internal Whether the sort order is reversed (DESC). */
2034
2041
  _reversed;
2035
- /** @internal Maximum number of results (SQL LIMIT). */
2036
2042
  _limit;
2037
- /** @internal Number of results to skip (SQL OFFSET). */
2038
2043
  _offset;
2039
- /** @internal Binding to the database execution layer. */
2040
2044
  _config;
2041
2045
  constructor(config, options) {
2042
2046
  this._config = config;
@@ -2046,10 +2050,6 @@ var init_query = __esm({
2046
2050
  this._limit = options?.limit;
2047
2051
  this._offset = options?.offset;
2048
2052
  }
2049
- /**
2050
- * Create a clone of this query with some options overridden.
2051
- * Used internally by chain methods to maintain immutability.
2052
- */
2053
2053
  _clone(overrides) {
2054
2054
  return new _Query(this._config, {
2055
2055
  predicates: overrides.predicates ?? this._predicates,
@@ -2060,126 +2060,73 @@ var init_query = __esm({
2060
2060
  });
2061
2061
  }
2062
2062
  // -------------------------------------------------------------------------
2063
- // Chain methods — return new Query instances
2063
+ // Chain methods
2064
2064
  // -------------------------------------------------------------------------
2065
- /**
2066
- * Add a filter predicate. Multiple filters are ANDed together.
2067
- *
2068
- * @example
2069
- * ```ts
2070
- * const active = Orders.filter(o => o.status === 'active');
2071
- * const expensive = active.filter(o => o.amount > 5000);
2072
- * // WHERE status = 'active' AND amount > 5000
2073
- * ```
2074
- */
2075
2065
  filter(predicate) {
2076
- return this._clone({
2077
- predicates: [...this._predicates, predicate]
2078
- });
2066
+ return this._clone({ predicates: [...this._predicates, predicate] });
2079
2067
  }
2080
- /**
2081
- * Sort results by a field (ascending by default).
2082
- * Use `.reverse()` after `.sortBy()` for descending order.
2083
- *
2084
- * @example
2085
- * ```ts
2086
- * const newest = Orders.sortBy(o => o.createdAt).reverse();
2087
- * ```
2088
- */
2089
2068
  sortBy(accessor) {
2090
2069
  return this._clone({ sortAccessor: accessor });
2091
2070
  }
2092
- /**
2093
- * Reverse the current sort order. If no sort is set, this has no effect.
2094
- */
2095
2071
  reverse() {
2096
2072
  return this._clone({ reversed: !this._reversed });
2097
2073
  }
2098
- /**
2099
- * Limit the number of results returned.
2100
- *
2101
- * @example
2102
- * ```ts
2103
- * const top10 = Orders.sortBy(o => o.amount).reverse().take(10);
2104
- * ```
2105
- */
2106
2074
  take(n) {
2107
2075
  return this._clone({ limit: n });
2108
2076
  }
2109
- /**
2110
- * Skip the first n results. Use with `.take()` for pagination.
2111
- *
2112
- * @example
2113
- * ```ts
2114
- * const page2 = Orders.sortBy(o => o.createdAt).skip(50).take(50);
2115
- * ```
2116
- */
2117
2077
  skip(n) {
2118
2078
  return this._clone({ offset: n });
2119
2079
  }
2120
2080
  // -------------------------------------------------------------------------
2121
- // Terminal methods — execute the query and return results
2081
+ // Terminal methods
2122
2082
  // -------------------------------------------------------------------------
2123
- /**
2124
- * Return the first matching row, or null if no rows match.
2125
- * Applies the current sort order before taking the first result.
2126
- */
2127
2083
  async first() {
2128
2084
  const rows = await this._clone({ limit: 1 })._execute();
2129
2085
  return rows[0] ?? null;
2130
2086
  }
2131
- /**
2132
- * Return the last matching row (per current sort), or null.
2133
- * Flips the sort direction and takes 1 row.
2134
- */
2135
2087
  async last() {
2136
2088
  const rows = await this._clone({ limit: 1, reversed: !this._reversed })._execute();
2137
2089
  return rows[0] ?? null;
2138
2090
  }
2139
- /**
2140
- * Count matching rows. Returns a number, not the rows themselves.
2141
- * Executes as `SELECT COUNT(*)` when predicates compile to SQL.
2142
- */
2143
2091
  async count() {
2144
2092
  const compiled = this._compilePredicates();
2145
2093
  if (compiled.allSql) {
2146
- const where = compiled.sqlWhere || void 0;
2147
- const sql = buildCount(this._config.tableName, where);
2148
- const result = await this._config.executeQuery(sql);
2149
- const row = result.rows[0];
2094
+ const query = buildCount(
2095
+ this._config.tableName,
2096
+ compiled.sqlWhere || void 0
2097
+ );
2098
+ const results = await this._config.executeBatch([query]);
2099
+ const row = results[0]?.rows[0];
2150
2100
  return row?.count ?? 0;
2151
2101
  }
2152
2102
  const rows = await this._fetchAndFilterInJs(compiled);
2153
2103
  return rows.length;
2154
2104
  }
2155
- /**
2156
- * Check if any row matches the current filters. Short-circuits —
2157
- * doesn't load all rows when using SQL.
2158
- */
2159
2105
  async some() {
2160
2106
  const compiled = this._compilePredicates();
2161
2107
  if (compiled.allSql) {
2162
- const where = compiled.sqlWhere || void 0;
2163
- const sql = buildExists(this._config.tableName, where);
2164
- const result = await this._config.executeQuery(sql);
2165
- const row = result.rows[0];
2108
+ const query = buildExists(
2109
+ this._config.tableName,
2110
+ compiled.sqlWhere || void 0
2111
+ );
2112
+ const results = await this._config.executeBatch([query]);
2113
+ const row = results[0]?.rows[0];
2166
2114
  return row?.result === 1;
2167
2115
  }
2168
2116
  const rows = await this._fetchAndFilterInJs(compiled);
2169
2117
  return rows.length > 0;
2170
2118
  }
2171
- /**
2172
- * Check if all rows match the current filters. Short-circuits on false.
2173
- *
2174
- * Implemented as NOT EXISTS(... WHERE NOT predicate) — returns true
2175
- * if no rows fail the predicate.
2176
- */
2177
2119
  async every() {
2178
2120
  const compiled = this._compilePredicates();
2179
2121
  if (compiled.allSql && compiled.sqlWhere) {
2180
- const sql = buildExists(this._config.tableName, `NOT (${compiled.sqlWhere})`, true);
2181
- const result = await this._config.executeQuery(sql);
2182
- const row = result.rows[0];
2122
+ const query = buildExists(
2123
+ this._config.tableName,
2124
+ `NOT (${compiled.sqlWhere})`,
2125
+ void 0,
2126
+ true
2127
+ );
2128
+ const results = await this._config.executeBatch([query]);
2129
+ const row = results[0]?.rows[0];
2183
2130
  return row?.result === 1;
2184
2131
  }
2185
2132
  if (this._predicates.length === 0) return true;
@@ -2188,24 +2135,12 @@ var init_query = __esm({
2188
2135
  (row) => this._predicates.every((pred) => pred(row))
2189
2136
  );
2190
2137
  }
2191
- /**
2192
- * Return the row with the minimum value for the given field.
2193
- * Executes as `ORDER BY field ASC LIMIT 1` in SQL.
2194
- */
2195
2138
  async min(accessor) {
2196
2139
  return this.sortBy(accessor).first();
2197
2140
  }
2198
- /**
2199
- * Return the row with the maximum value for the given field.
2200
- * Executes as `ORDER BY field DESC LIMIT 1` in SQL.
2201
- */
2202
2141
  async max(accessor) {
2203
2142
  return this.sortBy(accessor).reverse().first();
2204
2143
  }
2205
- /**
2206
- * Group rows by a field value. Returns a Map.
2207
- * Always executes in JS (no SQL equivalent for grouping into a Map).
2208
- */
2209
2144
  async groupBy(accessor) {
2210
2145
  const rows = await this._execute();
2211
2146
  const map = /* @__PURE__ */ new Map();
@@ -2221,40 +2156,97 @@ var init_query = __esm({
2221
2156
  return map;
2222
2157
  }
2223
2158
  // -------------------------------------------------------------------------
2224
- // PromiseLike implementationmakes `await query` work
2159
+ // Batch compilationused by db.batch() to extract SQL without executing
2225
2160
  // -------------------------------------------------------------------------
2226
2161
  /**
2227
- * PromiseLike.then() executes the query and pipes the result.
2228
- * This is what makes `const rows = await query` work.
2162
+ * @internal Compile this query into a SqlQuery for batch execution.
2163
+ *
2164
+ * Returns the compiled SQL query (if all predicates compile to SQL),
2165
+ * or null (if JS fallback is needed). In the fallback case, a bare
2166
+ * `SELECT *` is returned as `fallbackQuery` so the batch can fetch
2167
+ * all rows and this query can filter them in JS post-fetch.
2168
+ */
2169
+ _compile() {
2170
+ const compiled = this._compilePredicates();
2171
+ const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
2172
+ if (compiled.allSql) {
2173
+ const query = buildSelect(this._config.tableName, {
2174
+ where: compiled.sqlWhere || void 0,
2175
+ orderBy: sortField ?? void 0,
2176
+ desc: this._reversed,
2177
+ limit: this._limit,
2178
+ offset: this._offset
2179
+ });
2180
+ return { query, fallbackQuery: null, config: this._config };
2181
+ }
2182
+ const fallbackQuery = buildSelect(this._config.tableName);
2183
+ return {
2184
+ query: null,
2185
+ fallbackQuery,
2186
+ config: this._config,
2187
+ predicates: this._predicates,
2188
+ sortAccessor: this._sortAccessor,
2189
+ reversed: this._reversed,
2190
+ limit: this._limit,
2191
+ offset: this._offset
2192
+ };
2193
+ }
2194
+ /**
2195
+ * @internal Process raw SQL results into typed rows. Used by db.batch()
2196
+ * after executing the compiled query.
2197
+ *
2198
+ * For SQL-compiled queries: just deserialize the rows.
2199
+ * For JS-fallback queries: filter, sort, and slice in JS.
2229
2200
  */
2201
+ static _processResults(result, compiled) {
2202
+ const rows = result.rows.map(
2203
+ (row) => deserializeRow(
2204
+ row,
2205
+ compiled.config.columns
2206
+ )
2207
+ );
2208
+ if (compiled.query) return rows;
2209
+ let filtered = compiled.predicates ? rows.filter((row) => compiled.predicates.every((pred) => pred(row))) : rows;
2210
+ if (compiled.sortAccessor) {
2211
+ const accessor = compiled.sortAccessor;
2212
+ const reversed = compiled.reversed ?? false;
2213
+ filtered.sort((a, b) => {
2214
+ const aVal = accessor(a);
2215
+ const bVal = accessor(b);
2216
+ if (aVal < bVal) return reversed ? 1 : -1;
2217
+ if (aVal > bVal) return reversed ? -1 : 1;
2218
+ return 0;
2219
+ });
2220
+ }
2221
+ if (compiled.offset != null || compiled.limit != null) {
2222
+ const start = compiled.offset ?? 0;
2223
+ const end = compiled.limit != null ? start + compiled.limit : void 0;
2224
+ filtered = filtered.slice(start, end);
2225
+ }
2226
+ return filtered;
2227
+ }
2228
+ // -------------------------------------------------------------------------
2229
+ // PromiseLike
2230
+ // -------------------------------------------------------------------------
2230
2231
  then(onfulfilled, onrejected) {
2231
2232
  return this._execute().then(onfulfilled, onrejected);
2232
2233
  }
2233
2234
  // -------------------------------------------------------------------------
2234
2235
  // Execution internals
2235
2236
  // -------------------------------------------------------------------------
2236
- /**
2237
- * Execute the query and return typed result rows.
2238
- *
2239
- * This is the core execution method. It:
2240
- * 1. Tries to compile all predicates to SQL
2241
- * 2. If all compile → builds and executes a single SQL query
2242
- * 3. If any fail → fetches all rows and processes in JS
2243
- * 4. Deserializes rows (user prefix stripping, JSON parsing)
2244
- */
2245
2237
  async _execute() {
2246
2238
  const compiled = this._compilePredicates();
2247
2239
  if (compiled.allSql) {
2248
2240
  const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
2249
- const sql = buildSelect(this._config.tableName, {
2241
+ const query = buildSelect(this._config.tableName, {
2250
2242
  where: compiled.sqlWhere || void 0,
2251
2243
  orderBy: sortField ?? void 0,
2252
2244
  desc: this._reversed,
2253
2245
  limit: this._limit,
2254
2246
  offset: this._offset
2255
2247
  });
2256
- const result = await this._config.executeQuery(sql);
2257
- return result.rows.map(
2248
+ const results = await this._config.executeBatch([query]);
2249
+ return results[0].rows.map(
2258
2250
  (row) => deserializeRow(
2259
2251
  row,
2260
2252
  this._config.columns
@@ -2279,14 +2271,6 @@ var init_query = __esm({
2279
2271
  }
2280
2272
  return rows;
2281
2273
  }
2282
- /**
2283
- * Compile all accumulated predicates and determine the execution strategy.
2284
- *
2285
- * Returns an object with:
2286
- * - `allSql`: whether all predicates compiled to SQL
2287
- * - `sqlWhere`: combined WHERE clause (ANDed) if all compiled
2288
- * - `compiled`: individual compilation results
2289
- */
2290
2274
  _compilePredicates() {
2291
2275
  if (this._predicates.length === 0) {
2292
2276
  return { allSql: true, sqlWhere: "", compiled: [] };
@@ -2299,12 +2283,6 @@ var init_query = __esm({
2299
2283
  }
2300
2284
  return { allSql, sqlWhere, compiled };
2301
2285
  }
2302
- /**
2303
- * Fetch all rows from the table and apply JS predicates.
2304
- * This is the fallback path when SQL compilation fails.
2305
- *
2306
- * Logs a warning to stderr so developers know they're on the slow path.
2307
- */
2308
2286
  async _fetchAndFilterInJs(compiled) {
2309
2287
  const allRows = await this._fetchAllRows();
2310
2288
  if (compiled.compiled.some((c) => c.type === "js")) {
@@ -2316,14 +2294,10 @@ var init_query = __esm({
2316
2294
  (row) => this._predicates.every((pred) => pred(row))
2317
2295
  );
2318
2296
  }
2319
- /**
2320
- * Fetch all rows from the table (SELECT * with no WHERE).
2321
- * Used by the JS fallback path.
2322
- */
2323
2297
  async _fetchAllRows() {
2324
- const sql = buildSelect(this._config.tableName);
2325
- const result = await this._config.executeQuery(sql);
2326
- return result.rows.map(
2298
+ const query = buildSelect(this._config.tableName);
2299
+ const results = await this._config.executeBatch([query]);
2300
+ return results[0].rows.map(
2327
2301
  (row) => deserializeRow(row, this._config.columns)
2328
2302
  );
2329
2303
  }
@@ -2340,300 +2314,149 @@ var init_table = __esm({
2340
2314
  init_predicate();
2341
2315
  init_sql();
2342
2316
  Table = class {
2343
- /** @internal Runtime config binding this table to the execution layer. */
2317
+ /** @internal */
2344
2318
  _config;
2345
2319
  constructor(config) {
2346
2320
  this._config = config;
2347
2321
  }
2348
2322
  // -------------------------------------------------------------------------
2349
- // Reads — direct (return Promises)
2323
+ // Reads — direct
2350
2324
  // -------------------------------------------------------------------------
2351
- /**
2352
- * Get a single row by ID. Returns null if not found.
2353
- *
2354
- * @example
2355
- * ```ts
2356
- * const order = await Orders.get('abc-123');
2357
- * if (order) console.log(order.status);
2358
- * ```
2359
- */
2360
2325
  async get(id) {
2361
- const sql = buildSelect(this._config.tableName, {
2362
- where: `id = ${escapeValue(id)}`,
2326
+ const query = buildSelect(this._config.tableName, {
2327
+ where: `id = ?`,
2328
+ whereParams: [id],
2363
2329
  limit: 1
2364
2330
  });
2365
- const result = await this._config.executeQuery(sql);
2366
- if (result.rows.length === 0) return null;
2331
+ const results = await this._config.executeBatch([query]);
2332
+ if (results[0].rows.length === 0) return null;
2367
2333
  return deserializeRow(
2368
- result.rows[0],
2334
+ results[0].rows[0],
2369
2335
  this._config.columns
2370
2336
  );
2371
2337
  }
2372
- /**
2373
- * Find the first row matching a predicate. Returns null if none match.
2374
- *
2375
- * @example
2376
- * ```ts
2377
- * const activeOrder = await Orders.findOne(o => o.status === 'active');
2378
- * ```
2379
- */
2380
2338
  async findOne(predicate) {
2381
2339
  return this.filter(predicate).first();
2382
2340
  }
2383
- /**
2384
- * Count rows, optionally filtered by a predicate.
2385
- *
2386
- * @example
2387
- * ```ts
2388
- * const total = await Orders.count();
2389
- * const pending = await Orders.count(o => o.status === 'pending');
2390
- * ```
2391
- */
2392
2341
  async count(predicate) {
2393
- if (predicate) {
2394
- return this.filter(predicate).count();
2395
- }
2396
- const sql = buildCount(this._config.tableName);
2397
- const result = await this._config.executeQuery(sql);
2398
- const row = result.rows[0];
2342
+ if (predicate) return this.filter(predicate).count();
2343
+ const query = buildCount(this._config.tableName);
2344
+ const results = await this._config.executeBatch([query]);
2345
+ const row = results[0]?.rows[0];
2399
2346
  return row?.count ?? 0;
2400
2347
  }
2401
- /**
2402
- * Check if any row matches a predicate. Short-circuits.
2403
- *
2404
- * @example
2405
- * ```ts
2406
- * const hasActive = await Orders.some(o => o.status === 'active');
2407
- * ```
2408
- */
2409
2348
  async some(predicate) {
2410
2349
  return this.filter(predicate).some();
2411
2350
  }
2412
- /**
2413
- * Check if all rows match a predicate.
2414
- *
2415
- * @example
2416
- * ```ts
2417
- * const allComplete = await Orders.every(o => o.status === 'completed');
2418
- * ```
2419
- */
2420
2351
  async every(predicate) {
2421
2352
  return this.filter(predicate).every();
2422
2353
  }
2423
- /**
2424
- * Check if the table has zero rows.
2425
- *
2426
- * @example
2427
- * ```ts
2428
- * if (await Orders.isEmpty()) console.log('No orders yet');
2429
- * ```
2430
- */
2431
2354
  async isEmpty() {
2432
- const sql = buildExists(this._config.tableName, void 0, true);
2433
- const result = await this._config.executeQuery(sql);
2434
- const row = result.rows[0];
2355
+ const query = buildExists(this._config.tableName, void 0, void 0, true);
2356
+ const results = await this._config.executeBatch([query]);
2357
+ const row = results[0]?.rows[0];
2435
2358
  return row?.result === 1;
2436
2359
  }
2437
- /**
2438
- * Return the row with the minimum value for a field.
2439
- * Executes as `ORDER BY field ASC LIMIT 1`.
2440
- *
2441
- * @example
2442
- * ```ts
2443
- * const cheapest = await Orders.min(o => o.amount);
2444
- * ```
2445
- */
2446
2360
  async min(accessor) {
2447
2361
  return this.sortBy(accessor).first();
2448
2362
  }
2449
- /**
2450
- * Return the row with the maximum value for a field.
2451
- * Executes as `ORDER BY field DESC LIMIT 1`.
2452
- *
2453
- * @example
2454
- * ```ts
2455
- * const mostExpensive = await Orders.max(o => o.amount);
2456
- * ```
2457
- */
2458
2363
  async max(accessor) {
2459
2364
  return this.sortBy(accessor).reverse().first();
2460
2365
  }
2461
- /**
2462
- * Group all rows by a field value. Returns a Map.
2463
- *
2464
- * @example
2465
- * ```ts
2466
- * const byStatus = await Orders.groupBy(o => o.status);
2467
- * // Map { 'pending' => [...], 'approved' => [...] }
2468
- * ```
2469
- */
2470
2366
  async groupBy(accessor) {
2471
2367
  return new Query(this._config).groupBy(accessor);
2472
2368
  }
2473
2369
  // -------------------------------------------------------------------------
2474
- // Reads — chainable (return Query<T>)
2370
+ // Reads — chainable
2475
2371
  // -------------------------------------------------------------------------
2476
- /**
2477
- * Filter rows by a predicate. Returns a chainable Query.
2478
- *
2479
- * The predicate is compiled to SQL when possible. If compilation fails,
2480
- * the query falls back to fetching all rows and filtering in JS.
2481
- *
2482
- * @example
2483
- * ```ts
2484
- * const active = await Orders.filter(o => o.status === 'active');
2485
- * const recentActive = await Orders
2486
- * .filter(o => o.status === 'active')
2487
- * .sortBy(o => o.createdAt)
2488
- * .reverse()
2489
- * .take(10);
2490
- * ```
2491
- */
2492
2372
  filter(predicate) {
2493
2373
  return new Query(this._config).filter(predicate);
2494
2374
  }
2495
- /**
2496
- * Sort all rows by a field. Returns a chainable Query.
2497
- *
2498
- * @example
2499
- * ```ts
2500
- * const newest = await Orders.sortBy(o => o.createdAt).reverse().take(5);
2501
- * ```
2502
- */
2503
2375
  sortBy(accessor) {
2504
2376
  return new Query(this._config).sortBy(accessor);
2505
2377
  }
2506
2378
  async push(data) {
2507
2379
  const isArray = Array.isArray(data);
2508
2380
  const items = isArray ? data : [data];
2509
- const results = [];
2510
- for (const item of items) {
2511
- const insertSql = buildInsert(
2381
+ const queries = items.map(
2382
+ (item) => buildInsert(
2512
2383
  this._config.tableName,
2513
2384
  item,
2514
2385
  this._config.columns
2515
- );
2516
- await this._config.executeQuery(insertSql);
2517
- const fetchSql = `SELECT * FROM ${this._config.tableName} WHERE rowid = last_insert_rowid()`;
2518
- const fetchResult = await this._config.executeQuery(fetchSql);
2519
- if (fetchResult.rows.length > 0) {
2520
- results.push(
2521
- deserializeRow(
2522
- fetchResult.rows[0],
2523
- this._config.columns
2524
- )
2386
+ )
2387
+ );
2388
+ const results = await this._config.executeBatch(queries);
2389
+ const rows = results.map((r) => {
2390
+ if (r.rows.length > 0) {
2391
+ return deserializeRow(
2392
+ r.rows[0],
2393
+ this._config.columns
2525
2394
  );
2526
2395
  }
2527
- }
2528
- return isArray ? results : results[0];
2396
+ return void 0;
2397
+ });
2398
+ return isArray ? rows : rows[0];
2529
2399
  }
2530
2400
  /**
2531
2401
  * Update a row by ID. Only the provided fields are changed.
2532
- * Returns the updated row.
2533
- *
2534
- * System columns cannot be updated — they're stripped automatically.
2535
- * `updatedAt` and `lastUpdatedBy` are set by the platform.
2536
- *
2537
- * @example
2538
- * ```ts
2539
- * const updated = await Orders.update(order.id, { status: 'approved' });
2540
- * console.log(updated.updatedAt); // freshly updated
2541
- * ```
2402
+ * Returns the updated row via `UPDATE ... RETURNING *`.
2542
2403
  */
2543
2404
  async update(id, data) {
2544
- const updateSql = buildUpdate(
2405
+ const query = buildUpdate(
2545
2406
  this._config.tableName,
2546
2407
  id,
2547
2408
  data,
2548
2409
  this._config.columns
2549
2410
  );
2550
- await this._config.executeQuery(updateSql);
2551
- const fetchSql = buildSelect(this._config.tableName, {
2552
- where: `id = ${escapeValue(id)}`,
2553
- limit: 1
2554
- });
2555
- const result = await this._config.executeQuery(fetchSql);
2411
+ const results = await this._config.executeBatch([query]);
2556
2412
  return deserializeRow(
2557
- result.rows[0],
2413
+ results[0].rows[0],
2558
2414
  this._config.columns
2559
2415
  );
2560
2416
  }
2561
- /**
2562
- * Remove a row by ID.
2563
- *
2564
- * @example
2565
- * ```ts
2566
- * await Orders.remove('abc-123');
2567
- * ```
2568
- */
2569
2417
  async remove(id) {
2570
- const sql = buildDelete(
2571
- this._config.tableName,
2572
- `id = ${escapeValue(id)}`
2573
- );
2574
- await this._config.executeQuery(sql);
2418
+ const query = buildDelete(this._config.tableName, `id = ?`, [id]);
2419
+ await this._config.executeBatch([query]);
2575
2420
  }
2576
2421
  /**
2577
2422
  * Remove all rows matching a predicate. Returns the count removed.
2578
- *
2579
- * The predicate is compiled to SQL when possible. If compilation fails,
2580
- * the function fetches all matching rows, collects their IDs, and
2581
- * deletes them individually.
2582
- *
2583
- * @example
2584
- * ```ts
2585
- * const removed = await Orders.removeAll(o => o.status === 'rejected');
2586
- * console.log(`Removed ${removed} orders`);
2587
- * ```
2588
2423
  */
2589
2424
  async removeAll(predicate) {
2590
2425
  const compiled = compilePredicate(predicate);
2591
2426
  if (compiled.type === "sql") {
2592
- const sql = buildDelete(this._config.tableName, compiled.where);
2593
- const result = await this._config.executeQuery(sql);
2594
- return result.changes;
2427
+ const query = buildDelete(this._config.tableName, compiled.where);
2428
+ const results = await this._config.executeBatch([query]);
2429
+ return results[0].changes;
2595
2430
  }
2596
2431
  console.warn(
2597
2432
  `[mindstudio] removeAll predicate on ${this._config.tableName} could not be compiled to SQL \u2014 fetching all rows first`
2598
2433
  );
2599
- const allSql = buildSelect(this._config.tableName);
2600
- const allResult = await this._config.executeQuery(allSql);
2601
- const allRows = allResult.rows.map(
2434
+ const allQuery = buildSelect(this._config.tableName);
2435
+ const allResults = await this._config.executeBatch([allQuery]);
2436
+ const allRows = allResults[0].rows.map(
2602
2437
  (r) => deserializeRow(
2603
2438
  r,
2604
2439
  this._config.columns
2605
2440
  )
2606
2441
  );
2607
2442
  const matching = allRows.filter((row) => predicate(row));
2608
- let count = 0;
2609
- for (const row of matching) {
2610
- const id = row.id;
2611
- if (id) {
2612
- const sql = buildDelete(this._config.tableName, `id = ${escapeValue(id)}`);
2613
- await this._config.executeQuery(sql);
2614
- count++;
2615
- }
2443
+ if (matching.length === 0) return 0;
2444
+ const deleteQueries = matching.filter((row) => row.id).map((row) => buildDelete(this._config.tableName, `id = ?`, [row.id]));
2445
+ if (deleteQueries.length > 0) {
2446
+ await this._config.executeBatch(deleteQueries);
2616
2447
  }
2617
- return count;
2448
+ return matching.length;
2618
2449
  }
2619
- /**
2620
- * Remove all rows from the table.
2621
- *
2622
- * @example
2623
- * ```ts
2624
- * await Orders.clear();
2625
- * ```
2626
- */
2627
2450
  async clear() {
2628
- const sql = buildDelete(this._config.tableName);
2629
- await this._config.executeQuery(sql);
2451
+ const query = buildDelete(this._config.tableName);
2452
+ await this._config.executeBatch([query]);
2630
2453
  }
2631
2454
  };
2632
2455
  }
2633
2456
  });
2634
2457
 
2635
2458
  // src/db/index.ts
2636
- function createDb(databases, executeQuery) {
2459
+ function createDb(databases, executeBatch) {
2637
2460
  return {
2638
2461
  defineTable(name, options) {
2639
2462
  const resolved = resolveTable(databases, name, options?.database);
@@ -2641,7 +2464,7 @@ function createDb(databases, executeQuery) {
2641
2464
  databaseId: resolved.databaseId,
2642
2465
  tableName: name,
2643
2466
  columns: resolved.columns,
2644
- executeQuery: (sql) => executeQuery(resolved.databaseId, sql)
2467
+ executeBatch: (queries) => executeBatch(resolved.databaseId, queries)
2645
2468
  };
2646
2469
  return new Table(config);
2647
2470
  },
@@ -2652,7 +2475,49 @@ function createDb(databases, executeQuery) {
2652
2475
  hours: (n) => n * 36e5,
2653
2476
  minutes: (n) => n * 6e4,
2654
2477
  ago: (ms) => Date.now() - ms,
2655
- fromNow: (ms) => Date.now() + ms
2478
+ fromNow: (ms) => Date.now() + ms,
2479
+ // --- Batch execution ---
2480
+ batch: ((...queries) => {
2481
+ return (async () => {
2482
+ const compiled = queries.map((q) => {
2483
+ if (!(q instanceof Query)) {
2484
+ throw new MindStudioError(
2485
+ "db.batch() only accepts Query objects (from .filter(), .sortBy(), etc.)",
2486
+ "invalid_batch_query",
2487
+ 400
2488
+ );
2489
+ }
2490
+ return q._compile();
2491
+ });
2492
+ const groups = /* @__PURE__ */ new Map();
2493
+ for (let i = 0; i < compiled.length; i++) {
2494
+ const c = compiled[i];
2495
+ const dbId = c.config.databaseId;
2496
+ const sqlQuery = c.query ?? c.fallbackQuery;
2497
+ if (!groups.has(dbId)) groups.set(dbId, []);
2498
+ groups.get(dbId).push({ index: i, sqlQuery });
2499
+ }
2500
+ const allResults = new Array(compiled.length);
2501
+ await Promise.all(
2502
+ Array.from(groups.entries()).map(async ([dbId, entries]) => {
2503
+ const sqlQueries = entries.map((e) => e.sqlQuery);
2504
+ const results = await executeBatch(dbId, sqlQueries);
2505
+ for (let i = 0; i < entries.length; i++) {
2506
+ allResults[entries[i].index] = results[i];
2507
+ }
2508
+ })
2509
+ );
2510
+ return compiled.map((c, i) => {
2511
+ const result = allResults[i];
2512
+ if (!c.query && c.predicates?.length) {
2513
+ console.warn(
2514
+ `[mindstudio] db.batch(): filter on ${c.config.tableName} could not be compiled to SQL \u2014 processing in JS`
2515
+ );
2516
+ }
2517
+ return Query._processResults(result, c);
2518
+ });
2519
+ })();
2520
+ })
2656
2521
  };
2657
2522
  }
2658
2523
  function resolveTable(databases, tableName, databaseHint) {
@@ -2707,6 +2572,7 @@ var init_db = __esm({
2707
2572
  "use strict";
2708
2573
  init_errors();
2709
2574
  init_table();
2575
+ init_query();
2710
2576
  init_table();
2711
2577
  }
2712
2578
  });
@@ -3612,6 +3478,9 @@ var init_client = __esm({
3612
3478
  * ```
3613
3479
  */
3614
3480
  get auth() {
3481
+ if (!this._auth) {
3482
+ this._trySandboxHydration();
3483
+ }
3615
3484
  if (!this._auth) {
3616
3485
  throw new MindStudioError(
3617
3486
  "Auth context not yet loaded. Call `await agent.ensureContext()` or perform any db operation first (which auto-hydrates context). Inside the MindStudio sandbox, context is loaded automatically.",
@@ -3637,6 +3506,9 @@ var init_client = __esm({
3637
3506
  * ```
3638
3507
  */
3639
3508
  get db() {
3509
+ if (!this._db) {
3510
+ this._trySandboxHydration();
3511
+ }
3640
3512
  if (this._db) return this._db;
3641
3513
  return this._createLazyDb();
3642
3514
  }
@@ -3694,7 +3566,7 @@ var init_client = __esm({
3694
3566
  this._auth = new AuthContext(context.auth);
3695
3567
  this._db = createDb(
3696
3568
  context.databases,
3697
- this._executeDbQuery.bind(this)
3569
+ this._executeDbBatch.bind(this)
3698
3570
  );
3699
3571
  }
3700
3572
  /**
@@ -3715,25 +3587,40 @@ var init_client = __esm({
3715
3587
  }
3716
3588
  }
3717
3589
  /**
3718
- * @internal Execute a SQL query against a managed database.
3719
- * Used as the `executeQuery` callback for Table instances.
3590
+ * @internal Execute a batch of SQL queries against a managed database.
3591
+ * Used as the `executeBatch` callback for Table/Query instances.
3720
3592
  *
3721
- * Calls the `queryAppDatabase` step with `parameterize: false`
3722
- * (the SDK builds fully-formed SQL with escaped inline values).
3593
+ * Calls `POST /_internal/v2/db/query` directly with the hook token
3594
+ * (raw, no Bearer prefix). All queries run on a single SQLite connection,
3595
+ * enabling RETURNING clauses and multi-statement batches.
3723
3596
  */
3724
- async _executeDbQuery(databaseId, sql) {
3725
- const result = await this.executeStep("queryAppDatabase", {
3726
- databaseId,
3727
- sql,
3728
- parameterize: false
3597
+ async _executeDbBatch(databaseId, queries) {
3598
+ const url = `${this._httpConfig.baseUrl}/_internal/v2/db/query`;
3599
+ const res = await fetch(url, {
3600
+ method: "POST",
3601
+ headers: {
3602
+ "Content-Type": "application/json",
3603
+ Authorization: this._httpConfig.token
3604
+ },
3605
+ body: JSON.stringify({ databaseId, queries })
3729
3606
  });
3730
- return { rows: result.rows ?? [], changes: result.changes ?? 0 };
3607
+ if (!res.ok) {
3608
+ let message = `Database query failed: ${res.status} ${res.statusText}`;
3609
+ try {
3610
+ const body = await res.json();
3611
+ if (body.error) message = body.error;
3612
+ } catch {
3613
+ }
3614
+ throw new MindStudioError(message, "db_query_error", res.status);
3615
+ }
3616
+ const data = await res.json();
3617
+ return data.results;
3731
3618
  }
3732
3619
  /**
3733
3620
  * @internal Create a lazy Db proxy that auto-hydrates context.
3734
3621
  *
3735
3622
  * defineTable() returns Table instances immediately (no async needed).
3736
- * But the Table's executeQuery callback is wrapped to call ensureContext()
3623
+ * But the Table's executeBatch callback is wrapped to call ensureContext()
3737
3624
  * before the first query, so context is fetched lazily.
3738
3625
  */
3739
3626
  _createLazyDb() {
@@ -3745,7 +3632,7 @@ var init_client = __esm({
3745
3632
  databaseId: "",
3746
3633
  tableName: name,
3747
3634
  columns: [],
3748
- executeQuery: async (sql) => {
3635
+ executeBatch: async (queries) => {
3749
3636
  await agent.ensureContext();
3750
3637
  const databases = agent._context.databases;
3751
3638
  let targetDb;
@@ -3759,7 +3646,7 @@ var init_client = __esm({
3759
3646
  );
3760
3647
  }
3761
3648
  const databaseId = targetDb?.id ?? databases[0]?.id ?? "";
3762
- return agent._executeDbQuery(databaseId, sql);
3649
+ return agent._executeDbBatch(databaseId, queries);
3763
3650
  }
3764
3651
  });
3765
3652
  },
@@ -3769,7 +3656,14 @@ var init_client = __esm({
3769
3656
  hours: (n) => n * 36e5,
3770
3657
  minutes: (n) => n * 6e4,
3771
3658
  ago: (ms) => Date.now() - ms,
3772
- fromNow: (ms) => Date.now() + ms
3659
+ fromNow: (ms) => Date.now() + ms,
3660
+ // Batch needs context — hydrate first, then delegate to real db
3661
+ batch: ((...queries) => {
3662
+ return (async () => {
3663
+ await agent.ensureContext();
3664
+ return agent._db.batch(...queries);
3665
+ })();
3666
+ })
3773
3667
  };
3774
3668
  }
3775
3669
  // -------------------------------------------------------------------------
@@ -3974,7 +3868,7 @@ async function startMcpServer(options) {
3974
3868
  capabilities: { tools: {} },
3975
3869
  serverInfo: {
3976
3870
  name: "mindstudio-agent",
3977
- version: "0.1.9"
3871
+ version: "0.1.11"
3978
3872
  },
3979
3873
  instructions: "Welcome to MindStudio \u2014 a platform with 200+ AI models, 850+ third-party integrations, and pre-built agents.\n\nGetting started:\n1. Call `listAgents` to verify your connection and see available agents.\n2. Call `changeName` to set your display name \u2014 use your name or whatever your user calls you. This is how you'll appear in MindStudio request logs.\n3. If you have a profile picture or icon, call `uploadFile` to upload it, then `changeProfilePicture` with the returned URL. This helps users identify your requests in their logs.\n4. Call `listActions` to discover all available actions.\n\nThen use the tools to generate text, images, video, audio, search the web, work with data sources, run agents, and more.\n\nImportant:\n- AI-powered actions (text generation, image generation, video, audio, etc.) cost money. Before running these, call `estimateActionCost` and confirm with the user before proceeding \u2014 unless they've explicitly told you to go ahead.\n- Not all agents from `listAgents` are configured for API use. Do not try to run an agent just because it appears in the list \u2014 it will likely fail. Only run agents the user specifically asks you to run."
3980
3874
  });
@@ -4882,7 +4776,7 @@ function isNewerVersion(current, latest) {
4882
4776
  return false;
4883
4777
  }
4884
4778
  async function checkForUpdate() {
4885
- const currentVersion = "0.1.9";
4779
+ const currentVersion = "0.1.11";
4886
4780
  if (!currentVersion) return null;
4887
4781
  try {
4888
4782
  const { loadConfig: loadConfig2, saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
@@ -4911,7 +4805,7 @@ async function checkForUpdate() {
4911
4805
  }
4912
4806
  }
4913
4807
  function printUpdateNotice(latestVersion) {
4914
- const currentVersion = "0.1.9";
4808
+ const currentVersion = "0.1.11";
4915
4809
  process.stderr.write(
4916
4810
  `
4917
4811
  ${ansi.cyanBright("Update available")} ${ansi.gray(currentVersion + " \u2192")} ${ansi.cyanBold(latestVersion)}
@@ -4986,7 +4880,7 @@ async function cmdLogin(options) {
4986
4880
  process.stderr.write("\n");
4987
4881
  printLogo();
4988
4882
  process.stderr.write("\n");
4989
- const ver = "0.1.9";
4883
+ const ver = "0.1.11";
4990
4884
  process.stderr.write(
4991
4885
  ` ${ansi.bold("MindStudio Agent")} ${ver ? " " + ansi.gray("v" + ver) : ""}
4992
4886
  `
@@ -5069,6 +4963,9 @@ async function cmdLogin(options) {
5069
4963
  ` ${ansi.greenBold("\u2714")} Authenticated successfully!
5070
4964
  ${ansi.gray("Credentials saved to")} ${getConfigPath2()}
5071
4965
 
4966
+ ${ansi.bold("Using with Claude Code?")} Run once to enable the MCP server:
4967
+ ${ansi.cyan("claude mcp add mindstudio -- mindstudio mcp")}
4968
+
5072
4969
  `
5073
4970
  );
5074
4971
  return;