@mindstudio-ai/agent 0.1.20 → 0.1.22

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
@@ -612,6 +612,17 @@ interface TableConfig {
612
612
  * (which need @@user@@ prefix handling) and for validation.
613
613
  */
614
614
  columns: AppDatabaseColumnSchema[];
615
+ /**
616
+ * Unique constraints declared via defineTable options.
617
+ * Each entry is an array of column names that form a unique constraint.
618
+ * e.g. [['email'], ['userId', 'orgId']]
619
+ */
620
+ unique?: string[][];
621
+ /**
622
+ * Default values for columns, applied client-side in push() and upsert().
623
+ * Explicit values in the input override defaults.
624
+ */
625
+ defaults?: Record<string, unknown>;
615
626
  /**
616
627
  * Execute one or more SQL queries against the managed database in a
617
628
  * single round trip. All queries run on the same SQLite connection,
@@ -716,6 +727,7 @@ declare class Query<T> implements PromiseLike<T[]> {
716
727
  */
717
728
  static _processResults<T>(result: SqlResult, compiled: CompiledQuery<T>): T[];
718
729
  then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
730
+ catch<TResult2 = never>(onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<T[] | TResult2>;
719
731
  private _execute;
720
732
  private _compilePredicates;
721
733
  private _fetchAndFilterInJs;
@@ -794,6 +806,7 @@ declare class Mutation<TResult> implements PromiseLike<TResult> {
794
806
  */
795
807
  static fromExecutor<T>(config: TableConfig, executor: () => Promise<T>): Mutation<T>;
796
808
  then<T1 = TResult, T2 = never>(onfulfilled?: ((value: TResult) => T1 | PromiseLike<T1>) | null, onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null): Promise<T1 | T2>;
809
+ catch<T2 = never>(onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null): Promise<TResult | T2>;
797
810
  /**
798
811
  * @internal Compile this mutation into SQL for batch execution.
799
812
  * Returns the queries and a result processor.
@@ -859,6 +872,20 @@ declare class Table<T> {
859
872
  */
860
873
  removeAll(predicate: Predicate<T>): Mutation<number>;
861
874
  clear(): Mutation<void>;
875
+ /**
876
+ * Insert a row, or update it if a row with the same unique key already
877
+ * exists. The conflict key must match a `unique` constraint declared in
878
+ * defineTable options. Returns the created or updated row.
879
+ *
880
+ * Uses SQLite's `INSERT ... ON CONFLICT ... DO UPDATE SET ... RETURNING *`.
881
+ *
882
+ * @param conflictKey - Column name(s) that form the unique constraint.
883
+ * Pass a single string for single-column unique, or an array for compound.
884
+ * @param data - Row data to insert (or update on conflict). Defaults apply.
885
+ */
886
+ upsert(conflictKey: (keyof Omit<T, SystemFields> & string) | (keyof Omit<T, SystemFields> & string)[], data: PushInput<T>): Mutation<T>;
887
+ /** @internal Validate that the given columns match a declared unique constraint. */
888
+ private _validateUniqueConstraint;
862
889
  }
863
890
 
864
891
  /**
@@ -912,7 +939,7 @@ declare class Table<T> {
912
939
  /**
913
940
  * Options for `db.defineTable()`.
914
941
  */
915
- interface DefineTableOptions {
942
+ interface DefineTableOptions<T = unknown> {
916
943
  /**
917
944
  * Database name or ID to target. Required when the app has multiple
918
945
  * databases and the table name alone is ambiguous.
@@ -925,6 +952,39 @@ interface DefineTableOptions {
925
952
  * - Multiple databases → searched by table name
926
953
  */
927
954
  database?: string;
955
+ /**
956
+ * Unique constraints for the table. Each entry is an array of column
957
+ * names that together must be unique. The SDK communicates these to
958
+ * the platform which creates the corresponding SQLite UNIQUE indexes.
959
+ *
960
+ * Required for `upsert()` — the conflict key must match a declared
961
+ * unique constraint.
962
+ *
963
+ * @example
964
+ * ```ts
965
+ * // Single column unique
966
+ * db.defineTable<User>('users', { unique: [['email']] });
967
+ *
968
+ * // Compound unique
969
+ * db.defineTable<Membership>('memberships', { unique: [['userId', 'orgId']] });
970
+ *
971
+ * // Multiple constraints
972
+ * db.defineTable<User>('users', { unique: [['email'], ['slug']] });
973
+ * ```
974
+ */
975
+ unique?: (keyof T & string)[][];
976
+ /**
977
+ * Default values for columns, applied client-side in `push()` and
978
+ * `upsert()`. Explicit values in the input override defaults.
979
+ *
980
+ * @example
981
+ * ```ts
982
+ * db.defineTable<Order>('orders', {
983
+ * defaults: { status: 'pending', retryCount: 0 },
984
+ * });
985
+ * ```
986
+ */
987
+ defaults?: Partial<Omit<T, SystemFields>>;
928
988
  }
929
989
 
930
990
  /**
@@ -955,7 +1015,7 @@ interface Db {
955
1015
  * const Orders = db.defineTable<Order>('orders', { database: 'main' });
956
1016
  * ```
957
1017
  */
958
- defineTable<T>(name: string, options?: DefineTableOptions): Table<T & SystemColumns>;
1018
+ defineTable<T>(name: string, options?: DefineTableOptions<T>): Table<T & SystemColumns>;
959
1019
  /** Returns the current time as a unix timestamp (ms). Equivalent to `Date.now()`. */
960
1020
  now(): number;
961
1021
  /** Returns milliseconds for n days. Composable with `+`. */
@@ -2506,10 +2566,7 @@ interface FetchYoutubeChannelStepInput {
2506
2566
  /** YouTube channel URL (e.g. https://www.youtube.com/@ChannelName or /channel/ID) */
2507
2567
  channelUrl: string;
2508
2568
  }
2509
- interface FetchYoutubeChannelStepOutput {
2510
- /** Channel metadata and video listings */
2511
- channel: Record<string, unknown>;
2512
- }
2569
+ type FetchYoutubeChannelStepOutput = Record<string, unknown>;
2513
2570
  interface FetchYoutubeCommentsStepInput {
2514
2571
  /** YouTube video URL to fetch comments for */
2515
2572
  videoUrl: string;
@@ -2545,10 +2602,7 @@ interface FetchYoutubeVideoStepInput {
2545
2602
  /** YouTube video URL to fetch metadata for */
2546
2603
  videoUrl: string;
2547
2604
  }
2548
- interface FetchYoutubeVideoStepOutput {
2549
- /** Video metadata including title, description, stats, and channel info */
2550
- video: Record<string, unknown>;
2551
- }
2605
+ type FetchYoutubeVideoStepOutput = Record<string, unknown>;
2552
2606
  interface GenerateChartStepInput {
2553
2607
  /** Chart configuration including type, data, and rendering options */
2554
2608
  chart: {
@@ -4318,10 +4372,7 @@ interface SearchYoutubeTrendsStepInput {
4318
4372
  /** Country code (e.g. "US") */
4319
4373
  gl: string;
4320
4374
  }
4321
- interface SearchYoutubeTrendsStepOutput {
4322
- /** Trending video data for the selected category and region */
4323
- trends: Record<string, unknown>;
4324
- }
4375
+ type SearchYoutubeTrendsStepOutput = Record<string, unknown>;
4325
4376
  interface SendEmailStepInput {
4326
4377
  /** Email subject line */
4327
4378
  subject: string;
package/dist/index.js CHANGED
@@ -297,6 +297,18 @@ function buildUpdate(table, id, data, columns) {
297
297
  params
298
298
  };
299
299
  }
300
+ function buildUpsert(table, data, conflictColumns, columns) {
301
+ const filtered = stripSystemColumns(data);
302
+ const keys = Object.keys(filtered);
303
+ const placeholders = keys.map(() => "?").join(", ");
304
+ const params = keys.map(
305
+ (k) => serializeColumnParam(filtered[k], k, columns)
306
+ );
307
+ const updateKeys = keys.filter((k) => !conflictColumns.includes(k));
308
+ const conflict = conflictColumns.join(", ");
309
+ const sql = updateKeys.length > 0 ? `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${conflict}) DO UPDATE SET ${updateKeys.map((k) => `${k} = excluded.${k}`).join(", ")} RETURNING *` : `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${conflict}) DO NOTHING RETURNING *`;
310
+ return { sql, params };
311
+ }
300
312
  function buildDelete(table, where, whereParams) {
301
313
  let sql = `DELETE FROM ${table}`;
302
314
  if (where) sql += ` WHERE ${where}`;
@@ -1006,6 +1018,9 @@ var Query = class _Query {
1006
1018
  then(onfulfilled, onrejected) {
1007
1019
  return this._execute().then(onfulfilled, onrejected);
1008
1020
  }
1021
+ catch(onrejected) {
1022
+ return this._execute().catch(onrejected);
1023
+ }
1009
1024
  // -------------------------------------------------------------------------
1010
1025
  // Execution internals
1011
1026
  // -------------------------------------------------------------------------
@@ -1121,6 +1136,9 @@ var Mutation = class _Mutation {
1121
1136
  then(onfulfilled, onrejected) {
1122
1137
  return this._execute().then(onfulfilled, onrejected);
1123
1138
  }
1139
+ catch(onrejected) {
1140
+ return this._execute().catch(onrejected);
1141
+ }
1124
1142
  // -------------------------------------------------------------------------
1125
1143
  // Batch compilation — used by db.batch()
1126
1144
  // -------------------------------------------------------------------------
@@ -1227,7 +1245,9 @@ var Table = class {
1227
1245
  }
1228
1246
  push(data) {
1229
1247
  const isArray = Array.isArray(data);
1230
- const items = isArray ? data : [data];
1248
+ const items = (isArray ? data : [data]).map(
1249
+ (item) => this._config.defaults ? { ...this._config.defaults, ...item } : item
1250
+ );
1231
1251
  const queries = items.map(
1232
1252
  (item) => buildInsert(
1233
1253
  this._config.tableName,
@@ -1306,6 +1326,60 @@ var Table = class {
1306
1326
  const query = buildDelete(this._config.tableName);
1307
1327
  return new Mutation(this._config, [query], () => void 0);
1308
1328
  }
1329
+ /**
1330
+ * Insert a row, or update it if a row with the same unique key already
1331
+ * exists. The conflict key must match a `unique` constraint declared in
1332
+ * defineTable options. Returns the created or updated row.
1333
+ *
1334
+ * Uses SQLite's `INSERT ... ON CONFLICT ... DO UPDATE SET ... RETURNING *`.
1335
+ *
1336
+ * @param conflictKey - Column name(s) that form the unique constraint.
1337
+ * Pass a single string for single-column unique, or an array for compound.
1338
+ * @param data - Row data to insert (or update on conflict). Defaults apply.
1339
+ */
1340
+ upsert(conflictKey, data) {
1341
+ const conflictColumns = Array.isArray(conflictKey) ? conflictKey : [conflictKey];
1342
+ this._validateUniqueConstraint(conflictColumns);
1343
+ const withDefaults = this._config.defaults ? { ...this._config.defaults, ...data } : data;
1344
+ const query = buildUpsert(
1345
+ this._config.tableName,
1346
+ withDefaults,
1347
+ conflictColumns,
1348
+ this._config.columns
1349
+ );
1350
+ return new Mutation(
1351
+ this._config,
1352
+ [query],
1353
+ (results) => deserializeRow(
1354
+ results[0].rows[0],
1355
+ this._config.columns
1356
+ )
1357
+ );
1358
+ }
1359
+ // -------------------------------------------------------------------------
1360
+ // Internal helpers
1361
+ // -------------------------------------------------------------------------
1362
+ /** @internal Validate that the given columns match a declared unique constraint. */
1363
+ _validateUniqueConstraint(columns) {
1364
+ if (!this._config.unique?.length) {
1365
+ throw new MindStudioError(
1366
+ `Cannot upsert on ${this._config.tableName}: no unique constraints declared. Add unique: [[${columns.map((c) => `'${c}'`).join(", ")}]] to defineTable options.`,
1367
+ "no_unique_constraint",
1368
+ 400
1369
+ );
1370
+ }
1371
+ const sorted = [...columns].sort().join(",");
1372
+ const match = this._config.unique.some(
1373
+ (u) => [...u].sort().join(",") === sorted
1374
+ );
1375
+ if (!match) {
1376
+ throw new MindStudioError(
1377
+ `Cannot upsert on (${columns.join(", ")}): no matching unique constraint declared on ${this._config.tableName}.`,
1378
+ "no_unique_constraint",
1379
+ 400
1380
+ );
1381
+ }
1382
+ }
1309
1383
  };
1310
1384
 
1311
1385
  // src/db/index.ts
@@ -1317,6 +1391,8 @@ function createDb(databases, executeBatch) {
1317
1391
  databaseId: resolved.databaseId,
1318
1392
  tableName: name,
1319
1393
  columns: resolved.columns,
1394
+ unique: options?.unique,
1395
+ defaults: options?.defaults,
1320
1396
  executeBatch: (queries) => executeBatch(resolved.databaseId, queries)
1321
1397
  };
1322
1398
  return new Table(config);
@@ -2516,6 +2592,8 @@ var MindStudioAgent = class {
2516
2592
  databaseId: "",
2517
2593
  tableName: name,
2518
2594
  columns: [],
2595
+ unique: options?.unique,
2596
+ defaults: options?.defaults,
2519
2597
  executeBatch: async (queries) => {
2520
2598
  await agent.ensureContext();
2521
2599
  const databases = agent._context.databases;
@@ -2754,9 +2832,9 @@ var monacoSnippets = {
2754
2832
  "fetchGoogleSheet": { fields: [["spreadsheetId", "string"], ["range", "string"], ["exportType", ["csv", "json"]]], outputKeys: ["content"] },
2755
2833
  "fetchSlackChannelHistory": { fields: [["channelId", "string"]], outputKeys: ["messages"] },
2756
2834
  "fetchYoutubeCaptions": { fields: [["videoUrl", "string"], ["exportType", ["text", "json"]], ["language", "string"]], outputKeys: ["transcripts"] },
2757
- "fetchYoutubeChannel": { fields: [["channelUrl", "string"]], outputKeys: ["channel"] },
2835
+ "fetchYoutubeChannel": { fields: [["channelUrl", "string"]], outputKeys: [] },
2758
2836
  "fetchYoutubeComments": { fields: [["videoUrl", "string"], ["exportType", ["text", "json"]], ["limitPages", "string"]], outputKeys: ["comments"] },
2759
- "fetchYoutubeVideo": { fields: [["videoUrl", "string"]], outputKeys: ["video"] },
2837
+ "fetchYoutubeVideo": { fields: [["videoUrl", "string"]], outputKeys: [] },
2760
2838
  "generateAsset": { fields: [["source", "string"], ["sourceType", ["html", "markdown", "spa", "raw", "dynamic", "customInterface"]], ["outputFormat", ["pdf", "png", "html", "mp4", "openGraph"]], ["pageSize", ["full", "letter", "A4", "custom"]], ["testData", "object"]], outputKeys: ["url"] },
2761
2839
  "generateChart": { fields: [["chart", "object"]], outputKeys: ["chartUrl"] },
2762
2840
  "generateImage": { fields: [["prompt", "string"]], outputKeys: ["imageUrl"] },
@@ -2840,7 +2918,7 @@ var monacoSnippets = {
2840
2918
  "searchPerplexity": { fields: [["query", "string"], ["exportType", ["text", "json"]]], outputKeys: ["results"] },
2841
2919
  "searchXPosts": { fields: [["query", "string"], ["scope", ["recent", "all"]], ["options", "object"]], outputKeys: ["posts"] },
2842
2920
  "searchYoutube": { fields: [["query", "string"], ["limitPages", "string"], ["filter", "string"], ["filterType", "string"]], outputKeys: ["results"] },
2843
- "searchYoutubeTrends": { fields: [["bp", ["now", "music", "gaming", "films"]], ["hl", "string"], ["gl", "string"]], outputKeys: ["trends"] },
2921
+ "searchYoutubeTrends": { fields: [["bp", ["now", "music", "gaming", "films"]], ["hl", "string"], ["gl", "string"]], outputKeys: [] },
2844
2922
  "sendEmail": { fields: [["subject", "string"], ["body", "string"]], outputKeys: ["recipients"] },
2845
2923
  "sendGmailDraft": { fields: [["draftId", "string"]], outputKeys: [] },
2846
2924
  "sendGmailMessage": { fields: [["to", "string"], ["subject", "string"], ["message", "string"], ["messageType", ["plain", "html", "markdown"]]], outputKeys: ["messageId"] },
@@ -3188,7 +3266,7 @@ var stepMetadata = {
3188
3266
  description: "Retrieve metadata and recent videos for a YouTube channel.",
3189
3267
  usageNotes: "- Accepts a YouTube channel URL (e.g. https://www.youtube.com/@ChannelName or /channel/ID).\n- Returns channel info and video listings as a JSON object.",
3190
3268
  inputSchema: { "type": "object", "properties": { "channelUrl": { "type": "string", "description": "YouTube channel URL (e.g. https://www.youtube.com/@ChannelName or /channel/ID)" } }, "required": ["channelUrl"] },
3191
- outputSchema: { "type": "object", "properties": { "channel": { "type": "object", "description": "Channel metadata and video listings" } }, "required": ["channel"] }
3269
+ outputSchema: { "type": "object" }
3192
3270
  },
3193
3271
  "fetchYoutubeComments": {
3194
3272
  stepType: "fetchYoutubeComments",
@@ -3202,7 +3280,7 @@ var stepMetadata = {
3202
3280
  description: "Retrieve metadata for a YouTube video (title, description, stats, channel info).",
3203
3281
  usageNotes: "- Returns video metadata, channel info, and engagement stats.\n- Video format data is excluded from the response.",
3204
3282
  inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "YouTube video URL to fetch metadata for" } }, "required": ["videoUrl"] },
3205
- outputSchema: { "type": "object", "properties": { "video": { "type": "object", "description": "Video metadata including title, description, stats, and channel info" } }, "required": ["video"] }
3283
+ outputSchema: { "type": "object" }
3206
3284
  },
3207
3285
  "generateAsset": {
3208
3286
  stepType: "generatePdf",
@@ -3807,7 +3885,7 @@ var stepMetadata = {
3807
3885
  description: "Retrieve trending videos on YouTube by category and region.",
3808
3886
  usageNotes: '- Categories: "now" (trending now), "music", "gaming", "films".\n- Supports country and language filtering.',
3809
3887
  inputSchema: { "type": "object", "properties": { "bp": { "enum": ["now", "music", "gaming", "films"], "type": "string", "description": 'Trending category: "now" (trending now), "music", "gaming", or "films"' }, "hl": { "type": "string", "description": 'Language code (e.g. "en")' }, "gl": { "type": "string", "description": 'Country code (e.g. "US")' } }, "required": ["bp", "hl", "gl"] },
3810
- outputSchema: { "type": "object", "properties": { "trends": { "type": "object", "description": "Trending video data for the selected category and region" } }, "required": ["trends"] }
3888
+ outputSchema: { "type": "object" }
3811
3889
  },
3812
3890
  "sendEmail": {
3813
3891
  stepType: "sendEmail",