@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.
- package/README.md +79 -179
- package/dist/channel.d.ts +41 -129
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +220 -394
- package/dist/channel.js.map +1 -1
- package/dist/client.d.ts +3 -55
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -93
- package/dist/client.js.map +1 -1
- package/dist/graphql.d.ts +4 -46
- package/dist/graphql.d.ts.map +1 -1
- package/dist/graphql.js +28 -233
- package/dist/graphql.js.map +1 -1
- package/dist/index.d.ts +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/path.d.ts +6 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +47 -0
- package/dist/path.js.map +1 -0
- package/dist/reroute.d.ts +22 -0
- package/dist/reroute.d.ts.map +1 -0
- package/dist/reroute.js +61 -0
- package/dist/reroute.js.map +1 -0
- package/dist/rest.d.ts +9 -0
- package/dist/rest.d.ts.map +1 -1
- package/dist/rest.js +33 -1
- package/dist/rest.js.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +25 -10
- package/dist/router.js.map +1 -1
- package/dist/space.d.ts +10 -17
- package/dist/space.d.ts.map +1 -1
- package/dist/space.js +79 -55
- package/dist/space.js.map +1 -1
- package/dist/subscription.d.ts.map +1 -1
- package/dist/subscription.js +40 -32
- package/dist/subscription.js.map +1 -1
- package/dist/types.d.ts +52 -217
- package/dist/types.d.ts.map +1 -1
- package/dist/webdav.d.ts +44 -21
- package/dist/webdav.d.ts.map +1 -1
- package/dist/webdav.js +94 -57
- package/dist/webdav.js.map +1 -1
- package/package.json +2 -1
- package/dist/apps.d.ts +0 -30
- package/dist/apps.d.ts.map +0 -1
- package/dist/apps.js +0 -81
- package/dist/apps.js.map +0 -1
- package/dist/locations.d.ts +0 -34
- package/dist/locations.d.ts.map +0 -1
- package/dist/locations.js +0 -90
- package/dist/locations.js.map +0 -1
- package/dist/machine.d.ts +0 -16
- package/dist/machine.d.ts.map +0 -1
- package/dist/machine.js +0 -51
- package/dist/machine.js.map +0 -1
package/dist/reroute.js
ADDED
|
@@ -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
|
}
|
package/dist/rest.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"rest.js","sourceRoot":"","sources":["../src/rest.ts"],"names":[],"mappings":"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"}
|
package/dist/router.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
package/dist/router.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"
|
|
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
|
|
76
|
-
|
|
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
|
*/
|
package/dist/space.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"space.d.ts","sourceRoot":"","sources":["../src/space.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;
|
|
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
|
|
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
|
|
106
|
-
async
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
335
|
-
|
|
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
|
-
|
|
345
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
425
|
+
this.logger.info(`[RoolSpace] Space ${this._id} resync complete`);
|
|
426
|
+
this._finishResync();
|
|
416
427
|
}).catch((error) => {
|
|
417
|
-
this.
|
|
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;
|