@relayfile/sdk 0.9.6 → 0.10.1

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 CHANGED
@@ -1,4 +1,4 @@
1
- import { type AdminIngressStatusResponse, type AdminSyncStatusResponse, type BulkWriteInput, type BulkWriteResponse, type BackendStatusResponse, type AckResponse, type CommitForkInput, type CommitForkResponse, type CreateForkInput, type DeleteFileInput, type DeadLetterItem, type DeadLetterFeedResponse, type DeleteWebhookOptions, type DiscardForkInput, type EventFeedResponse, type ExportJsonResponse, type ExportOptions, type FileQueryResponse, type FileReadResponse, type FilesystemEvent, type GetEventsOptions, type GetAdminIngressStatusOptions, type GetAdminSyncStatusOptions, type GetOperationsOptions, type GetSyncDeadLettersOptions, type GetSyncIngressStatusOptions, type GetSyncStatusOptions, type GetWebhookDeadLettersOptions, type ListWebhooksOptions, type ListTreeOptions, type OperationFeedResponse, type OperationStatusResponse, type QueuedResponse, type ResourceAtEventResult, type ReadFileInput, type QueryFilesOptions, type RegisterWebhookInput, type RegisterWebhookResponse, type Subscription, type SyncIngressStatusResponse, type SyncStatusResponse, type TreeResponse, type WriteFileInput, type WriteQueuedResponse, type IngestWebhookInput, type WritebackItem, type WebhookDeliveryDeadLetterFeedResponse, type WebhookSubscription, type AckWritebackInput, type AckWritebackResponse, type SweepWritebackDraftsInput, type SweepWritebackDraftsResponse, type ChangeEvent, type ChangeLogQueryResult, type ChangeStreamConnection, type ChangeStreamConnectionOptions, type SubscribeOptions } from "./types.js";
1
+ import { type AdminIngressStatusResponse, type AdminSyncStatusResponse, type BulkWriteInput, type BulkWriteResponse, type BackendStatusResponse, type AckResponse, type CommitForkInput, type CommitForkResponse, type CreateForkInput, type DeleteFileInput, type DeadLetterItem, type DeadLetterFeedResponse, type DeleteWebhookOptions, type DiscardForkInput, type EventFeedResponse, type ExportJsonResponse, type ExportOptions, type FileQueryResponse, type FileReadResponse, type FilesystemEvent, type GetEventsOptions, type GetAdminIngressStatusOptions, type GetAdminSyncStatusOptions, type GetOperationsOptions, type GetSyncDeadLettersOptions, type GetSyncIngressStatusOptions, type GetSyncStatusOptions, type GetWebhookDeadLettersOptions, type ListWebhooksOptions, type ListTreeOptions, type OperationFeedResponse, type OperationStatusResponse, type QueuedResponse, type ResourceAtEventResult, type ReadFileInput, type QueryFilesOptions, type RegisterWebhookInput, type RegisterWebhookResponse, type RelayFileReadCacheOptions, type Subscription, type SyncIngressStatusResponse, type SyncStatusResponse, type TreeResponse, type WriteFileInput, type WriteQueuedResponse, type IngestWebhookInput, type WritebackItem, type WebhookDeliveryDeadLetterFeedResponse, type WebhookSubscription, type AckWritebackInput, type AckWritebackResponse, type SweepWritebackDraftsInput, type SweepWritebackDraftsResponse, type ChangeEvent, type ChangeLogQueryResult, type ChangeStreamConnection, type ChangeStreamConnectionOptions, type SubscribeOptions } from "./types.js";
2
2
  import type { ForkHandle } from "@relayfile/core";
3
3
  /**
4
4
  * Bearer token or token factory used for Relayfile API requests.
@@ -41,6 +41,12 @@ export interface RelayFileClientOptions {
41
41
  userAgent?: string;
42
42
  retry?: RelayFileRetryOptions;
43
43
  changeLog?: RelayFileChangeLogOptions;
44
+ /**
45
+ * Client-side file read cache with in-flight deduplication.
46
+ * Enabled by default. Set to `false` to disable.
47
+ * Active change streams automatically evict paths on remote mutations.
48
+ */
49
+ readCache?: false | RelayFileReadCacheOptions;
44
50
  }
45
51
  type WebSocketEventName = "event" | "error" | "open" | "close";
46
52
  type WebSocketHandlerMap = {
package/dist/client.js CHANGED
@@ -17,6 +17,72 @@ const changeStreamManagers = new WeakMap();
17
17
  const changeLogCaches = new WeakMap();
18
18
  const changeLogSettings = new WeakMap();
19
19
  const pendingChangeHydrations = new WeakMap();
20
+ const fileReadCaches = new WeakMap();
21
+ const DEFAULT_READ_CACHE_TTL_MS = 5_000;
22
+ const DEFAULT_READ_CACHE_MAX_ENTRIES = 500;
23
+ class FileReadCache {
24
+ ttlMs;
25
+ maxEntries;
26
+ entries = new Map();
27
+ inFlight = new Map();
28
+ constructor(options) {
29
+ this.ttlMs = options?.ttlMs ?? DEFAULT_READ_CACHE_TTL_MS;
30
+ this.maxEntries = options?.maxEntries ?? DEFAULT_READ_CACHE_MAX_ENTRIES;
31
+ }
32
+ get(key) {
33
+ const entry = this.entries.get(key);
34
+ if (!entry)
35
+ return undefined;
36
+ if (Date.now() > entry.expiresAt) {
37
+ this.entries.delete(key);
38
+ return undefined;
39
+ }
40
+ return entry.value;
41
+ }
42
+ set(key, value) {
43
+ if (this.entries.size >= this.maxEntries && !this.entries.has(key)) {
44
+ const oldest = this.entries.keys().next().value;
45
+ if (oldest !== undefined) {
46
+ this.entries.delete(oldest);
47
+ }
48
+ }
49
+ this.entries.set(key, { value, expiresAt: Date.now() + this.ttlMs });
50
+ }
51
+ evict(workspaceId, path) {
52
+ this.entries.delete(`${workspaceId}:${path}`);
53
+ this.inFlight.delete(`${workspaceId}:${path}`);
54
+ }
55
+ getInFlight(key) {
56
+ return this.inFlight.get(key);
57
+ }
58
+ setInFlight(key, promise) {
59
+ this.inFlight.set(key, promise);
60
+ promise.then((result) => {
61
+ if (this.inFlight.get(key) === promise) {
62
+ this.inFlight.delete(key);
63
+ this.set(key, result);
64
+ }
65
+ }, () => {
66
+ if (this.inFlight.get(key) === promise) {
67
+ this.inFlight.delete(key);
68
+ }
69
+ });
70
+ }
71
+ }
72
+ function getFileReadCache(client) {
73
+ const cached = fileReadCaches.get(client);
74
+ if (cached !== undefined)
75
+ return cached;
76
+ return false;
77
+ }
78
+ function initFileReadCache(client, options) {
79
+ if (options.readCache === false) {
80
+ fileReadCaches.set(client, false);
81
+ }
82
+ else {
83
+ fileReadCaches.set(client, new FileReadCache(options.readCache));
84
+ }
85
+ }
20
86
  function createM2NotImplementedError(feature) {
21
87
  const error = new Error(`M2_NOT_IMPLEMENTED: ${feature} is reserved for proactive runtime M2.`);
22
88
  error.name = "M2NotImplementedError";
@@ -473,6 +539,14 @@ class RelayFileChangeStreamManager {
473
539
  }
474
540
  });
475
541
  sync.on("event", (event) => {
542
+ // Evict the cached read when the server signals a mutation so stale
543
+ // content is never returned after a change event arrives.
544
+ if (event.type === "file.updated" || event.type === "file.created" || event.type === "file.deleted") {
545
+ const cacheOrFalse = getFileReadCache(this.client);
546
+ if (cacheOrFalse !== false) {
547
+ cacheOrFalse.evict(this.workspaceId, event.path);
548
+ }
549
+ }
476
550
  for (const subscription of this.subscriptions) {
477
551
  subscription.push(event);
478
552
  }
@@ -1037,6 +1111,7 @@ export class RelayFileClient {
1037
1111
  this.userAgent = options.userAgent;
1038
1112
  this.retryOptions = normalizeRetryOptions(options.retry);
1039
1113
  changeLogSettings.set(this, normalizeChangeLogOptions(options.changeLog));
1114
+ initFileReadCache(this, options);
1040
1115
  }
1041
1116
  /**
1042
1117
  * Resolve the current access token via the configured token provider.
@@ -1085,14 +1160,30 @@ export class RelayFileClient {
1085
1160
  signal
1086
1161
  }
1087
1162
  : workspaceOrInput;
1163
+ const cacheRaw = getFileReadCache(this);
1164
+ // Skip cache for fork-scoped reads (isolated state) and when cache is disabled.
1165
+ const cache = cacheRaw !== false ? cacheRaw : undefined;
1166
+ const cacheKey = (cache && !input.forkId) ? `${input.workspaceId}:${input.path}` : undefined;
1167
+ if (cache && cacheKey) {
1168
+ const hit = cache.get(cacheKey);
1169
+ if (hit)
1170
+ return hit;
1171
+ const pending = cache.getInFlight(cacheKey);
1172
+ if (pending)
1173
+ return pending;
1174
+ }
1088
1175
  const query = buildQuery({ path: input.path, forkId: input.forkId });
1089
- return this.request({
1176
+ const fetch = this.request({
1090
1177
  method: "GET",
1091
1178
  path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/fs/file${query}`,
1092
1179
  correlationId: input.correlationId,
1093
1180
  signal: input.signal,
1094
1181
  tokenOverride: input.token
1095
1182
  });
1183
+ if (cache && cacheKey) {
1184
+ cache.setInFlight(cacheKey, fetch);
1185
+ }
1186
+ return fetch;
1096
1187
  }
1097
1188
  async queryFiles(workspaceId, options = {}) {
1098
1189
  const params = new URLSearchParams();
@@ -1131,7 +1222,7 @@ export class RelayFileClient {
1131
1222
  async writeFile(input) {
1132
1223
  const { workspaceId, path, correlationId, baseRevision, content, contentType, encoding, contentIdentity, signal } = input;
1133
1224
  const query = buildQuery({ path, forkId: input.forkId });
1134
- return this.request({
1225
+ const result = await this.request({
1135
1226
  method: "PUT",
1136
1227
  path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/fs/file${query}`,
1137
1228
  correlationId,
@@ -1148,6 +1239,10 @@ export class RelayFileClient {
1148
1239
  },
1149
1240
  signal
1150
1241
  });
1242
+ const cache = getFileReadCache(this);
1243
+ if (cache !== false)
1244
+ cache.evict(workspaceId, path);
1245
+ return result;
1151
1246
  }
1152
1247
  async bulkWrite(input) {
1153
1248
  const query = buildQuery({ forkId: input.forkId });
@@ -1160,11 +1255,18 @@ export class RelayFileClient {
1160
1255
  },
1161
1256
  signal: input.signal
1162
1257
  });
1163
- return this.readPayload(response);
1258
+ const result = await this.readPayload(response);
1259
+ const cache = getFileReadCache(this);
1260
+ if (cache !== false) {
1261
+ for (const file of input.files) {
1262
+ cache.evict(input.workspaceId, file.path);
1263
+ }
1264
+ }
1265
+ return result;
1164
1266
  }
1165
1267
  async deleteFile(input) {
1166
1268
  const query = buildQuery({ path: input.path, forkId: input.forkId });
1167
- return this.request({
1269
+ const result = await this.request({
1168
1270
  method: "DELETE",
1169
1271
  path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/fs/file${query}`,
1170
1272
  correlationId: input.correlationId,
@@ -1173,6 +1275,10 @@ export class RelayFileClient {
1173
1275
  },
1174
1276
  signal: input.signal
1175
1277
  });
1278
+ const cache = getFileReadCache(this);
1279
+ if (cache !== false)
1280
+ cache.evict(input.workspaceId, input.path);
1281
+ return result;
1176
1282
  }
1177
1283
  async createFork(input) {
1178
1284
  const body = {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { RelayFileClient, DEFAULT_RELAYFILE_BASE_URL, type AccessTokenProvider, type RelayFileChangeLogOptions, type ConnectWebSocketOptions, type RelayFileClientOptions, type RelayFileRetryOptions, type WebSocketConnection } from "./client.js";
2
+ export type { RelayFileReadCacheOptions } from "./types.js";
2
3
  export { RelayfileSetup, RELAYFILE_SDK_VERSION, WorkspaceHandle } from "./setup.js";
3
4
  export { type RelayfileCloudLoginOptions, type RelayfileCloudTokenSet, type RelayfileCloudTokenSetupOptions } from "./cloud-login.js";
4
5
  export { CloudAbortError, CloudApiError, CloudTimeoutError, InvalidLocalDirError, InvalidMountModeError, InvalidRemotePathError, IntegrationConnectionTimeoutError, MalformedCloudResponseError, MissingConnectionIdError, MountModeUnavailableError, MountReadyTimeoutError, MountSessionInputError, ProviderNotConnectedError, ProviderNotReadyError, RelayfileSetupError, UnknownProviderError } from "./setup-errors.js";
package/dist/types.d.ts CHANGED
@@ -56,6 +56,12 @@ export interface ContentIdentity {
56
56
  key: string;
57
57
  ttlSeconds?: number;
58
58
  }
59
+ export interface RelayFileReadCacheOptions {
60
+ /** Cache TTL in ms. Default: 5000. */
61
+ ttlMs?: number;
62
+ /** Max cached entries before LRU eviction. Default: 500. */
63
+ maxEntries?: number;
64
+ }
59
65
  export interface FileReadResponse {
60
66
  path: string;
61
67
  revision: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relayfile/sdk",
3
- "version": "0.9.6",
3
+ "version": "0.10.1",
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.9.6",
62
+ "@relayfile/core": "0.10.1",
63
63
  "ignore": "^7.0.5",
64
64
  "tar": "^7.5.10"
65
65
  },
66
66
  "optionalDependencies": {
67
- "@relayfile/mount-darwin-arm64": "0.9.6",
68
- "@relayfile/mount-darwin-x64": "0.9.6",
69
- "@relayfile/mount-linux-arm64": "0.9.6",
70
- "@relayfile/mount-linux-x64": "0.9.6"
67
+ "@relayfile/mount-darwin-arm64": "0.10.1",
68
+ "@relayfile/mount-darwin-x64": "0.10.1",
69
+ "@relayfile/mount-linux-arm64": "0.10.1",
70
+ "@relayfile/mount-linux-x64": "0.10.1"
71
71
  },
72
72
  "devDependencies": {
73
73
  "typescript": "^5.7.3",