@relayfile/sdk 0.8.10 → 0.8.11

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.ts CHANGED
@@ -2,7 +2,7 @@ export { RelayFileClient, DEFAULT_RELAYFILE_BASE_URL, type AccessTokenProvider,
2
2
  export { RelayfileSetup, RELAYFILE_SDK_VERSION, WorkspaceHandle } from "./setup.js";
3
3
  export { type RelayfileCloudLoginOptions, type RelayfileCloudTokenSet, type RelayfileCloudTokenSetupOptions } from "./cloud-login.js";
4
4
  export { CloudAbortError, CloudApiError, CloudTimeoutError, InvalidLocalDirError, InvalidMountModeError, InvalidRemotePathError, IntegrationConnectionTimeoutError, MalformedCloudResponseError, MissingConnectionIdError, MountModeUnavailableError, MountReadyTimeoutError, MountSessionInputError, ProviderNotConnectedError, ProviderNotReadyError, RelayfileSetupError, UnknownProviderError } from "./setup-errors.js";
5
- export { type EnsureMountedWorkspaceInput, WORKSPACE_INTEGRATION_PROVIDERS, type AgentWorkspaceInvite, type AgentWorkspaceInviteOptions, type AgentWorkspaceScopedInviteOptions, type ConnectIntegrationOptions, type ConnectIntegrationResult, type CreateWorkspaceOptions, type JoinWorkspaceOptions, type MountLauncher, type MountLauncherEvent, type MountLauncherInstance, type MountLauncherStart, type MountMode, type MountSessionRequest, type MountSessionResponse, type MountSessionResult, type MountedWorkspaceHandle, type MountedWorkspaceStatus, type MountWorkspaceInput, type ReadMountedWorkspaceStatusInput, type RelayfileSetupOptions, type RelayfileSetupRetryOptions, type WaitForConnectionOptions, type WorkspaceInfo, type WorkspaceIntegrationProvider, type WorkspaceMountEnv, type WorkspaceMountEnvOptions, type WorkspacePermissions } from "./setup-types.js";
5
+ export { type EnsureMountedWorkspaceInput, WORKSPACE_INTEGRATION_PROVIDERS, type AgentWorkspaceInvite, type AgentWorkspaceInviteOptions, type AgentWorkspaceScopedInviteOptions, type ConnectIntegrationOptions, type ConnectIntegrationResult, type CreateWorkspaceOptions, type JoinWorkspaceOptions, type MountLauncher, type MountLauncherEvent, type MountLauncherInstance, type MountLauncherStart, type MountLocalLayout, type MountMode, type MountSessionRequest, type MountSessionResponse, type MountSessionResult, type MountSyncMode, type MountedWorkspaceHandle, type MountedWorkspaceStatus, type MountWorkspaceInput, type ReadMountedWorkspaceStatusInput, type RelayfileSetupOptions, type RelayfileSetupRetryOptions, type WaitForConnectionOptions, type WorkspaceInfo, type WorkspaceIntegrationProvider, type WorkspaceMountEnv, type WorkspaceMountEnvOptions, type WorkspacePermissions } from "./setup-types.js";
6
6
  export { RelayFileSync, type RelayFileSyncOptions, type RelayFileSyncPong, type RelayFileSyncReconnectOptions, type RelayFileSyncSocket, type RelayFileSyncStart, type RelayFileSyncState, type RelayFileSyncTokenProvider } from "./sync.js";
7
7
  export { onWrite, pathMatches, type OnWriteClient, type OnWriteHandler, type OnWriteHandlerError, type OnWriteOptions } from "./onWrite.js";
8
8
  export { InvalidStateError, PayloadTooLargeError, QueueFullError, RelayFileApiError, RevisionConflictError } from "./errors.js";
@@ -20,7 +20,7 @@ export function createDefaultMountLauncher(options = {}) {
20
20
  };
21
21
  }
22
22
  export async function readMountedWorkspaceStatus(input) {
23
- const state = await readMountStateFile(input.localDir);
23
+ const state = await readMountStateFile(resolveMountLocalDir(input.localDir, input.remotePath, input.localLayout));
24
24
  if (state && !isMountStateStale(state)) {
25
25
  return {
26
26
  ready: isMountStateReady(state),
@@ -46,7 +46,8 @@ export async function readMountedWorkspaceStatus(input) {
46
46
  }
47
47
  async function startRelayfileMount(input, options) {
48
48
  const localDir = path.resolve(input.env.RELAYFILE_LOCAL_DIR ?? process.cwd());
49
- const relayDir = path.join(localDir, ".relay");
49
+ const mountLocalDir = resolveMountLocalDir(localDir, input.env.RELAYFILE_REMOTE_PATH, input.env.RELAYFILE_MOUNT_LOCAL_LAYOUT);
50
+ const relayDir = path.join(mountLocalDir, ".relay");
50
51
  const logPath = path.join(relayDir, "mount.log");
51
52
  const pidPath = path.join(relayDir, "mount.pid");
52
53
  await mkdir(relayDir, { recursive: true });
@@ -54,7 +55,7 @@ async function startRelayfileMount(input, options) {
54
55
  const command = await resolveRelayfileMountCommand();
55
56
  const args = input.background === false ? ["--once"] : [];
56
57
  const child = (options.spawnImpl ?? spawn)(command, args, {
57
- cwd: input.cwd ?? localDir,
58
+ cwd: input.cwd ?? mountLocalDir,
58
59
  env: {
59
60
  ...process.env,
60
61
  ...input.env
@@ -73,7 +74,7 @@ async function startRelayfileMount(input, options) {
73
74
  pidPath,
74
75
  outputBuffer,
75
76
  input,
76
- localDir,
77
+ localDir: mountLocalDir,
77
78
  now: options.now ?? Date.now,
78
79
  readyPollIntervalMs: options.readyPollIntervalMs ?? DEFAULT_READY_POLL_INTERVAL_MS
79
80
  });
@@ -113,6 +114,8 @@ class RelayfileMountProcessInstance {
113
114
  workspaceId: this.input.env.RELAYFILE_WORKSPACE ?? "",
114
115
  remotePath: this.input.env.RELAYFILE_REMOTE_PATH ?? "/",
115
116
  mode: normalizeMountMode(this.input.env.RELAYFILE_MOUNT_MODE) ?? "poll",
117
+ localLayout: normalizeMountLocalLayout(this.input.env.RELAYFILE_MOUNT_LOCAL_LAYOUT),
118
+ syncMode: normalizeMountSyncMode(this.input.env.RELAYFILE_MOUNT_SYNC_MODE),
116
119
  relayfileBaseUrl: this.input.env.RELAYFILE_BASE_URL ?? "",
117
120
  relayfileToken: this.input.env.RELAYFILE_TOKEN ?? "",
118
121
  expiresAt: null,
@@ -251,6 +254,32 @@ function isMountStateStale(state) {
251
254
  function normalizeMountMode(mode) {
252
255
  return mode === "fuse" ? "fuse" : mode === "poll" ? "poll" : undefined;
253
256
  }
257
+ function normalizeMountLocalLayout(layout) {
258
+ return layout === "scoped" ? "scoped" : "exact";
259
+ }
260
+ function normalizeMountSyncMode(mode) {
261
+ return mode === "write-only" ? "write-only" : "mirror";
262
+ }
263
+ function resolveMountLocalDir(localDir, remotePath, localLayout) {
264
+ const root = path.resolve(localDir);
265
+ if (normalizeMountLocalLayout(localLayout) !== "scoped") {
266
+ return root;
267
+ }
268
+ const normalizedRemote = normalizeRemotePath(remotePath);
269
+ if (normalizedRemote === "/") {
270
+ return root;
271
+ }
272
+ return path.join(root, ...normalizedRemote.split("/").filter(Boolean));
273
+ }
274
+ function normalizeRemotePath(remotePath) {
275
+ const trimmed = typeof remotePath === "string" ? remotePath.trim() : "";
276
+ if (!trimmed || trimmed === "/") {
277
+ return "/";
278
+ }
279
+ const slashNormalized = trimmed.replace(/\\/g, "/");
280
+ const normalized = path.posix.normalize(slashNormalized.startsWith("/") ? slashNormalized : `/${slashNormalized}`);
281
+ return normalized === "/" ? "/" : normalized.replace(/\/+$/, "");
282
+ }
254
283
  function normalizeIsoString(value) {
255
284
  if (typeof value !== "string" || value.trim() === "") {
256
285
  return undefined;
@@ -67,6 +67,8 @@ export interface WorkspaceMountEnvOptions {
67
67
  }
68
68
  export type WorkspaceMountEnv = Record<string, string>;
69
69
  export type MountMode = "poll" | "fuse";
70
+ export type MountLocalLayout = "exact" | "scoped";
71
+ export type MountSyncMode = "mirror" | "write-only";
70
72
  export interface MountSessionRequest {
71
73
  localDir: string;
72
74
  remotePath?: string;
@@ -98,6 +100,8 @@ export interface MountSessionResult {
98
100
  remotePath: string;
99
101
  localDir: string;
100
102
  mode: MountMode;
103
+ localLayout: MountLocalLayout;
104
+ syncMode: MountSyncMode;
101
105
  scopes: string[];
102
106
  tokenIssuedAt: string | null;
103
107
  expiresAt: string | null;
@@ -122,6 +126,8 @@ export interface ReadMountedWorkspaceStatusInput {
122
126
  workspaceId: string;
123
127
  remotePath: string;
124
128
  mode: MountMode;
129
+ localLayout?: MountLocalLayout;
130
+ syncMode?: MountSyncMode;
125
131
  relayfileBaseUrl: string;
126
132
  relayfileToken: string;
127
133
  expiresAt: string | null;
@@ -167,6 +173,8 @@ export interface MountWorkspaceInput {
167
173
  localDir: string;
168
174
  remotePath?: string;
169
175
  mode?: MountMode;
176
+ localLayout?: MountLocalLayout;
177
+ syncMode?: MountSyncMode;
170
178
  background?: boolean;
171
179
  agentName?: string;
172
180
  scopes?: string[];
package/dist/setup.js CHANGED
@@ -16,6 +16,8 @@ const DEFAULT_WAIT_INTERVAL_MS = 2_000;
16
16
  const DEFAULT_WAIT_TIMEOUT_MS = 300_000;
17
17
  const DEFAULT_MOUNT_READY_TIMEOUT_MS = 60_000;
18
18
  const DEFAULT_MOUNT_AGENT_NAME = "relayfile-mount";
19
+ const DEFAULT_MOUNT_LOCAL_LAYOUT = "exact";
20
+ const DEFAULT_MOUNT_SYNC_MODE = "mirror";
19
21
  const TOKEN_REFRESH_AGE_MS = 55 * 60 * 1000;
20
22
  const nodeOnlyMountLauncher = {
21
23
  async start() {
@@ -142,6 +144,8 @@ export class RelayfileSetup {
142
144
  localDir: normalized.localDir,
143
145
  remotePath: normalized.remotePath,
144
146
  mode: normalized.mode,
147
+ localLayout: normalized.localLayout,
148
+ syncMode: normalized.syncMode,
145
149
  background: normalized.background,
146
150
  agentName: normalized.agentName,
147
151
  scopes: normalized.scopes,
@@ -246,13 +250,18 @@ export class RelayfileSetup {
246
250
  : undefined
247
251
  });
248
252
  try {
249
- return validateMountSessionResponse(await workspace.requestJson({
253
+ const session = validateMountSessionResponse(await workspace.requestJson({
250
254
  operation: "mountWorkspace",
251
255
  method: "POST",
252
256
  path: `api/v1/workspaces/${encodeURIComponent(workspace.workspaceId)}/relayfile/mount-session`,
253
257
  body: request,
254
258
  signal: input.signal
255
259
  }), input.localDir);
260
+ return {
261
+ ...session,
262
+ localLayout: input.localLayout,
263
+ syncMode: input.syncMode
264
+ };
256
265
  }
257
266
  catch (error) {
258
267
  throw mapMountSessionError(error, request);
@@ -731,6 +740,8 @@ class MountedWorkspaceHandleImpl {
731
740
  workspaceId: this.workspaceId,
732
741
  remotePath: this.remotePath,
733
742
  mode: this.mode,
743
+ localLayout: this.mountSession.localLayout,
744
+ syncMode: this.mountSession.syncMode,
734
745
  relayfileBaseUrl: this.mountSession.relayfileBaseUrl,
735
746
  relayfileToken: this.mountSession.relayfileToken,
736
747
  expiresAt: this.expiresAt,
@@ -837,6 +848,8 @@ function validateMountSessionResponse(payload, localDir) {
837
848
  remotePath: requireStringField(payload, "remotePath"),
838
849
  localDir,
839
850
  mode: requireMountModeField(payload, "mode"),
851
+ localLayout: DEFAULT_MOUNT_LOCAL_LAYOUT,
852
+ syncMode: DEFAULT_MOUNT_SYNC_MODE,
840
853
  scopes: requireStringArrayField(payload, "scopes"),
841
854
  tokenIssuedAt: readNullableStringField(payload, "tokenIssuedAt"),
842
855
  expiresAt: readNullableStringField(payload, "expiresAt"),
@@ -930,6 +943,8 @@ function normalizeMountWorkspaceInput(input) {
930
943
  localDir: resolveLocalDir(localDir),
931
944
  remotePath: normalizeMountRemotePath(input.remotePath),
932
945
  mode: normalizeMountModeInput(input.mode),
946
+ localLayout: normalizeMountLocalLayoutInput(input.localLayout),
947
+ syncMode: normalizeMountSyncModeInput(input.syncMode),
933
948
  background: input.background !== false,
934
949
  agentName: normalizeNonEmptyString(input.agentName),
935
950
  scopes: input.scopes && input.scopes.length > 0 ? [...input.scopes] : undefined,
@@ -954,6 +969,20 @@ function normalizeMountModeInput(mode) {
954
969
  }
955
970
  return normalized;
956
971
  }
972
+ function normalizeMountLocalLayoutInput(layout) {
973
+ const normalized = normalizeNonEmptyString(layout) ?? DEFAULT_MOUNT_LOCAL_LAYOUT;
974
+ if (normalized !== "exact" && normalized !== "scoped") {
975
+ throw new MountSessionInputError(`Invalid localLayout "${normalized}" for mount session.`);
976
+ }
977
+ return normalized;
978
+ }
979
+ function normalizeMountSyncModeInput(mode) {
980
+ const normalized = normalizeNonEmptyString(mode) ?? DEFAULT_MOUNT_SYNC_MODE;
981
+ if (normalized !== "mirror" && normalized !== "write-only") {
982
+ throw new MountSessionInputError(`Invalid syncMode "${normalized}" for mount session.`);
983
+ }
984
+ return normalized;
985
+ }
957
986
  function normalizeMountRemotePath(remotePath) {
958
987
  const normalized = normalizeNonEmptyString(remotePath) ?? "/";
959
988
  if (normalized.includes("\u0000")) {
@@ -1052,6 +1081,8 @@ function buildMountedWorkspaceEnv(mountSession) {
1052
1081
  RELAYFILE_REMOTE_PATH: mountSession.remotePath,
1053
1082
  RELAYFILE_LOCAL_DIR: mountSession.localDir,
1054
1083
  RELAYFILE_MOUNT_MODE: mountSession.mode,
1084
+ RELAYFILE_MOUNT_LOCAL_LAYOUT: mountSession.localLayout,
1085
+ RELAYFILE_MOUNT_SYNC_MODE: mountSession.syncMode,
1055
1086
  RELAYCAST_API_KEY: mountSession.relaycastApiKey,
1056
1087
  RELAY_API_KEY: mountSession.relaycastApiKey,
1057
1088
  RELAYCAST_BASE_URL: relaycastBaseUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relayfile/sdk",
3
- "version": "0.8.10",
3
+ "version": "0.8.11",
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",
@@ -55,7 +55,7 @@
55
55
  "prepublishOnly": "npm run build"
56
56
  },
57
57
  "dependencies": {
58
- "@relayfile/core": "0.8.10",
58
+ "@relayfile/core": "0.8.11",
59
59
  "ignore": "^7.0.5",
60
60
  "tar": "^7.5.10"
61
61
  },