@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/README.md +8 -2
- package/dist/cli.js +302 -405
- package/dist/index.d.ts +93 -342
- package/dist/index.js +294 -401
- package/dist/index.js.map +1 -1
- package/dist/postinstall.js +302 -405
- 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) {
|
|
@@ -693,8 +716,7 @@ var Parser = class {
|
|
|
693
716
|
return PARSE_FAILED;
|
|
694
717
|
}
|
|
695
718
|
/**
|
|
696
|
-
* Attempt to resolve a closure variable
|
|
697
|
-
* with a recording Proxy and inspecting what values it compares against.
|
|
719
|
+
* Attempt to resolve a closure variable's value.
|
|
698
720
|
*
|
|
699
721
|
* This handles the common pattern:
|
|
700
722
|
* ```ts
|
|
@@ -702,40 +724,28 @@ var Parser = class {
|
|
|
702
724
|
* orders.filter(o => o.requestedBy === userId)
|
|
703
725
|
* ```
|
|
704
726
|
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
727
|
+
* Closure variable resolution is fundamentally limited in JavaScript —
|
|
728
|
+
* we can't access another function's closure scope from outside without
|
|
729
|
+
* `eval`. The `===` operator can't be overridden via Proxy or
|
|
730
|
+
* Symbol.toPrimitive, so we can't intercept comparisons.
|
|
731
|
+
*
|
|
732
|
+
* For now, this falls back to JS execution. The predicate still works
|
|
733
|
+
* correctly — it just scans all rows instead of generating SQL.
|
|
734
|
+
* This is the most common reason for JS fallback in practice, since
|
|
735
|
+
* almost every real-world filter references a variable like `userId`.
|
|
736
|
+
*
|
|
737
|
+
* A future improvement could accept an explicit `vars` argument:
|
|
738
|
+
* ```ts
|
|
739
|
+
* orders.filter(o => o.requestedBy === $userId, { $userId: auth.userId })
|
|
740
|
+
* ```
|
|
709
741
|
*/
|
|
710
742
|
resolveClosureVariable() {
|
|
711
|
-
|
|
712
|
-
let closureExpr = identToken.value;
|
|
743
|
+
this.advance();
|
|
713
744
|
while (this.match("dot") && this.tokens[this.pos + 1]?.type === "identifier") {
|
|
714
745
|
this.advance();
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
try {
|
|
718
|
-
const MARKER = /* @__PURE__ */ Symbol("field_access_marker");
|
|
719
|
-
const accessed = [];
|
|
720
|
-
const proxy = new Proxy(
|
|
721
|
-
{},
|
|
722
|
-
{
|
|
723
|
-
get(_, prop) {
|
|
724
|
-
accessed.push(prop);
|
|
725
|
-
return new Proxy(() => MARKER, {
|
|
726
|
-
get(_2, nestedProp) {
|
|
727
|
-
accessed.push(nestedProp);
|
|
728
|
-
return MARKER;
|
|
729
|
-
}
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
);
|
|
734
|
-
void proxy;
|
|
735
|
-
return PARSE_FAILED;
|
|
736
|
-
} catch {
|
|
737
|
-
return PARSE_FAILED;
|
|
746
|
+
this.advance();
|
|
738
747
|
}
|
|
748
|
+
return PARSE_FAILED;
|
|
739
749
|
}
|
|
740
750
|
/**
|
|
741
751
|
* Look ahead to check if the next tokens form `.includes(`.
|
|
@@ -792,17 +802,11 @@ function isComparisonOp(value) {
|
|
|
792
802
|
|
|
793
803
|
// src/db/query.ts
|
|
794
804
|
var Query = class _Query {
|
|
795
|
-
/** @internal Accumulated predicate functions to filter by. */
|
|
796
805
|
_predicates;
|
|
797
|
-
/** @internal The field accessor for sorting, if set. */
|
|
798
806
|
_sortAccessor;
|
|
799
|
-
/** @internal Whether the sort order is reversed (DESC). */
|
|
800
807
|
_reversed;
|
|
801
|
-
/** @internal Maximum number of results (SQL LIMIT). */
|
|
802
808
|
_limit;
|
|
803
|
-
/** @internal Number of results to skip (SQL OFFSET). */
|
|
804
809
|
_offset;
|
|
805
|
-
/** @internal Binding to the database execution layer. */
|
|
806
810
|
_config;
|
|
807
811
|
constructor(config, options) {
|
|
808
812
|
this._config = config;
|
|
@@ -812,10 +816,6 @@ var Query = class _Query {
|
|
|
812
816
|
this._limit = options?.limit;
|
|
813
817
|
this._offset = options?.offset;
|
|
814
818
|
}
|
|
815
|
-
/**
|
|
816
|
-
* Create a clone of this query with some options overridden.
|
|
817
|
-
* Used internally by chain methods to maintain immutability.
|
|
818
|
-
*/
|
|
819
819
|
_clone(overrides) {
|
|
820
820
|
return new _Query(this._config, {
|
|
821
821
|
predicates: overrides.predicates ?? this._predicates,
|
|
@@ -826,126 +826,73 @@ var Query = class _Query {
|
|
|
826
826
|
});
|
|
827
827
|
}
|
|
828
828
|
// -------------------------------------------------------------------------
|
|
829
|
-
// Chain methods
|
|
829
|
+
// Chain methods
|
|
830
830
|
// -------------------------------------------------------------------------
|
|
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
831
|
filter(predicate) {
|
|
842
|
-
return this._clone({
|
|
843
|
-
predicates: [...this._predicates, predicate]
|
|
844
|
-
});
|
|
832
|
+
return this._clone({ predicates: [...this._predicates, predicate] });
|
|
845
833
|
}
|
|
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
834
|
sortBy(accessor) {
|
|
856
835
|
return this._clone({ sortAccessor: accessor });
|
|
857
836
|
}
|
|
858
|
-
/**
|
|
859
|
-
* Reverse the current sort order. If no sort is set, this has no effect.
|
|
860
|
-
*/
|
|
861
837
|
reverse() {
|
|
862
838
|
return this._clone({ reversed: !this._reversed });
|
|
863
839
|
}
|
|
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
840
|
take(n) {
|
|
873
841
|
return this._clone({ limit: n });
|
|
874
842
|
}
|
|
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
843
|
skip(n) {
|
|
884
844
|
return this._clone({ offset: n });
|
|
885
845
|
}
|
|
886
846
|
// -------------------------------------------------------------------------
|
|
887
|
-
// Terminal methods
|
|
847
|
+
// Terminal methods
|
|
888
848
|
// -------------------------------------------------------------------------
|
|
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
849
|
async first() {
|
|
894
850
|
const rows = await this._clone({ limit: 1 })._execute();
|
|
895
851
|
return rows[0] ?? null;
|
|
896
852
|
}
|
|
897
|
-
/**
|
|
898
|
-
* Return the last matching row (per current sort), or null.
|
|
899
|
-
* Flips the sort direction and takes 1 row.
|
|
900
|
-
*/
|
|
901
853
|
async last() {
|
|
902
854
|
const rows = await this._clone({ limit: 1, reversed: !this._reversed })._execute();
|
|
903
855
|
return rows[0] ?? null;
|
|
904
856
|
}
|
|
905
|
-
/**
|
|
906
|
-
* Count matching rows. Returns a number, not the rows themselves.
|
|
907
|
-
* Executes as `SELECT COUNT(*)` when predicates compile to SQL.
|
|
908
|
-
*/
|
|
909
857
|
async count() {
|
|
910
858
|
const compiled = this._compilePredicates();
|
|
911
859
|
if (compiled.allSql) {
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
860
|
+
const query = buildCount(
|
|
861
|
+
this._config.tableName,
|
|
862
|
+
compiled.sqlWhere || void 0
|
|
863
|
+
);
|
|
864
|
+
const results = await this._config.executeBatch([query]);
|
|
865
|
+
const row = results[0]?.rows[0];
|
|
916
866
|
return row?.count ?? 0;
|
|
917
867
|
}
|
|
918
868
|
const rows = await this._fetchAndFilterInJs(compiled);
|
|
919
869
|
return rows.length;
|
|
920
870
|
}
|
|
921
|
-
/**
|
|
922
|
-
* Check if any row matches the current filters. Short-circuits —
|
|
923
|
-
* doesn't load all rows when using SQL.
|
|
924
|
-
*/
|
|
925
871
|
async some() {
|
|
926
872
|
const compiled = this._compilePredicates();
|
|
927
873
|
if (compiled.allSql) {
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
874
|
+
const query = buildExists(
|
|
875
|
+
this._config.tableName,
|
|
876
|
+
compiled.sqlWhere || void 0
|
|
877
|
+
);
|
|
878
|
+
const results = await this._config.executeBatch([query]);
|
|
879
|
+
const row = results[0]?.rows[0];
|
|
932
880
|
return row?.result === 1;
|
|
933
881
|
}
|
|
934
882
|
const rows = await this._fetchAndFilterInJs(compiled);
|
|
935
883
|
return rows.length > 0;
|
|
936
884
|
}
|
|
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
885
|
async every() {
|
|
944
886
|
const compiled = this._compilePredicates();
|
|
945
887
|
if (compiled.allSql && compiled.sqlWhere) {
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
|
|
888
|
+
const query = buildExists(
|
|
889
|
+
this._config.tableName,
|
|
890
|
+
`NOT (${compiled.sqlWhere})`,
|
|
891
|
+
void 0,
|
|
892
|
+
true
|
|
893
|
+
);
|
|
894
|
+
const results = await this._config.executeBatch([query]);
|
|
895
|
+
const row = results[0]?.rows[0];
|
|
949
896
|
return row?.result === 1;
|
|
950
897
|
}
|
|
951
898
|
if (this._predicates.length === 0) return true;
|
|
@@ -954,24 +901,12 @@ var Query = class _Query {
|
|
|
954
901
|
(row) => this._predicates.every((pred) => pred(row))
|
|
955
902
|
);
|
|
956
903
|
}
|
|
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
904
|
async min(accessor) {
|
|
962
905
|
return this.sortBy(accessor).first();
|
|
963
906
|
}
|
|
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
907
|
async max(accessor) {
|
|
969
908
|
return this.sortBy(accessor).reverse().first();
|
|
970
909
|
}
|
|
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
910
|
async groupBy(accessor) {
|
|
976
911
|
const rows = await this._execute();
|
|
977
912
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -987,40 +922,97 @@ var Query = class _Query {
|
|
|
987
922
|
return map;
|
|
988
923
|
}
|
|
989
924
|
// -------------------------------------------------------------------------
|
|
990
|
-
//
|
|
925
|
+
// Batch compilation — used by db.batch() to extract SQL without executing
|
|
991
926
|
// -------------------------------------------------------------------------
|
|
992
927
|
/**
|
|
993
|
-
*
|
|
994
|
-
*
|
|
928
|
+
* @internal Compile this query into a SqlQuery for batch execution.
|
|
929
|
+
*
|
|
930
|
+
* Returns the compiled SQL query (if all predicates compile to SQL),
|
|
931
|
+
* or null (if JS fallback is needed). In the fallback case, a bare
|
|
932
|
+
* `SELECT *` is returned as `fallbackQuery` so the batch can fetch
|
|
933
|
+
* all rows and this query can filter them in JS post-fetch.
|
|
934
|
+
*/
|
|
935
|
+
_compile() {
|
|
936
|
+
const compiled = this._compilePredicates();
|
|
937
|
+
const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
|
|
938
|
+
if (compiled.allSql) {
|
|
939
|
+
const query = buildSelect(this._config.tableName, {
|
|
940
|
+
where: compiled.sqlWhere || void 0,
|
|
941
|
+
orderBy: sortField ?? void 0,
|
|
942
|
+
desc: this._reversed,
|
|
943
|
+
limit: this._limit,
|
|
944
|
+
offset: this._offset
|
|
945
|
+
});
|
|
946
|
+
return { query, fallbackQuery: null, config: this._config };
|
|
947
|
+
}
|
|
948
|
+
const fallbackQuery = buildSelect(this._config.tableName);
|
|
949
|
+
return {
|
|
950
|
+
query: null,
|
|
951
|
+
fallbackQuery,
|
|
952
|
+
config: this._config,
|
|
953
|
+
predicates: this._predicates,
|
|
954
|
+
sortAccessor: this._sortAccessor,
|
|
955
|
+
reversed: this._reversed,
|
|
956
|
+
limit: this._limit,
|
|
957
|
+
offset: this._offset
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* @internal Process raw SQL results into typed rows. Used by db.batch()
|
|
962
|
+
* after executing the compiled query.
|
|
963
|
+
*
|
|
964
|
+
* For SQL-compiled queries: just deserialize the rows.
|
|
965
|
+
* For JS-fallback queries: filter, sort, and slice in JS.
|
|
995
966
|
*/
|
|
967
|
+
static _processResults(result, compiled) {
|
|
968
|
+
const rows = result.rows.map(
|
|
969
|
+
(row) => deserializeRow(
|
|
970
|
+
row,
|
|
971
|
+
compiled.config.columns
|
|
972
|
+
)
|
|
973
|
+
);
|
|
974
|
+
if (compiled.query) return rows;
|
|
975
|
+
let filtered = compiled.predicates ? rows.filter((row) => compiled.predicates.every((pred) => pred(row))) : rows;
|
|
976
|
+
if (compiled.sortAccessor) {
|
|
977
|
+
const accessor = compiled.sortAccessor;
|
|
978
|
+
const reversed = compiled.reversed ?? false;
|
|
979
|
+
filtered.sort((a, b) => {
|
|
980
|
+
const aVal = accessor(a);
|
|
981
|
+
const bVal = accessor(b);
|
|
982
|
+
if (aVal < bVal) return reversed ? 1 : -1;
|
|
983
|
+
if (aVal > bVal) return reversed ? -1 : 1;
|
|
984
|
+
return 0;
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
if (compiled.offset != null || compiled.limit != null) {
|
|
988
|
+
const start = compiled.offset ?? 0;
|
|
989
|
+
const end = compiled.limit != null ? start + compiled.limit : void 0;
|
|
990
|
+
filtered = filtered.slice(start, end);
|
|
991
|
+
}
|
|
992
|
+
return filtered;
|
|
993
|
+
}
|
|
994
|
+
// -------------------------------------------------------------------------
|
|
995
|
+
// PromiseLike
|
|
996
|
+
// -------------------------------------------------------------------------
|
|
996
997
|
then(onfulfilled, onrejected) {
|
|
997
998
|
return this._execute().then(onfulfilled, onrejected);
|
|
998
999
|
}
|
|
999
1000
|
// -------------------------------------------------------------------------
|
|
1000
1001
|
// Execution internals
|
|
1001
1002
|
// -------------------------------------------------------------------------
|
|
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
1003
|
async _execute() {
|
|
1012
1004
|
const compiled = this._compilePredicates();
|
|
1013
1005
|
if (compiled.allSql) {
|
|
1014
1006
|
const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
|
|
1015
|
-
const
|
|
1007
|
+
const query = buildSelect(this._config.tableName, {
|
|
1016
1008
|
where: compiled.sqlWhere || void 0,
|
|
1017
1009
|
orderBy: sortField ?? void 0,
|
|
1018
1010
|
desc: this._reversed,
|
|
1019
1011
|
limit: this._limit,
|
|
1020
1012
|
offset: this._offset
|
|
1021
1013
|
});
|
|
1022
|
-
const
|
|
1023
|
-
return
|
|
1014
|
+
const results = await this._config.executeBatch([query]);
|
|
1015
|
+
return results[0].rows.map(
|
|
1024
1016
|
(row) => deserializeRow(
|
|
1025
1017
|
row,
|
|
1026
1018
|
this._config.columns
|
|
@@ -1045,14 +1037,6 @@ var Query = class _Query {
|
|
|
1045
1037
|
}
|
|
1046
1038
|
return rows;
|
|
1047
1039
|
}
|
|
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
1040
|
_compilePredicates() {
|
|
1057
1041
|
if (this._predicates.length === 0) {
|
|
1058
1042
|
return { allSql: true, sqlWhere: "", compiled: [] };
|
|
@@ -1065,12 +1049,6 @@ var Query = class _Query {
|
|
|
1065
1049
|
}
|
|
1066
1050
|
return { allSql, sqlWhere, compiled };
|
|
1067
1051
|
}
|
|
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
1052
|
async _fetchAndFilterInJs(compiled) {
|
|
1075
1053
|
const allRows = await this._fetchAllRows();
|
|
1076
1054
|
if (compiled.compiled.some((c) => c.type === "js")) {
|
|
@@ -1082,14 +1060,10 @@ var Query = class _Query {
|
|
|
1082
1060
|
(row) => this._predicates.every((pred) => pred(row))
|
|
1083
1061
|
);
|
|
1084
1062
|
}
|
|
1085
|
-
/**
|
|
1086
|
-
* Fetch all rows from the table (SELECT * with no WHERE).
|
|
1087
|
-
* Used by the JS fallback path.
|
|
1088
|
-
*/
|
|
1089
1063
|
async _fetchAllRows() {
|
|
1090
|
-
const
|
|
1091
|
-
const
|
|
1092
|
-
return
|
|
1064
|
+
const query = buildSelect(this._config.tableName);
|
|
1065
|
+
const results = await this._config.executeBatch([query]);
|
|
1066
|
+
return results[0].rows.map(
|
|
1093
1067
|
(row) => deserializeRow(row, this._config.columns)
|
|
1094
1068
|
);
|
|
1095
1069
|
}
|
|
@@ -1104,298 +1078,147 @@ function extractFieldName(accessor) {
|
|
|
1104
1078
|
|
|
1105
1079
|
// src/db/table.ts
|
|
1106
1080
|
var Table = class {
|
|
1107
|
-
/** @internal
|
|
1081
|
+
/** @internal */
|
|
1108
1082
|
_config;
|
|
1109
1083
|
constructor(config) {
|
|
1110
1084
|
this._config = config;
|
|
1111
1085
|
}
|
|
1112
1086
|
// -------------------------------------------------------------------------
|
|
1113
|
-
// Reads — direct
|
|
1087
|
+
// Reads — direct
|
|
1114
1088
|
// -------------------------------------------------------------------------
|
|
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
1089
|
async get(id) {
|
|
1125
|
-
const
|
|
1126
|
-
where: `id =
|
|
1090
|
+
const query = buildSelect(this._config.tableName, {
|
|
1091
|
+
where: `id = ?`,
|
|
1092
|
+
whereParams: [id],
|
|
1127
1093
|
limit: 1
|
|
1128
1094
|
});
|
|
1129
|
-
const
|
|
1130
|
-
if (
|
|
1095
|
+
const results = await this._config.executeBatch([query]);
|
|
1096
|
+
if (results[0].rows.length === 0) return null;
|
|
1131
1097
|
return deserializeRow(
|
|
1132
|
-
|
|
1098
|
+
results[0].rows[0],
|
|
1133
1099
|
this._config.columns
|
|
1134
1100
|
);
|
|
1135
1101
|
}
|
|
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
1102
|
async findOne(predicate) {
|
|
1145
1103
|
return this.filter(predicate).first();
|
|
1146
1104
|
}
|
|
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
1105
|
async count(predicate) {
|
|
1157
|
-
if (predicate)
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
const
|
|
1161
|
-
const result = await this._config.executeQuery(sql);
|
|
1162
|
-
const row = result.rows[0];
|
|
1106
|
+
if (predicate) return this.filter(predicate).count();
|
|
1107
|
+
const query = buildCount(this._config.tableName);
|
|
1108
|
+
const results = await this._config.executeBatch([query]);
|
|
1109
|
+
const row = results[0]?.rows[0];
|
|
1163
1110
|
return row?.count ?? 0;
|
|
1164
1111
|
}
|
|
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
1112
|
async some(predicate) {
|
|
1174
1113
|
return this.filter(predicate).some();
|
|
1175
1114
|
}
|
|
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
1115
|
async every(predicate) {
|
|
1185
1116
|
return this.filter(predicate).every();
|
|
1186
1117
|
}
|
|
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
1118
|
async isEmpty() {
|
|
1196
|
-
const
|
|
1197
|
-
const
|
|
1198
|
-
const row =
|
|
1119
|
+
const query = buildExists(this._config.tableName, void 0, void 0, true);
|
|
1120
|
+
const results = await this._config.executeBatch([query]);
|
|
1121
|
+
const row = results[0]?.rows[0];
|
|
1199
1122
|
return row?.result === 1;
|
|
1200
1123
|
}
|
|
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
1124
|
async min(accessor) {
|
|
1211
1125
|
return this.sortBy(accessor).first();
|
|
1212
1126
|
}
|
|
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
1127
|
async max(accessor) {
|
|
1223
1128
|
return this.sortBy(accessor).reverse().first();
|
|
1224
1129
|
}
|
|
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
1130
|
async groupBy(accessor) {
|
|
1235
1131
|
return new Query(this._config).groupBy(accessor);
|
|
1236
1132
|
}
|
|
1237
1133
|
// -------------------------------------------------------------------------
|
|
1238
|
-
// Reads — chainable
|
|
1134
|
+
// Reads — chainable
|
|
1239
1135
|
// -------------------------------------------------------------------------
|
|
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
1136
|
filter(predicate) {
|
|
1257
1137
|
return new Query(this._config).filter(predicate);
|
|
1258
1138
|
}
|
|
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
1139
|
sortBy(accessor) {
|
|
1268
1140
|
return new Query(this._config).sortBy(accessor);
|
|
1269
1141
|
}
|
|
1270
1142
|
async push(data) {
|
|
1271
1143
|
const isArray = Array.isArray(data);
|
|
1272
1144
|
const items = isArray ? data : [data];
|
|
1273
|
-
const
|
|
1274
|
-
|
|
1275
|
-
const insertSql = buildInsert(
|
|
1145
|
+
const queries = items.map(
|
|
1146
|
+
(item) => buildInsert(
|
|
1276
1147
|
this._config.tableName,
|
|
1277
1148
|
item,
|
|
1278
1149
|
this._config.columns
|
|
1279
|
-
)
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
if (
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
this._config.columns
|
|
1288
|
-
)
|
|
1150
|
+
)
|
|
1151
|
+
);
|
|
1152
|
+
const results = await this._config.executeBatch(queries);
|
|
1153
|
+
const rows = results.map((r) => {
|
|
1154
|
+
if (r.rows.length > 0) {
|
|
1155
|
+
return deserializeRow(
|
|
1156
|
+
r.rows[0],
|
|
1157
|
+
this._config.columns
|
|
1289
1158
|
);
|
|
1290
1159
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1160
|
+
return void 0;
|
|
1161
|
+
});
|
|
1162
|
+
return isArray ? rows : rows[0];
|
|
1293
1163
|
}
|
|
1294
1164
|
/**
|
|
1295
1165
|
* 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
|
-
* ```
|
|
1166
|
+
* Returns the updated row via `UPDATE ... RETURNING *`.
|
|
1306
1167
|
*/
|
|
1307
1168
|
async update(id, data) {
|
|
1308
|
-
const
|
|
1169
|
+
const query = buildUpdate(
|
|
1309
1170
|
this._config.tableName,
|
|
1310
1171
|
id,
|
|
1311
1172
|
data,
|
|
1312
1173
|
this._config.columns
|
|
1313
1174
|
);
|
|
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);
|
|
1175
|
+
const results = await this._config.executeBatch([query]);
|
|
1320
1176
|
return deserializeRow(
|
|
1321
|
-
|
|
1177
|
+
results[0].rows[0],
|
|
1322
1178
|
this._config.columns
|
|
1323
1179
|
);
|
|
1324
1180
|
}
|
|
1325
|
-
/**
|
|
1326
|
-
* Remove a row by ID.
|
|
1327
|
-
*
|
|
1328
|
-
* @example
|
|
1329
|
-
* ```ts
|
|
1330
|
-
* await Orders.remove('abc-123');
|
|
1331
|
-
* ```
|
|
1332
|
-
*/
|
|
1333
1181
|
async remove(id) {
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1336
|
-
`id = ${escapeValue(id)}`
|
|
1337
|
-
);
|
|
1338
|
-
await this._config.executeQuery(sql);
|
|
1182
|
+
const query = buildDelete(this._config.tableName, `id = ?`, [id]);
|
|
1183
|
+
await this._config.executeBatch([query]);
|
|
1339
1184
|
}
|
|
1340
1185
|
/**
|
|
1341
1186
|
* 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
1187
|
*/
|
|
1353
1188
|
async removeAll(predicate) {
|
|
1354
1189
|
const compiled = compilePredicate(predicate);
|
|
1355
1190
|
if (compiled.type === "sql") {
|
|
1356
|
-
const
|
|
1357
|
-
const
|
|
1358
|
-
return
|
|
1191
|
+
const query = buildDelete(this._config.tableName, compiled.where);
|
|
1192
|
+
const results = await this._config.executeBatch([query]);
|
|
1193
|
+
return results[0].changes;
|
|
1359
1194
|
}
|
|
1360
1195
|
console.warn(
|
|
1361
1196
|
`[mindstudio] removeAll predicate on ${this._config.tableName} could not be compiled to SQL \u2014 fetching all rows first`
|
|
1362
1197
|
);
|
|
1363
|
-
const
|
|
1364
|
-
const
|
|
1365
|
-
const allRows =
|
|
1198
|
+
const allQuery = buildSelect(this._config.tableName);
|
|
1199
|
+
const allResults = await this._config.executeBatch([allQuery]);
|
|
1200
|
+
const allRows = allResults[0].rows.map(
|
|
1366
1201
|
(r) => deserializeRow(
|
|
1367
1202
|
r,
|
|
1368
1203
|
this._config.columns
|
|
1369
1204
|
)
|
|
1370
1205
|
);
|
|
1371
1206
|
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
|
-
}
|
|
1207
|
+
if (matching.length === 0) return 0;
|
|
1208
|
+
const deleteQueries = matching.filter((row) => row.id).map((row) => buildDelete(this._config.tableName, `id = ?`, [row.id]));
|
|
1209
|
+
if (deleteQueries.length > 0) {
|
|
1210
|
+
await this._config.executeBatch(deleteQueries);
|
|
1380
1211
|
}
|
|
1381
|
-
return
|
|
1212
|
+
return matching.length;
|
|
1382
1213
|
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Remove all rows from the table.
|
|
1385
|
-
*
|
|
1386
|
-
* @example
|
|
1387
|
-
* ```ts
|
|
1388
|
-
* await Orders.clear();
|
|
1389
|
-
* ```
|
|
1390
|
-
*/
|
|
1391
1214
|
async clear() {
|
|
1392
|
-
const
|
|
1393
|
-
await this._config.
|
|
1215
|
+
const query = buildDelete(this._config.tableName);
|
|
1216
|
+
await this._config.executeBatch([query]);
|
|
1394
1217
|
}
|
|
1395
1218
|
};
|
|
1396
1219
|
|
|
1397
1220
|
// src/db/index.ts
|
|
1398
|
-
function createDb(databases,
|
|
1221
|
+
function createDb(databases, executeBatch) {
|
|
1399
1222
|
return {
|
|
1400
1223
|
defineTable(name, options) {
|
|
1401
1224
|
const resolved = resolveTable(databases, name, options?.database);
|
|
@@ -1403,7 +1226,7 @@ function createDb(databases, executeQuery) {
|
|
|
1403
1226
|
databaseId: resolved.databaseId,
|
|
1404
1227
|
tableName: name,
|
|
1405
1228
|
columns: resolved.columns,
|
|
1406
|
-
|
|
1229
|
+
executeBatch: (queries) => executeBatch(resolved.databaseId, queries)
|
|
1407
1230
|
};
|
|
1408
1231
|
return new Table(config);
|
|
1409
1232
|
},
|
|
@@ -1414,7 +1237,49 @@ function createDb(databases, executeQuery) {
|
|
|
1414
1237
|
hours: (n) => n * 36e5,
|
|
1415
1238
|
minutes: (n) => n * 6e4,
|
|
1416
1239
|
ago: (ms) => Date.now() - ms,
|
|
1417
|
-
fromNow: (ms) => Date.now() + ms
|
|
1240
|
+
fromNow: (ms) => Date.now() + ms,
|
|
1241
|
+
// --- Batch execution ---
|
|
1242
|
+
batch: ((...queries) => {
|
|
1243
|
+
return (async () => {
|
|
1244
|
+
const compiled = queries.map((q) => {
|
|
1245
|
+
if (!(q instanceof Query)) {
|
|
1246
|
+
throw new MindStudioError(
|
|
1247
|
+
"db.batch() only accepts Query objects (from .filter(), .sortBy(), etc.)",
|
|
1248
|
+
"invalid_batch_query",
|
|
1249
|
+
400
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
return q._compile();
|
|
1253
|
+
});
|
|
1254
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1255
|
+
for (let i = 0; i < compiled.length; i++) {
|
|
1256
|
+
const c = compiled[i];
|
|
1257
|
+
const dbId = c.config.databaseId;
|
|
1258
|
+
const sqlQuery = c.query ?? c.fallbackQuery;
|
|
1259
|
+
if (!groups.has(dbId)) groups.set(dbId, []);
|
|
1260
|
+
groups.get(dbId).push({ index: i, sqlQuery });
|
|
1261
|
+
}
|
|
1262
|
+
const allResults = new Array(compiled.length);
|
|
1263
|
+
await Promise.all(
|
|
1264
|
+
Array.from(groups.entries()).map(async ([dbId, entries]) => {
|
|
1265
|
+
const sqlQueries = entries.map((e) => e.sqlQuery);
|
|
1266
|
+
const results = await executeBatch(dbId, sqlQueries);
|
|
1267
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1268
|
+
allResults[entries[i].index] = results[i];
|
|
1269
|
+
}
|
|
1270
|
+
})
|
|
1271
|
+
);
|
|
1272
|
+
return compiled.map((c, i) => {
|
|
1273
|
+
const result = allResults[i];
|
|
1274
|
+
if (!c.query && c.predicates?.length) {
|
|
1275
|
+
console.warn(
|
|
1276
|
+
`[mindstudio] db.batch(): filter on ${c.config.tableName} could not be compiled to SQL \u2014 processing in JS`
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
return Query._processResults(result, c);
|
|
1280
|
+
});
|
|
1281
|
+
})();
|
|
1282
|
+
})
|
|
1418
1283
|
};
|
|
1419
1284
|
}
|
|
1420
1285
|
function resolveTable(databases, tableName, databaseHint) {
|
|
@@ -2325,6 +2190,9 @@ var MindStudioAgent = class {
|
|
|
2325
2190
|
* ```
|
|
2326
2191
|
*/
|
|
2327
2192
|
get auth() {
|
|
2193
|
+
if (!this._auth) {
|
|
2194
|
+
this._trySandboxHydration();
|
|
2195
|
+
}
|
|
2328
2196
|
if (!this._auth) {
|
|
2329
2197
|
throw new MindStudioError(
|
|
2330
2198
|
"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 +2218,9 @@ var MindStudioAgent = class {
|
|
|
2350
2218
|
* ```
|
|
2351
2219
|
*/
|
|
2352
2220
|
get db() {
|
|
2221
|
+
if (!this._db) {
|
|
2222
|
+
this._trySandboxHydration();
|
|
2223
|
+
}
|
|
2353
2224
|
if (this._db) return this._db;
|
|
2354
2225
|
return this._createLazyDb();
|
|
2355
2226
|
}
|
|
@@ -2407,7 +2278,7 @@ var MindStudioAgent = class {
|
|
|
2407
2278
|
this._auth = new AuthContext(context.auth);
|
|
2408
2279
|
this._db = createDb(
|
|
2409
2280
|
context.databases,
|
|
2410
|
-
this.
|
|
2281
|
+
this._executeDbBatch.bind(this)
|
|
2411
2282
|
);
|
|
2412
2283
|
}
|
|
2413
2284
|
/**
|
|
@@ -2428,25 +2299,40 @@ var MindStudioAgent = class {
|
|
|
2428
2299
|
}
|
|
2429
2300
|
}
|
|
2430
2301
|
/**
|
|
2431
|
-
* @internal Execute a SQL
|
|
2432
|
-
* Used as the `
|
|
2302
|
+
* @internal Execute a batch of SQL queries against a managed database.
|
|
2303
|
+
* Used as the `executeBatch` callback for Table/Query instances.
|
|
2433
2304
|
*
|
|
2434
|
-
* Calls
|
|
2435
|
-
* (
|
|
2305
|
+
* Calls `POST /_internal/v2/db/query` directly with the hook token
|
|
2306
|
+
* (raw, no Bearer prefix). All queries run on a single SQLite connection,
|
|
2307
|
+
* enabling RETURNING clauses and multi-statement batches.
|
|
2436
2308
|
*/
|
|
2437
|
-
async
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2309
|
+
async _executeDbBatch(databaseId, queries) {
|
|
2310
|
+
const url = `${this._httpConfig.baseUrl}/_internal/v2/db/query`;
|
|
2311
|
+
const res = await fetch(url, {
|
|
2312
|
+
method: "POST",
|
|
2313
|
+
headers: {
|
|
2314
|
+
"Content-Type": "application/json",
|
|
2315
|
+
Authorization: this._httpConfig.token
|
|
2316
|
+
},
|
|
2317
|
+
body: JSON.stringify({ databaseId, queries })
|
|
2442
2318
|
});
|
|
2443
|
-
|
|
2319
|
+
if (!res.ok) {
|
|
2320
|
+
let message = `Database query failed: ${res.status} ${res.statusText}`;
|
|
2321
|
+
try {
|
|
2322
|
+
const body = await res.json();
|
|
2323
|
+
if (body.error) message = body.error;
|
|
2324
|
+
} catch {
|
|
2325
|
+
}
|
|
2326
|
+
throw new MindStudioError(message, "db_query_error", res.status);
|
|
2327
|
+
}
|
|
2328
|
+
const data = await res.json();
|
|
2329
|
+
return data.results;
|
|
2444
2330
|
}
|
|
2445
2331
|
/**
|
|
2446
2332
|
* @internal Create a lazy Db proxy that auto-hydrates context.
|
|
2447
2333
|
*
|
|
2448
2334
|
* defineTable() returns Table instances immediately (no async needed).
|
|
2449
|
-
* But the Table's
|
|
2335
|
+
* But the Table's executeBatch callback is wrapped to call ensureContext()
|
|
2450
2336
|
* before the first query, so context is fetched lazily.
|
|
2451
2337
|
*/
|
|
2452
2338
|
_createLazyDb() {
|
|
@@ -2458,7 +2344,7 @@ var MindStudioAgent = class {
|
|
|
2458
2344
|
databaseId: "",
|
|
2459
2345
|
tableName: name,
|
|
2460
2346
|
columns: [],
|
|
2461
|
-
|
|
2347
|
+
executeBatch: async (queries) => {
|
|
2462
2348
|
await agent.ensureContext();
|
|
2463
2349
|
const databases = agent._context.databases;
|
|
2464
2350
|
let targetDb;
|
|
@@ -2472,7 +2358,7 @@ var MindStudioAgent = class {
|
|
|
2472
2358
|
);
|
|
2473
2359
|
}
|
|
2474
2360
|
const databaseId = targetDb?.id ?? databases[0]?.id ?? "";
|
|
2475
|
-
return agent.
|
|
2361
|
+
return agent._executeDbBatch(databaseId, queries);
|
|
2476
2362
|
}
|
|
2477
2363
|
});
|
|
2478
2364
|
},
|
|
@@ -2482,7 +2368,14 @@ var MindStudioAgent = class {
|
|
|
2482
2368
|
hours: (n) => n * 36e5,
|
|
2483
2369
|
minutes: (n) => n * 6e4,
|
|
2484
2370
|
ago: (ms) => Date.now() - ms,
|
|
2485
|
-
fromNow: (ms) => Date.now() + ms
|
|
2371
|
+
fromNow: (ms) => Date.now() + ms,
|
|
2372
|
+
// Batch needs context — hydrate first, then delegate to real db
|
|
2373
|
+
batch: ((...queries) => {
|
|
2374
|
+
return (async () => {
|
|
2375
|
+
await agent.ensureContext();
|
|
2376
|
+
return agent._db.batch(...queries);
|
|
2377
|
+
})();
|
|
2378
|
+
})
|
|
2486
2379
|
};
|
|
2487
2380
|
}
|
|
2488
2381
|
// -------------------------------------------------------------------------
|