@granular-software/sdk 0.4.6 → 0.4.8

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.mts CHANGED
@@ -40,9 +40,13 @@ type GranularAuth = string;
40
40
  * A user/subject object returned from recordUser()
41
41
  */
42
42
  interface User {
43
- /** Internal subject ID */
43
+ /** Internal Granular user identifier */
44
+ granularId: string;
45
+ /** External user identifier from your app */
46
+ userId: string;
47
+ /** @deprecated Use `granularId` instead */
44
48
  subjectId: string;
45
- /** External identity ID (e.g. Auth0 ID) */
49
+ /** @deprecated Use `userId` instead */
46
50
  identityId: string;
47
51
  /** User's display name */
48
52
  name?: string;
@@ -68,6 +72,10 @@ interface RecordUserOptions {
68
72
  * Subject as returned from the API
69
73
  */
70
74
  interface Subject {
75
+ /** Internal Granular user identifier */
76
+ granularId: string;
77
+ /** External user identifier from your app */
78
+ userId: string;
71
79
  subjectId: string;
72
80
  tenantId: string;
73
81
  identityId: string;
@@ -83,8 +91,24 @@ interface Subject {
83
91
  interface ConnectOptions {
84
92
  /** The sandbox name or ID to connect to */
85
93
  sandbox: string;
86
- /** The user to connect as (from recordUser()) */
87
- user: User;
94
+ /**
95
+ * External user identifier from your app. This is the primary input for
96
+ * connecting to a sandbox and the only required user field in the common case.
97
+ */
98
+ userId?: string;
99
+ /**
100
+ * Internal Granular user identifier. Optional fallback when you only know
101
+ * the Granular-side ID for an existing subject.
102
+ */
103
+ granularId?: string;
104
+ /** Optional display name used when upserting the user */
105
+ name?: string;
106
+ /** Optional email used when upserting the user */
107
+ email?: string;
108
+ /** Permission profile IDs or names to ensure before connecting */
109
+ permissions?: string[];
110
+ /** Backwards-compatible user object returned from recordUser() */
111
+ user?: User;
88
112
  /** Optional stable client ID. Defaults to `client_${Date.now()}`. Use a fixed
89
113
  * value for long-lived effect hosts so tool catalogs don't accumulate. */
90
114
  clientId?: string;
@@ -274,6 +298,8 @@ interface EffectHandlerContext {
274
298
  principalId?: string;
275
299
  permissionProfileId?: string;
276
300
  user: {
301
+ granularId?: string;
302
+ userId?: string;
277
303
  subjectId: string;
278
304
  identityId?: string;
279
305
  principalId?: string;
@@ -858,6 +884,7 @@ declare class Session {
858
884
  /** Last known tools for diffing */
859
885
  private lastKnownTools;
860
886
  constructor(client: WSClient, clientId?: string);
887
+ private extractDomainRevisionFromDoc;
861
888
  private buildLegacyEffectContext;
862
889
  get document(): Doc<Record<string, unknown>>;
863
890
  get domainRevision(): string | null;
@@ -1017,6 +1044,8 @@ declare class Environment extends Session {
1017
1044
  get sandboxId(): string;
1018
1045
  /** The subject ID */
1019
1046
  get subjectId(): string;
1047
+ /** Internal Granular user identifier for this environment */
1048
+ get granularId(): string;
1020
1049
  /** The permission profile ID */
1021
1050
  get permissionProfileId(): string;
1022
1051
  /** The GraphQL API endpoint URL */
@@ -1330,7 +1359,7 @@ declare class Granular {
1330
1359
  * Records/upserts a user and prepares them for sandbox connections
1331
1360
  *
1332
1361
  * @param options - User options
1333
- * @returns The user object to pass to connect()
1362
+ * @returns The recorded user with both `userId` and `granularId`
1334
1363
  *
1335
1364
  * @example
1336
1365
  * ```typescript
@@ -1342,6 +1371,7 @@ declare class Granular {
1342
1371
  * ```
1343
1372
  */
1344
1373
  recordUser(options: RecordUserOptions): Promise<User>;
1374
+ private resolveConnectUser;
1345
1375
  /**
1346
1376
  * Connect to a sandbox and establish a real-time environment session.
1347
1377
  *
@@ -1354,14 +1384,10 @@ declare class Granular {
1354
1384
  *
1355
1385
  * @example
1356
1386
  * ```typescript
1357
- * const user = await granular.recordUser({
1358
- * userId: 'user_123',
1359
- * permissions: ['agent'],
1360
- * });
1361
- *
1362
1387
  * const environment = await granular.connect({
1363
1388
  * sandbox: 'my-sandbox',
1364
- * user,
1389
+ * userId: 'user_123',
1390
+ * permissions: ['agent'],
1365
1391
  * });
1366
1392
  *
1367
1393
  * await granular.registerEffect('my-sandbox', {
package/dist/index.d.ts CHANGED
@@ -40,9 +40,13 @@ type GranularAuth = string;
40
40
  * A user/subject object returned from recordUser()
41
41
  */
42
42
  interface User {
43
- /** Internal subject ID */
43
+ /** Internal Granular user identifier */
44
+ granularId: string;
45
+ /** External user identifier from your app */
46
+ userId: string;
47
+ /** @deprecated Use `granularId` instead */
44
48
  subjectId: string;
45
- /** External identity ID (e.g. Auth0 ID) */
49
+ /** @deprecated Use `userId` instead */
46
50
  identityId: string;
47
51
  /** User's display name */
48
52
  name?: string;
@@ -68,6 +72,10 @@ interface RecordUserOptions {
68
72
  * Subject as returned from the API
69
73
  */
70
74
  interface Subject {
75
+ /** Internal Granular user identifier */
76
+ granularId: string;
77
+ /** External user identifier from your app */
78
+ userId: string;
71
79
  subjectId: string;
72
80
  tenantId: string;
73
81
  identityId: string;
@@ -83,8 +91,24 @@ interface Subject {
83
91
  interface ConnectOptions {
84
92
  /** The sandbox name or ID to connect to */
85
93
  sandbox: string;
86
- /** The user to connect as (from recordUser()) */
87
- user: User;
94
+ /**
95
+ * External user identifier from your app. This is the primary input for
96
+ * connecting to a sandbox and the only required user field in the common case.
97
+ */
98
+ userId?: string;
99
+ /**
100
+ * Internal Granular user identifier. Optional fallback when you only know
101
+ * the Granular-side ID for an existing subject.
102
+ */
103
+ granularId?: string;
104
+ /** Optional display name used when upserting the user */
105
+ name?: string;
106
+ /** Optional email used when upserting the user */
107
+ email?: string;
108
+ /** Permission profile IDs or names to ensure before connecting */
109
+ permissions?: string[];
110
+ /** Backwards-compatible user object returned from recordUser() */
111
+ user?: User;
88
112
  /** Optional stable client ID. Defaults to `client_${Date.now()}`. Use a fixed
89
113
  * value for long-lived effect hosts so tool catalogs don't accumulate. */
90
114
  clientId?: string;
@@ -274,6 +298,8 @@ interface EffectHandlerContext {
274
298
  principalId?: string;
275
299
  permissionProfileId?: string;
276
300
  user: {
301
+ granularId?: string;
302
+ userId?: string;
277
303
  subjectId: string;
278
304
  identityId?: string;
279
305
  principalId?: string;
@@ -858,6 +884,7 @@ declare class Session {
858
884
  /** Last known tools for diffing */
859
885
  private lastKnownTools;
860
886
  constructor(client: WSClient, clientId?: string);
887
+ private extractDomainRevisionFromDoc;
861
888
  private buildLegacyEffectContext;
862
889
  get document(): Doc<Record<string, unknown>>;
863
890
  get domainRevision(): string | null;
@@ -1017,6 +1044,8 @@ declare class Environment extends Session {
1017
1044
  get sandboxId(): string;
1018
1045
  /** The subject ID */
1019
1046
  get subjectId(): string;
1047
+ /** Internal Granular user identifier for this environment */
1048
+ get granularId(): string;
1020
1049
  /** The permission profile ID */
1021
1050
  get permissionProfileId(): string;
1022
1051
  /** The GraphQL API endpoint URL */
@@ -1330,7 +1359,7 @@ declare class Granular {
1330
1359
  * Records/upserts a user and prepares them for sandbox connections
1331
1360
  *
1332
1361
  * @param options - User options
1333
- * @returns The user object to pass to connect()
1362
+ * @returns The recorded user with both `userId` and `granularId`
1334
1363
  *
1335
1364
  * @example
1336
1365
  * ```typescript
@@ -1342,6 +1371,7 @@ declare class Granular {
1342
1371
  * ```
1343
1372
  */
1344
1373
  recordUser(options: RecordUserOptions): Promise<User>;
1374
+ private resolveConnectUser;
1345
1375
  /**
1346
1376
  * Connect to a sandbox and establish a real-time environment session.
1347
1377
  *
@@ -1354,14 +1384,10 @@ declare class Granular {
1354
1384
  *
1355
1385
  * @example
1356
1386
  * ```typescript
1357
- * const user = await granular.recordUser({
1358
- * userId: 'user_123',
1359
- * permissions: ['agent'],
1360
- * });
1361
- *
1362
1387
  * const environment = await granular.connect({
1363
1388
  * sandbox: 'my-sandbox',
1364
- * user,
1389
+ * userId: 'user_123',
1390
+ * permissions: ['agent'],
1365
1391
  * });
1366
1392
  *
1367
1393
  * await granular.registerEffect('my-sandbox', {
package/dist/index.js CHANGED
@@ -4496,6 +4496,18 @@ var Session = class {
4496
4496
  this.setupEventHandlers();
4497
4497
  this.setupToolInvokeHandler();
4498
4498
  }
4499
+ extractDomainRevisionFromDoc(doc) {
4500
+ const domain = doc?.domain;
4501
+ const active = domain?.active;
4502
+ if (typeof active === "string" && active.trim()) {
4503
+ return active;
4504
+ }
4505
+ const nestedRevision = active?.domainRevision;
4506
+ if (typeof nestedRevision === "string" && nestedRevision.trim()) {
4507
+ return nestedRevision;
4508
+ }
4509
+ return null;
4510
+ }
4499
4511
  buildLegacyEffectContext() {
4500
4512
  return {
4501
4513
  effectClientId: this.clientId,
@@ -4503,6 +4515,8 @@ var Session = class {
4503
4515
  environmentId: "",
4504
4516
  sessionId: "",
4505
4517
  user: {
4518
+ granularId: "",
4519
+ userId: "",
4506
4520
  subjectId: ""
4507
4521
  }
4508
4522
  };
@@ -4588,7 +4602,14 @@ var Session = class {
4588
4602
  * execute locally and return the result to the sandbox.
4589
4603
  */
4590
4604
  async submitJob(code, domainRevision) {
4591
- const revision = domainRevision || this.currentDomainRevision || this.client.doc?.domain?.active || void 0;
4605
+ let revision = domainRevision || this.currentDomainRevision || this.extractDomainRevisionFromDoc(this.client.doc) || void 0;
4606
+ if (!revision) {
4607
+ try {
4608
+ const summary = await this.getDomain();
4609
+ revision = summary.activeDomainRevision || this.extractDomainRevisionFromDoc(this.client.doc) || void 0;
4610
+ } catch {
4611
+ }
4612
+ }
4592
4613
  if (!revision) {
4593
4614
  throw new Error("No domain revision available. Register live effects or ensure the build schema is activated.");
4594
4615
  }
@@ -4713,7 +4734,13 @@ var Session = class {
4713
4734
  * Get the current domain state and available tools
4714
4735
  */
4715
4736
  async getDomain() {
4716
- return this.client.call("domain.getSummary", {});
4737
+ const summary = await this.client.call("domain.getSummary", {});
4738
+ if (summary.activeDomainRevision) {
4739
+ this.currentDomainRevision = summary.activeDomainRevision;
4740
+ } else {
4741
+ this.currentDomainRevision = this.extractDomainRevisionFromDoc(this.client.doc);
4742
+ }
4743
+ return summary;
4717
4744
  }
4718
4745
  /**
4719
4746
  * Fetch a domain package part from the backend (no fallback).
@@ -4931,6 +4958,7 @@ import { ${allImports} } from "./sandbox-tools";
4931
4958
  }
4932
4959
  setupEventHandlers() {
4933
4960
  this.client.on("sync", (doc) => {
4961
+ this.currentDomainRevision = this.extractDomainRevisionFromDoc(doc);
4934
4962
  this.emit("sync", doc);
4935
4963
  this.checkForToolChanges();
4936
4964
  });
@@ -5197,6 +5225,29 @@ function normalizeHeapSnapshot(raw) {
5197
5225
  updatedAt: typeof heap.updatedAt === "number" ? heap.updatedAt : Date.now()
5198
5226
  };
5199
5227
  }
5228
+ function normalizeSubject(subject) {
5229
+ const granularId = subject.granularId || subject.subjectId;
5230
+ const userId = subject.userId || subject.identityId || granularId;
5231
+ return {
5232
+ ...subject,
5233
+ granularId,
5234
+ userId,
5235
+ subjectId: granularId,
5236
+ identityId: subject.identityId || userId
5237
+ };
5238
+ }
5239
+ function normalizeUser(user) {
5240
+ const granularId = user.granularId || user.subjectId;
5241
+ const userId = user.userId || user.identityId || granularId;
5242
+ return {
5243
+ ...user,
5244
+ granularId,
5245
+ userId,
5246
+ subjectId: granularId,
5247
+ identityId: user.identityId || userId,
5248
+ permissions: Array.isArray(user.permissions) ? user.permissions : []
5249
+ };
5250
+ }
5200
5251
  var Environment = class _Environment extends Session {
5201
5252
  envData;
5202
5253
  _apiKey;
@@ -5219,6 +5270,10 @@ var Environment = class _Environment extends Session {
5219
5270
  get subjectId() {
5220
5271
  return this.envData.subjectId;
5221
5272
  }
5273
+ /** Internal Granular user identifier for this environment */
5274
+ get granularId() {
5275
+ return this.envData.subjectId;
5276
+ }
5222
5277
  /** The permission profile ID */
5223
5278
  get permissionProfileId() {
5224
5279
  return this.envData.permissionProfileId;
@@ -6016,7 +6071,7 @@ var Granular = class {
6016
6071
  * Records/upserts a user and prepares them for sandbox connections
6017
6072
  *
6018
6073
  * @param options - User options
6019
- * @returns The user object to pass to connect()
6074
+ * @returns The recorded user with both `userId` and `granularId`
6020
6075
  *
6021
6076
  * @example
6022
6077
  * ```typescript
@@ -6028,21 +6083,61 @@ var Granular = class {
6028
6083
  * ```
6029
6084
  */
6030
6085
  async recordUser(options) {
6031
- const subject = await this.request("/control/subjects", {
6086
+ const subject = normalizeSubject(await this.request("/control/subjects", {
6032
6087
  method: "POST",
6033
6088
  body: JSON.stringify({
6034
6089
  identityId: options.userId,
6035
6090
  name: options.name,
6036
6091
  email: options.email
6037
6092
  })
6038
- });
6039
- return {
6093
+ }));
6094
+ return normalizeUser({
6095
+ granularId: subject.granularId,
6096
+ userId: options.userId,
6040
6097
  subjectId: subject.subjectId,
6041
- identityId: options.userId,
6042
- name: options.name,
6043
- email: options.email,
6098
+ identityId: subject.identityId,
6099
+ name: options.name || subject.name || void 0,
6100
+ email: options.email || subject.email || void 0,
6044
6101
  permissions: options.permissions || []
6045
- };
6102
+ });
6103
+ }
6104
+ async resolveConnectUser(options) {
6105
+ if (options.user) {
6106
+ const user = normalizeUser(options.user);
6107
+ return {
6108
+ granularId: user.granularId,
6109
+ userId: user.userId,
6110
+ name: options.name || user.name,
6111
+ email: options.email || user.email,
6112
+ permissions: options.permissions || user.permissions
6113
+ };
6114
+ }
6115
+ if (options.userId) {
6116
+ const user = await this.recordUser({
6117
+ userId: options.userId,
6118
+ name: options.name,
6119
+ email: options.email,
6120
+ permissions: options.permissions
6121
+ });
6122
+ return {
6123
+ granularId: user.granularId,
6124
+ userId: user.userId,
6125
+ name: user.name,
6126
+ email: user.email,
6127
+ permissions: options.permissions || user.permissions
6128
+ };
6129
+ }
6130
+ if (options.granularId) {
6131
+ const subject = await this.subjects.get(options.granularId);
6132
+ return {
6133
+ granularId: subject.granularId,
6134
+ userId: subject.userId,
6135
+ name: options.name || subject.name || void 0,
6136
+ email: options.email || subject.email || void 0,
6137
+ permissions: options.permissions || []
6138
+ };
6139
+ }
6140
+ throw new Error("connect() requires either userId, granularId, or a user object returned by recordUser().");
6046
6141
  }
6047
6142
  /**
6048
6143
  * Connect to a sandbox and establish a real-time environment session.
@@ -6056,14 +6151,10 @@ var Granular = class {
6056
6151
  *
6057
6152
  * @example
6058
6153
  * ```typescript
6059
- * const user = await granular.recordUser({
6060
- * userId: 'user_123',
6061
- * permissions: ['agent'],
6062
- * });
6063
- *
6064
6154
  * const environment = await granular.connect({
6065
6155
  * sandbox: 'my-sandbox',
6066
- * user,
6156
+ * userId: 'user_123',
6157
+ * permissions: ['agent'],
6067
6158
  * });
6068
6159
  *
6069
6160
  * await granular.registerEffect('my-sandbox', {
@@ -6084,13 +6175,14 @@ var Granular = class {
6084
6175
  */
6085
6176
  async connect(options) {
6086
6177
  const clientId = options.clientId || `client_${Date.now()}`;
6178
+ const user = await this.resolveConnectUser(options);
6087
6179
  const sandbox = await this.findOrCreateSandbox(options.sandbox);
6088
- for (const profileName of options.user.permissions) {
6180
+ for (const profileName of user.permissions) {
6089
6181
  const profileId = await this.ensurePermissionProfile(sandbox.sandboxId, profileName);
6090
- await this.ensureAssignment(options.user.subjectId, sandbox.sandboxId, profileId);
6182
+ await this.ensureAssignment(user.granularId, sandbox.sandboxId, profileId);
6091
6183
  }
6092
6184
  const envData = await this.environments.create(sandbox.sandboxId, {
6093
- subjectId: options.user.subjectId,
6185
+ subjectId: user.granularId,
6094
6186
  permissionProfileId: null
6095
6187
  });
6096
6188
  await this.activateEnvironment(envData.environmentId);
@@ -6523,7 +6615,7 @@ var Granular = class {
6523
6615
  get subjects() {
6524
6616
  return {
6525
6617
  get: async (subjectId) => {
6526
- return this.request(`/control/subjects/${subjectId}`);
6618
+ return normalizeSubject(await this.request(`/control/subjects/${subjectId}`));
6527
6619
  },
6528
6620
  listAssignments: async (subjectId) => {
6529
6621
  return this.request(`/control/subjects/${subjectId}/assignments`);
@@ -6536,17 +6628,17 @@ var Granular = class {
6536
6628
  get users() {
6537
6629
  return {
6538
6630
  create: async (data) => {
6539
- return this.request("/control/subjects", {
6631
+ return normalizeSubject(await this.request("/control/subjects", {
6540
6632
  method: "POST",
6541
6633
  body: JSON.stringify({
6542
6634
  identityId: data.id,
6543
6635
  name: data.name,
6544
6636
  email: data.email
6545
6637
  })
6546
- });
6638
+ }));
6547
6639
  },
6548
6640
  get: async (id) => {
6549
- return this.request(`/control/subjects/${id}`);
6641
+ return normalizeSubject(await this.request(`/control/subjects/${id}`));
6550
6642
  }
6551
6643
  };
6552
6644
  }