@relayfile/sdk 0.8.8 → 0.8.10
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/client.d.ts +3 -0
- package/dist/client.js +67 -6
- package/dist/index.d.ts +1 -1
- package/dist/sync.d.ts +6 -0
- package/dist/sync.js +66 -2
- package/dist/types.d.ts +4 -0
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -55,6 +55,9 @@ export interface WebSocketConnection {
|
|
|
55
55
|
}
|
|
56
56
|
export interface ConnectWebSocketOptions {
|
|
57
57
|
token?: string;
|
|
58
|
+
from?: "now" | "legacy";
|
|
59
|
+
cursor?: string;
|
|
60
|
+
paths?: string[];
|
|
58
61
|
onEvent?: (event: FilesystemEvent) => void;
|
|
59
62
|
}
|
|
60
63
|
interface ProactiveRequestContext {
|
package/dist/client.js
CHANGED
|
@@ -318,6 +318,14 @@ class RelayFileChangeSubscription {
|
|
|
318
318
|
}
|
|
319
319
|
await drain;
|
|
320
320
|
}
|
|
321
|
+
serverPathFilters() {
|
|
322
|
+
const filters = new Set();
|
|
323
|
+
const patterns = this.pathScopes ?? this.globPatterns;
|
|
324
|
+
for (const pattern of patterns) {
|
|
325
|
+
filters.add(`/${pattern.join("/")}`);
|
|
326
|
+
}
|
|
327
|
+
return Array.from(filters);
|
|
328
|
+
}
|
|
321
329
|
matches(path) {
|
|
322
330
|
const pathSegments = normalizeChangePath(path);
|
|
323
331
|
const matchesGlob = this.globPatterns.some((pattern) => matchChangeSegments(pattern, pathSegments));
|
|
@@ -353,18 +361,21 @@ class RelayFileChangeStreamManager {
|
|
|
353
361
|
workspaceId;
|
|
354
362
|
token;
|
|
355
363
|
baseUrl;
|
|
364
|
+
startOptions;
|
|
356
365
|
subscriptions = new Set();
|
|
357
366
|
openHandleCount = 0;
|
|
358
367
|
sync;
|
|
368
|
+
activePathFilterKey;
|
|
359
369
|
readyResolved = false;
|
|
360
370
|
readyInternal;
|
|
361
371
|
resolveReady;
|
|
362
372
|
rejectReady;
|
|
363
|
-
constructor(client, workspaceId, token, baseUrl) {
|
|
373
|
+
constructor(client, workspaceId, token, baseUrl, startOptions) {
|
|
364
374
|
this.client = client;
|
|
365
375
|
this.workspaceId = workspaceId;
|
|
366
376
|
this.token = token;
|
|
367
377
|
this.baseUrl = baseUrl;
|
|
378
|
+
this.startOptions = startOptions;
|
|
368
379
|
this.readyInternal = new Promise((resolve, reject) => {
|
|
369
380
|
this.resolveReady = resolve;
|
|
370
381
|
this.rejectReady = reject;
|
|
@@ -376,22 +387,26 @@ class RelayFileChangeStreamManager {
|
|
|
376
387
|
addSubscription(globs, onChange, options) {
|
|
377
388
|
const subscription = new RelayFileChangeSubscription(this, globs, onChange, options);
|
|
378
389
|
this.subscriptions.add(subscription);
|
|
390
|
+
this.restartIfPathScopeChanged();
|
|
379
391
|
this.ensureStarted();
|
|
380
392
|
return {
|
|
381
393
|
unsubscribe: async () => {
|
|
382
394
|
this.subscriptions.delete(subscription);
|
|
383
395
|
await subscription.close();
|
|
396
|
+
this.restartIfPathScopeChanged();
|
|
384
397
|
await this.maybeStop();
|
|
385
398
|
}
|
|
386
399
|
};
|
|
387
400
|
}
|
|
388
401
|
open() {
|
|
389
402
|
this.openHandleCount += 1;
|
|
403
|
+
this.restartIfPathScopeChanged();
|
|
390
404
|
this.ensureStarted();
|
|
391
405
|
return {
|
|
392
406
|
ready: this.ready,
|
|
393
407
|
unsubscribe: async () => {
|
|
394
408
|
this.openHandleCount = Math.max(0, this.openHandleCount - 1);
|
|
409
|
+
this.restartIfPathScopeChanged();
|
|
395
410
|
await this.maybeStop();
|
|
396
411
|
}
|
|
397
412
|
};
|
|
@@ -429,11 +444,16 @@ class RelayFileChangeStreamManager {
|
|
|
429
444
|
if (this.sync) {
|
|
430
445
|
return;
|
|
431
446
|
}
|
|
447
|
+
const paths = this.serverPathFilters();
|
|
448
|
+
this.activePathFilterKey = paths.join("\n");
|
|
432
449
|
const sync = new RelayFileSync({
|
|
433
450
|
client: this.client,
|
|
434
451
|
workspaceId: this.workspaceId,
|
|
435
452
|
baseUrl: this.baseUrl,
|
|
436
453
|
token: this.token,
|
|
454
|
+
from: this.startOptions.from,
|
|
455
|
+
cursor: this.startOptions.cursor,
|
|
456
|
+
paths,
|
|
437
457
|
onPollingFallback: () => {
|
|
438
458
|
this.resolveReadyOnce();
|
|
439
459
|
}
|
|
@@ -462,6 +482,34 @@ class RelayFileChangeStreamManager {
|
|
|
462
482
|
this.resolveReadyOnce();
|
|
463
483
|
}
|
|
464
484
|
}
|
|
485
|
+
restartIfPathScopeChanged() {
|
|
486
|
+
if (!this.sync) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const nextKey = this.serverPathFilters().join("\n");
|
|
490
|
+
const currentKey = this.activePathFilterKey;
|
|
491
|
+
if (nextKey === currentKey) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const sync = this.sync;
|
|
495
|
+
this.sync = undefined;
|
|
496
|
+
void sync.stop();
|
|
497
|
+
if (this.openHandleCount > 0 || this.subscriptions.size > 0) {
|
|
498
|
+
this.ensureStarted();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
serverPathFilters() {
|
|
502
|
+
if (this.openHandleCount > 0) {
|
|
503
|
+
return [];
|
|
504
|
+
}
|
|
505
|
+
const filters = new Set();
|
|
506
|
+
for (const subscription of this.subscriptions) {
|
|
507
|
+
for (const path of subscription.serverPathFilters()) {
|
|
508
|
+
filters.add(path);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return Array.from(filters).sort();
|
|
512
|
+
}
|
|
465
513
|
resolveReadyOnce() {
|
|
466
514
|
if (this.readyResolved) {
|
|
467
515
|
return;
|
|
@@ -476,6 +524,7 @@ class RelayFileChangeStreamManager {
|
|
|
476
524
|
if (this.sync) {
|
|
477
525
|
const sync = this.sync;
|
|
478
526
|
this.sync = undefined;
|
|
527
|
+
this.activePathFilterKey = undefined;
|
|
479
528
|
await sync.stop();
|
|
480
529
|
}
|
|
481
530
|
const managers = changeStreamManagers.get(this.client);
|
|
@@ -489,18 +538,21 @@ class RelayFileChangeStreamManager {
|
|
|
489
538
|
}
|
|
490
539
|
}
|
|
491
540
|
}
|
|
492
|
-
function getStreamManager(client, workspaceId, token, baseUrl) {
|
|
541
|
+
function getStreamManager(client, workspaceId, token, baseUrl, startOptions = {}) {
|
|
493
542
|
let managers = changeStreamManagers.get(client);
|
|
494
543
|
if (!managers) {
|
|
495
544
|
managers = new Map();
|
|
496
545
|
changeStreamManagers.set(client, managers);
|
|
497
546
|
}
|
|
498
|
-
const key = `${workspaceId}:${token ?? CLIENT_TOKEN_STREAM_KEY}`;
|
|
547
|
+
const key = `${workspaceId}:${token ?? CLIENT_TOKEN_STREAM_KEY}:${startOptions.from ?? "now"}:${startOptions.cursor ?? ""}`;
|
|
499
548
|
const existing = managers.get(key);
|
|
500
549
|
if (existing) {
|
|
501
550
|
return existing;
|
|
502
551
|
}
|
|
503
|
-
const manager = new RelayFileChangeStreamManager(client, workspaceId, token, baseUrl
|
|
552
|
+
const manager = new RelayFileChangeStreamManager(client, workspaceId, token, baseUrl, {
|
|
553
|
+
from: startOptions.from,
|
|
554
|
+
cursor: startOptions.cursor
|
|
555
|
+
});
|
|
504
556
|
managers.set(key, manager);
|
|
505
557
|
return manager;
|
|
506
558
|
}
|
|
@@ -1125,7 +1177,7 @@ export class RelayFileClient {
|
|
|
1125
1177
|
subscribe(globs, onChange, options) {
|
|
1126
1178
|
const setup = this.resolveWorkspaceId(options?.aclToken)
|
|
1127
1179
|
.then((workspaceId) => {
|
|
1128
|
-
const manager = getStreamManager(this, workspaceId, options?.aclToken, this.baseUrl);
|
|
1180
|
+
const manager = getStreamManager(this, workspaceId, options?.aclToken, this.baseUrl, options);
|
|
1129
1181
|
return manager.addSubscription(globs, onChange, options);
|
|
1130
1182
|
});
|
|
1131
1183
|
return {
|
|
@@ -1136,7 +1188,7 @@ export class RelayFileClient {
|
|
|
1136
1188
|
};
|
|
1137
1189
|
}
|
|
1138
1190
|
open(options) {
|
|
1139
|
-
const manager = getStreamManager(this, options.workspaceId, options.aclToken, this.baseUrl);
|
|
1191
|
+
const manager = getStreamManager(this, options.workspaceId, options.aclToken, this.baseUrl, options);
|
|
1140
1192
|
const connection = manager.open();
|
|
1141
1193
|
const replay = this.primeReplayCache(options).catch((error) => {
|
|
1142
1194
|
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
@@ -1252,6 +1304,15 @@ export class RelayFileClient {
|
|
|
1252
1304
|
const url = new URL(`${this.baseUrl}/v1/workspaces/${encodeURIComponent(workspaceId)}/fs/ws`);
|
|
1253
1305
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
1254
1306
|
url.searchParams.set("token", token);
|
|
1307
|
+
if (options.cursor) {
|
|
1308
|
+
url.searchParams.set("cursor", options.cursor);
|
|
1309
|
+
}
|
|
1310
|
+
else if ((options.from ?? "now") === "now") {
|
|
1311
|
+
url.searchParams.set("from", "now");
|
|
1312
|
+
}
|
|
1313
|
+
for (const path of options.paths ?? []) {
|
|
1314
|
+
url.searchParams.append("path", path);
|
|
1315
|
+
}
|
|
1255
1316
|
const socket = new WebSocket(url.toString());
|
|
1256
1317
|
return new RelayFileWebSocketConnection(socket, options.onEvent);
|
|
1257
1318
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { RelayfileSetup, RELAYFILE_SDK_VERSION, WorkspaceHandle } from "./setup.
|
|
|
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
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";
|
|
6
|
-
export { RelayFileSync, type RelayFileSyncOptions, type RelayFileSyncPong, type RelayFileSyncReconnectOptions, type RelayFileSyncSocket, type RelayFileSyncState, type RelayFileSyncTokenProvider } from "./sync.js";
|
|
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";
|
|
9
9
|
export { IntegrationProvider, computeCanonicalPath } from "./provider.js";
|
package/dist/sync.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { FilesystemEvent } from "./types.js";
|
|
|
9
9
|
*/
|
|
10
10
|
export type RelayFileSyncTokenProvider = string | (() => string | undefined | Promise<string | undefined>);
|
|
11
11
|
export type RelayFileSyncState = "idle" | "connecting" | "open" | "polling" | "reconnecting" | "closed";
|
|
12
|
+
export type RelayFileSyncStart = "now" | "legacy";
|
|
12
13
|
export interface RelayFileSyncPong {
|
|
13
14
|
type: "pong";
|
|
14
15
|
timestamp?: string;
|
|
@@ -32,7 +33,9 @@ export interface RelayFileSyncOptions {
|
|
|
32
33
|
* normally NOT pass this and let it inherit from the client.
|
|
33
34
|
*/
|
|
34
35
|
token?: RelayFileSyncTokenProvider;
|
|
36
|
+
from?: RelayFileSyncStart;
|
|
35
37
|
cursor?: string;
|
|
38
|
+
paths?: string[];
|
|
36
39
|
preferPolling?: boolean;
|
|
37
40
|
pollIntervalMs?: number;
|
|
38
41
|
pingIntervalMs?: number;
|
|
@@ -87,6 +90,8 @@ export declare class RelayFileSync {
|
|
|
87
90
|
private readonly handlers;
|
|
88
91
|
private state;
|
|
89
92
|
private cursor?;
|
|
93
|
+
private readonly from;
|
|
94
|
+
private readonly paths;
|
|
90
95
|
private readonly polledEventIds;
|
|
91
96
|
private readonly polledEventOrder;
|
|
92
97
|
private firstPollComplete;
|
|
@@ -116,6 +121,7 @@ export declare class RelayFileSync {
|
|
|
116
121
|
private pollLoop;
|
|
117
122
|
private rememberPolledEvent;
|
|
118
123
|
private handleSocketMessage;
|
|
124
|
+
private emitFilesystemEvent;
|
|
119
125
|
private startPingLoop;
|
|
120
126
|
private forceReconnect;
|
|
121
127
|
private scheduleReconnect;
|
package/dist/sync.js
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
function normalizeWebSocketPathFilters(paths) {
|
|
2
|
+
const seen = new Set();
|
|
3
|
+
const normalized = [];
|
|
4
|
+
for (const value of paths ?? []) {
|
|
5
|
+
const path = typeof value === "string" ? value.trim() : "";
|
|
6
|
+
if (!path) {
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
const absolute = path.startsWith("/") ? path : `/${path}`;
|
|
10
|
+
if (seen.has(absolute)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
seen.add(absolute);
|
|
14
|
+
normalized.push(absolute);
|
|
15
|
+
}
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
function pathMatchesAnyFilter(filters, path) {
|
|
19
|
+
if (filters.length === 0) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const pathSegments = normalizePathSegments(path);
|
|
23
|
+
return filters.some((filter) => matchPathSegments(normalizePathSegments(filter), pathSegments));
|
|
24
|
+
}
|
|
25
|
+
function normalizePathSegments(path) {
|
|
26
|
+
const absolute = path.startsWith("/") ? path : `/${path}`;
|
|
27
|
+
const trimmed = absolute.replace(/\/+$/, "");
|
|
28
|
+
if (!trimmed) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
return trimmed.split("/").filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
function matchPathSegments(pattern, path) {
|
|
34
|
+
if (pattern.length > 0 && pattern[pattern.length - 1] === "**") {
|
|
35
|
+
const prefix = pattern.slice(0, -1);
|
|
36
|
+
return path.length >= prefix.length && prefix.every((segment, index) => segment === "*" || segment === path[index]);
|
|
37
|
+
}
|
|
38
|
+
if (pattern.length !== path.length) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return pattern.every((segment, index) => segment === "*" || segment === path[index]);
|
|
42
|
+
}
|
|
1
43
|
const DEFAULT_POLL_INTERVAL_MS = 5000;
|
|
2
44
|
const DEFAULT_PING_INTERVAL_MS = 30000;
|
|
3
45
|
const DEFAULT_RECONNECT_MIN_DELAY_MS = 250;
|
|
@@ -131,6 +173,8 @@ export class RelayFileSync {
|
|
|
131
173
|
};
|
|
132
174
|
state = "idle";
|
|
133
175
|
cursor;
|
|
176
|
+
from;
|
|
177
|
+
paths;
|
|
134
178
|
polledEventIds = new Set();
|
|
135
179
|
polledEventOrder = [];
|
|
136
180
|
firstPollComplete = false;
|
|
@@ -176,7 +220,9 @@ export class RelayFileSync {
|
|
|
176
220
|
const literal = options.token;
|
|
177
221
|
this.tokenProvider = () => literal;
|
|
178
222
|
}
|
|
223
|
+
this.from = options.from ?? "now";
|
|
179
224
|
this.cursor = options.cursor;
|
|
225
|
+
this.paths = normalizeWebSocketPathFilters(options.paths);
|
|
180
226
|
this.onPollingFallback = options.onPollingFallback;
|
|
181
227
|
this.pollIntervalMs = Math.max(1, Math.floor(options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS));
|
|
182
228
|
this.pingIntervalMs = Math.max(1, Math.floor(options.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS));
|
|
@@ -327,6 +373,15 @@ export class RelayFileSync {
|
|
|
327
373
|
if (token) {
|
|
328
374
|
url.searchParams.set("token", token);
|
|
329
375
|
}
|
|
376
|
+
if (this.cursor) {
|
|
377
|
+
url.searchParams.set("cursor", this.cursor);
|
|
378
|
+
}
|
|
379
|
+
else if (this.from === "now") {
|
|
380
|
+
url.searchParams.set("from", "now");
|
|
381
|
+
}
|
|
382
|
+
for (const path of this.paths) {
|
|
383
|
+
url.searchParams.append("path", path);
|
|
384
|
+
}
|
|
330
385
|
let socket;
|
|
331
386
|
try {
|
|
332
387
|
socket = this.webSocketFactory(url.toString());
|
|
@@ -524,7 +579,7 @@ export class RelayFileSync {
|
|
|
524
579
|
}
|
|
525
580
|
else {
|
|
526
581
|
for (const event of pending) {
|
|
527
|
-
this.
|
|
582
|
+
this.emitFilesystemEvent(event);
|
|
528
583
|
}
|
|
529
584
|
}
|
|
530
585
|
await this.sleep(this.pollIntervalMs);
|
|
@@ -594,8 +649,17 @@ export class RelayFileSync {
|
|
|
594
649
|
return;
|
|
595
650
|
}
|
|
596
651
|
const normalized = normalizeFilesystemEvent(parsed);
|
|
652
|
+
if (parsed.eventId) {
|
|
653
|
+
this.cursor = parsed.eventId;
|
|
654
|
+
}
|
|
597
655
|
debugLog("event", { type: normalized.type, path: normalized.path, revision: normalized.revision });
|
|
598
|
-
this.
|
|
656
|
+
this.emitFilesystemEvent(normalized);
|
|
657
|
+
}
|
|
658
|
+
emitFilesystemEvent(event) {
|
|
659
|
+
if (!pathMatchesAnyFilter(this.paths, event.path)) {
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
this.emit("event", event);
|
|
599
663
|
}
|
|
600
664
|
startPingLoop(socket) {
|
|
601
665
|
this.clearPingTimer();
|
package/dist/types.d.ts
CHANGED
|
@@ -257,6 +257,8 @@ export interface SubscribeOptions {
|
|
|
257
257
|
coalesce?: "none" | "fire-once";
|
|
258
258
|
coalesceMs?: number;
|
|
259
259
|
pathScope?: string[];
|
|
260
|
+
from?: "now" | "legacy";
|
|
261
|
+
cursor?: string;
|
|
260
262
|
aclToken?: string;
|
|
261
263
|
drainMs?: number;
|
|
262
264
|
}
|
|
@@ -273,6 +275,8 @@ export type ReplayOptions = {
|
|
|
273
275
|
export type ChangeStreamConnectionOptions = ReplayOptions & {
|
|
274
276
|
workspaceId: string;
|
|
275
277
|
aclToken?: string;
|
|
278
|
+
from?: "now" | "legacy";
|
|
279
|
+
cursor?: string;
|
|
276
280
|
};
|
|
277
281
|
export interface ChangeStreamConnection extends Subscription {
|
|
278
282
|
readonly ready: Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relayfile/sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.10",
|
|
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.
|
|
58
|
+
"@relayfile/core": "0.8.10",
|
|
59
59
|
"ignore": "^7.0.5",
|
|
60
60
|
"tar": "^7.5.10"
|
|
61
61
|
},
|