@rool-dev/svelte 0.10.2 → 0.11.0

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.
@@ -1,122 +1,81 @@
1
+ import { isObjectPath, machinePath } from '@rool-dev/sdk';
2
+ function objectCollection(path) {
3
+ if (!isObjectPath(path))
4
+ return undefined;
5
+ return path.split('/')[2];
6
+ }
7
+ function eventTouchesObject(event, objectPath, collection) {
8
+ if (event.reset)
9
+ return true;
10
+ for (const path of [...event.changedPaths, ...event.deletedPaths]) {
11
+ if (objectPath && path === objectPath)
12
+ return true;
13
+ if (!objectPath && isObjectPath(path)) {
14
+ if (!collection || objectCollection(path) === collection)
15
+ return true;
16
+ }
17
+ }
18
+ return false;
19
+ }
20
+ function sameJsonValue(a, b) {
21
+ return JSON.stringify(a) === JSON.stringify(b);
22
+ }
23
+ async function watchObjectsFromTree(channel, fileTree, options) {
24
+ if (fileTree.loading)
25
+ await fileTree.ready();
26
+ const paths = fileTree.objectPaths({ collection: options.collection, order: options.order });
27
+ const objects = [];
28
+ for (const path of paths) {
29
+ const object = await channel.getObject(path);
30
+ if (!object)
31
+ continue;
32
+ if (options.where) {
33
+ let matches = true;
34
+ for (const [key, value] of Object.entries(options.where)) {
35
+ if (!sameJsonValue(object.body[key], value)) {
36
+ matches = false;
37
+ break;
38
+ }
39
+ }
40
+ if (!matches)
41
+ continue;
42
+ }
43
+ objects.push(object);
44
+ if (options.limit !== undefined && objects.length >= options.limit)
45
+ break;
46
+ }
47
+ return objects;
48
+ }
1
49
  /**
2
- * A reactive watch of objects that auto-updates when matching objects change.
50
+ * A reactive watch of objects that auto-updates when matching object files change.
3
51
  */
4
52
  class ReactiveWatchImpl {
5
53
  #channel;
54
+ #fileTree;
6
55
  #options;
7
56
  #unsubscribers = [];
8
- #currentLocations = new Set();
9
57
  // Reactive state
10
58
  objects = $state([]);
11
59
  loading = $state(true);
12
- constructor(channel, options) {
60
+ constructor(channel, fileTree, options) {
13
61
  this.#channel = channel;
62
+ this.#fileTree = fileTree;
14
63
  this.#options = options;
15
64
  this.#setup();
16
65
  }
17
66
  #setup() {
18
- // Initial fetch
19
67
  this.refresh();
20
- const onObjectCreated = ({ object }) => {
21
- if (this.#matches(object)) {
22
- this.refresh();
23
- }
24
- };
25
- this.#channel.on('objectCreated', onObjectCreated);
26
- this.#unsubscribers.push(() => this.#channel.off('objectCreated', onObjectCreated));
27
- const onObjectUpdated = ({ location, object }) => {
28
- const wasInCollection = this.#currentLocations.has(location);
29
- const nowMatches = this.#matches(object);
30
- if (wasInCollection && nowMatches) {
31
- // Update in place (merge to preserve fields from partial optimistic updates)
32
- const index = this.objects.findIndex((o) => o.location === location);
33
- if (index !== -1) {
34
- this.objects[index] = {
35
- ...this.objects[index],
36
- ...object,
37
- body: { ...this.objects[index].body, ...object.body },
38
- };
39
- }
40
- }
41
- else if (wasInCollection && !nowMatches) {
42
- // Check if the mismatch is due to missing keys in body (partial optimistic update)
43
- const where = this.#options.where;
44
- const isPartialUpdate = where && Object.keys(where).some((key) => !(key in object.body));
45
- if (isPartialUpdate) {
46
- const index = this.objects.findIndex((o) => o.location === location);
47
- if (index !== -1) {
48
- this.objects[index] = {
49
- ...this.objects[index],
50
- ...object,
51
- body: { ...this.objects[index].body, ...object.body },
52
- };
53
- }
54
- }
55
- else {
56
- // Genuine mismatch — remove from collection
57
- this.objects = this.objects.filter((o) => o.location !== location);
58
- this.#currentLocations.delete(location);
59
- }
60
- }
61
- else if (!wasInCollection && nowMatches) {
62
- // Add to collection (re-fetch to respect limit/order)
63
- this.refresh();
64
- }
65
- };
66
- this.#channel.on('objectUpdated', onObjectUpdated);
67
- this.#unsubscribers.push(() => this.#channel.off('objectUpdated', onObjectUpdated));
68
- const onObjectDeleted = ({ location }) => {
69
- if (this.#currentLocations.has(location)) {
70
- this.objects = this.objects.filter((o) => o.location !== location);
71
- this.#currentLocations.delete(location);
72
- }
73
- };
74
- this.#channel.on('objectDeleted', onObjectDeleted);
75
- this.#unsubscribers.push(() => this.#channel.off('objectDeleted', onObjectDeleted));
76
- const onObjectMoved = ({ from, object }) => {
77
- const wasInCollection = this.#currentLocations.has(from);
78
- const nowMatches = this.#matches(object);
79
- if (wasInCollection || nowMatches) {
80
- this.refresh();
81
- }
82
- };
83
- this.#channel.on('objectMoved', onObjectMoved);
84
- this.#unsubscribers.push(() => this.#channel.off('objectMoved', onObjectMoved));
85
- const onReset = () => this.refresh();
86
- this.#channel.on('reset', onReset);
87
- this.#unsubscribers.push(() => this.#channel.off('reset', onReset));
68
+ const unsubscribe = this.#fileTree.subscribe((event) => {
69
+ if (eventTouchesObject(event, undefined, this.#options.collection))
70
+ void this.refresh();
71
+ });
72
+ this.#unsubscribers.push(unsubscribe);
88
73
  }
89
- /**
90
- * Check if an object matches the filter (collection + where on body).
91
- */
92
- #matches(object) {
93
- if (this.#options.collection && object.collection !== this.#options.collection)
94
- return false;
95
- const where = this.#options.where;
96
- if (!where)
97
- return true;
98
- for (const [key, value] of Object.entries(where)) {
99
- if (object.body[key] !== value)
100
- return false;
101
- }
102
- return true;
103
- }
104
- /**
105
- * Re-fetch the watched objects from the channel.
106
- */
74
+ /** Re-fetch matching objects using the canonical file tree for paths. */
107
75
  async refresh() {
108
76
  this.loading = true;
109
77
  try {
110
- const findOptions = {
111
- where: this.#options.where,
112
- collection: this.#options.collection,
113
- limit: this.#options.limit,
114
- order: this.#options.order,
115
- ephemeral: true,
116
- };
117
- const { objects } = await this.#channel.findObjects(findOptions);
118
- this.objects = objects;
119
- this.#currentLocations = new Set(objects.map((o) => o.location));
78
+ this.objects = await watchObjectsFromTree(this.#channel, this.#fileTree, this.#options);
120
79
  }
121
80
  finally {
122
81
  this.loading = false;
@@ -133,58 +92,34 @@ class ReactiveWatchImpl {
133
92
  */
134
93
  class ReactiveObjectImpl {
135
94
  #channel;
136
- #location;
95
+ #fileTree;
96
+ #path;
137
97
  #unsubscribers = [];
138
98
  // Reactive state
139
99
  data = $state(undefined);
140
100
  loading = $state(true);
141
- constructor(channel, location) {
101
+ constructor(channel, fileTree, path) {
142
102
  this.#channel = channel;
143
- this.#location = location;
103
+ this.#fileTree = fileTree;
104
+ this.#path = machinePath(path);
144
105
  this.#setup();
145
106
  }
146
107
  #setup() {
147
108
  this.refresh();
148
- const onObjectUpdated = ({ location, object }) => {
149
- if (location === this.#location) {
150
- this.data = object;
151
- }
152
- };
153
- this.#channel.on('objectUpdated', onObjectUpdated);
154
- this.#unsubscribers.push(() => this.#channel.off('objectUpdated', onObjectUpdated));
155
- const onObjectCreated = ({ location, object }) => {
156
- if (location === this.#location) {
157
- this.data = object;
158
- }
159
- };
160
- this.#channel.on('objectCreated', onObjectCreated);
161
- this.#unsubscribers.push(() => this.#channel.off('objectCreated', onObjectCreated));
162
- const onObjectDeleted = ({ location }) => {
163
- if (location === this.#location) {
164
- this.data = undefined;
165
- }
166
- };
167
- this.#channel.on('objectDeleted', onObjectDeleted);
168
- this.#unsubscribers.push(() => this.#channel.off('objectDeleted', onObjectDeleted));
169
- const onObjectMoved = ({ from, to, object }) => {
170
- if (from === this.#location) {
171
- // Object moved away from this location; data is gone.
109
+ const unsubscribe = this.#fileTree.subscribe((event) => {
110
+ if (event.deletedPaths.has(this.#path)) {
172
111
  this.data = undefined;
112
+ return;
173
113
  }
174
- else if (to === this.#location) {
175
- this.data = object;
176
- }
177
- };
178
- this.#channel.on('objectMoved', onObjectMoved);
179
- this.#unsubscribers.push(() => this.#channel.off('objectMoved', onObjectMoved));
180
- const onReset = () => this.refresh();
181
- this.#channel.on('reset', onReset);
182
- this.#unsubscribers.push(() => this.#channel.off('reset', onReset));
114
+ if (eventTouchesObject(event, this.#path))
115
+ void this.refresh();
116
+ });
117
+ this.#unsubscribers.push(unsubscribe);
183
118
  }
184
119
  async refresh() {
185
120
  this.loading = true;
186
121
  try {
187
- this.data = await this.#channel.getObject(this.#location);
122
+ this.data = await this.#channel.getObject(this.#path);
188
123
  }
189
124
  finally {
190
125
  this.loading = false;
@@ -232,11 +167,12 @@ class ReactiveConversationHandleImpl {
232
167
  setSystemInstruction(...args) { return this.#handle.setSystemInstruction(...args); }
233
168
  rename(...args) { return this.#handle.rename(...args); }
234
169
  // Object operations
235
- findObjects(...args) { return this.#handle.findObjects(...args); }
236
- createObject(...args) { return this.#handle.createObject(...args); }
237
- updateObject(...args) { return this.#handle.updateObject(...args); }
170
+ putObject(...args) { return this.#handle.putObject(...args); }
171
+ patchObject(...args) { return this.#handle.patchObject(...args); }
238
172
  moveObject(...args) { return this.#handle.moveObject(...args); }
239
173
  deleteObjects(...args) { return this.#handle.deleteObjects(...args); }
174
+ /** @deprecated Use deleteObjects instead. */
175
+ deletePaths(...args) { return this.#handle.deletePaths(...args); }
240
176
  // AI
241
177
  prompt(...args) { return this.#handle.prompt(...args); }
242
178
  // Schema
@@ -257,21 +193,24 @@ class ReactiveConversationHandleImpl {
257
193
  */
258
194
  class ReactiveChannelImpl {
259
195
  #channel;
196
+ #fileTree;
260
197
  #unsubscribers = [];
261
198
  #closed = false;
262
199
  // Reactive state
263
200
  interactions = $state([]);
264
- objectLocations = $state([]);
201
+ objectPaths = $state([]);
265
202
  collections = $state([]);
266
203
  conversations = $state([]);
267
- constructor(channel) {
204
+ constructor(channel, fileTree) {
268
205
  this.#channel = channel;
206
+ this.#fileTree = fileTree;
269
207
  this.interactions = channel.getInteractions();
270
- this.objectLocations = channel.getObjectLocations();
271
- this.collections = Object.keys(channel.getSchema());
208
+ this.objectPaths = fileTree.objectPaths();
209
+ this.collections = fileTree.collections();
272
210
  this.conversations = channel.getConversations();
273
211
  const onChannelUpdated = () => {
274
212
  this.interactions = channel.getInteractions();
213
+ this.conversations = channel.getConversations();
275
214
  };
276
215
  channel.on('channelUpdated', onChannelUpdated);
277
216
  this.#unsubscribers.push(() => channel.off('channelUpdated', onChannelUpdated));
@@ -280,24 +219,18 @@ class ReactiveChannelImpl {
280
219
  };
281
220
  channel.on('conversationUpdated', onConversationUpdated);
282
221
  this.#unsubscribers.push(() => channel.off('conversationUpdated', onConversationUpdated));
283
- const refreshObjectLocations = () => {
284
- this.objectLocations = channel.getObjectLocations();
222
+ const refreshFromFileTree = () => {
223
+ this.objectPaths = fileTree.objectPaths();
224
+ this.collections = fileTree.collections();
285
225
  };
286
- channel.on('objectCreated', refreshObjectLocations);
287
- this.#unsubscribers.push(() => channel.off('objectCreated', refreshObjectLocations));
288
- channel.on('objectDeleted', refreshObjectLocations);
289
- this.#unsubscribers.push(() => channel.off('objectDeleted', refreshObjectLocations));
290
- channel.on('objectMoved', refreshObjectLocations);
291
- this.#unsubscribers.push(() => channel.off('objectMoved', refreshObjectLocations));
292
- const onSchemaUpdated = () => {
293
- this.collections = Object.keys(channel.getSchema());
294
- };
295
- channel.on('schemaUpdated', onSchemaUpdated);
296
- this.#unsubscribers.push(() => channel.off('schemaUpdated', onSchemaUpdated));
226
+ this.#unsubscribers.push(fileTree.subscribe((event) => {
227
+ if (event.reset || eventTouchesObject(event) || [...event.changedPaths, ...event.deletedPaths].some((path) => path === '/space' || path.startsWith('/space/'))) {
228
+ refreshFromFileTree();
229
+ }
230
+ }));
297
231
  const onReset = () => {
298
232
  this.interactions = channel.getInteractions();
299
- this.objectLocations = channel.getObjectLocations();
300
- this.collections = Object.keys(channel.getSchema());
233
+ refreshFromFileTree();
301
234
  this.conversations = channel.getConversations();
302
235
  };
303
236
  channel.on('reset', onReset);
@@ -312,8 +245,6 @@ class ReactiveChannelImpl {
312
245
  get channelName() { return this.#channel.channelName; }
313
246
  get isReadOnly() { return this.#channel.isReadOnly; }
314
247
  get linkAccess() { return this.#channel.linkAccess; }
315
- get extensionUrl() { return this.#channel.extensionUrl; }
316
- get manifest() { return this.#channel.manifest; }
317
248
  get isClosed() { return this.#closed; }
318
249
  close() {
319
250
  if (this.#closed)
@@ -326,13 +257,14 @@ class ReactiveChannelImpl {
326
257
  }
327
258
  // Object operations
328
259
  getObject(...args) { return this.#channel.getObject(...args); }
260
+ getObjects(...args) { return this.#channel.getObjects(...args); }
329
261
  stat(...args) { return this.#channel.stat(...args); }
330
- findObjects(...args) { return this.#channel.findObjects(...args); }
331
- getObjectLocations(...args) { return this.#channel.getObjectLocations(...args); }
332
- createObject(...args) { return this.#channel.createObject(...args); }
333
- updateObject(...args) { return this.#channel.updateObject(...args); }
262
+ putObject(...args) { return this.#channel.putObject(...args); }
263
+ patchObject(...args) { return this.#channel.patchObject(...args); }
334
264
  moveObject(...args) { return this.#channel.moveObject(...args); }
335
265
  deleteObjects(...args) { return this.#channel.deleteObjects(...args); }
266
+ /** @deprecated Use deleteObjects instead. */
267
+ deletePaths(...args) { return this.#channel.deletePaths(...args); }
336
268
  // AI
337
269
  prompt(...args) { return this.#channel.prompt(...args); }
338
270
  // Undo/redo
@@ -376,12 +308,12 @@ class ReactiveChannelImpl {
376
308
  off(...args) { return this.#channel.off(...args); }
377
309
  // Reactive primitives
378
310
  /**
379
- * Create a reactive object that auto-updates when the object at this location changes.
311
+ * Create a reactive object that auto-updates when the object at this path changes.
380
312
  */
381
- object(location) {
313
+ object(path) {
382
314
  if (this.#closed)
383
315
  throw new Error('Cannot create reactive object: channel is closed');
384
- return new ReactiveObjectImpl(this.#channel, location);
316
+ return new ReactiveObjectImpl(this.#channel, this.#fileTree, path);
385
317
  }
386
318
  /**
387
319
  * Create a reactive watch that auto-updates when matching objects change.
@@ -389,11 +321,11 @@ class ReactiveChannelImpl {
389
321
  watch(options) {
390
322
  if (this.#closed)
391
323
  throw new Error('Cannot create reactive watch: channel is closed');
392
- return new ReactiveWatchImpl(this.#channel, options);
324
+ return new ReactiveWatchImpl(this.#channel, this.#fileTree, options);
393
325
  }
394
326
  }
395
- export function wrapChannel(channel) {
396
- return new ReactiveChannelImpl(channel);
327
+ export function wrapChannel(channel, fileTree) {
328
+ return new ReactiveChannelImpl(channel, fileTree);
397
329
  }
398
330
  /**
399
331
  * A reactive list of channels for a space that auto-updates via SSE events.
@@ -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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"}