@relayfile/sdk 0.10.32 → 0.10.33

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/setup.d.ts CHANGED
@@ -42,6 +42,7 @@ export declare class RelayfileSetup {
42
42
  private readonly accessToken?;
43
43
  private readonly requestTimeoutMs;
44
44
  private readonly retryOptions;
45
+ private readonly ensuredMounts;
45
46
  static login(options?: RelayfileCloudLoginOptions): Promise<RelayfileSetup>;
46
47
  static fromCloudTokens(tokens: RelayfileCloudTokenSet, options?: RelayfileCloudTokenSetupOptions): RelayfileSetup;
47
48
  constructor(options?: RelayfileSetupOptions);
@@ -49,6 +50,7 @@ export declare class RelayfileSetup {
49
50
  joinWorkspace(workspaceId: string, options?: JoinWorkspaceOptions): Promise<WorkspaceHandle>;
50
51
  mountWorkspace(input: MountWorkspaceInput): Promise<MountedWorkspaceHandle>;
51
52
  ensureMountedWorkspace(input: EnsureMountedWorkspaceInput): Promise<MountedWorkspaceHandle>;
53
+ private startEnsuredMount;
52
54
  joinWorkspaceResponse(workspaceId: string, options: NormalizedJoinWorkspaceOptions, overrides?: {
53
55
  tokenProvider?: AccessTokenProvider;
54
56
  }): Promise<ValidatedJoinWorkspaceResponse>;
package/dist/setup.js CHANGED
@@ -32,6 +32,7 @@ export class RelayfileSetup {
32
32
  accessToken;
33
33
  requestTimeoutMs;
34
34
  retryOptions;
35
+ ensuredMounts = new Map();
35
36
  static async login(options = {}) {
36
37
  void options;
37
38
  throw new RelayfileSetupError("RelayfileSetup.login() starts a local HTTP callback server and is only available from @relayfile/sdk/cli. Import RelayfileSetup from @relayfile/sdk/cli for interactive Cloud login.", "node_only_sdk_feature");
@@ -136,6 +137,35 @@ export class RelayfileSetup {
136
137
  async ensureMountedWorkspace(input) {
137
138
  const normalized = normalizeEnsureMountedWorkspaceInput(input);
138
139
  const workspace = await this.resolveWorkspaceForMount(normalized);
140
+ throwIfAborted(normalized.signal, "ensureMountedWorkspace");
141
+ const mountKey = ensuredMountKey(workspace, normalized);
142
+ const existing = this.ensuredMounts.get(mountKey);
143
+ if (existing) {
144
+ return waitForEnsuredMount(existing, normalized.signal);
145
+ }
146
+ let shared;
147
+ // The physical mount belongs to the logical target, not the first caller.
148
+ // Individual abort signals only cancel that caller's wait below.
149
+ shared = this.startEnsuredMount(workspace, {
150
+ ...normalized,
151
+ signal: undefined
152
+ }).then((mounted) => new SharedMountedWorkspaceHandle({
153
+ mounted,
154
+ onStopped: () => {
155
+ if (this.ensuredMounts.get(mountKey) === shared) {
156
+ this.ensuredMounts.delete(mountKey);
157
+ }
158
+ }
159
+ }));
160
+ this.ensuredMounts.set(mountKey, shared);
161
+ void shared.catch(() => {
162
+ if (this.ensuredMounts.get(mountKey) === shared) {
163
+ this.ensuredMounts.delete(mountKey);
164
+ }
165
+ });
166
+ return waitForEnsuredMount(shared, normalized.signal);
167
+ }
168
+ async startEnsuredMount(workspace, normalized) {
139
169
  if (normalized.verifyProvider) {
140
170
  if (!normalized.provider) {
141
171
  throw new MountSessionInputError("provider required when verifyProvider=true");
@@ -781,6 +811,55 @@ class MountedWorkspaceHandleImpl {
781
811
  await safeStopLauncher(this.launcherInstance);
782
812
  }
783
813
  }
814
+ class SharedMountedWorkspaceHandle {
815
+ mounted;
816
+ onStopped;
817
+ stopPromise;
818
+ constructor(input) {
819
+ this.mounted = input.mounted;
820
+ this.onStopped = input.onStopped;
821
+ }
822
+ get workspaceId() {
823
+ return this.mounted.workspaceId;
824
+ }
825
+ get localDir() {
826
+ return this.mounted.localDir;
827
+ }
828
+ get remotePath() {
829
+ return this.mounted.remotePath;
830
+ }
831
+ get mode() {
832
+ return this.mounted.mode;
833
+ }
834
+ get ready() {
835
+ return this.mounted.ready;
836
+ }
837
+ get expiresAt() {
838
+ return this.mounted.expiresAt;
839
+ }
840
+ get suggestedRefreshAt() {
841
+ return this.mounted.suggestedRefreshAt;
842
+ }
843
+ env() {
844
+ return this.mounted.env();
845
+ }
846
+ status() {
847
+ return this.mounted.status();
848
+ }
849
+ async stop() {
850
+ if (!this.stopPromise)
851
+ this.stopPromise = this.performStop();
852
+ await this.stopPromise;
853
+ }
854
+ async performStop() {
855
+ try {
856
+ await this.mounted.stop();
857
+ }
858
+ finally {
859
+ this.onStopped();
860
+ }
861
+ }
862
+ }
784
863
  class SupervisedMountedWorkspaceHandle {
785
864
  mounted;
786
865
  launch;
@@ -941,6 +1020,37 @@ class SupervisedMountedWorkspaceHandle {
941
1020
  void Promise.resolve(this.onEvent?.(event)).catch(() => { });
942
1021
  }
943
1022
  }
1023
+ function ensuredMountKey(workspace, input) {
1024
+ return JSON.stringify([
1025
+ workspace.workspaceId,
1026
+ input.localDir,
1027
+ input.remotePath,
1028
+ input.mode,
1029
+ input.localLayout,
1030
+ input.syncMode,
1031
+ input.background
1032
+ ]);
1033
+ }
1034
+ async function waitForEnsuredMount(mounted, signal) {
1035
+ if (!signal)
1036
+ return mounted;
1037
+ if (signal.aborted)
1038
+ throw new CloudAbortError("ensureMountedWorkspace");
1039
+ let onAbort;
1040
+ try {
1041
+ return await Promise.race([
1042
+ mounted,
1043
+ new Promise((_, reject) => {
1044
+ onAbort = () => reject(new CloudAbortError("ensureMountedWorkspace"));
1045
+ signal.addEventListener("abort", onAbort, { once: true });
1046
+ })
1047
+ ]);
1048
+ }
1049
+ finally {
1050
+ if (onAbort)
1051
+ signal.removeEventListener("abort", onAbort);
1052
+ }
1053
+ }
944
1054
  function setupErrorCode(error) {
945
1055
  return error instanceof RelayfileSetupError ? error.code : "mount_refresh_failed";
946
1056
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relayfile/sdk",
3
- "version": "0.10.32",
3
+ "version": "0.10.33",
4
4
  "description": "TypeScript SDK for relayfile — real-time filesystem for humans and agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -59,15 +59,15 @@
59
59
  "prepublishOnly": "npm run build"
60
60
  },
61
61
  "dependencies": {
62
- "@relayfile/core": "0.10.32",
62
+ "@relayfile/core": "0.10.33",
63
63
  "ignore": "^7.0.5",
64
64
  "tar": "^7.5.10"
65
65
  },
66
66
  "optionalDependencies": {
67
- "@relayfile/mount-darwin-arm64": "0.10.32",
68
- "@relayfile/mount-darwin-x64": "0.10.32",
69
- "@relayfile/mount-linux-arm64": "0.10.32",
70
- "@relayfile/mount-linux-x64": "0.10.32"
67
+ "@relayfile/mount-darwin-arm64": "0.10.33",
68
+ "@relayfile/mount-darwin-x64": "0.10.33",
69
+ "@relayfile/mount-linux-arm64": "0.10.33",
70
+ "@relayfile/mount-linux-x64": "0.10.33"
71
71
  },
72
72
  "devDependencies": {
73
73
  "typescript": "^5.7.3",