@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.js CHANGED
@@ -4503,6 +4503,8 @@ var Session = class {
4503
4503
  environmentId: "",
4504
4504
  sessionId: "",
4505
4505
  user: {
4506
+ granularId: "",
4507
+ userId: "",
4506
4508
  subjectId: ""
4507
4509
  }
4508
4510
  };
@@ -4577,7 +4579,8 @@ var Session = class {
4577
4579
  * ```typescript
4578
4580
  * import { Author, Book, global_search } from './sandbox-tools';
4579
4581
  *
4580
- * const tolkien = await Author.find({ id: 'tolkien' });
4582
+ * const authors = await Author.list({ limit: 10, saveAs: 'recent_authors' });
4583
+ * const tolkien = await Author.get({ path: 'author_tolkien' });
4581
4584
  * const bio = await tolkien.get_bio({ detailed: true });
4582
4585
  * const books = await tolkien.get_books();
4583
4586
  * ```
@@ -5196,6 +5199,29 @@ function normalizeHeapSnapshot(raw) {
5196
5199
  updatedAt: typeof heap.updatedAt === "number" ? heap.updatedAt : Date.now()
5197
5200
  };
5198
5201
  }
5202
+ function normalizeSubject(subject) {
5203
+ const granularId = subject.granularId || subject.subjectId;
5204
+ const userId = subject.userId || subject.identityId || granularId;
5205
+ return {
5206
+ ...subject,
5207
+ granularId,
5208
+ userId,
5209
+ subjectId: granularId,
5210
+ identityId: subject.identityId || userId
5211
+ };
5212
+ }
5213
+ function normalizeUser(user) {
5214
+ const granularId = user.granularId || user.subjectId;
5215
+ const userId = user.userId || user.identityId || granularId;
5216
+ return {
5217
+ ...user,
5218
+ granularId,
5219
+ userId,
5220
+ subjectId: granularId,
5221
+ identityId: user.identityId || userId,
5222
+ permissions: Array.isArray(user.permissions) ? user.permissions : []
5223
+ };
5224
+ }
5199
5225
  var Environment = class _Environment extends Session {
5200
5226
  envData;
5201
5227
  _apiKey;
@@ -5218,6 +5244,10 @@ var Environment = class _Environment extends Session {
5218
5244
  get subjectId() {
5219
5245
  return this.envData.subjectId;
5220
5246
  }
5247
+ /** Internal Granular user identifier for this environment */
5248
+ get granularId() {
5249
+ return this.envData.subjectId;
5250
+ }
5221
5251
  /** The permission profile ID */
5222
5252
  get permissionProfileId() {
5223
5253
  return this.envData.permissionProfileId;
@@ -6015,7 +6045,7 @@ var Granular = class {
6015
6045
  * Records/upserts a user and prepares them for sandbox connections
6016
6046
  *
6017
6047
  * @param options - User options
6018
- * @returns The user object to pass to connect()
6048
+ * @returns The recorded user with both `userId` and `granularId`
6019
6049
  *
6020
6050
  * @example
6021
6051
  * ```typescript
@@ -6027,21 +6057,61 @@ var Granular = class {
6027
6057
  * ```
6028
6058
  */
6029
6059
  async recordUser(options) {
6030
- const subject = await this.request("/control/subjects", {
6060
+ const subject = normalizeSubject(await this.request("/control/subjects", {
6031
6061
  method: "POST",
6032
6062
  body: JSON.stringify({
6033
6063
  identityId: options.userId,
6034
6064
  name: options.name,
6035
6065
  email: options.email
6036
6066
  })
6037
- });
6038
- return {
6067
+ }));
6068
+ return normalizeUser({
6069
+ granularId: subject.granularId,
6070
+ userId: options.userId,
6039
6071
  subjectId: subject.subjectId,
6040
- identityId: options.userId,
6041
- name: options.name,
6042
- email: options.email,
6072
+ identityId: subject.identityId,
6073
+ name: options.name || subject.name || void 0,
6074
+ email: options.email || subject.email || void 0,
6043
6075
  permissions: options.permissions || []
6044
- };
6076
+ });
6077
+ }
6078
+ async resolveConnectUser(options) {
6079
+ if (options.user) {
6080
+ const user = normalizeUser(options.user);
6081
+ return {
6082
+ granularId: user.granularId,
6083
+ userId: user.userId,
6084
+ name: options.name || user.name,
6085
+ email: options.email || user.email,
6086
+ permissions: options.permissions || user.permissions
6087
+ };
6088
+ }
6089
+ if (options.userId) {
6090
+ const user = await this.recordUser({
6091
+ userId: options.userId,
6092
+ name: options.name,
6093
+ email: options.email,
6094
+ permissions: options.permissions
6095
+ });
6096
+ return {
6097
+ granularId: user.granularId,
6098
+ userId: user.userId,
6099
+ name: user.name,
6100
+ email: user.email,
6101
+ permissions: options.permissions || user.permissions
6102
+ };
6103
+ }
6104
+ if (options.granularId) {
6105
+ const subject = await this.subjects.get(options.granularId);
6106
+ return {
6107
+ granularId: subject.granularId,
6108
+ userId: subject.userId,
6109
+ name: options.name || subject.name || void 0,
6110
+ email: options.email || subject.email || void 0,
6111
+ permissions: options.permissions || []
6112
+ };
6113
+ }
6114
+ throw new Error("connect() requires either userId, granularId, or a user object returned by recordUser().");
6045
6115
  }
6046
6116
  /**
6047
6117
  * Connect to a sandbox and establish a real-time environment session.
@@ -6055,14 +6125,10 @@ var Granular = class {
6055
6125
  *
6056
6126
  * @example
6057
6127
  * ```typescript
6058
- * const user = await granular.recordUser({
6059
- * userId: 'user_123',
6060
- * permissions: ['agent'],
6061
- * });
6062
- *
6063
6128
  * const environment = await granular.connect({
6064
6129
  * sandbox: 'my-sandbox',
6065
- * user,
6130
+ * userId: 'user_123',
6131
+ * permissions: ['agent'],
6066
6132
  * });
6067
6133
  *
6068
6134
  * await granular.registerEffect('my-sandbox', {
@@ -6083,13 +6149,14 @@ var Granular = class {
6083
6149
  */
6084
6150
  async connect(options) {
6085
6151
  const clientId = options.clientId || `client_${Date.now()}`;
6152
+ const user = await this.resolveConnectUser(options);
6086
6153
  const sandbox = await this.findOrCreateSandbox(options.sandbox);
6087
- for (const profileName of options.user.permissions) {
6154
+ for (const profileName of user.permissions) {
6088
6155
  const profileId = await this.ensurePermissionProfile(sandbox.sandboxId, profileName);
6089
- await this.ensureAssignment(options.user.subjectId, sandbox.sandboxId, profileId);
6156
+ await this.ensureAssignment(user.granularId, sandbox.sandboxId, profileId);
6090
6157
  }
6091
6158
  const envData = await this.environments.create(sandbox.sandboxId, {
6092
- subjectId: options.user.subjectId,
6159
+ subjectId: user.granularId,
6093
6160
  permissionProfileId: null
6094
6161
  });
6095
6162
  await this.activateEnvironment(envData.environmentId);
@@ -6149,7 +6216,21 @@ var Granular = class {
6149
6216
  const effects = Array.from(this.getSandboxEffectMap(host.sandboxId).values()).map(
6150
6217
  (effect) => this.serializeEffect(effect)
6151
6218
  );
6152
- await host.wsClient.call("effects.publishCatalog", { effects });
6219
+ const result = await host.wsClient.call("effects.publishCatalog", { effects });
6220
+ const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
6221
+ const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
6222
+ if (acceptedCount === 0 && rejected.length > 0) {
6223
+ const detail = rejected.map((entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`).join("; ");
6224
+ throw new Error(
6225
+ `Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
6226
+ );
6227
+ }
6228
+ if (rejected.length > 0) {
6229
+ console.warn(
6230
+ `[Granular] Some live effects were rejected for sandbox ${host.sandboxId}:`,
6231
+ rejected
6232
+ );
6233
+ }
6153
6234
  }
6154
6235
  async syncSandboxEffectCatalog(sandboxId) {
6155
6236
  const host = await this.ensureSandboxEffectHost(sandboxId);
@@ -6315,6 +6396,22 @@ var Granular = class {
6315
6396
  }
6316
6397
  await this.syncSandboxEffectCatalog(sandboxId);
6317
6398
  }
6399
+ /**
6400
+ * Disconnect one sandbox-scoped effect host, or all of them when no sandbox is provided.
6401
+ *
6402
+ * This is primarily useful for long-lived helper processes such as generated
6403
+ * `granular-effects.ts` scripts that need to shut down cleanly on SIGINT/SIGTERM.
6404
+ */
6405
+ async disconnectEffects(sandboxNameOrId) {
6406
+ if (sandboxNameOrId) {
6407
+ const sandbox = await this.findOrCreateSandbox(sandboxNameOrId);
6408
+ this.disconnectSandboxEffectHost(sandbox.sandboxId);
6409
+ return;
6410
+ }
6411
+ for (const sandboxId of Array.from(this.sandboxEffectHosts.keys())) {
6412
+ this.disconnectSandboxEffectHost(sandboxId);
6413
+ }
6414
+ }
6318
6415
  /**
6319
6416
  * Unregister all effects for a sandbox.
6320
6417
  */
@@ -6492,7 +6589,7 @@ var Granular = class {
6492
6589
  get subjects() {
6493
6590
  return {
6494
6591
  get: async (subjectId) => {
6495
- return this.request(`/control/subjects/${subjectId}`);
6592
+ return normalizeSubject(await this.request(`/control/subjects/${subjectId}`));
6496
6593
  },
6497
6594
  listAssignments: async (subjectId) => {
6498
6595
  return this.request(`/control/subjects/${subjectId}/assignments`);
@@ -6505,17 +6602,17 @@ var Granular = class {
6505
6602
  get users() {
6506
6603
  return {
6507
6604
  create: async (data) => {
6508
- return this.request("/control/subjects", {
6605
+ return normalizeSubject(await this.request("/control/subjects", {
6509
6606
  method: "POST",
6510
6607
  body: JSON.stringify({
6511
6608
  identityId: data.id,
6512
6609
  name: data.name,
6513
6610
  email: data.email
6514
6611
  })
6515
- });
6612
+ }));
6516
6613
  },
6517
6614
  get: async (id) => {
6518
- return this.request(`/control/subjects/${id}`);
6615
+ return normalizeSubject(await this.request(`/control/subjects/${id}`));
6519
6616
  }
6520
6617
  };
6521
6618
  }