@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/README.md +8 -2
- package/dist/cli.js +164 -374
- package/dist/index.d.ts +35 -343
- package/dist/index.js +157 -370
- package/dist/index.js.map +1 -1
- package/dist/postinstall.js +164 -374
- package/package.json +1 -1
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
|
-
|
|
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
|
|
270
|
-
|
|
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
|
|
275
|
-
|
|
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
|
|
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
|
|
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
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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
|
|
947
|
-
|
|
948
|
-
|
|
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
|
|
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
|
|
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
|
|
1023
|
-
return
|
|
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
|
|
1091
|
-
const
|
|
1092
|
-
return
|
|
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
|
|
1024
|
+
/** @internal */
|
|
1108
1025
|
_config;
|
|
1109
1026
|
constructor(config) {
|
|
1110
1027
|
this._config = config;
|
|
1111
1028
|
}
|
|
1112
1029
|
// -------------------------------------------------------------------------
|
|
1113
|
-
// Reads — direct
|
|
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
|
|
1126
|
-
where: `id =
|
|
1033
|
+
const query = buildSelect(this._config.tableName, {
|
|
1034
|
+
where: `id = ?`,
|
|
1035
|
+
whereParams: [id],
|
|
1127
1036
|
limit: 1
|
|
1128
1037
|
});
|
|
1129
|
-
const
|
|
1130
|
-
if (
|
|
1038
|
+
const results = await this._config.executeBatch([query]);
|
|
1039
|
+
if (results[0].rows.length === 0) return null;
|
|
1131
1040
|
return deserializeRow(
|
|
1132
|
-
|
|
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
|
-
|
|
1159
|
-
|
|
1160
|
-
const
|
|
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
|
|
1197
|
-
const
|
|
1198
|
-
const row =
|
|
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
|
|
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
|
|
1274
|
-
|
|
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
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
if (
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
1335
|
-
|
|
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
|
|
1357
|
-
const
|
|
1358
|
-
return
|
|
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
|
|
1364
|
-
const
|
|
1365
|
-
const allRows =
|
|
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
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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
|
|
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
|
|
1393
|
-
await this._config.
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
|
2432
|
-
* Used as the `
|
|
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
|
|
2435
|
-
* (
|
|
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
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
2262
|
+
return agent._executeDbBatch(databaseId, queries);
|
|
2476
2263
|
}
|
|
2477
2264
|
});
|
|
2478
2265
|
},
|