@granular-software/sdk 0.4.56 → 0.4.58

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
@@ -16606,24 +16606,72 @@ var Granular = class _Granular {
16606
16606
  if (!tagName) {
16607
16607
  throw new Error(`${methodName}() requires \`tag\`.`);
16608
16608
  }
16609
- const user = await this.resolveConnectUser(options);
16610
- if (!Array.isArray(user.permissions) || user.permissions.length === 0) {
16609
+ const assignmentMode = options.assignmentMode ?? "ensure";
16610
+ if (assignmentMode !== "ensure" && assignmentMode !== "require-existing") {
16611
16611
  throw new Error(
16612
- `${methodName}() requires at least one permission so the SDK can ensure assignments for new users.`
16612
+ `${methodName}() received an unsupported assignmentMode: ${String(options.assignmentMode)}.`
16613
16613
  );
16614
16614
  }
16615
- const sandbox = await this.findOrCreateSandbox(ontology);
16616
- for (const profileName of user.permissions) {
16617
- const profileId = await this.ensurePermissionProfile(
16618
- sandbox.sandboxId,
16619
- profileName
16615
+ const requiredGranularId = options.granularId?.trim();
16616
+ if (assignmentMode === "require-existing" && (!requiredGranularId || options.userId || options.user)) {
16617
+ throw new Error(
16618
+ `${methodName}() with assignmentMode "require-existing" requires granularId and does not accept userId or user.`
16620
16619
  );
16621
- await this.ensureAssignment(
16622
- user.granularId,
16623
- sandbox.sandboxId,
16624
- profileId
16620
+ }
16621
+ const user = await this.resolveConnectUser(
16622
+ assignmentMode === "require-existing" ? { ...options, granularId: requiredGranularId } : options
16623
+ );
16624
+ let requiredPermissionProfileId = null;
16625
+ const sandbox = assignmentMode === "require-existing" ? { sandboxId: await this._resolveSandboxId(ontology) } : await this.findOrCreateSandbox(ontology);
16626
+ if (assignmentMode === "require-existing") {
16627
+ const assignments = await this.subjects.listAssignments(user.granularId);
16628
+ const assignment = assignments.items.find(
16629
+ (item) => item.sandboxId === sandbox.sandboxId
16625
16630
  );
16631
+ if (!assignment) {
16632
+ throw new Error(
16633
+ `${methodName}() requires an existing assignment for subject ${user.granularId} in ontology ${sandbox.sandboxId}.`
16634
+ );
16635
+ }
16636
+ const assignedPermissionProfileId = assignment.permissionProfileId?.trim();
16637
+ if (!assignedPermissionProfileId) {
16638
+ throw new Error(
16639
+ `${methodName}() found an invalid assignment without a permission profile for subject ${user.granularId} in ontology ${sandbox.sandboxId}.`
16640
+ );
16641
+ }
16642
+ const expectedPermissionProfileId = options.expectedPermissionProfileId?.trim();
16643
+ if (expectedPermissionProfileId && assignedPermissionProfileId !== expectedPermissionProfileId) {
16644
+ throw new Error(
16645
+ `${methodName}() assignment mismatch for subject ${user.granularId}: expected permission profile ${expectedPermissionProfileId}, received ${assignedPermissionProfileId}.`
16646
+ );
16647
+ }
16648
+ requiredPermissionProfileId = assignedPermissionProfileId;
16649
+ } else {
16650
+ if (!Array.isArray(user.permissions) || user.permissions.length === 0) {
16651
+ throw new Error(
16652
+ `${methodName}() requires at least one permission so the SDK can ensure assignments for new users.`
16653
+ );
16654
+ }
16655
+ for (const profileName of user.permissions) {
16656
+ const profileId = await this.ensurePermissionProfile(
16657
+ sandbox.sandboxId,
16658
+ profileName
16659
+ );
16660
+ await this.ensureAssignment(
16661
+ user.granularId,
16662
+ sandbox.sandboxId,
16663
+ profileId
16664
+ );
16665
+ }
16626
16666
  }
16667
+ const requireMatchingEnvironmentProfile = (environment2) => {
16668
+ if (requiredPermissionProfileId && environment2.permissionProfileId !== requiredPermissionProfileId) {
16669
+ throw new Error(
16670
+ `${methodName}() resolved environment ${environment2.environmentId} with permission profile ${environment2.permissionProfileId || "none"}; current assignment requires ${requiredPermissionProfileId}.`
16671
+ );
16672
+ }
16673
+ return environment2;
16674
+ };
16627
16675
  const tags = await this.request(
16628
16676
  `/control/sandboxes/${sandbox.sandboxId}/tags`
16629
16677
  );
@@ -16646,6 +16694,9 @@ var Granular = class _Granular {
16646
16694
  const resolveActive = async (operationKey) => {
16647
16695
  const query = new URLSearchParams({ tagId: tag.tagId, slot });
16648
16696
  if (operationKey) query.set("operationKey", operationKey);
16697
+ if (requiredPermissionProfileId) {
16698
+ query.set("expectedPermissionProfileId", requiredPermissionProfileId);
16699
+ }
16649
16700
  try {
16650
16701
  const payload = await this.request(
16651
16702
  `/control/sandboxes/${encodeURIComponent(sandbox.sandboxId)}/subjects/${encodeURIComponent(user.granularId)}/active-environment?${query.toString()}`
@@ -16669,7 +16720,8 @@ var Granular = class _Granular {
16669
16720
  tagId: tag.tagId,
16670
16721
  slot,
16671
16722
  operationKey: resetKey,
16672
- operation: resetKey ? "explicit_reset" : void 0
16723
+ operation: resetKey ? "explicit_reset" : void 0,
16724
+ expectedPermissionProfileId: requiredPermissionProfileId || void 0
16673
16725
  })
16674
16726
  }
16675
16727
  );
@@ -16679,7 +16731,7 @@ var Granular = class _Granular {
16679
16731
  const resetEnvironment = await resolveActive(resetKey);
16680
16732
  if (resetEnvironment) {
16681
16733
  return {
16682
- environment: resetEnvironment,
16734
+ environment: requireMatchingEnvironmentProfile(resetEnvironment),
16683
16735
  requestedOntology: ontology,
16684
16736
  sandboxId: sandbox.sandboxId,
16685
16737
  subjectId: user.granularId,
@@ -16695,7 +16747,7 @@ var Granular = class _Granular {
16695
16747
  const active = await resolveActive();
16696
16748
  if (active && (active.versionId === targetVersionId || options.createFreshIfOutdated !== true)) {
16697
16749
  return {
16698
- environment: active,
16750
+ environment: requireMatchingEnvironmentProfile(active),
16699
16751
  requestedOntology: ontology,
16700
16752
  sandboxId: sandbox.sandboxId,
16701
16753
  subjectId: user.granularId,
@@ -16713,7 +16765,10 @@ var Granular = class _Granular {
16713
16765
  )
16714
16766
  );
16715
16767
  if (!resetKey && currentMatches.length > 0) {
16716
- const environment2 = await activate(currentMatches[0]);
16768
+ requireMatchingEnvironmentProfile(currentMatches[0]);
16769
+ const environment2 = requireMatchingEnvironmentProfile(
16770
+ await activate(currentMatches[0])
16771
+ );
16717
16772
  return {
16718
16773
  environment: environment2,
16719
16774
  requestedOntology: ontology,
@@ -16726,7 +16781,10 @@ var Granular = class _Granular {
16726
16781
  userEnvironments.filter((environment2) => environment2.tagId === tag.tagId)
16727
16782
  );
16728
16783
  if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
16729
- const environment2 = await activate(outdatedMatches[0]);
16784
+ requireMatchingEnvironmentProfile(outdatedMatches[0]);
16785
+ const environment2 = requireMatchingEnvironmentProfile(
16786
+ await activate(outdatedMatches[0])
16787
+ );
16730
16788
  return {
16731
16789
  environment: environment2,
16732
16790
  requestedOntology: ontology,
@@ -16745,7 +16803,10 @@ var Granular = class _Granular {
16745
16803
  tagId: tag.tagId,
16746
16804
  permissionProfileId: null
16747
16805
  });
16748
- const environment = await activate(created);
16806
+ requireMatchingEnvironmentProfile(created);
16807
+ const environment = requireMatchingEnvironmentProfile(
16808
+ await activate(created)
16809
+ );
16749
16810
  return {
16750
16811
  environment,
16751
16812
  requestedOntology: ontology,
@@ -16955,22 +17016,16 @@ var Granular = class _Granular {
16955
17016
  metadata: options?.metadata
16956
17017
  });
16957
17018
  }
16958
- /**
16959
- * Mark a session closed in the control plane. If `environment` is the connected handle for that
16960
- * `sessionId`, disconnects the WebSocket so the runtime tears down cleanly.
16961
- */
17019
+ /** End a session through the runtime, or disconnect its connected handle. */
16962
17020
  async closeSession(sessionId, environment) {
16963
17021
  if (environment && environment.sessionId === sessionId) {
16964
17022
  await environment.disconnect();
16965
17023
  return;
16966
17024
  }
16967
- await this.request(`/control/sessions/${encodeURIComponent(sessionId)}`, {
16968
- method: "PATCH",
16969
- body: JSON.stringify({
16970
- status: "closed",
16971
- lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
16972
- })
16973
- });
17025
+ await this.request(
17026
+ `/control/sessions/${encodeURIComponent(sessionId)}/close`,
17027
+ { method: "POST" }
17028
+ );
16974
17029
  }
16975
17030
  /**
16976
17031
  * Re-open a closed session in the index and connect to its existing runtime document.