@granular-software/sdk 0.4.5 → 0.4.7

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.mjs CHANGED
@@ -4481,6 +4481,8 @@ var Session = class {
4481
4481
  environmentId: "",
4482
4482
  sessionId: "",
4483
4483
  user: {
4484
+ granularId: "",
4485
+ userId: "",
4484
4486
  subjectId: ""
4485
4487
  }
4486
4488
  };
@@ -4555,7 +4557,8 @@ var Session = class {
4555
4557
  * ```typescript
4556
4558
  * import { Author, Book, global_search } from './sandbox-tools';
4557
4559
  *
4558
- * const tolkien = await Author.find({ id: 'tolkien' });
4560
+ * const authors = await Author.list({ limit: 10, saveAs: 'recent_authors' });
4561
+ * const tolkien = await Author.get({ path: 'author_tolkien' });
4559
4562
  * const bio = await tolkien.get_bio({ detailed: true });
4560
4563
  * const books = await tolkien.get_books();
4561
4564
  * ```
@@ -5174,6 +5177,29 @@ function normalizeHeapSnapshot(raw) {
5174
5177
  updatedAt: typeof heap.updatedAt === "number" ? heap.updatedAt : Date.now()
5175
5178
  };
5176
5179
  }
5180
+ function normalizeSubject(subject) {
5181
+ const granularId = subject.granularId || subject.subjectId;
5182
+ const userId = subject.userId || subject.identityId || granularId;
5183
+ return {
5184
+ ...subject,
5185
+ granularId,
5186
+ userId,
5187
+ subjectId: granularId,
5188
+ identityId: subject.identityId || userId
5189
+ };
5190
+ }
5191
+ function normalizeUser(user) {
5192
+ const granularId = user.granularId || user.subjectId;
5193
+ const userId = user.userId || user.identityId || granularId;
5194
+ return {
5195
+ ...user,
5196
+ granularId,
5197
+ userId,
5198
+ subjectId: granularId,
5199
+ identityId: user.identityId || userId,
5200
+ permissions: Array.isArray(user.permissions) ? user.permissions : []
5201
+ };
5202
+ }
5177
5203
  var Environment = class _Environment extends Session {
5178
5204
  envData;
5179
5205
  _apiKey;
@@ -5196,6 +5222,10 @@ var Environment = class _Environment extends Session {
5196
5222
  get subjectId() {
5197
5223
  return this.envData.subjectId;
5198
5224
  }
5225
+ /** Internal Granular user identifier for this environment */
5226
+ get granularId() {
5227
+ return this.envData.subjectId;
5228
+ }
5199
5229
  /** The permission profile ID */
5200
5230
  get permissionProfileId() {
5201
5231
  return this.envData.permissionProfileId;
@@ -5993,7 +6023,7 @@ var Granular = class {
5993
6023
  * Records/upserts a user and prepares them for sandbox connections
5994
6024
  *
5995
6025
  * @param options - User options
5996
- * @returns The user object to pass to connect()
6026
+ * @returns The recorded user with both `userId` and `granularId`
5997
6027
  *
5998
6028
  * @example
5999
6029
  * ```typescript
@@ -6005,21 +6035,61 @@ var Granular = class {
6005
6035
  * ```
6006
6036
  */
6007
6037
  async recordUser(options) {
6008
- const subject = await this.request("/control/subjects", {
6038
+ const subject = normalizeSubject(await this.request("/control/subjects", {
6009
6039
  method: "POST",
6010
6040
  body: JSON.stringify({
6011
6041
  identityId: options.userId,
6012
6042
  name: options.name,
6013
6043
  email: options.email
6014
6044
  })
6015
- });
6016
- return {
6045
+ }));
6046
+ return normalizeUser({
6047
+ granularId: subject.granularId,
6048
+ userId: options.userId,
6017
6049
  subjectId: subject.subjectId,
6018
- identityId: options.userId,
6019
- name: options.name,
6020
- email: options.email,
6050
+ identityId: subject.identityId,
6051
+ name: options.name || subject.name || void 0,
6052
+ email: options.email || subject.email || void 0,
6021
6053
  permissions: options.permissions || []
6022
- };
6054
+ });
6055
+ }
6056
+ async resolveConnectUser(options) {
6057
+ if (options.user) {
6058
+ const user = normalizeUser(options.user);
6059
+ return {
6060
+ granularId: user.granularId,
6061
+ userId: user.userId,
6062
+ name: options.name || user.name,
6063
+ email: options.email || user.email,
6064
+ permissions: options.permissions || user.permissions
6065
+ };
6066
+ }
6067
+ if (options.userId) {
6068
+ const user = await this.recordUser({
6069
+ userId: options.userId,
6070
+ name: options.name,
6071
+ email: options.email,
6072
+ permissions: options.permissions
6073
+ });
6074
+ return {
6075
+ granularId: user.granularId,
6076
+ userId: user.userId,
6077
+ name: user.name,
6078
+ email: user.email,
6079
+ permissions: options.permissions || user.permissions
6080
+ };
6081
+ }
6082
+ if (options.granularId) {
6083
+ const subject = await this.subjects.get(options.granularId);
6084
+ return {
6085
+ granularId: subject.granularId,
6086
+ userId: subject.userId,
6087
+ name: options.name || subject.name || void 0,
6088
+ email: options.email || subject.email || void 0,
6089
+ permissions: options.permissions || []
6090
+ };
6091
+ }
6092
+ throw new Error("connect() requires either userId, granularId, or a user object returned by recordUser().");
6023
6093
  }
6024
6094
  /**
6025
6095
  * Connect to a sandbox and establish a real-time environment session.
@@ -6033,14 +6103,10 @@ var Granular = class {
6033
6103
  *
6034
6104
  * @example
6035
6105
  * ```typescript
6036
- * const user = await granular.recordUser({
6037
- * userId: 'user_123',
6038
- * permissions: ['agent'],
6039
- * });
6040
- *
6041
6106
  * const environment = await granular.connect({
6042
6107
  * sandbox: 'my-sandbox',
6043
- * user,
6108
+ * userId: 'user_123',
6109
+ * permissions: ['agent'],
6044
6110
  * });
6045
6111
  *
6046
6112
  * await granular.registerEffect('my-sandbox', {
@@ -6061,13 +6127,14 @@ var Granular = class {
6061
6127
  */
6062
6128
  async connect(options) {
6063
6129
  const clientId = options.clientId || `client_${Date.now()}`;
6130
+ const user = await this.resolveConnectUser(options);
6064
6131
  const sandbox = await this.findOrCreateSandbox(options.sandbox);
6065
- for (const profileName of options.user.permissions) {
6132
+ for (const profileName of user.permissions) {
6066
6133
  const profileId = await this.ensurePermissionProfile(sandbox.sandboxId, profileName);
6067
- await this.ensureAssignment(options.user.subjectId, sandbox.sandboxId, profileId);
6134
+ await this.ensureAssignment(user.granularId, sandbox.sandboxId, profileId);
6068
6135
  }
6069
6136
  const envData = await this.environments.create(sandbox.sandboxId, {
6070
- subjectId: options.user.subjectId,
6137
+ subjectId: user.granularId,
6071
6138
  permissionProfileId: null
6072
6139
  });
6073
6140
  await this.activateEnvironment(envData.environmentId);
@@ -6127,7 +6194,21 @@ var Granular = class {
6127
6194
  const effects = Array.from(this.getSandboxEffectMap(host.sandboxId).values()).map(
6128
6195
  (effect) => this.serializeEffect(effect)
6129
6196
  );
6130
- await host.wsClient.call("effects.publishCatalog", { effects });
6197
+ const result = await host.wsClient.call("effects.publishCatalog", { effects });
6198
+ const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
6199
+ const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
6200
+ if (acceptedCount === 0 && rejected.length > 0) {
6201
+ const detail = rejected.map((entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`).join("; ");
6202
+ throw new Error(
6203
+ `Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
6204
+ );
6205
+ }
6206
+ if (rejected.length > 0) {
6207
+ console.warn(
6208
+ `[Granular] Some live effects were rejected for sandbox ${host.sandboxId}:`,
6209
+ rejected
6210
+ );
6211
+ }
6131
6212
  }
6132
6213
  async syncSandboxEffectCatalog(sandboxId) {
6133
6214
  const host = await this.ensureSandboxEffectHost(sandboxId);
@@ -6293,6 +6374,22 @@ var Granular = class {
6293
6374
  }
6294
6375
  await this.syncSandboxEffectCatalog(sandboxId);
6295
6376
  }
6377
+ /**
6378
+ * Disconnect one sandbox-scoped effect host, or all of them when no sandbox is provided.
6379
+ *
6380
+ * This is primarily useful for long-lived helper processes such as generated
6381
+ * `granular-effects.ts` scripts that need to shut down cleanly on SIGINT/SIGTERM.
6382
+ */
6383
+ async disconnectEffects(sandboxNameOrId) {
6384
+ if (sandboxNameOrId) {
6385
+ const sandbox = await this.findOrCreateSandbox(sandboxNameOrId);
6386
+ this.disconnectSandboxEffectHost(sandbox.sandboxId);
6387
+ return;
6388
+ }
6389
+ for (const sandboxId of Array.from(this.sandboxEffectHosts.keys())) {
6390
+ this.disconnectSandboxEffectHost(sandboxId);
6391
+ }
6392
+ }
6296
6393
  /**
6297
6394
  * Unregister all effects for a sandbox.
6298
6395
  */
@@ -6470,7 +6567,7 @@ var Granular = class {
6470
6567
  get subjects() {
6471
6568
  return {
6472
6569
  get: async (subjectId) => {
6473
- return this.request(`/control/subjects/${subjectId}`);
6570
+ return normalizeSubject(await this.request(`/control/subjects/${subjectId}`));
6474
6571
  },
6475
6572
  listAssignments: async (subjectId) => {
6476
6573
  return this.request(`/control/subjects/${subjectId}/assignments`);
@@ -6483,17 +6580,17 @@ var Granular = class {
6483
6580
  get users() {
6484
6581
  return {
6485
6582
  create: async (data) => {
6486
- return this.request("/control/subjects", {
6583
+ return normalizeSubject(await this.request("/control/subjects", {
6487
6584
  method: "POST",
6488
6585
  body: JSON.stringify({
6489
6586
  identityId: data.id,
6490
6587
  name: data.name,
6491
6588
  email: data.email
6492
6589
  })
6493
- });
6590
+ }));
6494
6591
  },
6495
6592
  get: async (id) => {
6496
- return this.request(`/control/subjects/${id}`);
6593
+ return normalizeSubject(await this.request(`/control/subjects/${id}`));
6497
6594
  }
6498
6595
  };
6499
6596
  }