@mindstudio-ai/agent 0.1.9 → 0.1.10

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
@@ -211,6 +211,19 @@ var Roles = new Proxy(
211
211
  );
212
212
 
213
213
  // src/db/sql.ts
214
+ function serializeParam(val) {
215
+ if (val === null || val === void 0) return null;
216
+ if (typeof val === "boolean") return val ? 1 : 0;
217
+ if (typeof val === "number" || typeof val === "string") return val;
218
+ return JSON.stringify(val);
219
+ }
220
+ function serializeColumnParam(val, columnName, columns) {
221
+ const col = columns.find((c) => c.name === columnName);
222
+ if (col?.type === "user" && typeof val === "string") {
223
+ return `@@user@@${val}`;
224
+ }
225
+ return serializeParam(val);
226
+ }
214
227
  function escapeValue(val) {
215
228
  if (val === null || val === void 0) return "NULL";
216
229
  if (typeof val === "boolean") return val ? "1" : "0";
@@ -219,13 +232,6 @@ function escapeValue(val) {
219
232
  const json = JSON.stringify(val);
220
233
  return `'${json.replace(/'/g, "''")}'`;
221
234
  }
222
- function serializeValue(val, columnName, columns) {
223
- const col = columns.find((c) => c.name === columnName);
224
- if (col?.type === "user" && typeof val === "string") {
225
- return escapeValue(`@@user@@${val}`);
226
- }
227
- return escapeValue(val);
228
- }
229
235
  var USER_PREFIX = "@@user@@";
230
236
  function deserializeRow(row, columns) {
231
237
  const result = {};
@@ -247,37 +253,54 @@ function deserializeRow(row, columns) {
247
253
  }
248
254
  function buildSelect(table, options = {}) {
249
255
  let sql = `SELECT * FROM ${table}`;
250
- if (options.where) sql += ` WHERE ${options.where}`;
256
+ const params = [];
257
+ if (options.where) {
258
+ sql += ` WHERE ${options.where}`;
259
+ if (options.whereParams) params.push(...options.whereParams);
260
+ }
251
261
  if (options.orderBy) sql += ` ORDER BY ${options.orderBy}${options.desc ? " DESC" : " ASC"}`;
252
262
  if (options.limit != null) sql += ` LIMIT ${options.limit}`;
253
263
  if (options.offset != null) sql += ` OFFSET ${options.offset}`;
254
- return sql;
264
+ return { sql, params: params.length > 0 ? params : void 0 };
255
265
  }
256
- function buildCount(table, where) {
266
+ function buildCount(table, where, whereParams) {
257
267
  let sql = `SELECT COUNT(*) as count FROM ${table}`;
258
268
  if (where) sql += ` WHERE ${where}`;
259
- return sql;
269
+ return { sql, params: whereParams?.length ? whereParams : void 0 };
260
270
  }
261
- function buildExists(table, where, negate) {
271
+ function buildExists(table, where, whereParams, negate) {
262
272
  const inner = where ? `SELECT 1 FROM ${table} WHERE ${where}` : `SELECT 1 FROM ${table}`;
263
273
  const fn = negate ? "NOT EXISTS" : "EXISTS";
264
- return `SELECT ${fn}(${inner}) as result`;
274
+ return { sql: `SELECT ${fn}(${inner}) as result`, params: whereParams?.length ? whereParams : void 0 };
265
275
  }
266
276
  function buildInsert(table, data, columns) {
267
277
  const filtered = stripSystemColumns(data);
268
278
  const keys = Object.keys(filtered);
269
- const vals = keys.map((k) => serializeValue(filtered[k], k, columns));
270
- return `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${vals.join(", ")})`;
279
+ const placeholders = keys.map(() => "?").join(", ");
280
+ const params = keys.map((k) => serializeColumnParam(filtered[k], k, columns));
281
+ return {
282
+ sql: `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${placeholders}) RETURNING *`,
283
+ params
284
+ };
271
285
  }
272
286
  function buildUpdate(table, id, data, columns) {
273
287
  const filtered = stripSystemColumns(data);
274
- const assignments = Object.entries(filtered).map(([k, v]) => `${k} = ${serializeValue(v, k, columns)}`).join(", ");
275
- return `UPDATE ${table} SET ${assignments} WHERE id = ${escapeValue(id)}`;
288
+ const keys = Object.keys(filtered);
289
+ const assignments = keys.map((k) => `${k} = ?`).join(", ");
290
+ const params = [
291
+ ...keys.map((k) => serializeColumnParam(filtered[k], k, columns)),
292
+ id
293
+ // for WHERE id = ?
294
+ ];
295
+ return {
296
+ sql: `UPDATE ${table} SET ${assignments} WHERE id = ? RETURNING *`,
297
+ params
298
+ };
276
299
  }
277
- function buildDelete(table, where) {
300
+ function buildDelete(table, where, whereParams) {
278
301
  let sql = `DELETE FROM ${table}`;
279
302
  if (where) sql += ` WHERE ${where}`;
280
- return sql;
303
+ return { sql, params: whereParams?.length ? whereParams : void 0 };
281
304
  }
282
305
  var SYSTEM_COLUMNS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "lastUpdatedBy"]);
283
306
  function stripSystemColumns(data) {
@@ -792,17 +815,11 @@ function isComparisonOp(value) {
792
815
 
793
816
  // src/db/query.ts
794
817
  var Query = class _Query {
795
- /** @internal Accumulated predicate functions to filter by. */
796
818
  _predicates;
797
- /** @internal The field accessor for sorting, if set. */
798
819
  _sortAccessor;
799
- /** @internal Whether the sort order is reversed (DESC). */
800
820
  _reversed;
801
- /** @internal Maximum number of results (SQL LIMIT). */
802
821
  _limit;
803
- /** @internal Number of results to skip (SQL OFFSET). */
804
822
  _offset;
805
- /** @internal Binding to the database execution layer. */
806
823
  _config;
807
824
  constructor(config, options) {
808
825
  this._config = config;
@@ -812,10 +829,6 @@ var Query = class _Query {
812
829
  this._limit = options?.limit;
813
830
  this._offset = options?.offset;
814
831
  }
815
- /**
816
- * Create a clone of this query with some options overridden.
817
- * Used internally by chain methods to maintain immutability.
818
- */
819
832
  _clone(overrides) {
820
833
  return new _Query(this._config, {
821
834
  predicates: overrides.predicates ?? this._predicates,
@@ -826,126 +839,73 @@ var Query = class _Query {
826
839
  });
827
840
  }
828
841
  // -------------------------------------------------------------------------
829
- // Chain methods — return new Query instances
842
+ // Chain methods
830
843
  // -------------------------------------------------------------------------
831
- /**
832
- * Add a filter predicate. Multiple filters are ANDed together.
833
- *
834
- * @example
835
- * ```ts
836
- * const active = Orders.filter(o => o.status === 'active');
837
- * const expensive = active.filter(o => o.amount > 5000);
838
- * // WHERE status = 'active' AND amount > 5000
839
- * ```
840
- */
841
844
  filter(predicate) {
842
- return this._clone({
843
- predicates: [...this._predicates, predicate]
844
- });
845
+ return this._clone({ predicates: [...this._predicates, predicate] });
845
846
  }
846
- /**
847
- * Sort results by a field (ascending by default).
848
- * Use `.reverse()` after `.sortBy()` for descending order.
849
- *
850
- * @example
851
- * ```ts
852
- * const newest = Orders.sortBy(o => o.createdAt).reverse();
853
- * ```
854
- */
855
847
  sortBy(accessor) {
856
848
  return this._clone({ sortAccessor: accessor });
857
849
  }
858
- /**
859
- * Reverse the current sort order. If no sort is set, this has no effect.
860
- */
861
850
  reverse() {
862
851
  return this._clone({ reversed: !this._reversed });
863
852
  }
864
- /**
865
- * Limit the number of results returned.
866
- *
867
- * @example
868
- * ```ts
869
- * const top10 = Orders.sortBy(o => o.amount).reverse().take(10);
870
- * ```
871
- */
872
853
  take(n) {
873
854
  return this._clone({ limit: n });
874
855
  }
875
- /**
876
- * Skip the first n results. Use with `.take()` for pagination.
877
- *
878
- * @example
879
- * ```ts
880
- * const page2 = Orders.sortBy(o => o.createdAt).skip(50).take(50);
881
- * ```
882
- */
883
856
  skip(n) {
884
857
  return this._clone({ offset: n });
885
858
  }
886
859
  // -------------------------------------------------------------------------
887
- // Terminal methods — execute the query and return results
860
+ // Terminal methods
888
861
  // -------------------------------------------------------------------------
889
- /**
890
- * Return the first matching row, or null if no rows match.
891
- * Applies the current sort order before taking the first result.
892
- */
893
862
  async first() {
894
863
  const rows = await this._clone({ limit: 1 })._execute();
895
864
  return rows[0] ?? null;
896
865
  }
897
- /**
898
- * Return the last matching row (per current sort), or null.
899
- * Flips the sort direction and takes 1 row.
900
- */
901
866
  async last() {
902
867
  const rows = await this._clone({ limit: 1, reversed: !this._reversed })._execute();
903
868
  return rows[0] ?? null;
904
869
  }
905
- /**
906
- * Count matching rows. Returns a number, not the rows themselves.
907
- * Executes as `SELECT COUNT(*)` when predicates compile to SQL.
908
- */
909
870
  async count() {
910
871
  const compiled = this._compilePredicates();
911
872
  if (compiled.allSql) {
912
- const where = compiled.sqlWhere || void 0;
913
- const sql = buildCount(this._config.tableName, where);
914
- const result = await this._config.executeQuery(sql);
915
- const row = result.rows[0];
873
+ const query = buildCount(
874
+ this._config.tableName,
875
+ compiled.sqlWhere || void 0
876
+ );
877
+ const results = await this._config.executeBatch([query]);
878
+ const row = results[0]?.rows[0];
916
879
  return row?.count ?? 0;
917
880
  }
918
881
  const rows = await this._fetchAndFilterInJs(compiled);
919
882
  return rows.length;
920
883
  }
921
- /**
922
- * Check if any row matches the current filters. Short-circuits —
923
- * doesn't load all rows when using SQL.
924
- */
925
884
  async some() {
926
885
  const compiled = this._compilePredicates();
927
886
  if (compiled.allSql) {
928
- const where = compiled.sqlWhere || void 0;
929
- const sql = buildExists(this._config.tableName, where);
930
- const result = await this._config.executeQuery(sql);
931
- const row = result.rows[0];
887
+ const query = buildExists(
888
+ this._config.tableName,
889
+ compiled.sqlWhere || void 0
890
+ );
891
+ const results = await this._config.executeBatch([query]);
892
+ const row = results[0]?.rows[0];
932
893
  return row?.result === 1;
933
894
  }
934
895
  const rows = await this._fetchAndFilterInJs(compiled);
935
896
  return rows.length > 0;
936
897
  }
937
- /**
938
- * Check if all rows match the current filters. Short-circuits on false.
939
- *
940
- * Implemented as NOT EXISTS(... WHERE NOT predicate) — returns true
941
- * if no rows fail the predicate.
942
- */
943
898
  async every() {
944
899
  const compiled = this._compilePredicates();
945
900
  if (compiled.allSql && compiled.sqlWhere) {
946
- const sql = buildExists(this._config.tableName, `NOT (${compiled.sqlWhere})`, true);
947
- const result = await this._config.executeQuery(sql);
948
- const row = result.rows[0];
901
+ const query = buildExists(
902
+ this._config.tableName,
903
+ `NOT (${compiled.sqlWhere})`,
904
+ void 0,
905
+ true
906
+ );
907
+ const results = await this._config.executeBatch([query]);
908
+ const row = results[0]?.rows[0];
949
909
  return row?.result === 1;
950
910
  }
951
911
  if (this._predicates.length === 0) return true;
@@ -954,24 +914,12 @@ var Query = class _Query {
954
914
  (row) => this._predicates.every((pred) => pred(row))
955
915
  );
956
916
  }
957
- /**
958
- * Return the row with the minimum value for the given field.
959
- * Executes as `ORDER BY field ASC LIMIT 1` in SQL.
960
- */
961
917
  async min(accessor) {
962
918
  return this.sortBy(accessor).first();
963
919
  }
964
- /**
965
- * Return the row with the maximum value for the given field.
966
- * Executes as `ORDER BY field DESC LIMIT 1` in SQL.
967
- */
968
920
  async max(accessor) {
969
921
  return this.sortBy(accessor).reverse().first();
970
922
  }
971
- /**
972
- * Group rows by a field value. Returns a Map.
973
- * Always executes in JS (no SQL equivalent for grouping into a Map).
974
- */
975
923
  async groupBy(accessor) {
976
924
  const rows = await this._execute();
977
925
  const map = /* @__PURE__ */ new Map();
@@ -987,40 +935,27 @@ var Query = class _Query {
987
935
  return map;
988
936
  }
989
937
  // -------------------------------------------------------------------------
990
- // PromiseLike implementation — makes `await query` work
938
+ // PromiseLike
991
939
  // -------------------------------------------------------------------------
992
- /**
993
- * PromiseLike.then() — executes the query and pipes the result.
994
- * This is what makes `const rows = await query` work.
995
- */
996
940
  then(onfulfilled, onrejected) {
997
941
  return this._execute().then(onfulfilled, onrejected);
998
942
  }
999
943
  // -------------------------------------------------------------------------
1000
944
  // Execution internals
1001
945
  // -------------------------------------------------------------------------
1002
- /**
1003
- * Execute the query and return typed result rows.
1004
- *
1005
- * This is the core execution method. It:
1006
- * 1. Tries to compile all predicates to SQL
1007
- * 2. If all compile → builds and executes a single SQL query
1008
- * 3. If any fail → fetches all rows and processes in JS
1009
- * 4. Deserializes rows (user prefix stripping, JSON parsing)
1010
- */
1011
946
  async _execute() {
1012
947
  const compiled = this._compilePredicates();
1013
948
  if (compiled.allSql) {
1014
949
  const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
1015
- const sql = buildSelect(this._config.tableName, {
950
+ const query = buildSelect(this._config.tableName, {
1016
951
  where: compiled.sqlWhere || void 0,
1017
952
  orderBy: sortField ?? void 0,
1018
953
  desc: this._reversed,
1019
954
  limit: this._limit,
1020
955
  offset: this._offset
1021
956
  });
1022
- const result = await this._config.executeQuery(sql);
1023
- return result.rows.map(
957
+ const results = await this._config.executeBatch([query]);
958
+ return results[0].rows.map(
1024
959
  (row) => deserializeRow(
1025
960
  row,
1026
961
  this._config.columns
@@ -1045,14 +980,6 @@ var Query = class _Query {
1045
980
  }
1046
981
  return rows;
1047
982
  }
1048
- /**
1049
- * Compile all accumulated predicates and determine the execution strategy.
1050
- *
1051
- * Returns an object with:
1052
- * - `allSql`: whether all predicates compiled to SQL
1053
- * - `sqlWhere`: combined WHERE clause (ANDed) if all compiled
1054
- * - `compiled`: individual compilation results
1055
- */
1056
983
  _compilePredicates() {
1057
984
  if (this._predicates.length === 0) {
1058
985
  return { allSql: true, sqlWhere: "", compiled: [] };
@@ -1065,12 +992,6 @@ var Query = class _Query {
1065
992
  }
1066
993
  return { allSql, sqlWhere, compiled };
1067
994
  }
1068
- /**
1069
- * Fetch all rows from the table and apply JS predicates.
1070
- * This is the fallback path when SQL compilation fails.
1071
- *
1072
- * Logs a warning to stderr so developers know they're on the slow path.
1073
- */
1074
995
  async _fetchAndFilterInJs(compiled) {
1075
996
  const allRows = await this._fetchAllRows();
1076
997
  if (compiled.compiled.some((c) => c.type === "js")) {
@@ -1082,14 +1003,10 @@ var Query = class _Query {
1082
1003
  (row) => this._predicates.every((pred) => pred(row))
1083
1004
  );
1084
1005
  }
1085
- /**
1086
- * Fetch all rows from the table (SELECT * with no WHERE).
1087
- * Used by the JS fallback path.
1088
- */
1089
1006
  async _fetchAllRows() {
1090
- const sql = buildSelect(this._config.tableName);
1091
- const result = await this._config.executeQuery(sql);
1092
- return result.rows.map(
1007
+ const query = buildSelect(this._config.tableName);
1008
+ const results = await this._config.executeBatch([query]);
1009
+ return results[0].rows.map(
1093
1010
  (row) => deserializeRow(row, this._config.columns)
1094
1011
  );
1095
1012
  }
@@ -1104,298 +1021,147 @@ function extractFieldName(accessor) {
1104
1021
 
1105
1022
  // src/db/table.ts
1106
1023
  var Table = class {
1107
- /** @internal Runtime config binding this table to the execution layer. */
1024
+ /** @internal */
1108
1025
  _config;
1109
1026
  constructor(config) {
1110
1027
  this._config = config;
1111
1028
  }
1112
1029
  // -------------------------------------------------------------------------
1113
- // Reads — direct (return Promises)
1030
+ // Reads — direct
1114
1031
  // -------------------------------------------------------------------------
1115
- /**
1116
- * Get a single row by ID. Returns null if not found.
1117
- *
1118
- * @example
1119
- * ```ts
1120
- * const order = await Orders.get('abc-123');
1121
- * if (order) console.log(order.status);
1122
- * ```
1123
- */
1124
1032
  async get(id) {
1125
- const sql = buildSelect(this._config.tableName, {
1126
- where: `id = ${escapeValue(id)}`,
1033
+ const query = buildSelect(this._config.tableName, {
1034
+ where: `id = ?`,
1035
+ whereParams: [id],
1127
1036
  limit: 1
1128
1037
  });
1129
- const result = await this._config.executeQuery(sql);
1130
- if (result.rows.length === 0) return null;
1038
+ const results = await this._config.executeBatch([query]);
1039
+ if (results[0].rows.length === 0) return null;
1131
1040
  return deserializeRow(
1132
- result.rows[0],
1041
+ results[0].rows[0],
1133
1042
  this._config.columns
1134
1043
  );
1135
1044
  }
1136
- /**
1137
- * Find the first row matching a predicate. Returns null if none match.
1138
- *
1139
- * @example
1140
- * ```ts
1141
- * const activeOrder = await Orders.findOne(o => o.status === 'active');
1142
- * ```
1143
- */
1144
1045
  async findOne(predicate) {
1145
1046
  return this.filter(predicate).first();
1146
1047
  }
1147
- /**
1148
- * Count rows, optionally filtered by a predicate.
1149
- *
1150
- * @example
1151
- * ```ts
1152
- * const total = await Orders.count();
1153
- * const pending = await Orders.count(o => o.status === 'pending');
1154
- * ```
1155
- */
1156
1048
  async count(predicate) {
1157
- if (predicate) {
1158
- return this.filter(predicate).count();
1159
- }
1160
- const sql = buildCount(this._config.tableName);
1161
- const result = await this._config.executeQuery(sql);
1162
- const row = result.rows[0];
1049
+ if (predicate) return this.filter(predicate).count();
1050
+ const query = buildCount(this._config.tableName);
1051
+ const results = await this._config.executeBatch([query]);
1052
+ const row = results[0]?.rows[0];
1163
1053
  return row?.count ?? 0;
1164
1054
  }
1165
- /**
1166
- * Check if any row matches a predicate. Short-circuits.
1167
- *
1168
- * @example
1169
- * ```ts
1170
- * const hasActive = await Orders.some(o => o.status === 'active');
1171
- * ```
1172
- */
1173
1055
  async some(predicate) {
1174
1056
  return this.filter(predicate).some();
1175
1057
  }
1176
- /**
1177
- * Check if all rows match a predicate.
1178
- *
1179
- * @example
1180
- * ```ts
1181
- * const allComplete = await Orders.every(o => o.status === 'completed');
1182
- * ```
1183
- */
1184
1058
  async every(predicate) {
1185
1059
  return this.filter(predicate).every();
1186
1060
  }
1187
- /**
1188
- * Check if the table has zero rows.
1189
- *
1190
- * @example
1191
- * ```ts
1192
- * if (await Orders.isEmpty()) console.log('No orders yet');
1193
- * ```
1194
- */
1195
1061
  async isEmpty() {
1196
- const sql = buildExists(this._config.tableName, void 0, true);
1197
- const result = await this._config.executeQuery(sql);
1198
- const row = result.rows[0];
1062
+ const query = buildExists(this._config.tableName, void 0, void 0, true);
1063
+ const results = await this._config.executeBatch([query]);
1064
+ const row = results[0]?.rows[0];
1199
1065
  return row?.result === 1;
1200
1066
  }
1201
- /**
1202
- * Return the row with the minimum value for a field.
1203
- * Executes as `ORDER BY field ASC LIMIT 1`.
1204
- *
1205
- * @example
1206
- * ```ts
1207
- * const cheapest = await Orders.min(o => o.amount);
1208
- * ```
1209
- */
1210
1067
  async min(accessor) {
1211
1068
  return this.sortBy(accessor).first();
1212
1069
  }
1213
- /**
1214
- * Return the row with the maximum value for a field.
1215
- * Executes as `ORDER BY field DESC LIMIT 1`.
1216
- *
1217
- * @example
1218
- * ```ts
1219
- * const mostExpensive = await Orders.max(o => o.amount);
1220
- * ```
1221
- */
1222
1070
  async max(accessor) {
1223
1071
  return this.sortBy(accessor).reverse().first();
1224
1072
  }
1225
- /**
1226
- * Group all rows by a field value. Returns a Map.
1227
- *
1228
- * @example
1229
- * ```ts
1230
- * const byStatus = await Orders.groupBy(o => o.status);
1231
- * // Map { 'pending' => [...], 'approved' => [...] }
1232
- * ```
1233
- */
1234
1073
  async groupBy(accessor) {
1235
1074
  return new Query(this._config).groupBy(accessor);
1236
1075
  }
1237
1076
  // -------------------------------------------------------------------------
1238
- // Reads — chainable (return Query<T>)
1077
+ // Reads — chainable
1239
1078
  // -------------------------------------------------------------------------
1240
- /**
1241
- * Filter rows by a predicate. Returns a chainable Query.
1242
- *
1243
- * The predicate is compiled to SQL when possible. If compilation fails,
1244
- * the query falls back to fetching all rows and filtering in JS.
1245
- *
1246
- * @example
1247
- * ```ts
1248
- * const active = await Orders.filter(o => o.status === 'active');
1249
- * const recentActive = await Orders
1250
- * .filter(o => o.status === 'active')
1251
- * .sortBy(o => o.createdAt)
1252
- * .reverse()
1253
- * .take(10);
1254
- * ```
1255
- */
1256
1079
  filter(predicate) {
1257
1080
  return new Query(this._config).filter(predicate);
1258
1081
  }
1259
- /**
1260
- * Sort all rows by a field. Returns a chainable Query.
1261
- *
1262
- * @example
1263
- * ```ts
1264
- * const newest = await Orders.sortBy(o => o.createdAt).reverse().take(5);
1265
- * ```
1266
- */
1267
1082
  sortBy(accessor) {
1268
1083
  return new Query(this._config).sortBy(accessor);
1269
1084
  }
1270
1085
  async push(data) {
1271
1086
  const isArray = Array.isArray(data);
1272
1087
  const items = isArray ? data : [data];
1273
- const results = [];
1274
- for (const item of items) {
1275
- const insertSql = buildInsert(
1088
+ const queries = items.map(
1089
+ (item) => buildInsert(
1276
1090
  this._config.tableName,
1277
1091
  item,
1278
1092
  this._config.columns
1279
- );
1280
- await this._config.executeQuery(insertSql);
1281
- const fetchSql = `SELECT * FROM ${this._config.tableName} WHERE rowid = last_insert_rowid()`;
1282
- const fetchResult = await this._config.executeQuery(fetchSql);
1283
- if (fetchResult.rows.length > 0) {
1284
- results.push(
1285
- deserializeRow(
1286
- fetchResult.rows[0],
1287
- this._config.columns
1288
- )
1093
+ )
1094
+ );
1095
+ const results = await this._config.executeBatch(queries);
1096
+ const rows = results.map((r) => {
1097
+ if (r.rows.length > 0) {
1098
+ return deserializeRow(
1099
+ r.rows[0],
1100
+ this._config.columns
1289
1101
  );
1290
1102
  }
1291
- }
1292
- return isArray ? results : results[0];
1103
+ return void 0;
1104
+ });
1105
+ return isArray ? rows : rows[0];
1293
1106
  }
1294
1107
  /**
1295
1108
  * Update a row by ID. Only the provided fields are changed.
1296
- * Returns the updated row.
1297
- *
1298
- * System columns cannot be updated — they're stripped automatically.
1299
- * `updatedAt` and `lastUpdatedBy` are set by the platform.
1300
- *
1301
- * @example
1302
- * ```ts
1303
- * const updated = await Orders.update(order.id, { status: 'approved' });
1304
- * console.log(updated.updatedAt); // freshly updated
1305
- * ```
1109
+ * Returns the updated row via `UPDATE ... RETURNING *`.
1306
1110
  */
1307
1111
  async update(id, data) {
1308
- const updateSql = buildUpdate(
1112
+ const query = buildUpdate(
1309
1113
  this._config.tableName,
1310
1114
  id,
1311
1115
  data,
1312
1116
  this._config.columns
1313
1117
  );
1314
- await this._config.executeQuery(updateSql);
1315
- const fetchSql = buildSelect(this._config.tableName, {
1316
- where: `id = ${escapeValue(id)}`,
1317
- limit: 1
1318
- });
1319
- const result = await this._config.executeQuery(fetchSql);
1118
+ const results = await this._config.executeBatch([query]);
1320
1119
  return deserializeRow(
1321
- result.rows[0],
1120
+ results[0].rows[0],
1322
1121
  this._config.columns
1323
1122
  );
1324
1123
  }
1325
- /**
1326
- * Remove a row by ID.
1327
- *
1328
- * @example
1329
- * ```ts
1330
- * await Orders.remove('abc-123');
1331
- * ```
1332
- */
1333
1124
  async remove(id) {
1334
- const sql = buildDelete(
1335
- this._config.tableName,
1336
- `id = ${escapeValue(id)}`
1337
- );
1338
- await this._config.executeQuery(sql);
1125
+ const query = buildDelete(this._config.tableName, `id = ?`, [id]);
1126
+ await this._config.executeBatch([query]);
1339
1127
  }
1340
1128
  /**
1341
1129
  * Remove all rows matching a predicate. Returns the count removed.
1342
- *
1343
- * The predicate is compiled to SQL when possible. If compilation fails,
1344
- * the function fetches all matching rows, collects their IDs, and
1345
- * deletes them individually.
1346
- *
1347
- * @example
1348
- * ```ts
1349
- * const removed = await Orders.removeAll(o => o.status === 'rejected');
1350
- * console.log(`Removed ${removed} orders`);
1351
- * ```
1352
1130
  */
1353
1131
  async removeAll(predicate) {
1354
1132
  const compiled = compilePredicate(predicate);
1355
1133
  if (compiled.type === "sql") {
1356
- const sql = buildDelete(this._config.tableName, compiled.where);
1357
- const result = await this._config.executeQuery(sql);
1358
- return result.changes;
1134
+ const query = buildDelete(this._config.tableName, compiled.where);
1135
+ const results = await this._config.executeBatch([query]);
1136
+ return results[0].changes;
1359
1137
  }
1360
1138
  console.warn(
1361
1139
  `[mindstudio] removeAll predicate on ${this._config.tableName} could not be compiled to SQL \u2014 fetching all rows first`
1362
1140
  );
1363
- const allSql = buildSelect(this._config.tableName);
1364
- const allResult = await this._config.executeQuery(allSql);
1365
- const allRows = allResult.rows.map(
1141
+ const allQuery = buildSelect(this._config.tableName);
1142
+ const allResults = await this._config.executeBatch([allQuery]);
1143
+ const allRows = allResults[0].rows.map(
1366
1144
  (r) => deserializeRow(
1367
1145
  r,
1368
1146
  this._config.columns
1369
1147
  )
1370
1148
  );
1371
1149
  const matching = allRows.filter((row) => predicate(row));
1372
- let count = 0;
1373
- for (const row of matching) {
1374
- const id = row.id;
1375
- if (id) {
1376
- const sql = buildDelete(this._config.tableName, `id = ${escapeValue(id)}`);
1377
- await this._config.executeQuery(sql);
1378
- count++;
1379
- }
1150
+ if (matching.length === 0) return 0;
1151
+ const deleteQueries = matching.filter((row) => row.id).map((row) => buildDelete(this._config.tableName, `id = ?`, [row.id]));
1152
+ if (deleteQueries.length > 0) {
1153
+ await this._config.executeBatch(deleteQueries);
1380
1154
  }
1381
- return count;
1155
+ return matching.length;
1382
1156
  }
1383
- /**
1384
- * Remove all rows from the table.
1385
- *
1386
- * @example
1387
- * ```ts
1388
- * await Orders.clear();
1389
- * ```
1390
- */
1391
1157
  async clear() {
1392
- const sql = buildDelete(this._config.tableName);
1393
- await this._config.executeQuery(sql);
1158
+ const query = buildDelete(this._config.tableName);
1159
+ await this._config.executeBatch([query]);
1394
1160
  }
1395
1161
  };
1396
1162
 
1397
1163
  // src/db/index.ts
1398
- function createDb(databases, executeQuery) {
1164
+ function createDb(databases, executeBatch) {
1399
1165
  return {
1400
1166
  defineTable(name, options) {
1401
1167
  const resolved = resolveTable(databases, name, options?.database);
@@ -1403,7 +1169,7 @@ function createDb(databases, executeQuery) {
1403
1169
  databaseId: resolved.databaseId,
1404
1170
  tableName: name,
1405
1171
  columns: resolved.columns,
1406
- executeQuery: (sql) => executeQuery(resolved.databaseId, sql)
1172
+ executeBatch: (queries) => executeBatch(resolved.databaseId, queries)
1407
1173
  };
1408
1174
  return new Table(config);
1409
1175
  },
@@ -2325,6 +2091,9 @@ var MindStudioAgent = class {
2325
2091
  * ```
2326
2092
  */
2327
2093
  get auth() {
2094
+ if (!this._auth) {
2095
+ this._trySandboxHydration();
2096
+ }
2328
2097
  if (!this._auth) {
2329
2098
  throw new MindStudioError(
2330
2099
  "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.",
@@ -2350,6 +2119,9 @@ var MindStudioAgent = class {
2350
2119
  * ```
2351
2120
  */
2352
2121
  get db() {
2122
+ if (!this._db) {
2123
+ this._trySandboxHydration();
2124
+ }
2353
2125
  if (this._db) return this._db;
2354
2126
  return this._createLazyDb();
2355
2127
  }
@@ -2407,7 +2179,7 @@ var MindStudioAgent = class {
2407
2179
  this._auth = new AuthContext(context.auth);
2408
2180
  this._db = createDb(
2409
2181
  context.databases,
2410
- this._executeDbQuery.bind(this)
2182
+ this._executeDbBatch.bind(this)
2411
2183
  );
2412
2184
  }
2413
2185
  /**
@@ -2428,25 +2200,40 @@ var MindStudioAgent = class {
2428
2200
  }
2429
2201
  }
2430
2202
  /**
2431
- * @internal Execute a SQL query against a managed database.
2432
- * Used as the `executeQuery` callback for Table instances.
2203
+ * @internal Execute a batch of SQL queries against a managed database.
2204
+ * Used as the `executeBatch` callback for Table/Query instances.
2433
2205
  *
2434
- * Calls the `queryAppDatabase` step with `parameterize: false`
2435
- * (the SDK builds fully-formed SQL with escaped inline values).
2206
+ * Calls `POST /_internal/v2/db/query` directly with the hook token
2207
+ * (raw, no Bearer prefix). All queries run on a single SQLite connection,
2208
+ * enabling RETURNING clauses and multi-statement batches.
2436
2209
  */
2437
- async _executeDbQuery(databaseId, sql) {
2438
- const result = await this.executeStep("queryAppDatabase", {
2439
- databaseId,
2440
- sql,
2441
- parameterize: false
2210
+ async _executeDbBatch(databaseId, queries) {
2211
+ const url = `${this._httpConfig.baseUrl}/_internal/v2/db/query`;
2212
+ const res = await fetch(url, {
2213
+ method: "POST",
2214
+ headers: {
2215
+ "Content-Type": "application/json",
2216
+ Authorization: this._httpConfig.token
2217
+ },
2218
+ body: JSON.stringify({ databaseId, queries })
2442
2219
  });
2443
- return { rows: result.rows ?? [], changes: result.changes ?? 0 };
2220
+ if (!res.ok) {
2221
+ let message = `Database query failed: ${res.status} ${res.statusText}`;
2222
+ try {
2223
+ const body = await res.json();
2224
+ if (body.error) message = body.error;
2225
+ } catch {
2226
+ }
2227
+ throw new MindStudioError(message, "db_query_error", res.status);
2228
+ }
2229
+ const data = await res.json();
2230
+ return data.results;
2444
2231
  }
2445
2232
  /**
2446
2233
  * @internal Create a lazy Db proxy that auto-hydrates context.
2447
2234
  *
2448
2235
  * defineTable() returns Table instances immediately (no async needed).
2449
- * But the Table's executeQuery callback is wrapped to call ensureContext()
2236
+ * But the Table's executeBatch callback is wrapped to call ensureContext()
2450
2237
  * before the first query, so context is fetched lazily.
2451
2238
  */
2452
2239
  _createLazyDb() {
@@ -2458,7 +2245,7 @@ var MindStudioAgent = class {
2458
2245
  databaseId: "",
2459
2246
  tableName: name,
2460
2247
  columns: [],
2461
- executeQuery: async (sql) => {
2248
+ executeBatch: async (queries) => {
2462
2249
  await agent.ensureContext();
2463
2250
  const databases = agent._context.databases;
2464
2251
  let targetDb;
@@ -2472,7 +2259,7 @@ var MindStudioAgent = class {
2472
2259
  );
2473
2260
  }
2474
2261
  const databaseId = targetDb?.id ?? databases[0]?.id ?? "";
2475
- return agent._executeDbQuery(databaseId, sql);
2262
+ return agent._executeDbBatch(databaseId, queries);
2476
2263
  }
2477
2264
  });
2478
2265
  },