@rool-dev/sdk 0.10.1 → 0.10.2-dev.0bf8edb

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.
Files changed (58) hide show
  1. package/README.md +79 -179
  2. package/dist/channel.d.ts +41 -129
  3. package/dist/channel.d.ts.map +1 -1
  4. package/dist/channel.js +220 -394
  5. package/dist/channel.js.map +1 -1
  6. package/dist/client.d.ts +3 -55
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +7 -93
  9. package/dist/client.js.map +1 -1
  10. package/dist/graphql.d.ts +4 -46
  11. package/dist/graphql.d.ts.map +1 -1
  12. package/dist/graphql.js +28 -233
  13. package/dist/graphql.js.map +1 -1
  14. package/dist/index.d.ts +3 -6
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/path.d.ts +6 -0
  19. package/dist/path.d.ts.map +1 -0
  20. package/dist/path.js +47 -0
  21. package/dist/path.js.map +1 -0
  22. package/dist/reroute.d.ts +22 -0
  23. package/dist/reroute.d.ts.map +1 -0
  24. package/dist/reroute.js +61 -0
  25. package/dist/reroute.js.map +1 -0
  26. package/dist/rest.d.ts +9 -0
  27. package/dist/rest.d.ts.map +1 -1
  28. package/dist/rest.js +33 -1
  29. package/dist/rest.js.map +1 -1
  30. package/dist/router.d.ts.map +1 -1
  31. package/dist/router.js +25 -10
  32. package/dist/router.js.map +1 -1
  33. package/dist/space.d.ts +10 -17
  34. package/dist/space.d.ts.map +1 -1
  35. package/dist/space.js +79 -55
  36. package/dist/space.js.map +1 -1
  37. package/dist/subscription.d.ts.map +1 -1
  38. package/dist/subscription.js +40 -32
  39. package/dist/subscription.js.map +1 -1
  40. package/dist/types.d.ts +52 -217
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/webdav.d.ts +44 -21
  43. package/dist/webdav.d.ts.map +1 -1
  44. package/dist/webdav.js +94 -57
  45. package/dist/webdav.js.map +1 -1
  46. package/package.json +2 -1
  47. package/dist/apps.d.ts +0 -30
  48. package/dist/apps.d.ts.map +0 -1
  49. package/dist/apps.js +0 -81
  50. package/dist/apps.js.map +0 -1
  51. package/dist/locations.d.ts +0 -34
  52. package/dist/locations.d.ts.map +0 -1
  53. package/dist/locations.js +0 -90
  54. package/dist/locations.js.map +0 -1
  55. package/dist/machine.d.ts +0 -16
  56. package/dist/machine.d.ts.map +0 -1
  57. package/dist/machine.js +0 -51
  58. package/dist/machine.js.map +0 -1
@@ -0,0 +1,61 @@
1
+ const REQUEST_MAX_RETRIES = 6;
2
+ const RETRY_BASE_MS = 150;
3
+ const RETRY_MAX_MS = 5_000;
4
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5
+ function retryBackoffMs(attempt) {
6
+ const ceil = Math.min(RETRY_BASE_MS * 2 ** attempt, RETRY_MAX_MS);
7
+ return ceil / 2 + Math.random() * (ceil / 2);
8
+ }
9
+ // Methods safe to re-send after a thrown fetch: reads, plus the idempotent
10
+ // writes. A node that has fully rolled away makes fetch reject opaquely instead
11
+ // of returning a readable 503, and we can't prove the request didn't execute, so
12
+ // we only re-send when re-sending is harmless. POST/MKCOL/MOVE/COPY/LOCK are not.
13
+ const THROW_RETRYABLE = new Set(['GET', 'HEAD', 'OPTIONS', 'PROPFIND', 'REPORT', 'PUT', 'DELETE']);
14
+ export function isThrowRetryable(method) {
15
+ return method ? THROW_RETRYABLE.has(method.toUpperCase()) : false;
16
+ }
17
+ /**
18
+ * Fetch with shard-reroute retries. A node being drained returns a readable
19
+ * 421/503: re-resolve the owner and retry. A node that has fully rolled away no
20
+ * longer answers, so the LB's CORS-less 5xx makes fetch reject opaquely (no
21
+ * status to inspect); for idempotent requests we treat that throw as the same
22
+ * reroute signal, since otherwise the request keeps targeting a dead node and
23
+ * surfaces only as an opaque CORS error.
24
+ */
25
+ export async function fetchWithReroute(opts) {
26
+ const { send, reroute, retryOnThrow } = opts;
27
+ let response = null;
28
+ let thrown;
29
+ try {
30
+ response = await send();
31
+ }
32
+ catch (error) {
33
+ if (!reroute || !retryOnThrow)
34
+ throw error;
35
+ thrown = error;
36
+ }
37
+ for (let attempt = 0; reroute && attempt < REQUEST_MAX_RETRIES &&
38
+ (response === null || response.status === 421 || response.status === 503); attempt++) {
39
+ await delay(retryBackoffMs(attempt));
40
+ try {
41
+ await reroute();
42
+ }
43
+ catch {
44
+ continue; // reroute itself failed (e.g. /route exhausted its own retries); keep backing off
45
+ }
46
+ try {
47
+ response = await send();
48
+ thrown = undefined;
49
+ }
50
+ catch (error) {
51
+ if (!retryOnThrow)
52
+ throw error;
53
+ response = null;
54
+ thrown = error;
55
+ }
56
+ }
57
+ if (response === null)
58
+ throw thrown;
59
+ return response;
60
+ }
61
+ //# sourceMappingURL=reroute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reroute.js","sourceRoot":"","sources":["../src/reroute.ts"],"names":[],"mappings":"AAAA,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAC9B,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,MAAM,YAAY,GAAG,KAAK,CAAC;AAC3B,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAEtF,SAAS,cAAc,CAAC,OAAe;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,IAAI,OAAO,EAAE,YAAY,CAAC,CAAC;IAClE,OAAO,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,2EAA2E;AAC3E,gFAAgF;AAChF,iFAAiF;AACjF,kFAAkF;AAClF,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEnG,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC9C,OAAO,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACpE,CAAC;AAcD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAoB;IACzD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAE7C,IAAI,QAAQ,GAAoB,IAAI,CAAC;IACrC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY;YAAE,MAAM,KAAK,CAAC;QAC3C,MAAM,GAAG,KAAK,CAAC;IACjB,CAAC;IAED,KACE,IAAI,OAAO,GAAG,CAAC,EACf,OAAO,IAAI,OAAO,GAAG,mBAAmB;QACtC,CAAC,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC,EAC3E,OAAO,EAAE,EACT,CAAC;QACD,MAAM,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS,CAAC,kFAAkF;QAC9F,CAAC;QACD,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAC;YACxB,MAAM,GAAG,SAAS,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,YAAY;gBAAE,MAAM,KAAK,CAAC;YAC/B,QAAQ,GAAG,IAAI,CAAC;YAChB,MAAM,GAAG,KAAK,CAAC;QACjB,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,KAAK,IAAI;QAAE,MAAM,MAAM,CAAC;IACpC,OAAO,QAAQ,CAAC;AAClB,CAAC"}
package/dist/rest.d.ts CHANGED
@@ -1,17 +1,26 @@
1
1
  import type { AuthManager } from './auth.js';
2
+ import type { GetObjectsResult } from './types.js';
2
3
  export interface RestClientConfig {
3
4
  apiUrl: string;
4
5
  authManager: AuthManager;
6
+ /** Called on shard refusal/drain (421/503). Return the new API base URL. */
7
+ onRefused?: () => Promise<string>;
5
8
  }
6
9
  export declare class RestClient {
7
10
  private apiUrl;
8
11
  private authManager;
12
+ private onRefused?;
9
13
  constructor(config: RestClientConfig);
14
+ /** Update the API base URL (used after shard rerouting). */
15
+ setApiUrl(apiUrl: string): void;
16
+ /** Wire the shard-reroute callback (used after shard rerouting). */
17
+ setOnRefused(onRefused: () => Promise<string>): void;
10
18
  proxyFetch(spaceId: string, url: string, init?: {
11
19
  method?: string;
12
20
  headers?: Record<string, string>;
13
21
  body?: unknown;
14
22
  }): Promise<Response>;
23
+ getObjects(spaceId: string, paths: string[]): Promise<GetObjectsResult>;
15
24
  importArchive(name: string, archive: Blob): Promise<string>;
16
25
  private authenticatedFetch;
17
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../src/rest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAc;gBAErB,MAAM,EAAE,gBAAgB;IAK9B,UAAU,CACd,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3E,OAAO,CAAC,QAAQ,CAAC;IAed,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;YAmBnD,kBAAkB;CAUjC"}
1
+ {"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../src/rest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGnD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;IACzB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CACnC;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAC,CAAwB;gBAE9B,MAAM,EAAE,gBAAgB;IAMpC,4DAA4D;IAC5D,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,oEAAoE;IACpE,YAAY,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI;IAI9C,UAAU,CACd,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3E,OAAO,CAAC,QAAQ,CAAC;IAed,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsBvE,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;YAmBnD,kBAAkB;CAejC"}
package/dist/rest.js CHANGED
@@ -1,9 +1,20 @@
1
+ import { fetchWithReroute, isThrowRetryable } from './reroute.js';
1
2
  export class RestClient {
2
3
  apiUrl;
3
4
  authManager;
5
+ onRefused;
4
6
  constructor(config) {
5
7
  this.apiUrl = config.apiUrl.replace(/\/+$/, '');
6
8
  this.authManager = config.authManager;
9
+ this.onRefused = config.onRefused;
10
+ }
11
+ /** Update the API base URL (used after shard rerouting). */
12
+ setApiUrl(apiUrl) {
13
+ this.apiUrl = apiUrl.replace(/\/+$/, '');
14
+ }
15
+ /** Wire the shard-reroute callback (used after shard rerouting). */
16
+ setOnRefused(onRefused) {
17
+ this.onRefused = onRefused;
7
18
  }
8
19
  async proxyFetch(spaceId, url, init) {
9
20
  const response = await this.authenticatedFetch(`/fetch/${encodeURIComponent(spaceId)}`, {
@@ -18,6 +29,22 @@ export class RestClient {
18
29
  });
19
30
  return response;
20
31
  }
32
+ async getObjects(spaceId, paths) {
33
+ const response = await this.authenticatedFetch(`/spaces/${encodeURIComponent(spaceId)}/getObjects`, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({ paths }),
37
+ });
38
+ if (!response.ok) {
39
+ const errorText = await response.text().catch(() => response.statusText);
40
+ throw new Error(`Failed to get objects: ${response.status} ${errorText}`);
41
+ }
42
+ const result = await response.json();
43
+ return {
44
+ objects: result.objects.map((object) => ({ path: object.path, body: object.body })),
45
+ missing: result.missing,
46
+ };
47
+ }
21
48
  async importArchive(name, archive) {
22
49
  const formData = new FormData();
23
50
  formData.append('name', name);
@@ -40,7 +67,12 @@ export class RestClient {
40
67
  const headers = new Headers(init.headers);
41
68
  headers.set('Authorization', `Bearer ${tokens.accessToken}`);
42
69
  headers.set('X-Rool-Token', tokens.roolToken);
43
- return fetch(`${this.apiUrl}${path}`, { ...init, headers });
70
+ const onRefused = this.onRefused;
71
+ return fetchWithReroute({
72
+ send: () => fetch(`${this.apiUrl}${path}`, { ...init, headers }),
73
+ reroute: onRefused ? async () => this.setApiUrl(await onRefused()) : undefined,
74
+ retryOnThrow: isThrowRetryable(init.method),
75
+ });
44
76
  }
45
77
  }
46
78
  //# sourceMappingURL=rest.js.map
package/dist/rest.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"rest.js","sourceRoot":"","sources":["../src/rest.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,UAAU;IACb,MAAM,CAAS;IACf,WAAW,CAAc;IAEjC,YAAY,MAAwB;QAClC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,UAAU,CACd,OAAe,EACf,GAAW,EACX,IAA4E;QAE5E,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,kBAAkB,CAAC,OAAO,CAAC,EAAE,EAAE;YACtF,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,GAAG;gBACH,MAAM,EAAE,IAAI,EAAE,MAAM;gBACpB,OAAO,EAAE,IAAI,EAAE,OAAO;gBACtB,IAAI,EAAE,IAAI,EAAE,IAAI;aACjB,CAAC;SACH,CAAC,CAAC;QAEH,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,IAAY,EAAE,OAAa;QAC7C,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;QAChC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9B,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QAEnD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,EAAE;YAC/D,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,QAAQ;SACf,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;YACzE,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAuC,CAAC;QAC1E,OAAO,MAAM,CAAC,OAAO,CAAC;IACxB,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAAC,IAAY,EAAE,IAAiB;QAC9D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;QAClD,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAElD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;QAE9C,OAAO,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9D,CAAC;CACF"}
1
+ {"version":3,"file":"rest.js","sourceRoot":"","sources":["../src/rest.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAyC;gBAE7C,MAAM,EAAE,iBAAiB;IAKrC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;YAW9B,UAAU;CAkBzB"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAW7C,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAyC;gBAE7C,MAAM,EAAE,iBAAiB;IAKrC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;YAW9B,UAAU;CAwBzB"}
package/dist/router.js CHANGED
@@ -1,3 +1,11 @@
1
+ const ROUTE_MAX_RETRIES = 6;
2
+ const ROUTE_RETRY_BASE_MS = 150;
3
+ const ROUTE_RETRY_MAX_MS = 2_000;
4
+ const delay = (ms) => new Promise((r) => setTimeout(r, ms));
5
+ function routeBackoffMs(attempt) {
6
+ const ceil = Math.min(ROUTE_RETRY_BASE_MS * 2 ** attempt, ROUTE_RETRY_MAX_MS);
7
+ return ceil / 2 + Math.random() * (ceil / 2);
8
+ }
1
9
  export class SpaceRouter {
2
10
  apiUrl;
3
11
  authManager;
@@ -20,17 +28,24 @@ export class SpaceRouter {
20
28
  const tokens = await this.authManager.getTokens();
21
29
  if (!tokens)
22
30
  throw new Error('Not authenticated');
23
- const response = await fetch(`${this.apiUrl}/route/${encodeURIComponent(spaceId)}`, {
24
- headers: {
25
- Authorization: `Bearer ${tokens.accessToken}`,
26
- 'X-Rool-Token': tokens.roolToken,
27
- },
28
- });
29
- if (!response.ok) {
30
- throw new Error(`Route resolution failed: ${response.status} ${response.statusText}`);
31
+ // A draining shard 503s /route (it must not re-claim the lease for a dying
32
+ // instance). /route is any-shard, so retry until a live shard answers.
33
+ for (let attempt = 0;; attempt++) {
34
+ const response = await fetch(`${this.apiUrl}/route/${encodeURIComponent(spaceId)}`, {
35
+ headers: {
36
+ Authorization: `Bearer ${tokens.accessToken}`,
37
+ 'X-Rool-Token': tokens.roolToken,
38
+ },
39
+ });
40
+ if (response.ok) {
41
+ const data = await response.json();
42
+ return { server: data.server, generation: data.generation };
43
+ }
44
+ if (response.status !== 503 || attempt >= ROUTE_MAX_RETRIES) {
45
+ throw new Error(`Route resolution failed: ${response.status} ${response.statusText}`);
46
+ }
47
+ await delay(routeBackoffMs(attempt));
31
48
  }
32
- const data = await response.json();
33
- return { server: data.server, generation: data.generation };
34
49
  }
35
50
  }
36
51
  //# sourceMappingURL=router.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAYA,MAAM,OAAO,WAAW;IACd,MAAM,CAAS;IACf,WAAW,CAAc;IACzB,QAAQ,GAAG,IAAI,GAAG,EAA8B,CAAC;IAEzD,YAAY,MAAyB;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;IACxC,CAAC;IAED,OAAO,CAAC,OAAe;QACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAE9B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;YACpD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACpC,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,OAAe;QACtC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;QAClD,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAElD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,UAAU,kBAAkB,CAAC,OAAO,CAAC,EAAE,EAAE;YAClF,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,MAAM,CAAC,WAAW,EAAE;gBAC7C,cAAc,EAAE,MAAM,CAAC,SAAS;aACjC;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACxF,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAA4C,CAAC;QAC7E,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;IAC9D,CAAC;CACF"}
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAEA,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAChC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACjC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AACpE,SAAS,cAAc,CAAC,OAAe;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,GAAG,CAAC,IAAI,OAAO,EAAE,kBAAkB,CAAC,CAAC;IAC9E,OAAO,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;AAC/C,CAAC;AAYD,MAAM,OAAO,WAAW;IACd,MAAM,CAAS;IACf,WAAW,CAAc;IACzB,QAAQ,GAAG,IAAI,GAAG,EAA8B,CAAC;IAEzD,YAAY,MAAyB;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;IACxC,CAAC;IAED,OAAO,CAAC,OAAe;QACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAE9B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;YACpD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACpC,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,OAAe;QACtC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;QAClD,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAElD,2EAA2E;QAC3E,uEAAuE;QACvE,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,UAAU,kBAAkB,CAAC,OAAO,CAAC,EAAE,EAAE;gBAClF,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,MAAM,CAAC,WAAW,EAAE;oBAC7C,cAAc,EAAE,MAAM,CAAC,SAAS;iBACjC;aACF,CAAC,CAAC;YAEH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAA4C,CAAC;gBAC7E,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;YAC9D,CAAC;YACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,OAAO,IAAI,iBAAiB,EAAE,CAAC;gBAC5D,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACxF,CAAC;YACD,MAAM,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;CACF"}
package/dist/space.d.ts CHANGED
@@ -3,7 +3,6 @@ import type { GraphQLClient, OpenSpaceFullResult } from './graphql.js';
3
3
  import type { RestClient } from './rest.js';
4
4
  import { RoolChannel } from './channel.js';
5
5
  import { RoolWebDAV, type SpaceFileStorageUsage } from './webdav.js';
6
- import type { MachineResource } from './machine.js';
7
6
  import type { AuthManager } from './auth.js';
8
7
  import type { Logger } from './logger.js';
9
8
  import type { SpaceRouter, RouteInfo } from './router.js';
@@ -20,7 +19,6 @@ export interface SpaceConfig {
20
19
  graphqlClient: GraphQLClient;
21
20
  restClient: RestClient;
22
21
  authManager: AuthManager;
23
- webdavUrl: string;
24
22
  router: SpaceRouter;
25
23
  initialRoute: RouteInfo;
26
24
  logger: Logger;
@@ -48,15 +46,18 @@ export declare class RoolSpace extends EventEmitter<RoolSpaceEvents> {
48
46
  private graphqlClient;
49
47
  private restClient;
50
48
  private authManager;
51
- private webdavUrl;
52
49
  private router;
53
50
  private _route;
51
+ private _webdav;
54
52
  private logger;
55
53
  private onCloseCallback;
56
54
  private subscriptionManager;
55
+ private _closed;
56
+ private _resyncing;
57
+ private _resyncPending;
58
+ private _resyncTimer;
57
59
  private _subscriptionReady;
58
60
  private openChannels;
59
- private _objectLocations;
60
61
  private _objectStats;
61
62
  private _schema;
62
63
  private _meta;
@@ -68,12 +69,12 @@ export declare class RoolSpace extends EventEmitter<RoolSpaceEvents> {
68
69
  get linkAccess(): LinkAccess;
69
70
  get memberCount(): number;
70
71
  get route(): RouteInfo;
71
- /** WebDAV client for this space's user-visible files. */
72
+ /** WebDAV client for this space's user-visible files and object filesystem. */
72
73
  get webdav(): RoolWebDAV;
73
74
  /** Return file-storage quota usage for this space. */
74
75
  getStorageUsage(): Promise<SpaceFileStorageUsage>;
75
- /** Fetch a user-visible machine file resource through the current space. */
76
- fetchMachineResource(resource: MachineResource, options?: {
76
+ /** Fetch a user-visible file path through the current space. */
77
+ fetchPath(path: string, options?: {
77
78
  range?: string | {
78
79
  start: number;
79
80
  end?: number;
@@ -120,12 +121,6 @@ export declare class RoolSpace extends EventEmitter<RoolSpaceEvents> {
120
121
  * Requires owner or admin role.
121
122
  */
122
123
  setLinkAccess(linkAccess: LinkAccess): Promise<void>;
123
- /**
124
- * List channels in this space.
125
- * Returns the live channel list (kept current via SSE).
126
- * @deprecated Use the `channels` property instead.
127
- */
128
- getChannels(): ChannelInfo[];
129
124
  /**
130
125
  * Rename a channel in this space.
131
126
  */
@@ -134,7 +129,6 @@ export declare class RoolSpace extends EventEmitter<RoolSpaceEvents> {
134
129
  * Delete a channel from this space.
135
130
  */
136
131
  deleteChannel(channelId: string): Promise<void>;
137
- installExtension(extensionId: string, channelId: string): Promise<string>;
138
132
  exportArchive(): Promise<Blob>;
139
133
  /**
140
134
  * Refresh space data from the server.
@@ -150,10 +144,9 @@ export declare class RoolSpace extends EventEmitter<RoolSpaceEvents> {
150
144
  * Routes to channels and emits channel lifecycle events.
151
145
  */
152
146
  private handleSpaceEvent;
153
- /**
154
- * Handle reconnection: fetch full state once, distribute to all channels.
155
- */
156
147
  private handleResync;
148
+ private _resyncWithRetry;
149
+ private _finishResync;
157
150
  /**
158
151
  * Apply full space data from server (initial load or resync).
159
152
  */
@@ -1 +1 @@
1
- {"version":3,"file":"space.d.ts","sourceRoot":"","sources":["../src/space.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"space.d.ts","sourceRoot":"","sources":["../src/space.ts"],"names":[],"mappings":"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"}
package/dist/space.js CHANGED
@@ -2,6 +2,7 @@ import { EventEmitter } from './event-emitter.js';
2
2
  import { SpaceSubscriptionManager } from './subscription.js';
3
3
  import { RoolChannel } from './channel.js';
4
4
  import { RoolWebDAV } from './webdav.js';
5
+ import { machinePath } from './path.js';
5
6
  /** Convert a full Channel object to a ChannelInfo summary. */
6
7
  function channelToInfo(id, ch) {
7
8
  return {
@@ -11,9 +12,6 @@ function channelToInfo(id, ch) {
11
12
  createdBy: ch.createdBy,
12
13
  createdByName: ch.createdByName ?? null,
13
14
  interactionCount: Object.values(ch.conversations ?? {}).reduce((sum, conv) => sum + (conv.interactions ? Object.keys(conv.interactions).length : 0), 0),
14
- extensionUrl: ch.extensionUrl ?? null,
15
- extensionId: ch.extensionId ?? null,
16
- manifest: ch.manifest ?? null,
17
15
  };
18
16
  }
19
17
  /**
@@ -37,18 +35,21 @@ export class RoolSpace extends EventEmitter {
37
35
  graphqlClient;
38
36
  restClient;
39
37
  authManager;
40
- webdavUrl;
41
38
  router;
42
39
  _route;
40
+ _webdav;
43
41
  logger;
44
42
  onCloseCallback;
45
43
  // Subscription
46
44
  subscriptionManager = null;
45
+ _closed = false;
46
+ _resyncing = false;
47
+ _resyncPending = false;
48
+ _resyncTimer = null;
47
49
  _subscriptionReady = null;
48
50
  // Open channels on this space
49
51
  openChannels = new Map();
50
52
  // Full space data (for channel creation)
51
- _objectLocations;
52
53
  _objectStats;
53
54
  _schema;
54
55
  _meta;
@@ -65,15 +66,26 @@ export class RoolSpace extends EventEmitter {
65
66
  this.graphqlClient = config.graphqlClient;
66
67
  this.restClient = config.restClient;
67
68
  this.authManager = config.authManager;
68
- this.webdavUrl = config.webdavUrl;
69
69
  this.router = config.router;
70
70
  this._route = config.initialRoute;
71
+ this._webdav = new RoolWebDAV({
72
+ webdavUrl: this._route.server,
73
+ spaceId: this._id,
74
+ authManager: this.authManager,
75
+ onRefused: async () => {
76
+ await this.reroute();
77
+ return this._route.server;
78
+ },
79
+ });
71
80
  this.logger = config.logger;
72
81
  this.onCloseCallback = config.onClose;
73
82
  this.graphqlClient.setOnRefused(() => this.reroute());
83
+ this.restClient.setOnRefused(async () => {
84
+ await this.reroute();
85
+ return this._route.server;
86
+ });
74
87
  // Store full space data
75
88
  const fd = config.fullData;
76
- this._objectLocations = fd.objectLocations;
77
89
  this._objectStats = fd.objectStats;
78
90
  this._schema = fd.schema;
79
91
  this._meta = fd.meta;
@@ -90,23 +102,20 @@ export class RoolSpace extends EventEmitter {
90
102
  get linkAccess() { return this._linkAccess; }
91
103
  get memberCount() { return this._memberCount; }
92
104
  get route() { return this._route; }
93
- /** WebDAV client for this space's user-visible files. */
105
+ /** WebDAV client for this space's user-visible files and object filesystem. */
94
106
  get webdav() {
95
- return new RoolWebDAV({
96
- webdavUrl: this.webdavUrl,
97
- spaceId: this._id,
98
- authManager: this.authManager,
99
- });
107
+ return this._webdav;
100
108
  }
101
109
  /** Return file-storage quota usage for this space. */
102
110
  async getStorageUsage() {
103
111
  return this.webdav.getStorageUsage();
104
112
  }
105
- /** Fetch a user-visible machine file resource through the current space. */
106
- async fetchMachineResource(resource, options) {
107
- if (resource.kind !== 'file')
108
- throw new Error('Machine resource is not fetchable');
109
- return this.webdav.get(resource.path.slice('/rool-drive/'.length), options);
113
+ /** Fetch a user-visible file path through the current space. */
114
+ async fetchPath(path, options) {
115
+ const canonical = machinePath(path);
116
+ if (!canonical.startsWith('/rool-drive/'))
117
+ throw new Error('Path is not a fetchable file');
118
+ return this.webdav.get(canonical, options);
110
119
  }
111
120
  /**
112
121
  * Live list of channels in this space.
@@ -141,6 +150,8 @@ export class RoolSpace extends EventEmitter {
141
150
  async reroute() {
142
151
  const route = await this.router.resolve(this._id);
143
152
  this._route = route;
153
+ this._webdav.setWebDAVUrl(route.server);
154
+ this.restClient.setApiUrl(route.server);
144
155
  const url = `${route.server.replace(/\/+$/, '')}/graphql`;
145
156
  this.graphqlClient.setGraphqlUrl(url);
146
157
  return url;
@@ -179,7 +190,6 @@ export class RoolSpace extends EventEmitter {
179
190
  role: this._role,
180
191
  linkAccess: this._linkAccess,
181
192
  userId: this._userId,
182
- objectLocations: this._objectLocations,
183
193
  objectStats: this._objectStats,
184
194
  schema: this._schema,
185
195
  meta: this._meta,
@@ -245,14 +255,6 @@ export class RoolSpace extends EventEmitter {
245
255
  throw error;
246
256
  }
247
257
  }
248
- /**
249
- * List channels in this space.
250
- * Returns the live channel list (kept current via SSE).
251
- * @deprecated Use the `channels` property instead.
252
- */
253
- getChannels() {
254
- return this._channels;
255
- }
256
258
  /**
257
259
  * Rename a channel in this space.
258
260
  */
@@ -269,10 +271,6 @@ export class RoolSpace extends EventEmitter {
269
271
  this._knownChannelIds.delete(channelId);
270
272
  delete this._channelData[channelId];
271
273
  }
272
- // Targets owning shard
273
- async installExtension(extensionId, channelId) {
274
- return this.graphqlClient.installExtension(this._id, extensionId, channelId);
275
- }
276
274
  // Targets the owning shard
277
275
  async exportArchive() {
278
276
  const tokens = await this.authManager.getTokens();
@@ -307,6 +305,11 @@ export class RoolSpace extends EventEmitter {
307
305
  * Close the space subscription and all open channels.
308
306
  */
309
307
  close() {
308
+ this._closed = true;
309
+ if (this._resyncTimer) {
310
+ clearTimeout(this._resyncTimer);
311
+ this._resyncTimer = null;
312
+ }
310
313
  // Close all open channels
311
314
  for (const channel of this.openChannels.values()) {
312
315
  channel.close();
@@ -331,19 +334,12 @@ export class RoolSpace extends EventEmitter {
331
334
  this.handleResync();
332
335
  return;
333
336
  }
334
- // Probe request: emit to space listeners (client runs the probe via the iframe bridge)
335
- if (event.type === 'probe_request' && event.requestId && event.channelId && event.method) {
336
- this.emit('probe', {
337
- requestId: event.requestId,
338
- channelId: event.channelId,
339
- method: event.method,
340
- args: event.args ?? {},
341
- });
337
+ if (event.type === 'space_files_changed') {
338
+ this.emit('filesChanged', { spaceId: event.spaceId, source: event.source, timestamp: event.timestamp });
342
339
  return;
343
340
  }
344
- // Open extension: emit to space listeners (client opens split view)
345
- if (event.type === 'open_extension' && event.channelId) {
346
- this.emit('openExtension', { channelId: event.channelId });
341
+ if (event.type === 'space_files_reset') {
342
+ this.emit('filesReset', { spaceId: event.spaceId, source: event.source, timestamp: event.timestamp });
347
343
  return;
348
344
  }
349
345
  // Channel lifecycle events: derive channelCreated/channelUpdated/channelDeleted
@@ -362,7 +358,7 @@ export class RoolSpace extends EventEmitter {
362
358
  this._channels = [...this._channels, info];
363
359
  this.emit('channelCreated', info);
364
360
  }
365
- // Also route to the open channel (for channel-internal handling like name/extension updates)
361
+ // Also route to the open channel for channel-internal handling.
366
362
  const channel = this.openChannels.get(event.channelId);
367
363
  if (channel)
368
364
  channel._handleEvent(event);
@@ -386,37 +382,66 @@ export class RoolSpace extends EventEmitter {
386
382
  channel._handleEvent(event);
387
383
  return;
388
384
  }
389
- // Space-wide events (objects, schema, metadata):
390
- // broadcast to all channels on this space
385
+ // Space-wide non-file events (schema, metadata): broadcast to all channels on this space
391
386
  for (const channel of this.openChannels.values()) {
392
387
  channel._handleEvent(event);
393
388
  }
394
389
  }
395
- /**
396
- * Handle reconnection: fetch full state once, distribute to all channels.
397
- */
390
+ // Reconnect resync: fetch full state and distribute. Retries until it lands —
391
+ // a single failure used to leave the client empty until reload. Single-flight:
392
+ // an event arriving mid-resync sets _resyncPending so we re-run once afterward,
393
+ // ensuring the final state reflects the latest space_changed.
398
394
  handleResync() {
399
- this.logger.info(`[RoolSpace] Space ${this._id} reconnected, resyncing...`);
395
+ if (this._resyncing) {
396
+ this._resyncPending = true;
397
+ return;
398
+ }
399
+ this._resyncing = true;
400
+ this._resyncWithRetry(0);
401
+ }
402
+ _resyncWithRetry(attempt) {
403
+ this._resyncTimer = null;
404
+ if (this._closed) {
405
+ this._resyncing = false;
406
+ return;
407
+ }
400
408
  void this.graphqlClient.openSpaceFull(this._id).then((result) => {
409
+ if (this._closed) {
410
+ this._resyncing = false;
411
+ return;
412
+ }
401
413
  this.applyFullData(result);
402
- // Distribute to all open channels
403
414
  for (const [channelId, channel] of this.openChannels) {
404
415
  const channelData = result.channels[channelId];
405
416
  if (!channelData)
406
- continue; // Channel was deleted between fetch and distribution
417
+ continue; // Channel deleted between fetch and distribution
407
418
  channel._applyResyncData({
408
419
  meta: result.meta,
409
420
  schema: result.schema,
410
- objectLocations: result.objectLocations,
411
421
  objectStats: result.objectStats,
412
422
  channel: channelData,
413
423
  });
414
424
  }
415
- this.logger.info(`[RoolSpace] Space ${this._id} resync complete (${result.objectLocations.length} objects)`);
425
+ this.logger.info(`[RoolSpace] Space ${this._id} resync complete`);
426
+ this._finishResync();
416
427
  }).catch((error) => {
417
- this.logger.error(`[RoolSpace] Space ${this._id} resync failed:`, error);
428
+ if (this._closed) {
429
+ this._resyncing = false;
430
+ return;
431
+ }
432
+ const ms = Math.min(1000 * 2 ** attempt, 30000);
433
+ this.logger.error(`[RoolSpace] Space ${this._id} resync failed (attempt ${attempt + 1}), retrying in ${ms}ms:`, error);
434
+ this._resyncTimer = setTimeout(() => this._resyncWithRetry(attempt + 1), ms);
418
435
  });
419
436
  }
437
+ // Clear in-flight flag; if an event arrived mid-resync, run one more pass.
438
+ _finishResync() {
439
+ this._resyncing = false;
440
+ if (this._resyncPending && !this._closed) {
441
+ this._resyncPending = false;
442
+ this.handleResync();
443
+ }
444
+ }
420
445
  /**
421
446
  * Apply full space data from server (initial load or resync).
422
447
  */
@@ -425,7 +450,6 @@ export class RoolSpace extends EventEmitter {
425
450
  this._role = data.role;
426
451
  this._linkAccess = data.linkAccess;
427
452
  this._memberCount = data.memberCount;
428
- this._objectLocations = data.objectLocations;
429
453
  this._objectStats = data.objectStats;
430
454
  this._schema = data.schema;
431
455
  this._meta = data.meta;