@mindstudio-ai/agent 0.1.34 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -37,13 +37,23 @@ async function requestWithRetry(config, method, url, body, attempt) {
37
37
  return requestWithRetry(config, method, url, body, attempt + 1);
38
38
  }
39
39
  if (!res.ok) {
40
- const errorBody = await res.json().catch(() => ({}));
41
- throw new MindStudioError(
42
- errorBody.message || `${res.status} ${res.statusText}`,
43
- errorBody.code || "api_error",
44
- res.status,
45
- errorBody
46
- );
40
+ let message = `${res.status} ${res.statusText}`;
41
+ let code = "api_error";
42
+ let details;
43
+ try {
44
+ const text = await res.text();
45
+ try {
46
+ const body2 = JSON.parse(text);
47
+ details = body2;
48
+ const errMsg = body2.error ?? body2.message ?? body2.details;
49
+ if (errMsg) message = errMsg;
50
+ if (body2.code) code = body2.code;
51
+ } catch {
52
+ if (text && text.length < 500) message = text;
53
+ }
54
+ } catch {
55
+ }
56
+ throw new MindStudioError(message, code, res.status, details);
47
57
  }
48
58
  const data = await res.json();
49
59
  return { data, headers: res.headers };
@@ -1248,6 +1258,9 @@ var Table = class {
1248
1258
  const items = (isArray ? data : [data]).map(
1249
1259
  (item) => this._config.defaults ? { ...this._config.defaults, ...item } : item
1250
1260
  );
1261
+ for (const item of items) {
1262
+ this._checkManagedColumns(item);
1263
+ }
1251
1264
  const queries = items.map(
1252
1265
  (item) => buildInsert(
1253
1266
  this._config.tableName,
@@ -1265,7 +1278,13 @@ var Table = class {
1265
1278
  }
1266
1279
  return void 0;
1267
1280
  });
1268
- return isArray ? rows : rows[0];
1281
+ const result = isArray ? rows : rows[0];
1282
+ this._syncRolesIfNeeded(
1283
+ items,
1284
+ result,
1285
+ isArray
1286
+ );
1287
+ return result;
1269
1288
  });
1270
1289
  }
1271
1290
  /**
@@ -1273,20 +1292,25 @@ var Table = class {
1273
1292
  * Returns the updated row via `UPDATE ... RETURNING *`.
1274
1293
  */
1275
1294
  update(id, data) {
1295
+ this._checkManagedColumns(data);
1276
1296
  const query = buildUpdate(
1277
1297
  this._config.tableName,
1278
1298
  id,
1279
1299
  data,
1280
1300
  this._config.columns
1281
1301
  );
1282
- return new Mutation(
1283
- this._config,
1284
- [query],
1285
- (results) => deserializeRow(
1302
+ return new Mutation(this._config, [query], (results) => {
1303
+ const result = deserializeRow(
1286
1304
  results[0].rows[0],
1287
1305
  this._config.columns
1288
- )
1289
- );
1306
+ );
1307
+ this._syncRolesIfNeeded(
1308
+ [data],
1309
+ result,
1310
+ false
1311
+ );
1312
+ return result;
1313
+ });
1290
1314
  }
1291
1315
  remove(id) {
1292
1316
  const query = buildDelete(this._config.tableName, `id = ?`, [id]);
@@ -1341,24 +1365,65 @@ var Table = class {
1341
1365
  const conflictColumns = Array.isArray(conflictKey) ? conflictKey : [conflictKey];
1342
1366
  this._validateUniqueConstraint(conflictColumns);
1343
1367
  const withDefaults = this._config.defaults ? { ...this._config.defaults, ...data } : data;
1368
+ this._checkManagedColumns(withDefaults);
1344
1369
  const query = buildUpsert(
1345
1370
  this._config.tableName,
1346
1371
  withDefaults,
1347
1372
  conflictColumns,
1348
1373
  this._config.columns
1349
1374
  );
1350
- return new Mutation(
1351
- this._config,
1352
- [query],
1353
- (results) => deserializeRow(
1375
+ return new Mutation(this._config, [query], (results) => {
1376
+ const result = deserializeRow(
1354
1377
  results[0].rows[0],
1355
1378
  this._config.columns
1356
- )
1357
- );
1379
+ );
1380
+ this._syncRolesIfNeeded([withDefaults], result, false);
1381
+ return result;
1382
+ });
1358
1383
  }
1359
1384
  // -------------------------------------------------------------------------
1360
1385
  // Internal helpers
1361
1386
  // -------------------------------------------------------------------------
1387
+ /** @internal Throw if data includes a platform-managed email/phone column. */
1388
+ _checkManagedColumns(data) {
1389
+ const mc = this._config.managedColumns;
1390
+ if (!mc) return;
1391
+ const keys = Object.keys(data);
1392
+ for (const key of keys) {
1393
+ if (mc.email && key === mc.email || mc.phone && key === mc.phone) {
1394
+ throw new MindStudioError(
1395
+ `Cannot write to "${key}" \u2014 this column is managed by auth. Use the auth API to change a user's ${key === mc.email ? "email" : "phone"}.`,
1396
+ "managed_column_write",
1397
+ 400
1398
+ );
1399
+ }
1400
+ }
1401
+ }
1402
+ /**
1403
+ * @internal Fire role sync for rows that wrote to the roles column.
1404
+ * Called inside processResult (runs after SQL execution in both
1405
+ * standalone and batch paths). Fire-and-forget.
1406
+ */
1407
+ _syncRolesIfNeeded(inputItems, result, isArray) {
1408
+ const rolesCol = this._config.managedColumns?.roles;
1409
+ const syncRoles = this._config.syncRoles;
1410
+ if (!rolesCol || !syncRoles) return;
1411
+ if (!inputItems.some((item) => rolesCol in item)) return;
1412
+ if (isArray) {
1413
+ for (const row of result) {
1414
+ if (row?.id) {
1415
+ syncRoles(row.id, row[rolesCol]).catch(() => {
1416
+ });
1417
+ }
1418
+ }
1419
+ } else {
1420
+ const row = result;
1421
+ if (row?.id) {
1422
+ syncRoles(row.id, row[rolesCol]).catch(() => {
1423
+ });
1424
+ }
1425
+ }
1426
+ }
1362
1427
  /** @internal Validate that the given columns match a declared unique constraint. */
1363
1428
  _validateUniqueConstraint(columns) {
1364
1429
  if (!this._config.unique?.length) {
@@ -1383,7 +1448,7 @@ var Table = class {
1383
1448
  };
1384
1449
 
1385
1450
  // src/db/index.ts
1386
- function createDb(databases, executeBatch) {
1451
+ function createDb(databases, executeBatch, authConfig, syncRoles) {
1387
1452
  return {
1388
1453
  defineTable(name, options) {
1389
1454
  const resolved = resolveTable(databases, name, options?.database);
@@ -1393,6 +1458,8 @@ function createDb(databases, executeBatch) {
1393
1458
  columns: resolved.columns,
1394
1459
  unique: options?.unique,
1395
1460
  defaults: options?.defaults,
1461
+ managedColumns: authConfig?.table === name ? authConfig.columns : void 0,
1462
+ syncRoles: authConfig?.table === name && authConfig.columns.roles ? syncRoles : void 0,
1396
1463
  executeBatch: (queries) => executeBatch(resolved.databaseId, queries)
1397
1464
  };
1398
1465
  return new Table(config);
@@ -3847,7 +3914,9 @@ var MindStudioAgent = class {
3847
3914
  this._auth = new AuthContext(context.auth);
3848
3915
  this._db = createDb(
3849
3916
  context.databases,
3850
- this._executeDbBatch.bind(this)
3917
+ this._executeDbBatch.bind(this),
3918
+ context.authConfig,
3919
+ this._syncRoles.bind(this)
3851
3920
  );
3852
3921
  }
3853
3922
  /**
@@ -3863,7 +3932,8 @@ var MindStudioAgent = class {
3863
3932
  if (ai?.auth && ai?.databases) {
3864
3933
  this._applyContext({
3865
3934
  auth: ai.auth,
3866
- databases: ai.databases
3935
+ databases: ai.databases,
3936
+ authConfig: ai.authConfig
3867
3937
  });
3868
3938
  }
3869
3939
  }
@@ -3909,6 +3979,39 @@ var MindStudioAgent = class {
3909
3979
  const data = await res.json();
3910
3980
  return data.results;
3911
3981
  }
3982
+ /**
3983
+ * @internal Sync a user's roles to the platform after a successful
3984
+ * auth table write. Calls POST /_internal/v2/auth/sync-user.
3985
+ * Fire-and-forget: errors are caught and logged, never propagated.
3986
+ */
3987
+ async _syncRoles(userId, roles) {
3988
+ try {
3989
+ const url = `${this._httpConfig.baseUrl}/_internal/v2/auth/sync-user`;
3990
+ const res = await fetch(url, {
3991
+ method: "POST",
3992
+ headers: {
3993
+ "Content-Type": "application/json",
3994
+ Authorization: this._token
3995
+ },
3996
+ body: JSON.stringify({
3997
+ appId: this._appId,
3998
+ userId,
3999
+ roles
4000
+ })
4001
+ });
4002
+ if (!res.ok) {
4003
+ const text = await res.text().catch(() => "");
4004
+ console.warn(
4005
+ `[mindstudio] Failed to sync roles for user ${userId}: ${res.status} ${text}`
4006
+ );
4007
+ }
4008
+ } catch (err) {
4009
+ console.warn(
4010
+ `[mindstudio] Failed to sync roles for user ${userId}:`,
4011
+ err
4012
+ );
4013
+ }
4014
+ }
3912
4015
  /**
3913
4016
  * @internal Create a lazy Db proxy that auto-hydrates context.
3914
4017
  *
@@ -3921,7 +4024,7 @@ var MindStudioAgent = class {
3921
4024
  return {
3922
4025
  defineTable(name, options) {
3923
4026
  const databaseHint = options?.database;
3924
- return new Table({
4027
+ const tableConfig = {
3925
4028
  databaseId: "",
3926
4029
  tableName: name,
3927
4030
  columns: [],
@@ -3929,6 +4032,13 @@ var MindStudioAgent = class {
3929
4032
  defaults: options?.defaults,
3930
4033
  executeBatch: async (queries) => {
3931
4034
  await agent.ensureContext();
4035
+ const ac = agent._context.authConfig;
4036
+ if (ac && ac.table === name && !tableConfig.managedColumns) {
4037
+ tableConfig.managedColumns = ac.columns;
4038
+ if (ac.columns.roles) {
4039
+ tableConfig.syncRoles = agent._syncRoles.bind(agent);
4040
+ }
4041
+ }
3932
4042
  const databases = agent._context.databases;
3933
4043
  let targetDb;
3934
4044
  if (databaseHint) {
@@ -3943,7 +4053,8 @@ var MindStudioAgent = class {
3943
4053
  const databaseId = targetDb?.id ?? databases[0]?.id ?? "";
3944
4054
  return agent._executeDbBatch(databaseId, queries);
3945
4055
  }
3946
- });
4056
+ };
4057
+ return new Table(tableConfig);
3947
4058
  },
3948
4059
  // Time helpers work without context
3949
4060
  now: () => Date.now(),