@relayfile/sdk 0.9.4 → 0.9.5

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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { RelayFileSync } from "./sync.js";
1
+ import { RelayFileSync, normalizeFilesystemEvent } from "./sync.js";
2
2
  import { InvalidStateError, PayloadTooLargeError, QueueFullError, RelayFileApiError, RevisionConflictError } from "./errors.js";
3
3
  /** Default base URL for the hosted Relayfile API */
4
4
  export const DEFAULT_RELAYFILE_BASE_URL = "https://api.relayfile.dev";
@@ -12,6 +12,7 @@ const DEFAULT_CHANGE_COALESCE_MS = 200;
12
12
  const DEFAULT_CHANGE_LOG_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
13
13
  const DEFAULT_CHANGE_LOG_MAX_ENTRIES = 10_000;
14
14
  const CLIENT_TOKEN_STREAM_KEY = "__client__";
15
+ const STABLE_FALLBACK_CHANGE_TIMESTAMP = "1970-01-01T00:00:00.000Z";
15
16
  const changeStreamManagers = new WeakMap();
16
17
  const changeLogCaches = new WeakMap();
17
18
  const changeLogSettings = new WeakMap();
@@ -161,7 +162,7 @@ class RelayFileWebSocketConnection {
161
162
  if (raw.path !== undefined && typeof raw.path !== "string") {
162
163
  throw new Error("Invalid WebSocket event: 'path' must be a string.");
163
164
  }
164
- parsed = raw;
165
+ parsed = normalizeFilesystemEvent(raw);
165
166
  }
166
167
  catch (error) {
167
168
  const parseError = error instanceof Error ? error : new Error("Failed to parse WebSocket event payload.");
@@ -882,23 +883,66 @@ function toChangeEvent(client, record, contextOverride) {
882
883
  }
883
884
  };
884
885
  }
885
- function normalizeWireChangeEvent(payload) {
886
+ function normalizeWireChangeEvent(payload, workspaceId) {
886
887
  const data = (payload ?? {});
887
888
  const resource = (data.resource ?? {});
888
889
  const summary = (data.summary ?? {});
890
+ const path = typeof resource.path === "string"
891
+ ? resource.path
892
+ : typeof data.path === "string"
893
+ ? data.path
894
+ : "";
895
+ const inferredResource = inferResourceMetadata(path, resource);
896
+ const occurredAt = typeof data.occurredAt === "string"
897
+ ? data.occurredAt
898
+ : typeof data.timestamp === "string"
899
+ ? data.timestamp
900
+ : typeof data.ts === "string"
901
+ ? data.ts
902
+ : STABLE_FALLBACK_CHANGE_TIMESTAMP;
903
+ const eventId = typeof data.id === "string"
904
+ ? data.id
905
+ : typeof data.eventId === "string"
906
+ ? data.eventId
907
+ : [
908
+ "relayfile",
909
+ workspaceId ?? "",
910
+ typeof data.type === "string" ? data.type : "relayfile.changed",
911
+ path,
912
+ typeof data.revision === "string" ? data.revision : "",
913
+ occurredAt
914
+ ].join(":");
915
+ const provider = typeof resource.provider === "string"
916
+ ? resource.provider
917
+ : typeof data.provider === "string"
918
+ ? data.provider
919
+ : inferredResource.provider;
889
920
  return {
890
- id: typeof data.id === "string" ? data.id : "",
891
- workspace: typeof data.workspace === "string" ? data.workspace : "",
921
+ id: eventId,
922
+ workspace: typeof data.workspace === "string" ? data.workspace : workspaceId ?? "",
892
923
  agentId: typeof data.agentId === "string" ? data.agentId : undefined,
893
924
  type: "relayfile.changed",
894
- occurredAt: typeof data.occurredAt === "string" ? data.occurredAt : new Date().toISOString(),
925
+ occurredAt,
895
926
  resource: {
896
- path: typeof resource.path === "string" ? resource.path : "",
897
- kind: typeof resource.kind === "string" ? resource.kind : "relayfile.resource",
898
- id: typeof resource.id === "string" ? resource.id : "",
899
- provider: typeof resource.provider === "string" ? resource.provider : "relayfile"
927
+ path,
928
+ kind: typeof resource.kind === "string"
929
+ ? resource.kind
930
+ : typeof data.kind === "string"
931
+ ? data.kind
932
+ : typeof data.resourceType === "string"
933
+ ? data.resourceType
934
+ : inferredResource.kind,
935
+ id: typeof resource.id === "string"
936
+ ? resource.id
937
+ : typeof data.resourceId === "string"
938
+ ? data.resourceId
939
+ : typeof data.providerObjectId === "string"
940
+ ? data.providerObjectId
941
+ : inferredResource.id,
942
+ provider
900
943
  },
901
944
  summary: {
945
+ ...buildChangeSummary(path, data),
902
946
  ...(typeof summary.title === "string" ? { title: summary.title } : {}),
903
947
  ...(typeof summary.status === "string" ? { status: summary.status } : {}),
904
948
  ...(typeof summary.priority === "string" ? { priority: summary.priority } : {}),
@@ -1167,12 +1211,16 @@ export class RelayFileClient {
1167
1211
  cursor: options.cursor,
1168
1212
  limit: options.limit
1169
1213
  });
1170
- return this.request({
1214
+ const response = await this.request({
1171
1215
  method: "GET",
1172
1216
  path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/fs/events${query}`,
1173
1217
  correlationId: options.correlationId,
1174
1218
  signal: options.signal
1175
1219
  });
1220
+ return {
1221
+ events: (response.events ?? []).map((event) => normalizeFilesystemEvent(event)),
1222
+ nextCursor: response.nextCursor ?? null
1223
+ };
1176
1224
  }
1177
1225
  subscribe(globs, onChange, options) {
1178
1226
  const setup = this.resolveWorkspaceId(options?.aclToken)
@@ -1241,7 +1289,7 @@ export class RelayFileClient {
1241
1289
  });
1242
1290
  records = mergeChangeRecords([
1243
1291
  ...cached,
1244
- ...(payload.events ?? []).map((event) => this.cacheWireChangeEvent(workspaceId, normalizeWireChangeEvent(event), effectiveContext))
1292
+ ...(payload.events ?? []).map((event) => this.cacheWireChangeEvent(workspaceId, normalizeWireChangeEvent(event, workspaceId), effectiveContext))
1245
1293
  ]);
1246
1294
  }
1247
1295
  catch (error) {
@@ -1271,7 +1319,7 @@ export class RelayFileClient {
1271
1319
  });
1272
1320
  records = mergeChangeRecords([
1273
1321
  ...cached,
1274
- ...(payload.events ?? []).map((event) => this.cacheWireChangeEvent(workspaceId, normalizeWireChangeEvent(event), effectiveContext))
1322
+ ...(payload.events ?? []).map((event) => this.cacheWireChangeEvent(workspaceId, normalizeWireChangeEvent(event, workspaceId), effectiveContext))
1275
1323
  ]).slice(-safeLimit);
1276
1324
  }
1277
1325
  catch (error) {
package/dist/sync.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { RelayFileClient } from "./client.js";
2
- import type { FilesystemEvent } from "./types.js";
2
+ import type { EventOrigin, FilesystemEvent } from "./types.js";
3
3
  /**
4
4
  * WebSocket auth source for {@link RelayFileSyncOptions.token}.
5
5
  *
@@ -73,6 +73,26 @@ type RelayFileSyncHandlerMap = {
73
73
  close: (event: CloseEvent) => void;
74
74
  pong: (event: RelayFileSyncPong) => void;
75
75
  };
76
+ interface RelayFileSyncWireEvent {
77
+ type: string;
78
+ id?: string;
79
+ eventId?: string;
80
+ path?: string;
81
+ revision?: string;
82
+ digest?: string;
83
+ contentHash?: string;
84
+ origin?: EventOrigin;
85
+ provider?: string;
86
+ correlationId?: string;
87
+ timestamp?: string;
88
+ ts?: string;
89
+ occurredAt?: string;
90
+ resource?: {
91
+ path?: unknown;
92
+ provider?: unknown;
93
+ };
94
+ }
95
+ export declare function normalizeFilesystemEvent(message?: RelayFileSyncWireEvent | null): FilesystemEvent;
76
96
  export declare function normalizeError(event: unknown): unknown;
77
97
  export declare class RelayFileSync {
78
98
  private readonly client;
package/dist/sync.js CHANGED
@@ -54,6 +54,7 @@ const DEFAULT_RECONNECT_MAX_DELAY_MS = 5000;
54
54
  // for long-running consumers like the factory daemon.
55
55
  const DEFAULT_WS_REPROBE_MIN_DELAY_MS = 5000;
56
56
  const DEFAULT_WS_REPROBE_MAX_DELAY_MS = 300000;
57
+ const STABLE_FALLBACK_TIMESTAMP = "1970-01-01T00:00:00.000Z";
57
58
  // Cap on the dedupe cache used in polling mode. Sized large enough that no
58
59
  // realistic workspace burst can churn through it within one poll interval
59
60
  // (the events API is itself capped at ~1000 per page), small enough to keep
@@ -122,25 +123,51 @@ function createAbortError() {
122
123
  return err;
123
124
  }
124
125
  function buildEventId(message) {
126
+ const path = readWirePath(message);
127
+ const timestamp = readWireTimestamp(message);
128
+ const type = readWireString(message?.type) ?? "file.updated";
125
129
  return [
126
130
  "ws",
127
- message.type,
128
- message.path ?? "",
129
- message.revision ?? "",
130
- message.timestamp ?? message.ts ?? ""
131
+ normalizeFilesystemEventType(type),
132
+ path,
133
+ readWireString(message?.revision) ?? "",
134
+ timestamp
131
135
  ].join(":");
132
136
  }
133
- function normalizeFilesystemEvent(message) {
137
+ function readWireString(value) {
138
+ return typeof value === "string" ? value : undefined;
139
+ }
140
+ function readWirePath(message) {
141
+ return readWireString(message?.path) ?? readWireString(message?.resource?.path) ?? "";
142
+ }
143
+ function readWireProvider(message) {
144
+ return readWireString(message?.provider) ?? readWireString(message?.resource?.provider);
145
+ }
146
+ function readWireTimestamp(message) {
147
+ return readWireString(message?.timestamp)
148
+ ?? readWireString(message?.ts)
149
+ ?? readWireString(message?.occurredAt)
150
+ ?? STABLE_FALLBACK_TIMESTAMP;
151
+ }
152
+ function normalizeFilesystemEventType(type) {
153
+ return type === "relayfile.changed" || type === "relayfile.changed.summary"
154
+ ? "file.updated"
155
+ : type;
156
+ }
157
+ export function normalizeFilesystemEvent(message) {
158
+ const path = readWirePath(message);
159
+ const type = readWireString(message?.type) ?? "file.updated";
160
+ const digest = readWireString(message?.digest);
134
161
  return {
135
- eventId: message.eventId ?? buildEventId(message),
136
- type: message.type,
137
- path: message.path ?? "",
138
- revision: message.revision ?? "",
139
- contentHash: message.contentHash,
140
- origin: message.origin,
141
- provider: message.provider,
142
- correlationId: message.correlationId,
143
- timestamp: message.timestamp ?? message.ts ?? new Date().toISOString()
162
+ eventId: readWireString(message?.eventId) ?? readWireString(message?.id) ?? buildEventId(message),
163
+ type: normalizeFilesystemEventType(type),
164
+ path,
165
+ revision: readWireString(message?.revision) ?? digest ?? "",
166
+ contentHash: readWireString(message?.contentHash) ?? digest,
167
+ origin: message?.origin,
168
+ provider: readWireProvider(message),
169
+ correlationId: readWireString(message?.correlationId),
170
+ timestamp: readWireTimestamp(message)
144
171
  };
145
172
  }
146
173
  // Exported for testing. `ErrorEvent` is a DOM/browser global that is not
@@ -684,6 +711,14 @@ export class RelayFileSync {
684
711
  this.emit("error", new Error("Invalid WebSocket payload: 'revision' must be a string."));
685
712
  return;
686
713
  }
714
+ if (parsed.resource !== undefined && (typeof parsed.resource !== "object" || parsed.resource === null)) {
715
+ this.emit("error", new Error("Invalid WebSocket payload: 'resource' must be an object."));
716
+ return;
717
+ }
718
+ if (parsed.resource?.path !== undefined && typeof parsed.resource.path !== "string") {
719
+ this.emit("error", new Error("Invalid WebSocket payload: 'resource.path' must be a string."));
720
+ return;
721
+ }
687
722
  if (parsed.timestamp !== undefined && typeof parsed.timestamp !== "string") {
688
723
  this.emit("error", new Error("Invalid WebSocket payload: 'timestamp' must be a string."));
689
724
  return;
@@ -701,8 +736,8 @@ export class RelayFileSync {
701
736
  return;
702
737
  }
703
738
  const normalized = normalizeFilesystemEvent(parsed);
704
- if (parsed.eventId) {
705
- this.cursor = parsed.eventId;
739
+ if (normalized.eventId) {
740
+ this.cursor = normalized.eventId;
706
741
  }
707
742
  debugLog("event", { type: normalized.type, path: normalized.path, revision: normalized.revision });
708
743
  this.emitFilesystemEvent(normalized);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relayfile/sdk",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
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.4",
62
+ "@relayfile/core": "0.9.5",
63
63
  "ignore": "^7.0.5",
64
64
  "tar": "^7.5.10"
65
65
  },
66
66
  "optionalDependencies": {
67
- "@relayfile/mount-darwin-arm64": "0.9.4",
68
- "@relayfile/mount-darwin-x64": "0.9.4",
69
- "@relayfile/mount-linux-arm64": "0.9.4",
70
- "@relayfile/mount-linux-x64": "0.9.4"
67
+ "@relayfile/mount-darwin-arm64": "0.9.5",
68
+ "@relayfile/mount-darwin-x64": "0.9.5",
69
+ "@relayfile/mount-linux-arm64": "0.9.5",
70
+ "@relayfile/mount-linux-x64": "0.9.5"
71
71
  },
72
72
  "peerDependencies": {},
73
73
  "devDependencies": {