@granular-software/sdk 0.4.57 → 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.
@@ -16511,24 +16511,72 @@ var Granular = class _Granular {
16511
16511
  if (!tagName) {
16512
16512
  throw new Error(`${methodName}() requires \`tag\`.`);
16513
16513
  }
16514
- const user = await this.resolveConnectUser(options);
16515
- if (!Array.isArray(user.permissions) || user.permissions.length === 0) {
16514
+ const assignmentMode = options.assignmentMode ?? "ensure";
16515
+ if (assignmentMode !== "ensure" && assignmentMode !== "require-existing") {
16516
16516
  throw new Error(
16517
- `${methodName}() requires at least one permission so the SDK can ensure assignments for new users.`
16517
+ `${methodName}() received an unsupported assignmentMode: ${String(options.assignmentMode)}.`
16518
16518
  );
16519
16519
  }
16520
- const sandbox = await this.findOrCreateSandbox(ontology);
16521
- for (const profileName of user.permissions) {
16522
- const profileId = await this.ensurePermissionProfile(
16523
- sandbox.sandboxId,
16524
- profileName
16520
+ const requiredGranularId = options.granularId?.trim();
16521
+ if (assignmentMode === "require-existing" && (!requiredGranularId || options.userId || options.user)) {
16522
+ throw new Error(
16523
+ `${methodName}() with assignmentMode "require-existing" requires granularId and does not accept userId or user.`
16525
16524
  );
16526
- await this.ensureAssignment(
16527
- user.granularId,
16528
- sandbox.sandboxId,
16529
- profileId
16525
+ }
16526
+ const user = await this.resolveConnectUser(
16527
+ assignmentMode === "require-existing" ? { ...options, granularId: requiredGranularId } : options
16528
+ );
16529
+ let requiredPermissionProfileId = null;
16530
+ const sandbox = assignmentMode === "require-existing" ? { sandboxId: await this._resolveSandboxId(ontology) } : await this.findOrCreateSandbox(ontology);
16531
+ if (assignmentMode === "require-existing") {
16532
+ const assignments = await this.subjects.listAssignments(user.granularId);
16533
+ const assignment = assignments.items.find(
16534
+ (item) => item.sandboxId === sandbox.sandboxId
16530
16535
  );
16536
+ if (!assignment) {
16537
+ throw new Error(
16538
+ `${methodName}() requires an existing assignment for subject ${user.granularId} in ontology ${sandbox.sandboxId}.`
16539
+ );
16540
+ }
16541
+ const assignedPermissionProfileId = assignment.permissionProfileId?.trim();
16542
+ if (!assignedPermissionProfileId) {
16543
+ throw new Error(
16544
+ `${methodName}() found an invalid assignment without a permission profile for subject ${user.granularId} in ontology ${sandbox.sandboxId}.`
16545
+ );
16546
+ }
16547
+ const expectedPermissionProfileId = options.expectedPermissionProfileId?.trim();
16548
+ if (expectedPermissionProfileId && assignedPermissionProfileId !== expectedPermissionProfileId) {
16549
+ throw new Error(
16550
+ `${methodName}() assignment mismatch for subject ${user.granularId}: expected permission profile ${expectedPermissionProfileId}, received ${assignedPermissionProfileId}.`
16551
+ );
16552
+ }
16553
+ requiredPermissionProfileId = assignedPermissionProfileId;
16554
+ } else {
16555
+ if (!Array.isArray(user.permissions) || user.permissions.length === 0) {
16556
+ throw new Error(
16557
+ `${methodName}() requires at least one permission so the SDK can ensure assignments for new users.`
16558
+ );
16559
+ }
16560
+ for (const profileName of user.permissions) {
16561
+ const profileId = await this.ensurePermissionProfile(
16562
+ sandbox.sandboxId,
16563
+ profileName
16564
+ );
16565
+ await this.ensureAssignment(
16566
+ user.granularId,
16567
+ sandbox.sandboxId,
16568
+ profileId
16569
+ );
16570
+ }
16531
16571
  }
16572
+ const requireMatchingEnvironmentProfile = (environment2) => {
16573
+ if (requiredPermissionProfileId && environment2.permissionProfileId !== requiredPermissionProfileId) {
16574
+ throw new Error(
16575
+ `${methodName}() resolved environment ${environment2.environmentId} with permission profile ${environment2.permissionProfileId || "none"}; current assignment requires ${requiredPermissionProfileId}.`
16576
+ );
16577
+ }
16578
+ return environment2;
16579
+ };
16532
16580
  const tags = await this.request(
16533
16581
  `/control/sandboxes/${sandbox.sandboxId}/tags`
16534
16582
  );
@@ -16551,6 +16599,9 @@ var Granular = class _Granular {
16551
16599
  const resolveActive = async (operationKey) => {
16552
16600
  const query = new URLSearchParams({ tagId: tag.tagId, slot });
16553
16601
  if (operationKey) query.set("operationKey", operationKey);
16602
+ if (requiredPermissionProfileId) {
16603
+ query.set("expectedPermissionProfileId", requiredPermissionProfileId);
16604
+ }
16554
16605
  try {
16555
16606
  const payload = await this.request(
16556
16607
  `/control/sandboxes/${encodeURIComponent(sandbox.sandboxId)}/subjects/${encodeURIComponent(user.granularId)}/active-environment?${query.toString()}`
@@ -16574,7 +16625,8 @@ var Granular = class _Granular {
16574
16625
  tagId: tag.tagId,
16575
16626
  slot,
16576
16627
  operationKey: resetKey,
16577
- operation: resetKey ? "explicit_reset" : void 0
16628
+ operation: resetKey ? "explicit_reset" : void 0,
16629
+ expectedPermissionProfileId: requiredPermissionProfileId || void 0
16578
16630
  })
16579
16631
  }
16580
16632
  );
@@ -16584,7 +16636,7 @@ var Granular = class _Granular {
16584
16636
  const resetEnvironment = await resolveActive(resetKey);
16585
16637
  if (resetEnvironment) {
16586
16638
  return {
16587
- environment: resetEnvironment,
16639
+ environment: requireMatchingEnvironmentProfile(resetEnvironment),
16588
16640
  requestedOntology: ontology,
16589
16641
  sandboxId: sandbox.sandboxId,
16590
16642
  subjectId: user.granularId,
@@ -16600,7 +16652,7 @@ var Granular = class _Granular {
16600
16652
  const active = await resolveActive();
16601
16653
  if (active && (active.versionId === targetVersionId || options.createFreshIfOutdated !== true)) {
16602
16654
  return {
16603
- environment: active,
16655
+ environment: requireMatchingEnvironmentProfile(active),
16604
16656
  requestedOntology: ontology,
16605
16657
  sandboxId: sandbox.sandboxId,
16606
16658
  subjectId: user.granularId,
@@ -16618,7 +16670,10 @@ var Granular = class _Granular {
16618
16670
  )
16619
16671
  );
16620
16672
  if (!resetKey && currentMatches.length > 0) {
16621
- const environment2 = await activate(currentMatches[0]);
16673
+ requireMatchingEnvironmentProfile(currentMatches[0]);
16674
+ const environment2 = requireMatchingEnvironmentProfile(
16675
+ await activate(currentMatches[0])
16676
+ );
16622
16677
  return {
16623
16678
  environment: environment2,
16624
16679
  requestedOntology: ontology,
@@ -16631,7 +16686,10 @@ var Granular = class _Granular {
16631
16686
  userEnvironments.filter((environment2) => environment2.tagId === tag.tagId)
16632
16687
  );
16633
16688
  if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
16634
- const environment2 = await activate(outdatedMatches[0]);
16689
+ requireMatchingEnvironmentProfile(outdatedMatches[0]);
16690
+ const environment2 = requireMatchingEnvironmentProfile(
16691
+ await activate(outdatedMatches[0])
16692
+ );
16635
16693
  return {
16636
16694
  environment: environment2,
16637
16695
  requestedOntology: ontology,
@@ -16650,7 +16708,10 @@ var Granular = class _Granular {
16650
16708
  tagId: tag.tagId,
16651
16709
  permissionProfileId: null
16652
16710
  });
16653
- const environment = await activate(created);
16711
+ requireMatchingEnvironmentProfile(created);
16712
+ const environment = requireMatchingEnvironmentProfile(
16713
+ await activate(created)
16714
+ );
16654
16715
  return {
16655
16716
  environment,
16656
16717
  requestedOntology: ontology,
@@ -16860,22 +16921,16 @@ var Granular = class _Granular {
16860
16921
  metadata: options?.metadata
16861
16922
  });
16862
16923
  }
16863
- /**
16864
- * Mark a session closed in the control plane. If `environment` is the connected handle for that
16865
- * `sessionId`, disconnects the WebSocket so the runtime tears down cleanly.
16866
- */
16924
+ /** End a session through the runtime, or disconnect its connected handle. */
16867
16925
  async closeSession(sessionId, environment) {
16868
16926
  if (environment && environment.sessionId === sessionId) {
16869
16927
  await environment.disconnect();
16870
16928
  return;
16871
16929
  }
16872
- await this.request(`/control/sessions/${encodeURIComponent(sessionId)}`, {
16873
- method: "PATCH",
16874
- body: JSON.stringify({
16875
- status: "closed",
16876
- lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
16877
- })
16878
- });
16930
+ await this.request(
16931
+ `/control/sessions/${encodeURIComponent(sessionId)}/close`,
16932
+ { method: "POST" }
16933
+ );
16879
16934
  }
16880
16935
  /**
16881
16936
  * Re-open a closed session in the index and connect to its existing runtime document.