@mindstudio-ai/agent 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -156,9 +156,7 @@ interface UserInfoResult {
156
156
  * }
157
157
  * ```
158
158
  */
159
- type User = string & {
160
- readonly __brand: 'User';
161
- };
159
+ type User = string;
162
160
  /**
163
161
  * Resolved display info for a platform user. Returned by `resolveUser()`
164
162
  * and `resolveUsers()`.
@@ -597,16 +595,26 @@ interface TableConfig {
597
595
  */
598
596
  columns: AppDatabaseColumnSchema[];
599
597
  /**
600
- * Execute a SQL query against the managed database. This is bound to
601
- * `agent.executeStep('queryAppDatabase', ...)` at creation time.
598
+ * Execute one or more SQL queries against the managed database in a
599
+ * single round trip. All queries run on the same SQLite connection,
600
+ * enabling RETURNING clauses and multi-statement batches.
601
+ *
602
+ * Bound to the `POST /_internal/v2/db/query` endpoint at creation time.
602
603
  *
603
- * @param sql - The SQL query string (fully formed, no placeholders)
604
- * @returns The query result: rows for SELECT, changes count for writes
604
+ * @param queries - Array of SQL queries with optional bind params
605
+ * @returns Array of results in the same order as the input queries
605
606
  */
606
- executeQuery: (sql: string) => Promise<{
607
- rows: unknown[];
608
- changes: number;
609
- }>;
607
+ executeBatch: (queries: SqlQuery[]) => Promise<SqlResult[]>;
608
+ }
609
+ /** A single SQL query with optional positional bind params. */
610
+ interface SqlQuery {
611
+ sql: string;
612
+ params?: unknown[];
613
+ }
614
+ /** Result of a single SQL query execution. */
615
+ interface SqlResult {
616
+ rows: unknown[];
617
+ changes: number;
610
618
  }
611
619
 
612
620
  /**
@@ -642,53 +650,14 @@ interface TableConfig {
642
650
  *
643
651
  * Both paths produce identical results. The SQL path is a transparent
644
652
  * performance optimization.
645
- *
646
- * ## Data flow
647
- *
648
- * ```
649
- * .filter(pred1).filter(pred2).sortBy(fn).reverse().take(10)
650
- * ↓
651
- * Query { predicates: [pred1, pred2], sortAccessor: fn, reversed: true, limit: 10 }
652
- * ↓ (on await)
653
- * compilePredicate(pred1) → SQL? compilePredicate(pred2) → SQL?
654
- * ↓
655
- * All SQL? → buildSelect('table', { where: 'p1 AND p2', orderBy, desc, limit })
656
- * → executeQuery(sql) → deserialize rows → return T[]
657
- *
658
- * Any JS? → buildSelect('table', {}) → executeQuery → deserialize all rows
659
- * → Array.filter(pred1).filter(pred2).sort(...).slice(0, 10) → return T[]
660
- * ```
661
653
  */
662
654
 
663
- /**
664
- * A lazy, chainable database query. Implements PromiseLike<T[]> so it
665
- * can be awaited directly to get the result rows.
666
- *
667
- * @example
668
- * ```ts
669
- * // Chain operations (nothing executes yet)
670
- * const query = Orders
671
- * .filter(o => o.status === 'active')
672
- * .sortBy(o => o.createdAt)
673
- * .reverse()
674
- * .take(10);
675
- *
676
- * // Execute the query by awaiting it
677
- * const rows = await query;
678
- * ```
679
- */
680
655
  declare class Query<T> implements PromiseLike<T[]> {
681
- /** @internal Accumulated predicate functions to filter by. */
682
656
  private readonly _predicates;
683
- /** @internal The field accessor for sorting, if set. */
684
657
  private readonly _sortAccessor;
685
- /** @internal Whether the sort order is reversed (DESC). */
686
658
  private readonly _reversed;
687
- /** @internal Maximum number of results (SQL LIMIT). */
688
659
  private readonly _limit;
689
- /** @internal Number of results to skip (SQL OFFSET). */
690
660
  private readonly _offset;
691
- /** @internal Binding to the database execution layer. */
692
661
  private readonly _config;
693
662
  constructor(config: TableConfig, options?: {
694
663
  predicates?: Predicate<T>[];
@@ -697,356 +666,115 @@ declare class Query<T> implements PromiseLike<T[]> {
697
666
  limit?: number;
698
667
  offset?: number;
699
668
  });
700
- /**
701
- * Create a clone of this query with some options overridden.
702
- * Used internally by chain methods to maintain immutability.
703
- */
704
669
  private _clone;
705
- /**
706
- * Add a filter predicate. Multiple filters are ANDed together.
707
- *
708
- * @example
709
- * ```ts
710
- * const active = Orders.filter(o => o.status === 'active');
711
- * const expensive = active.filter(o => o.amount > 5000);
712
- * // WHERE status = 'active' AND amount > 5000
713
- * ```
714
- */
715
670
  filter(predicate: Predicate<T>): Query<T>;
716
- /**
717
- * Sort results by a field (ascending by default).
718
- * Use `.reverse()` after `.sortBy()` for descending order.
719
- *
720
- * @example
721
- * ```ts
722
- * const newest = Orders.sortBy(o => o.createdAt).reverse();
723
- * ```
724
- */
725
671
  sortBy(accessor: Accessor<T>): Query<T>;
726
- /**
727
- * Reverse the current sort order. If no sort is set, this has no effect.
728
- */
729
672
  reverse(): Query<T>;
730
- /**
731
- * Limit the number of results returned.
732
- *
733
- * @example
734
- * ```ts
735
- * const top10 = Orders.sortBy(o => o.amount).reverse().take(10);
736
- * ```
737
- */
738
673
  take(n: number): Query<T>;
739
- /**
740
- * Skip the first n results. Use with `.take()` for pagination.
741
- *
742
- * @example
743
- * ```ts
744
- * const page2 = Orders.sortBy(o => o.createdAt).skip(50).take(50);
745
- * ```
746
- */
747
674
  skip(n: number): Query<T>;
748
- /**
749
- * Return the first matching row, or null if no rows match.
750
- * Applies the current sort order before taking the first result.
751
- */
752
675
  first(): Promise<T | null>;
753
- /**
754
- * Return the last matching row (per current sort), or null.
755
- * Flips the sort direction and takes 1 row.
756
- */
757
676
  last(): Promise<T | null>;
758
- /**
759
- * Count matching rows. Returns a number, not the rows themselves.
760
- * Executes as `SELECT COUNT(*)` when predicates compile to SQL.
761
- */
762
677
  count(): Promise<number>;
763
- /**
764
- * Check if any row matches the current filters. Short-circuits —
765
- * doesn't load all rows when using SQL.
766
- */
767
678
  some(): Promise<boolean>;
768
- /**
769
- * Check if all rows match the current filters. Short-circuits on false.
770
- *
771
- * Implemented as NOT EXISTS(... WHERE NOT predicate) — returns true
772
- * if no rows fail the predicate.
773
- */
774
679
  every(): Promise<boolean>;
775
- /**
776
- * Return the row with the minimum value for the given field.
777
- * Executes as `ORDER BY field ASC LIMIT 1` in SQL.
778
- */
779
680
  min(accessor: Accessor<T, number>): Promise<T | null>;
780
- /**
781
- * Return the row with the maximum value for the given field.
782
- * Executes as `ORDER BY field DESC LIMIT 1` in SQL.
783
- */
784
681
  max(accessor: Accessor<T, number>): Promise<T | null>;
785
- /**
786
- * Group rows by a field value. Returns a Map.
787
- * Always executes in JS (no SQL equivalent for grouping into a Map).
788
- */
789
682
  groupBy<K extends string | number>(accessor: Accessor<T, K>): Promise<Map<K, T[]>>;
790
683
  /**
791
- * PromiseLike.then() executes the query and pipes the result.
792
- * This is what makes `const rows = await query` work.
793
- */
794
- then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
795
- /**
796
- * Execute the query and return typed result rows.
684
+ * @internal Compile this query into a SqlQuery for batch execution.
797
685
  *
798
- * This is the core execution method. It:
799
- * 1. Tries to compile all predicates to SQL
800
- * 2. If all compile builds and executes a single SQL query
801
- * 3. If any fail fetches all rows and processes in JS
802
- * 4. Deserializes rows (user prefix stripping, JSON parsing)
686
+ * Returns the compiled SQL query (if all predicates compile to SQL),
687
+ * or null (if JS fallback is needed). In the fallback case, a bare
688
+ * `SELECT *` is returned as `fallbackQuery` so the batch can fetch
689
+ * all rows and this query can filter them in JS post-fetch.
803
690
  */
804
- private _execute;
691
+ _compile(): CompiledQuery<T>;
805
692
  /**
806
- * Compile all accumulated predicates and determine the execution strategy.
693
+ * @internal Process raw SQL results into typed rows. Used by db.batch()
694
+ * after executing the compiled query.
807
695
  *
808
- * Returns an object with:
809
- * - `allSql`: whether all predicates compiled to SQL
810
- * - `sqlWhere`: combined WHERE clause (ANDed) if all compiled
811
- * - `compiled`: individual compilation results
696
+ * For SQL-compiled queries: just deserialize the rows.
697
+ * For JS-fallback queries: filter, sort, and slice in JS.
812
698
  */
699
+ static _processResults<T>(result: SqlResult, compiled: CompiledQuery<T>): T[];
700
+ then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
701
+ private _execute;
813
702
  private _compilePredicates;
814
- /**
815
- * Fetch all rows from the table and apply JS predicates.
816
- * This is the fallback path when SQL compilation fails.
817
- *
818
- * Logs a warning to stderr so developers know they're on the slow path.
819
- */
820
703
  private _fetchAndFilterInJs;
821
- /**
822
- * Fetch all rows from the table (SELECT * with no WHERE).
823
- * Used by the JS fallback path.
824
- */
825
704
  private _fetchAllRows;
826
705
  }
706
+ /**
707
+ * Result of Query._compile(). Contains either a compiled SQL query
708
+ * (fast path) or a fallback SELECT * with JS processing metadata.
709
+ */
710
+ interface CompiledQuery<T> {
711
+ /** Compiled SQL query, or null if JS fallback needed. */
712
+ query: SqlQuery | null;
713
+ /** SELECT * fallback query, or null if SQL compiled. */
714
+ fallbackQuery: SqlQuery | null;
715
+ /** Table config for deserialization. */
716
+ config: TableConfig;
717
+ /** JS predicates (only for fallback). */
718
+ predicates?: Predicate<T>[];
719
+ /** Sort accessor (only for fallback). */
720
+ sortAccessor?: Accessor<T>;
721
+ /** Sort direction (only for fallback). */
722
+ reversed?: boolean;
723
+ /** Limit (only for fallback). */
724
+ limit?: number;
725
+ /** Offset (only for fallback). */
726
+ offset?: number;
727
+ }
827
728
 
828
729
  /**
829
730
  * Table<T> — a typed persistent collection backed by SQLite.
830
731
  *
831
- * Created via `db.defineTable<T>(name)`. The returned object is the full
832
- * API for interacting with that table. Every method either returns a
732
+ * Created via `db.defineTable<T>(name)`. Every method either returns a
833
733
  * chainable Query<T> (for lazy reads) or a Promise (for terminal reads
834
734
  * and writes).
835
735
  *
836
- * ## Read vs Write operations
837
- *
838
- * **Reads** come in two flavors:
839
- * - **Direct reads**: `get(id)`, `findOne()`, `count()`, `some()`, `every()`,
840
- * `isEmpty()`, `min()`, `max()`, `groupBy()` — return Promises directly.
841
- * - **Chainable reads**: `filter()`, `sortBy()` — return a Query<T> that
842
- * accumulates operations lazily. The query executes when awaited.
843
- *
844
- * **Writes** are always immediate (return Promises):
845
- * - `push()` — INSERT + SELECT to return the created row with system fields
846
- * - `update()` — UPDATE + SELECT to return the updated row
847
- * - `remove()` — DELETE by ID
848
- * - `removeAll()` — DELETE with compiled WHERE clause
849
- * - `clear()` — DELETE all rows
850
- *
851
- * ## System columns
736
+ * ## Write operations use RETURNING
852
737
  *
853
- * Every row has platform-managed columns: `id`, `createdAt`, `updatedAt`,
854
- * `lastUpdatedBy`. These are:
855
- * - Included in read results (populated by the platform)
856
- * - Excluded from `push()` and `update()` inputs (TypeScript enforces this
857
- * via PushInput<T> and UpdateInput<T>; runtime also strips them)
858
- *
859
- * ## User type handling
860
- *
861
- * Columns with schema type `'user'` store values with a `@@user@@` prefix
862
- * in SQLite. This is handled transparently:
863
- * - On read: prefix is stripped, application code gets clean UUID strings
864
- * - On write: prefix is added before sending to the database
738
+ * INSERT and UPDATE use `RETURNING *` to get the created/updated row
739
+ * back in a single round trip — no separate SELECT needed. This is
740
+ * executed via the batch endpoint which runs all queries on a single
741
+ * SQLite connection.
865
742
  */
866
743
 
867
744
  declare class Table<T> {
868
- /** @internal Runtime config binding this table to the execution layer. */
745
+ /** @internal */
869
746
  private readonly _config;
870
747
  constructor(config: TableConfig);
871
- /**
872
- * Get a single row by ID. Returns null if not found.
873
- *
874
- * @example
875
- * ```ts
876
- * const order = await Orders.get('abc-123');
877
- * if (order) console.log(order.status);
878
- * ```
879
- */
880
748
  get(id: string): Promise<T | null>;
881
- /**
882
- * Find the first row matching a predicate. Returns null if none match.
883
- *
884
- * @example
885
- * ```ts
886
- * const activeOrder = await Orders.findOne(o => o.status === 'active');
887
- * ```
888
- */
889
749
  findOne(predicate: Predicate<T>): Promise<T | null>;
890
- /**
891
- * Count rows, optionally filtered by a predicate.
892
- *
893
- * @example
894
- * ```ts
895
- * const total = await Orders.count();
896
- * const pending = await Orders.count(o => o.status === 'pending');
897
- * ```
898
- */
899
750
  count(predicate?: Predicate<T>): Promise<number>;
900
- /**
901
- * Check if any row matches a predicate. Short-circuits.
902
- *
903
- * @example
904
- * ```ts
905
- * const hasActive = await Orders.some(o => o.status === 'active');
906
- * ```
907
- */
908
751
  some(predicate: Predicate<T>): Promise<boolean>;
909
- /**
910
- * Check if all rows match a predicate.
911
- *
912
- * @example
913
- * ```ts
914
- * const allComplete = await Orders.every(o => o.status === 'completed');
915
- * ```
916
- */
917
752
  every(predicate: Predicate<T>): Promise<boolean>;
918
- /**
919
- * Check if the table has zero rows.
920
- *
921
- * @example
922
- * ```ts
923
- * if (await Orders.isEmpty()) console.log('No orders yet');
924
- * ```
925
- */
926
753
  isEmpty(): Promise<boolean>;
927
- /**
928
- * Return the row with the minimum value for a field.
929
- * Executes as `ORDER BY field ASC LIMIT 1`.
930
- *
931
- * @example
932
- * ```ts
933
- * const cheapest = await Orders.min(o => o.amount);
934
- * ```
935
- */
936
754
  min(accessor: Accessor<T, number>): Promise<T | null>;
937
- /**
938
- * Return the row with the maximum value for a field.
939
- * Executes as `ORDER BY field DESC LIMIT 1`.
940
- *
941
- * @example
942
- * ```ts
943
- * const mostExpensive = await Orders.max(o => o.amount);
944
- * ```
945
- */
946
755
  max(accessor: Accessor<T, number>): Promise<T | null>;
947
- /**
948
- * Group all rows by a field value. Returns a Map.
949
- *
950
- * @example
951
- * ```ts
952
- * const byStatus = await Orders.groupBy(o => o.status);
953
- * // Map { 'pending' => [...], 'approved' => [...] }
954
- * ```
955
- */
956
756
  groupBy<K extends string | number>(accessor: Accessor<T, K>): Promise<Map<K, T[]>>;
957
- /**
958
- * Filter rows by a predicate. Returns a chainable Query.
959
- *
960
- * The predicate is compiled to SQL when possible. If compilation fails,
961
- * the query falls back to fetching all rows and filtering in JS.
962
- *
963
- * @example
964
- * ```ts
965
- * const active = await Orders.filter(o => o.status === 'active');
966
- * const recentActive = await Orders
967
- * .filter(o => o.status === 'active')
968
- * .sortBy(o => o.createdAt)
969
- * .reverse()
970
- * .take(10);
971
- * ```
972
- */
973
757
  filter(predicate: Predicate<T>): Query<T>;
974
- /**
975
- * Sort all rows by a field. Returns a chainable Query.
976
- *
977
- * @example
978
- * ```ts
979
- * const newest = await Orders.sortBy(o => o.createdAt).reverse().take(5);
980
- * ```
981
- */
982
758
  sortBy(accessor: Accessor<T>): Query<T>;
983
759
  /**
984
760
  * Insert one or more rows. Returns the created row(s) with system fields
985
761
  * populated (id, createdAt, updatedAt, lastUpdatedBy).
986
762
  *
987
- * System columns are stripped from the input automatically you don't
988
- * need to (and can't) set id, createdAt, etc.
989
- *
990
- * @example
991
- * ```ts
992
- * // Single row
993
- * const order = await Orders.push({ item: 'Laptop', amount: 999, status: 'pending' });
994
- * console.log(order.id, order.createdAt); // system fields populated
995
- *
996
- * // Multiple rows
997
- * const orders = await Orders.push([
998
- * { item: 'Monitor', amount: 300, status: 'pending' },
999
- * { item: 'Keyboard', amount: 50, status: 'pending' },
1000
- * ]);
1001
- * ```
763
+ * Uses `INSERT ... RETURNING *` so the created row comes back in a
764
+ * single round trip no separate SELECT needed.
1002
765
  */
1003
766
  push(data: PushInput<T>): Promise<T>;
1004
767
  push(data: PushInput<T>[]): Promise<T[]>;
1005
768
  /**
1006
769
  * Update a row by ID. Only the provided fields are changed.
1007
- * Returns the updated row.
1008
- *
1009
- * System columns cannot be updated — they're stripped automatically.
1010
- * `updatedAt` and `lastUpdatedBy` are set by the platform.
1011
- *
1012
- * @example
1013
- * ```ts
1014
- * const updated = await Orders.update(order.id, { status: 'approved' });
1015
- * console.log(updated.updatedAt); // freshly updated
1016
- * ```
770
+ * Returns the updated row via `UPDATE ... RETURNING *`.
1017
771
  */
1018
772
  update(id: string, data: UpdateInput<T>): Promise<T>;
1019
- /**
1020
- * Remove a row by ID.
1021
- *
1022
- * @example
1023
- * ```ts
1024
- * await Orders.remove('abc-123');
1025
- * ```
1026
- */
1027
773
  remove(id: string): Promise<void>;
1028
774
  /**
1029
775
  * Remove all rows matching a predicate. Returns the count removed.
1030
- *
1031
- * The predicate is compiled to SQL when possible. If compilation fails,
1032
- * the function fetches all matching rows, collects their IDs, and
1033
- * deletes them individually.
1034
- *
1035
- * @example
1036
- * ```ts
1037
- * const removed = await Orders.removeAll(o => o.status === 'rejected');
1038
- * console.log(`Removed ${removed} orders`);
1039
- * ```
1040
776
  */
1041
777
  removeAll(predicate: Predicate<T>): Promise<number>;
1042
- /**
1043
- * Remove all rows from the table.
1044
- *
1045
- * @example
1046
- * ```ts
1047
- * await Orders.clear();
1048
- * ```
1049
- */
1050
778
  clear(): Promise<void>;
1051
779
  }
1052
780
 
@@ -1157,6 +885,28 @@ interface Db {
1157
885
  ago(ms: number): number;
1158
886
  /** Returns a unix timestamp for (now + duration). Use with days/hours/minutes. */
1159
887
  fromNow(ms: number): number;
888
+ /**
889
+ * Execute multiple queries in a single round trip. All queries run on
890
+ * the same database connection, eliminating per-query HTTP overhead.
891
+ *
892
+ * Accepts Query objects (lazy, not yet executed). Compiles them to SQL,
893
+ * sends all in one batch request, and returns typed results.
894
+ *
895
+ * @example
896
+ * ```ts
897
+ * const [orders, approvals, vendors] = await db.batch(
898
+ * Orders.filter(o => o.status === 'active').take(10),
899
+ * Approvals.filter(a => a.status === 'pending').take(25),
900
+ * Vendors.sortBy(v => v.createdAt).reverse().take(5),
901
+ * );
902
+ * ```
903
+ */
904
+ batch<A>(q1: PromiseLike<A>): Promise<[A]>;
905
+ batch<A, B>(q1: PromiseLike<A>, q2: PromiseLike<B>): Promise<[A, B]>;
906
+ batch<A, B, C>(q1: PromiseLike<A>, q2: PromiseLike<B>, q3: PromiseLike<C>): Promise<[A, B, C]>;
907
+ batch<A, B, C, D>(q1: PromiseLike<A>, q2: PromiseLike<B>, q3: PromiseLike<C>, q4: PromiseLike<D>): Promise<[A, B, C, D]>;
908
+ batch<A, B, C, D, E>(q1: PromiseLike<A>, q2: PromiseLike<B>, q3: PromiseLike<C>, q4: PromiseLike<D>, q5: PromiseLike<E>): Promise<[A, B, C, D, E]>;
909
+ batch(...queries: PromiseLike<unknown>[]): Promise<unknown[]>;
1160
910
  }
1161
911
 
1162
912
  /**
@@ -1408,18 +1158,19 @@ declare class MindStudioAgent$1 {
1408
1158
  */
1409
1159
  private _trySandboxHydration;
1410
1160
  /**
1411
- * @internal Execute a SQL query against a managed database.
1412
- * Used as the `executeQuery` callback for Table instances.
1161
+ * @internal Execute a batch of SQL queries against a managed database.
1162
+ * Used as the `executeBatch` callback for Table/Query instances.
1413
1163
  *
1414
- * Calls the `queryAppDatabase` step with `parameterize: false`
1415
- * (the SDK builds fully-formed SQL with escaped inline values).
1164
+ * Calls `POST /_internal/v2/db/query` directly with the hook token
1165
+ * (raw, no Bearer prefix). All queries run on a single SQLite connection,
1166
+ * enabling RETURNING clauses and multi-statement batches.
1416
1167
  */
1417
- private _executeDbQuery;
1168
+ private _executeDbBatch;
1418
1169
  /**
1419
1170
  * @internal Create a lazy Db proxy that auto-hydrates context.
1420
1171
  *
1421
1172
  * defineTable() returns Table instances immediately (no async needed).
1422
- * But the Table's executeQuery callback is wrapped to call ensureContext()
1173
+ * But the Table's executeBatch callback is wrapped to call ensureContext()
1423
1174
  * before the first query, so context is fetched lazily.
1424
1175
  */
1425
1176
  private _createLazyDb;