@mindstudio-ai/agent 0.1.10 → 0.1.12

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/index.js CHANGED
@@ -302,7 +302,15 @@ function buildDelete(table, where, whereParams) {
302
302
  if (where) sql += ` WHERE ${where}`;
303
303
  return { sql, params: whereParams?.length ? whereParams : void 0 };
304
304
  }
305
- var SYSTEM_COLUMNS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "lastUpdatedBy"]);
305
+ var SYSTEM_COLUMNS = /* @__PURE__ */ new Set([
306
+ "id",
307
+ "created_at",
308
+ "createdAt",
309
+ "updated_at",
310
+ "updatedAt",
311
+ "last_updated_by",
312
+ "lastUpdatedBy"
313
+ ]);
306
314
  function stripSystemColumns(data) {
307
315
  const result = {};
308
316
  for (const [key, value] of Object.entries(data)) {
@@ -716,8 +724,7 @@ var Parser = class {
716
724
  return PARSE_FAILED;
717
725
  }
718
726
  /**
719
- * Attempt to resolve a closure variable by invoking the original function
720
- * with a recording Proxy and inspecting what values it compares against.
727
+ * Attempt to resolve a closure variable's value.
721
728
  *
722
729
  * This handles the common pattern:
723
730
  * ```ts
@@ -725,40 +732,28 @@ var Parser = class {
725
732
  * orders.filter(o => o.requestedBy === userId)
726
733
  * ```
727
734
  *
728
- * The Proxy captures property accesses on the parameter and we can then
729
- * extract the comparison value from the function's behavior. However,
730
- * this approach has limitations if the function throws, has side effects,
731
- * or uses the variable in a non-comparison context, we fall back to JS.
735
+ * Closure variable resolution is fundamentally limited in JavaScript
736
+ * we can't access another function's closure scope from outside without
737
+ * `eval`. The `===` operator can't be overridden via Proxy or
738
+ * Symbol.toPrimitive, so we can't intercept comparisons.
739
+ *
740
+ * For now, this falls back to JS execution. The predicate still works
741
+ * correctly — it just scans all rows instead of generating SQL.
742
+ * This is the most common reason for JS fallback in practice, since
743
+ * almost every real-world filter references a variable like `userId`.
744
+ *
745
+ * A future improvement could accept an explicit `vars` argument:
746
+ * ```ts
747
+ * orders.filter(o => o.requestedBy === $userId, { $userId: auth.userId })
748
+ * ```
732
749
  */
733
750
  resolveClosureVariable() {
734
- const identToken = this.advance();
735
- let closureExpr = identToken.value;
751
+ this.advance();
736
752
  while (this.match("dot") && this.tokens[this.pos + 1]?.type === "identifier") {
737
753
  this.advance();
738
- closureExpr += "." + this.advance().value;
739
- }
740
- try {
741
- const MARKER = /* @__PURE__ */ Symbol("field_access_marker");
742
- const accessed = [];
743
- const proxy = new Proxy(
744
- {},
745
- {
746
- get(_, prop) {
747
- accessed.push(prop);
748
- return new Proxy(() => MARKER, {
749
- get(_2, nestedProp) {
750
- accessed.push(nestedProp);
751
- return MARKER;
752
- }
753
- });
754
- }
755
- }
756
- );
757
- void proxy;
758
- return PARSE_FAILED;
759
- } catch {
760
- return PARSE_FAILED;
754
+ this.advance();
761
755
  }
756
+ return PARSE_FAILED;
762
757
  }
763
758
  /**
764
759
  * Look ahead to check if the next tokens form `.includes(`.
@@ -935,6 +930,76 @@ var Query = class _Query {
935
930
  return map;
936
931
  }
937
932
  // -------------------------------------------------------------------------
933
+ // Batch compilation — used by db.batch() to extract SQL without executing
934
+ // -------------------------------------------------------------------------
935
+ /**
936
+ * @internal Compile this query into a SqlQuery for batch execution.
937
+ *
938
+ * Returns the compiled SQL query (if all predicates compile to SQL),
939
+ * or null (if JS fallback is needed). In the fallback case, a bare
940
+ * `SELECT *` is returned as `fallbackQuery` so the batch can fetch
941
+ * all rows and this query can filter them in JS post-fetch.
942
+ */
943
+ _compile() {
944
+ const compiled = this._compilePredicates();
945
+ const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
946
+ if (compiled.allSql) {
947
+ const query = buildSelect(this._config.tableName, {
948
+ where: compiled.sqlWhere || void 0,
949
+ orderBy: sortField ?? void 0,
950
+ desc: this._reversed,
951
+ limit: this._limit,
952
+ offset: this._offset
953
+ });
954
+ return { query, fallbackQuery: null, config: this._config };
955
+ }
956
+ const fallbackQuery = buildSelect(this._config.tableName);
957
+ return {
958
+ query: null,
959
+ fallbackQuery,
960
+ config: this._config,
961
+ predicates: this._predicates,
962
+ sortAccessor: this._sortAccessor,
963
+ reversed: this._reversed,
964
+ limit: this._limit,
965
+ offset: this._offset
966
+ };
967
+ }
968
+ /**
969
+ * @internal Process raw SQL results into typed rows. Used by db.batch()
970
+ * after executing the compiled query.
971
+ *
972
+ * For SQL-compiled queries: just deserialize the rows.
973
+ * For JS-fallback queries: filter, sort, and slice in JS.
974
+ */
975
+ static _processResults(result, compiled) {
976
+ const rows = result.rows.map(
977
+ (row) => deserializeRow(
978
+ row,
979
+ compiled.config.columns
980
+ )
981
+ );
982
+ if (compiled.query) return rows;
983
+ let filtered = compiled.predicates ? rows.filter((row) => compiled.predicates.every((pred) => pred(row))) : rows;
984
+ if (compiled.sortAccessor) {
985
+ const accessor = compiled.sortAccessor;
986
+ const reversed = compiled.reversed ?? false;
987
+ filtered.sort((a, b) => {
988
+ const aVal = accessor(a);
989
+ const bVal = accessor(b);
990
+ if (aVal < bVal) return reversed ? 1 : -1;
991
+ if (aVal > bVal) return reversed ? -1 : 1;
992
+ return 0;
993
+ });
994
+ }
995
+ if (compiled.offset != null || compiled.limit != null) {
996
+ const start = compiled.offset ?? 0;
997
+ const end = compiled.limit != null ? start + compiled.limit : void 0;
998
+ filtered = filtered.slice(start, end);
999
+ }
1000
+ return filtered;
1001
+ }
1002
+ // -------------------------------------------------------------------------
938
1003
  // PromiseLike
939
1004
  // -------------------------------------------------------------------------
940
1005
  then(onfulfilled, onrejected) {
@@ -1180,7 +1245,49 @@ function createDb(databases, executeBatch) {
1180
1245
  hours: (n) => n * 36e5,
1181
1246
  minutes: (n) => n * 6e4,
1182
1247
  ago: (ms) => Date.now() - ms,
1183
- fromNow: (ms) => Date.now() + ms
1248
+ fromNow: (ms) => Date.now() + ms,
1249
+ // --- Batch execution ---
1250
+ batch: ((...queries) => {
1251
+ return (async () => {
1252
+ const compiled = queries.map((q) => {
1253
+ if (!(q instanceof Query)) {
1254
+ throw new MindStudioError(
1255
+ "db.batch() only accepts Query objects (from .filter(), .sortBy(), etc.)",
1256
+ "invalid_batch_query",
1257
+ 400
1258
+ );
1259
+ }
1260
+ return q._compile();
1261
+ });
1262
+ const groups = /* @__PURE__ */ new Map();
1263
+ for (let i = 0; i < compiled.length; i++) {
1264
+ const c = compiled[i];
1265
+ const dbId = c.config.databaseId;
1266
+ const sqlQuery = c.query ?? c.fallbackQuery;
1267
+ if (!groups.has(dbId)) groups.set(dbId, []);
1268
+ groups.get(dbId).push({ index: i, sqlQuery });
1269
+ }
1270
+ const allResults = new Array(compiled.length);
1271
+ await Promise.all(
1272
+ Array.from(groups.entries()).map(async ([dbId, entries]) => {
1273
+ const sqlQueries = entries.map((e) => e.sqlQuery);
1274
+ const results = await executeBatch(dbId, sqlQueries);
1275
+ for (let i = 0; i < entries.length; i++) {
1276
+ allResults[entries[i].index] = results[i];
1277
+ }
1278
+ })
1279
+ );
1280
+ return compiled.map((c, i) => {
1281
+ const result = allResults[i];
1282
+ if (!c.query && c.predicates?.length) {
1283
+ console.warn(
1284
+ `[mindstudio] db.batch(): filter on ${c.config.tableName} could not be compiled to SQL \u2014 processing in JS`
1285
+ );
1286
+ }
1287
+ return Query._processResults(result, c);
1288
+ });
1289
+ })();
1290
+ })
1184
1291
  };
1185
1292
  }
1186
1293
  function resolveTable(databases, tableName, databaseHint) {
@@ -1951,10 +2058,12 @@ var MindStudioAgent = class {
1951
2058
  );
1952
2059
  }
1953
2060
  if (!res.ok) {
2061
+ const errorBody = await res.json().catch(() => ({}));
1954
2062
  throw new MindStudioError(
1955
- `Poll request failed: ${res.status} ${res.statusText}`,
1956
- "poll_error",
1957
- res.status
2063
+ errorBody.message ?? errorBody.error ?? `Poll request failed: ${res.status} ${res.statusText}`,
2064
+ errorBody.code ?? "poll_error",
2065
+ res.status,
2066
+ errorBody
1958
2067
  );
1959
2068
  }
1960
2069
  const poll = await res.json();
@@ -2219,12 +2328,24 @@ var MindStudioAgent = class {
2219
2328
  });
2220
2329
  if (!res.ok) {
2221
2330
  let message = `Database query failed: ${res.status} ${res.statusText}`;
2331
+ let code = "db_query_error";
2222
2332
  try {
2223
- const body = await res.json();
2224
- if (body.error) message = body.error;
2333
+ const text = await res.text();
2334
+ try {
2335
+ const body = JSON.parse(text);
2336
+ const errMsg = body.error ?? body.message ?? body.details;
2337
+ if (errMsg) message = errMsg;
2338
+ if (body.code) code = body.code;
2339
+ } catch {
2340
+ if (text && text.length < 500) message = text;
2341
+ }
2225
2342
  } catch {
2226
2343
  }
2227
- throw new MindStudioError(message, "db_query_error", res.status);
2344
+ throw new MindStudioError(
2345
+ `[db] ${message}`,
2346
+ code,
2347
+ res.status
2348
+ );
2228
2349
  }
2229
2350
  const data = await res.json();
2230
2351
  return data.results;
@@ -2269,7 +2390,14 @@ var MindStudioAgent = class {
2269
2390
  hours: (n) => n * 36e5,
2270
2391
  minutes: (n) => n * 6e4,
2271
2392
  ago: (ms) => Date.now() - ms,
2272
- fromNow: (ms) => Date.now() + ms
2393
+ fromNow: (ms) => Date.now() + ms,
2394
+ // Batch needs context — hydrate first, then delegate to real db
2395
+ batch: ((...queries) => {
2396
+ return (async () => {
2397
+ await agent.ensureContext();
2398
+ return agent._db.batch(...queries);
2399
+ })();
2400
+ })
2273
2401
  };
2274
2402
  }
2275
2403
  // -------------------------------------------------------------------------
@@ -2401,10 +2529,12 @@ var MindStudioAgent = class {
2401
2529
  headers: options.type ? { "Content-Type": options.type } : {}
2402
2530
  });
2403
2531
  if (!res.ok) {
2532
+ const errorBody = await res.json().catch(() => ({}));
2404
2533
  throw new MindStudioError(
2405
- `Upload failed: ${res.status} ${res.statusText}`,
2406
- "upload_error",
2407
- res.status
2534
+ errorBody.message ?? errorBody.error ?? `Upload failed: ${res.status} ${res.statusText}`,
2535
+ errorBody.code ?? "upload_error",
2536
+ res.status,
2537
+ errorBody
2408
2538
  );
2409
2539
  }
2410
2540
  return { url: data.url };