@rool-dev/svelte 0.10.1-dev.cdc8b46 → 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 +24 -19
- package/dist/channel.svelte.d.ts +20 -27
- package/dist/channel.svelte.d.ts.map +1 -1
- package/dist/channel.svelte.js +103 -171
- package/dist/file-tree.svelte.d.ts +91 -0
- package/dist/file-tree.svelte.d.ts.map +1 -0
- package/dist/file-tree.svelte.js +399 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/rool.svelte.d.ts +22 -22
- package/dist/rool.svelte.d.ts.map +1 -1
- package/dist/rool.svelte.js +33 -39
- package/dist/space.svelte.d.ts +3 -2
- package/dist/space.svelte.d.ts.map +1 -1
- package/dist/space.svelte.js +7 -3
- package/package.json +2 -2
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type RoolSpace, type WebDAVDepth, type WebDAVPropName, type WebDAVResponse, type WebDAVSyncLevel } from '@rool-dev/sdk';
|
|
2
|
+
export type ReactiveFilePath = string;
|
|
3
|
+
export type ReactiveFileRoot = '' | 'space' | 'rool-drive';
|
|
4
|
+
export interface ReactiveFileNode {
|
|
5
|
+
/** Stable node id. Same as `path`. */
|
|
6
|
+
id: ReactiveFilePath;
|
|
7
|
+
/** Machine/WebDAV path (`/`, `/space/...`, `/rool-drive/...`). */
|
|
8
|
+
path: ReactiveFilePath;
|
|
9
|
+
/** Parent path, or `null` for `/`. */
|
|
10
|
+
parent: ReactiveFilePath | null;
|
|
11
|
+
/** Last path segment, decoded by the server when available. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Which top-level filesystem this node belongs to. `/` has `root: ''`. */
|
|
14
|
+
root: ReactiveFileRoot;
|
|
15
|
+
isCollection: boolean;
|
|
16
|
+
size: number | null;
|
|
17
|
+
contentType: string | null;
|
|
18
|
+
etag: string | null;
|
|
19
|
+
modifiedAt: number | null;
|
|
20
|
+
href: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface ReactiveFileTreeEvent {
|
|
23
|
+
/** `true` when the tree was replaced from a full snapshot. */
|
|
24
|
+
reset: boolean;
|
|
25
|
+
changedPaths: Set<ReactiveFilePath>;
|
|
26
|
+
deletedPaths: Set<ReactiveFilePath>;
|
|
27
|
+
token: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface ReactiveFileTreeSyncResult extends ReactiveFileTreeEvent {
|
|
30
|
+
changed: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface ReactiveFileTreeTransport {
|
|
33
|
+
propfind(path: string, options: {
|
|
34
|
+
depth: WebDAVDepth;
|
|
35
|
+
props?: WebDAVPropName[];
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
responses: WebDAVResponse[];
|
|
39
|
+
}>;
|
|
40
|
+
syncCollection(path: string, options: {
|
|
41
|
+
token?: string | null;
|
|
42
|
+
level: WebDAVSyncLevel;
|
|
43
|
+
props?: WebDAVPropName[];
|
|
44
|
+
limit?: number;
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
token: string;
|
|
48
|
+
responses: WebDAVResponse[];
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
type Listener = (event: ReactiveFileTreeEvent) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Canonical Svelte-owned tree for the whole per-space WebDAV filesystem.
|
|
54
|
+
*
|
|
55
|
+
* It watches the SDK's coarse `filesChanged` / `filesReset` events and
|
|
56
|
+
* reconciles with WebDAV `sync-collection`. Consumers that care about both
|
|
57
|
+
* object files (`/space/...`) and user files (`/rool-drive/...`) should depend
|
|
58
|
+
* on this tree.
|
|
59
|
+
*/
|
|
60
|
+
export declare class ReactiveFileTree {
|
|
61
|
+
#private;
|
|
62
|
+
nodes: ReactiveFileNode[];
|
|
63
|
+
byPath: Record<string, ReactiveFileNode>;
|
|
64
|
+
token: string | null;
|
|
65
|
+
version: number;
|
|
66
|
+
loading: boolean;
|
|
67
|
+
syncing: boolean;
|
|
68
|
+
error: Error | null;
|
|
69
|
+
constructor(space: RoolSpace);
|
|
70
|
+
get isClosed(): boolean;
|
|
71
|
+
get root(): ReactiveFilePath;
|
|
72
|
+
subscribe(listener: Listener): () => void;
|
|
73
|
+
ready(): Promise<void>;
|
|
74
|
+
get(path: string): ReactiveFileNode | undefined;
|
|
75
|
+
has(path: string): boolean;
|
|
76
|
+
childrenOf(path: string): ReactiveFileNode[];
|
|
77
|
+
descendantsOf(path: string): ReactiveFileNode[];
|
|
78
|
+
/** Object file paths sorted by modified time descending. */
|
|
79
|
+
objectPaths(options?: {
|
|
80
|
+
collection?: string;
|
|
81
|
+
order?: 'asc' | 'desc';
|
|
82
|
+
limit?: number;
|
|
83
|
+
}): string[];
|
|
84
|
+
collections(): string[];
|
|
85
|
+
loadSnapshot(): Promise<ReactiveFileTreeSyncResult>;
|
|
86
|
+
sync(): Promise<ReactiveFileTreeSyncResult>;
|
|
87
|
+
refresh(): Promise<ReactiveFileTreeSyncResult>;
|
|
88
|
+
close(): void;
|
|
89
|
+
}
|
|
90
|
+
export {};
|
|
91
|
+
//# sourceMappingURL=file-tree.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-tree.svelte.d.ts","sourceRoot":"","sources":["../src/file-tree.svelte.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { isObjectPath, machinePath, } from '@rool-dev/sdk';
|
|
2
|
+
const ROOT = '/';
|
|
3
|
+
const DEFAULT_PROPS = [
|
|
4
|
+
'displayname',
|
|
5
|
+
'getcontentlength',
|
|
6
|
+
'getcontenttype',
|
|
7
|
+
'getetag',
|
|
8
|
+
'getlastmodified',
|
|
9
|
+
'resourcetype',
|
|
10
|
+
];
|
|
11
|
+
function normalizePath(path) {
|
|
12
|
+
return machinePath(path);
|
|
13
|
+
}
|
|
14
|
+
function parentPath(path) {
|
|
15
|
+
if (path === ROOT)
|
|
16
|
+
return null;
|
|
17
|
+
const parts = path.split('/').filter(Boolean);
|
|
18
|
+
parts.pop();
|
|
19
|
+
return parts.length === 0 ? ROOT : `/${parts.join('/')}`;
|
|
20
|
+
}
|
|
21
|
+
function leafName(path) {
|
|
22
|
+
if (path === ROOT)
|
|
23
|
+
return 'Space';
|
|
24
|
+
const leaf = path.split('/').filter(Boolean).pop() ?? '';
|
|
25
|
+
try {
|
|
26
|
+
return decodeURIComponent(leaf);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return leaf;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function rootOf(path) {
|
|
33
|
+
if (path === ROOT)
|
|
34
|
+
return '';
|
|
35
|
+
if (path === '/space' || path.startsWith('/space/'))
|
|
36
|
+
return 'space';
|
|
37
|
+
if (path === '/rool-drive' || path.startsWith('/rool-drive/'))
|
|
38
|
+
return 'rool-drive';
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
function modifiedAt(props) {
|
|
42
|
+
const raw = props.getlastmodified;
|
|
43
|
+
if (typeof raw !== 'string')
|
|
44
|
+
return null;
|
|
45
|
+
const parsed = Date.parse(raw);
|
|
46
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
47
|
+
}
|
|
48
|
+
function nodeFromResponse(response) {
|
|
49
|
+
const path = normalizePath(response.path);
|
|
50
|
+
return {
|
|
51
|
+
id: path,
|
|
52
|
+
path,
|
|
53
|
+
parent: parentPath(path),
|
|
54
|
+
name: typeof response.props.displayname === 'string' && response.props.displayname
|
|
55
|
+
? response.props.displayname
|
|
56
|
+
: leafName(path),
|
|
57
|
+
root: rootOf(path),
|
|
58
|
+
isCollection: response.isCollection,
|
|
59
|
+
size: typeof response.props.getcontentlength === 'number' ? response.props.getcontentlength : null,
|
|
60
|
+
contentType: typeof response.props.getcontenttype === 'string' ? response.props.getcontenttype : null,
|
|
61
|
+
etag: typeof response.props.getetag === 'string' ? response.props.getetag : null,
|
|
62
|
+
modifiedAt: modifiedAt(response.props),
|
|
63
|
+
href: response.href || null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function isDeletedResponse(response) {
|
|
67
|
+
if (response.status === 404 || response.status === 410)
|
|
68
|
+
return true;
|
|
69
|
+
return response.propstats.length > 0 && response.propstats.every((p) => p.status === 404 || p.status === 410);
|
|
70
|
+
}
|
|
71
|
+
function sameNode(a, b) {
|
|
72
|
+
return a.id === b.id
|
|
73
|
+
&& a.path === b.path
|
|
74
|
+
&& a.parent === b.parent
|
|
75
|
+
&& a.name === b.name
|
|
76
|
+
&& a.root === b.root
|
|
77
|
+
&& a.isCollection === b.isCollection
|
|
78
|
+
&& a.size === b.size
|
|
79
|
+
&& a.contentType === b.contentType
|
|
80
|
+
&& a.etag === b.etag
|
|
81
|
+
&& a.modifiedAt === b.modifiedAt
|
|
82
|
+
&& a.href === b.href;
|
|
83
|
+
}
|
|
84
|
+
function sortNodes(a, b) {
|
|
85
|
+
return Number(b.isCollection) - Number(a.isCollection) || a.name.localeCompare(b.name);
|
|
86
|
+
}
|
|
87
|
+
function emptyEvent(token, reset = false) {
|
|
88
|
+
return { reset, changedPaths: new Set(), deletedPaths: new Set(), token };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Canonical Svelte-owned tree for the whole per-space WebDAV filesystem.
|
|
92
|
+
*
|
|
93
|
+
* It watches the SDK's coarse `filesChanged` / `filesReset` events and
|
|
94
|
+
* reconciles with WebDAV `sync-collection`. Consumers that care about both
|
|
95
|
+
* object files (`/space/...`) and user files (`/rool-drive/...`) should depend
|
|
96
|
+
* on this tree.
|
|
97
|
+
*/
|
|
98
|
+
export class ReactiveFileTree {
|
|
99
|
+
#space;
|
|
100
|
+
#nodes = new Map();
|
|
101
|
+
#children = new Map();
|
|
102
|
+
#listeners = new Set();
|
|
103
|
+
#unsubscribers = [];
|
|
104
|
+
#syncing = null;
|
|
105
|
+
#initialLoad = null;
|
|
106
|
+
#syncAgain = false;
|
|
107
|
+
#closed = false;
|
|
108
|
+
nodes = $state([]);
|
|
109
|
+
byPath = $state({});
|
|
110
|
+
token = $state(null);
|
|
111
|
+
version = $state(0);
|
|
112
|
+
loading = $state(true);
|
|
113
|
+
syncing = $state(false);
|
|
114
|
+
error = $state(null);
|
|
115
|
+
constructor(space) {
|
|
116
|
+
this.#space = space;
|
|
117
|
+
this.#setNode(rootNode());
|
|
118
|
+
this.#publishState();
|
|
119
|
+
this.#setupSpaceListeners();
|
|
120
|
+
this.#initialLoad = this.loadSnapshot().then(() => undefined, () => undefined);
|
|
121
|
+
}
|
|
122
|
+
get isClosed() { return this.#closed; }
|
|
123
|
+
get root() { return ROOT; }
|
|
124
|
+
subscribe(listener) {
|
|
125
|
+
this.#listeners.add(listener);
|
|
126
|
+
return () => this.#listeners.delete(listener);
|
|
127
|
+
}
|
|
128
|
+
ready() {
|
|
129
|
+
return this.#initialLoad ?? Promise.resolve();
|
|
130
|
+
}
|
|
131
|
+
get(path) {
|
|
132
|
+
return this.#nodes.get(normalizePath(path));
|
|
133
|
+
}
|
|
134
|
+
has(path) {
|
|
135
|
+
return this.#nodes.has(normalizePath(path));
|
|
136
|
+
}
|
|
137
|
+
childrenOf(path) {
|
|
138
|
+
return (this.#children.get(normalizePath(path)) ?? [])
|
|
139
|
+
.map((child) => this.#nodes.get(child))
|
|
140
|
+
.filter((node) => !!node);
|
|
141
|
+
}
|
|
142
|
+
descendantsOf(path) {
|
|
143
|
+
const root = normalizePath(path);
|
|
144
|
+
return this.nodes.filter((node) => node.path !== root && isDescendant(node.path, root));
|
|
145
|
+
}
|
|
146
|
+
/** Object file paths sorted by modified time descending. */
|
|
147
|
+
objectPaths(options = {}) {
|
|
148
|
+
const paths = this.nodes
|
|
149
|
+
.filter((node) => !node.isCollection && isObjectPath(node.path))
|
|
150
|
+
.filter((node) => !options.collection || safeCollection(node.path) === options.collection)
|
|
151
|
+
.sort((a, b) => (b.modifiedAt ?? 0) - (a.modifiedAt ?? 0))
|
|
152
|
+
.map((node) => node.path);
|
|
153
|
+
if (options.order === 'asc')
|
|
154
|
+
paths.reverse();
|
|
155
|
+
return options.limit === undefined ? paths : paths.slice(0, options.limit);
|
|
156
|
+
}
|
|
157
|
+
collections() {
|
|
158
|
+
return this.childrenOf('/space')
|
|
159
|
+
.filter((node) => node.isCollection)
|
|
160
|
+
.map((node) => node.name)
|
|
161
|
+
.sort((a, b) => a.localeCompare(b));
|
|
162
|
+
}
|
|
163
|
+
async loadSnapshot() {
|
|
164
|
+
if (this.#closed)
|
|
165
|
+
return { changed: false, ...emptyEvent(this.token, true) };
|
|
166
|
+
this.loading = true;
|
|
167
|
+
this.error = null;
|
|
168
|
+
try {
|
|
169
|
+
let snapshot;
|
|
170
|
+
try {
|
|
171
|
+
snapshot = await this.#space.webdav.syncCollection('/', { token: null, level: 'infinite', props: [...DEFAULT_PROPS] });
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
const [listing, tokenListing] = await Promise.all([
|
|
175
|
+
this.#space.webdav.propfind('/', { depth: 'infinity', props: [...DEFAULT_PROPS] }),
|
|
176
|
+
this.#space.webdav.propfind('/', { depth: '0', props: ['sync-token'] }),
|
|
177
|
+
]);
|
|
178
|
+
snapshot = {
|
|
179
|
+
token: tokenListing.responses[0]?.props.syncToken ?? null,
|
|
180
|
+
responses: listing.responses,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
this.token = snapshot.token;
|
|
184
|
+
const result = this.#replaceSnapshot(snapshot.responses);
|
|
185
|
+
const event = { reset: true, changedPaths: result.changedPaths, deletedPaths: result.deletedPaths, token: this.token };
|
|
186
|
+
this.#emit(event);
|
|
187
|
+
return { changed: result.changed, ...event };
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
this.error = error instanceof Error ? error : new Error(String(error));
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
this.loading = false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async sync() {
|
|
198
|
+
if (this.#closed)
|
|
199
|
+
return { changed: false, ...emptyEvent(this.token) };
|
|
200
|
+
if (this.#syncing) {
|
|
201
|
+
this.#syncAgain = true;
|
|
202
|
+
return this.#syncing;
|
|
203
|
+
}
|
|
204
|
+
this.syncing = true;
|
|
205
|
+
this.error = null;
|
|
206
|
+
this.#syncing = this.#syncOnce();
|
|
207
|
+
try {
|
|
208
|
+
const result = await this.#syncing;
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
this.#syncing = null;
|
|
213
|
+
this.syncing = false;
|
|
214
|
+
if (this.#syncAgain && !this.#closed) {
|
|
215
|
+
this.#syncAgain = false;
|
|
216
|
+
void this.sync();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
refresh() {
|
|
221
|
+
return this.loadSnapshot();
|
|
222
|
+
}
|
|
223
|
+
close() {
|
|
224
|
+
if (this.#closed)
|
|
225
|
+
return;
|
|
226
|
+
this.#closed = true;
|
|
227
|
+
for (const unsub of this.#unsubscribers)
|
|
228
|
+
unsub();
|
|
229
|
+
this.#unsubscribers.length = 0;
|
|
230
|
+
this.#listeners.clear();
|
|
231
|
+
}
|
|
232
|
+
#setupSpaceListeners() {
|
|
233
|
+
const onFilesChanged = () => { void this.sync(); };
|
|
234
|
+
const onFilesReset = () => { void this.loadSnapshot(); };
|
|
235
|
+
const onConnectionStateChanged = (state) => {
|
|
236
|
+
if (state === 'connected' && !this.loading)
|
|
237
|
+
void this.sync();
|
|
238
|
+
};
|
|
239
|
+
this.#space.on('filesChanged', onFilesChanged);
|
|
240
|
+
this.#space.on('filesReset', onFilesReset);
|
|
241
|
+
this.#space.on('connectionStateChanged', onConnectionStateChanged);
|
|
242
|
+
this.#unsubscribers.push(() => this.#space.off('filesChanged', onFilesChanged));
|
|
243
|
+
this.#unsubscribers.push(() => this.#space.off('filesReset', onFilesReset));
|
|
244
|
+
this.#unsubscribers.push(() => this.#space.off('connectionStateChanged', onConnectionStateChanged));
|
|
245
|
+
}
|
|
246
|
+
async #syncOnce() {
|
|
247
|
+
try {
|
|
248
|
+
const delta = await this.#space.webdav.syncCollection('/', { token: this.token, level: 'infinite', props: [...DEFAULT_PROPS] });
|
|
249
|
+
this.token = delta.token;
|
|
250
|
+
const result = this.#applyResponses(delta.responses);
|
|
251
|
+
const event = { reset: false, changedPaths: result.changedPaths, deletedPaths: result.deletedPaths, token: this.token };
|
|
252
|
+
if (result.changed || event.deletedPaths.size > 0)
|
|
253
|
+
this.#emit(event);
|
|
254
|
+
return { changed: result.changed, ...event };
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return this.loadSnapshot();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
#replaceSnapshot(responses) {
|
|
261
|
+
const next = new Map();
|
|
262
|
+
next.set(ROOT, rootNode());
|
|
263
|
+
for (const response of responses) {
|
|
264
|
+
if (isDeletedResponse(response))
|
|
265
|
+
continue;
|
|
266
|
+
const node = nodeFromResponse(response);
|
|
267
|
+
next.set(node.path, node);
|
|
268
|
+
}
|
|
269
|
+
// Ensure aggregate roots exist even if a server/proxy omits them.
|
|
270
|
+
if (!next.has('/space'))
|
|
271
|
+
next.set('/space', syntheticRootChild('/space', 'space'));
|
|
272
|
+
if (!next.has('/rool-drive'))
|
|
273
|
+
next.set('/rool-drive', syntheticRootChild('/rool-drive', 'rool-drive'));
|
|
274
|
+
const changedPaths = new Set();
|
|
275
|
+
const deletedPaths = new Set();
|
|
276
|
+
for (const [path, node] of next) {
|
|
277
|
+
const old = this.#nodes.get(path);
|
|
278
|
+
if (!old || !sameNode(old, node))
|
|
279
|
+
changedPaths.add(path);
|
|
280
|
+
}
|
|
281
|
+
for (const path of this.#nodes.keys()) {
|
|
282
|
+
if (!next.has(path))
|
|
283
|
+
deletedPaths.add(path);
|
|
284
|
+
}
|
|
285
|
+
this.#nodes = next;
|
|
286
|
+
this.#rebuildChildren();
|
|
287
|
+
const changed = changedPaths.size > 0 || deletedPaths.size > 0;
|
|
288
|
+
if (changed)
|
|
289
|
+
this.#publishState();
|
|
290
|
+
return { changed, changedPaths, deletedPaths };
|
|
291
|
+
}
|
|
292
|
+
#applyResponses(responses) {
|
|
293
|
+
let changed = false;
|
|
294
|
+
const changedPaths = new Set();
|
|
295
|
+
const deletedPaths = new Set();
|
|
296
|
+
for (const response of responses) {
|
|
297
|
+
const path = normalizePath(response.path);
|
|
298
|
+
if (path === ROOT)
|
|
299
|
+
continue;
|
|
300
|
+
if (isDeletedResponse(response)) {
|
|
301
|
+
if (this.#deleteSubtree(path, deletedPaths))
|
|
302
|
+
changed = true;
|
|
303
|
+
else
|
|
304
|
+
deletedPaths.add(path);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const node = nodeFromResponse(response);
|
|
308
|
+
const old = this.#nodes.get(node.path);
|
|
309
|
+
if (!old || !sameNode(old, node)) {
|
|
310
|
+
this.#setNode(node);
|
|
311
|
+
changed = true;
|
|
312
|
+
changedPaths.add(node.path);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (changed) {
|
|
316
|
+
this.#rebuildChildren();
|
|
317
|
+
this.#publishState();
|
|
318
|
+
}
|
|
319
|
+
return { changed, changedPaths, deletedPaths };
|
|
320
|
+
}
|
|
321
|
+
#setNode(node) {
|
|
322
|
+
this.#nodes.set(node.path, node);
|
|
323
|
+
}
|
|
324
|
+
#deleteSubtree(path, deletedPaths) {
|
|
325
|
+
let deleted = false;
|
|
326
|
+
for (const nodePath of [...this.#nodes.keys()].sort((a, b) => b.length - a.length)) {
|
|
327
|
+
if (nodePath === path || isDescendant(nodePath, path)) {
|
|
328
|
+
this.#nodes.delete(nodePath);
|
|
329
|
+
deletedPaths.add(nodePath);
|
|
330
|
+
deleted = true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return deleted;
|
|
334
|
+
}
|
|
335
|
+
#rebuildChildren() {
|
|
336
|
+
this.#children = new Map();
|
|
337
|
+
for (const node of this.#nodes.values()) {
|
|
338
|
+
if (!node.parent)
|
|
339
|
+
continue;
|
|
340
|
+
const bucket = this.#children.get(node.parent) ?? [];
|
|
341
|
+
bucket.push(node.path);
|
|
342
|
+
this.#children.set(node.parent, bucket);
|
|
343
|
+
}
|
|
344
|
+
for (const [parent, children] of this.#children) {
|
|
345
|
+
children.sort((a, b) => sortNodes(this.#nodes.get(a), this.#nodes.get(b)));
|
|
346
|
+
this.#children.set(parent, children);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
#publishState() {
|
|
350
|
+
const nodes = [...this.#nodes.values()].sort((a, b) => a.path === ROOT ? -1 : b.path === ROOT ? 1 : a.path.localeCompare(b.path));
|
|
351
|
+
this.nodes = nodes;
|
|
352
|
+
this.byPath = Object.fromEntries(nodes.map((node) => [node.path, node]));
|
|
353
|
+
this.version += 1;
|
|
354
|
+
}
|
|
355
|
+
#emit(event) {
|
|
356
|
+
for (const listener of this.#listeners)
|
|
357
|
+
listener(event);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function rootNode() {
|
|
361
|
+
return {
|
|
362
|
+
id: ROOT,
|
|
363
|
+
path: ROOT,
|
|
364
|
+
parent: null,
|
|
365
|
+
name: 'Space',
|
|
366
|
+
root: '',
|
|
367
|
+
isCollection: true,
|
|
368
|
+
size: null,
|
|
369
|
+
contentType: null,
|
|
370
|
+
etag: null,
|
|
371
|
+
modifiedAt: null,
|
|
372
|
+
href: null,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function syntheticRootChild(path, name) {
|
|
376
|
+
return {
|
|
377
|
+
id: path,
|
|
378
|
+
path,
|
|
379
|
+
parent: ROOT,
|
|
380
|
+
name,
|
|
381
|
+
root: name,
|
|
382
|
+
isCollection: true,
|
|
383
|
+
size: null,
|
|
384
|
+
contentType: null,
|
|
385
|
+
etag: null,
|
|
386
|
+
modifiedAt: null,
|
|
387
|
+
href: null,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function isDescendant(path, ancestor) {
|
|
391
|
+
if (ancestor === ROOT)
|
|
392
|
+
return path !== ROOT;
|
|
393
|
+
return path.startsWith(`${ancestor}/`);
|
|
394
|
+
}
|
|
395
|
+
function safeCollection(path) {
|
|
396
|
+
if (!isObjectPath(path))
|
|
397
|
+
return undefined;
|
|
398
|
+
return path.split('/')[2];
|
|
399
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { createRool, generateId } from './rool.svelte.js';
|
|
2
|
-
export {
|
|
3
|
-
export type { ParsedLocation, MachineResource } from '@rool-dev/sdk';
|
|
2
|
+
export { isObjectPath, machinePath, machineUri } from '@rool-dev/sdk';
|
|
4
3
|
export { wrapChannel } from './channel.svelte.js';
|
|
5
4
|
export { wrapSpace } from './space.svelte.js';
|
|
5
|
+
export { ReactiveFileTree } from './file-tree.svelte.js';
|
|
6
6
|
export type { Rool } from './rool.svelte.js';
|
|
7
7
|
export type { ReactiveChannel, ReactiveConversationHandle, ReactiveObject, ReactiveWatch, ReactiveChannelList, WatchOptions } from './channel.svelte.js';
|
|
8
8
|
export type { ReactiveSpace } from './space.svelte.js';
|
|
9
|
-
export type {
|
|
9
|
+
export type { ReactiveFileNode, ReactiveFilePath, ReactiveFileRoot, ReactiveFileTreeEvent, ReactiveFileTreeSyncResult } from './file-tree.svelte.js';
|
|
10
|
+
export type { RoolClientConfig, RoolChannel, RoolSpace, RoolSpaceInfo, RoolObject, GetObjectsResult, RoolObjectStat, RoolUserRole, ConnectionState, ChannelInfo, Conversation, ConversationInfo, CurrentUser, Interaction, PromptOptions, PromptAttachment, UpdateObjectOptions, MoveObjectOptions, CollectionOptions, FieldType, FieldDef, CollectionDef, SpaceSchema, SpaceMember, UserResult, RoolClient, RoolSpaceEvents, SpaceFileStorageUsage, WebDAVDepth, WebDAVSyncLevel, WebDAVPropName, WebDAVResponse, WebDAVProps, } from '@rool-dev/sdk';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGtE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,YAAY,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,0BAA0B,EAAE,cAAc,EAAE,aAAa,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACzJ,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAGrJ,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,SAAS,EACT,QAAQ,EACR,aAAa,EACb,WAAW,EACX,WAAW,EACX,UAAU,EACV,UAAU,EACV,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,eAAe,EACf,cAAc,EACd,cAAc,EACd,WAAW,GACZ,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Main export
|
|
2
2
|
export { createRool, generateId } from './rool.svelte.js';
|
|
3
|
-
//
|
|
4
|
-
export {
|
|
3
|
+
// Machine path helpers — re-exported from the SDK for convenience
|
|
4
|
+
export { isObjectPath, machinePath, machineUri } from '@rool-dev/sdk';
|
|
5
5
|
// Reactive wrappers
|
|
6
6
|
export { wrapChannel } from './channel.svelte.js';
|
|
7
7
|
export { wrapSpace } from './space.svelte.js';
|
|
8
|
+
export { ReactiveFileTree } from './file-tree.svelte.js';
|
package/dist/rool.svelte.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RoolClient, type RoolSpaceInfo, type ConnectionState, type RoolClientConfig, type CurrentUser
|
|
1
|
+
import { RoolClient, type RoolSpaceInfo, type ConnectionState, type RoolClientConfig, type CurrentUser } from '@rool-dev/sdk';
|
|
2
2
|
import { type ReactiveChannelList } from './channel.svelte.js';
|
|
3
3
|
import { type ReactiveSpace } from './space.svelte.js';
|
|
4
4
|
/**
|
|
@@ -58,6 +58,10 @@ declare class RoolImpl {
|
|
|
58
58
|
* Create a new space. Returns a ReactiveSpace.
|
|
59
59
|
*/
|
|
60
60
|
createSpace(name: string): Promise<ReactiveSpace>;
|
|
61
|
+
/**
|
|
62
|
+
* Duplicate an existing space. Returns a ReactiveSpace.
|
|
63
|
+
*/
|
|
64
|
+
duplicateSpace(sourceSpaceId: string, name: string): Promise<ReactiveSpace>;
|
|
61
65
|
/**
|
|
62
66
|
* Manually refresh the spaces list.
|
|
63
67
|
*/
|
|
@@ -66,6 +70,22 @@ declare class RoolImpl {
|
|
|
66
70
|
* Delete a space.
|
|
67
71
|
*/
|
|
68
72
|
deleteSpace(spaceId: string): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Mark the current user for deletion, then log out locally.
|
|
75
|
+
*/
|
|
76
|
+
deleteCurrentUser(): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Set or change the current user's password.
|
|
79
|
+
*/
|
|
80
|
+
setPassword(password: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Get a value from user storage.
|
|
83
|
+
*/
|
|
84
|
+
getUserStorage<T = unknown>(key: string): T | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Get all user storage data.
|
|
87
|
+
*/
|
|
88
|
+
getAllUserStorage(): Record<string, unknown>;
|
|
69
89
|
/**
|
|
70
90
|
* Set a value in user storage.
|
|
71
91
|
* Updates reactive state immediately, then syncs to server.
|
|
@@ -98,28 +118,8 @@ declare class RoolImpl {
|
|
|
98
118
|
updateCurrentUser(input: {
|
|
99
119
|
name?: string;
|
|
100
120
|
slug?: string;
|
|
121
|
+
marketingOptIn?: boolean;
|
|
101
122
|
}): Promise<CurrentUser>;
|
|
102
|
-
/**
|
|
103
|
-
* Install an extension into a space.
|
|
104
|
-
* Creates/updates a channel with the extension's manifest settings.
|
|
105
|
-
* Returns the channel ID.
|
|
106
|
-
*/
|
|
107
|
-
/** Upload or update a user extension bundle. */
|
|
108
|
-
uploadExtension(extensionId: string, options: UploadExtensionOptions): Promise<ExtensionInfo>;
|
|
109
|
-
/** Delete a user extension permanently. */
|
|
110
|
-
deleteExtension(extensionId: string): Promise<void>;
|
|
111
|
-
/** List the current user's extensions. */
|
|
112
|
-
listExtensions(): Promise<ExtensionInfo[]>;
|
|
113
|
-
/** Get info for a specific user extension. */
|
|
114
|
-
getExtensionInfo(extensionId: string): Promise<ExtensionInfo | null>;
|
|
115
|
-
/** Search published extensions. */
|
|
116
|
-
findExtensions(options?: FindExtensionsOptions): Promise<PublishedExtensionInfo[]>;
|
|
117
|
-
/** Respond to a server-initiated probe with a method-specific result or error. */
|
|
118
|
-
probeResponse(requestId: string, result?: unknown, error?: string): Promise<boolean>;
|
|
119
|
-
/** Publish a user extension (make it publicly discoverable). */
|
|
120
|
-
publishToPublic(extensionId: string): Promise<void>;
|
|
121
|
-
/** Unpublish an extension (remove from public listing). */
|
|
122
|
-
unpublishFromPublic(extensionId: string): Promise<void>;
|
|
123
123
|
/**
|
|
124
124
|
* Create a reactive channel list for a space.
|
|
125
125
|
* Auto-updates when channels are created, updated, or deleted.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rool.svelte.d.ts","sourceRoot":"","sources":["../src/rool.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"rool.svelte.d.ts","sourceRoot":"","sources":["../src/rool.svelte.ts"],"names":[],"mappings":"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"}
|