@deepseek-ai/dsh-api-workspace-files 0.1.5-alpha.1
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +154 -0
- package/README.zh.md +154 -0
- package/lib/client.js +522 -0
- package/lib/index.js +551 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +853 -0
- package/lib/typert.remote-client.d.ts +38 -0
- package/lib/typert.remote-client.js +287 -0
- package/lib/types/changes.d.ts +26 -0
- package/lib/types/changes.js +109 -0
- package/lib/types/client/change-feed.d.ts +105 -0
- package/lib/types/client/change-feed.js +296 -0
- package/lib/types/client/index.d.ts +18 -0
- package/lib/types/client/index.js +26 -0
- package/lib/types/client/provider.d.ts +45 -0
- package/lib/types/client/provider.js +129 -0
- package/lib/types/client/remote.d.ts +44 -0
- package/lib/types/client/remote.js +2 -0
- package/lib/types/client/types.d.ts +65 -0
- package/lib/types/client/types.js +2 -0
- package/lib/types/index.d.ts +143 -0
- package/lib/types/index.js +368 -0
- package/lib/types/types.d.ts +163 -0
- package/lib/types/types.js +17 -0
- package/package.json +86 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@deepseek-ai/dsh-api-workspace-files",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
require("@deepseek-ai/cordis");
|
|
8
|
+
//#region lib/types/client/change-feed.js
|
|
9
|
+
/**
|
|
10
|
+
* The follower key of one absolute path.
|
|
11
|
+
* @param path - an absolute path from a Host stat or change frame.
|
|
12
|
+
* @returns the path with `\\` normalized to `/`.
|
|
13
|
+
*/
|
|
14
|
+
function keyOf(path) {
|
|
15
|
+
return path.replace(/\\/g, "/");
|
|
16
|
+
}
|
|
17
|
+
/** Notices of one follower, delivered in order and pulled by its consumer. */
|
|
18
|
+
var Follower = class {
|
|
19
|
+
address;
|
|
20
|
+
leave;
|
|
21
|
+
pending = [];
|
|
22
|
+
started = Promise.withResolvers();
|
|
23
|
+
wake;
|
|
24
|
+
ended = false;
|
|
25
|
+
hostKey;
|
|
26
|
+
/**
|
|
27
|
+
* Resolves true after the Host acknowledges its subscription, or false if
|
|
28
|
+
* this follower ends before acknowledgement.
|
|
29
|
+
*/
|
|
30
|
+
ready = this.started.promise;
|
|
31
|
+
/**
|
|
32
|
+
* @param address - resource address used for reload lookup.
|
|
33
|
+
* @param leave - unregisters this follower and its abort listener.
|
|
34
|
+
*/
|
|
35
|
+
constructor(address, leave) {
|
|
36
|
+
this.address = address;
|
|
37
|
+
this.leave = leave;
|
|
38
|
+
}
|
|
39
|
+
/** The normalized Host path, absent until a successful stat. */
|
|
40
|
+
get key() {
|
|
41
|
+
return this.hostKey;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Select the Host path for queued and future changes.
|
|
45
|
+
* @param absolutePath - the successful stat's absolute path.
|
|
46
|
+
*/
|
|
47
|
+
bind(absolutePath) {
|
|
48
|
+
this.hostKey = keyOf(absolutePath);
|
|
49
|
+
}
|
|
50
|
+
/** The Host acknowledged an active subscription and resolved workspace root. */
|
|
51
|
+
start() {
|
|
52
|
+
this.started.resolve(true);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Queue one notice.
|
|
56
|
+
* @param notice - what the consumer receives next.
|
|
57
|
+
* @param key - normalized Host path for a change; absent for a reload.
|
|
58
|
+
*/
|
|
59
|
+
push(notice, key) {
|
|
60
|
+
this.pending.push({
|
|
61
|
+
key,
|
|
62
|
+
notice
|
|
63
|
+
});
|
|
64
|
+
this.wake?.();
|
|
65
|
+
}
|
|
66
|
+
/** Deliver what is queued, then finish. */
|
|
67
|
+
end() {
|
|
68
|
+
this.ended = true;
|
|
69
|
+
this.started.resolve(false);
|
|
70
|
+
this.wake?.();
|
|
71
|
+
}
|
|
72
|
+
/** Unregister even when the consumer has not started pulling notices. */
|
|
73
|
+
dispose() {
|
|
74
|
+
this.leave();
|
|
75
|
+
}
|
|
76
|
+
/** @inheritdoc */
|
|
77
|
+
async *[Symbol.asyncIterator]() {
|
|
78
|
+
try {
|
|
79
|
+
while (true) {
|
|
80
|
+
const next = this.pending.shift();
|
|
81
|
+
if (next !== void 0) {
|
|
82
|
+
if (next.key === void 0 || this.hostKey === void 0 || next.key === this.hostKey) yield next.notice;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (this.ended) return;
|
|
86
|
+
await new Promise((resolve) => {
|
|
87
|
+
this.wake = resolve;
|
|
88
|
+
});
|
|
89
|
+
this.wake = void 0;
|
|
90
|
+
}
|
|
91
|
+
} finally {
|
|
92
|
+
this.dispose();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
/** The stream and followers of one session. */
|
|
97
|
+
var SessionFeed = class {
|
|
98
|
+
onClose;
|
|
99
|
+
followers = /* @__PURE__ */ new Set();
|
|
100
|
+
stream;
|
|
101
|
+
closed = false;
|
|
102
|
+
started = false;
|
|
103
|
+
/**
|
|
104
|
+
* @param remote - the Remote face carrying `workspaceFiles.changes`.
|
|
105
|
+
* @param sessionId - the session whose writes this feed follows.
|
|
106
|
+
* @param after - the previous feed of this session still closing, if any; the stream opens once it has settled.
|
|
107
|
+
* @param onClose - called once when the stream is gone, whatever the cause, with the dispose that is closing it.
|
|
108
|
+
*/
|
|
109
|
+
constructor(remote, sessionId, after, onClose) {
|
|
110
|
+
this.onClose = onClose;
|
|
111
|
+
this.stream = remote.$stream({
|
|
112
|
+
name: `workspace file changes of ${sessionId}`,
|
|
113
|
+
open: (signal) => {
|
|
114
|
+
this.started = false;
|
|
115
|
+
return openAfter(after, () => remote.workspaceFiles.changes(sessionId, signal));
|
|
116
|
+
},
|
|
117
|
+
ended: () => /* @__PURE__ */ new Error(`workspace file changes of ${sessionId} ended`)
|
|
118
|
+
});
|
|
119
|
+
this.pump();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Register one resource address before its Host path is known.
|
|
123
|
+
* @param follower - receives changes and binds its path after stat.
|
|
124
|
+
*/
|
|
125
|
+
add(follower) {
|
|
126
|
+
this.followers.add(follower);
|
|
127
|
+
if (this.started) follower.start();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Unregister one follower; the last one leaving disposes the stream.
|
|
131
|
+
* @param follower - the follower to drop.
|
|
132
|
+
*/
|
|
133
|
+
remove(follower) {
|
|
134
|
+
this.followers.delete(follower);
|
|
135
|
+
if (this.followers.size === 0) this.close();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Reload an address and every follower bound to the same Host path.
|
|
139
|
+
* @param address - the resource address requesting a reload.
|
|
140
|
+
*/
|
|
141
|
+
requestRestat(address) {
|
|
142
|
+
const keys = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const follower of this.followers) if (follower.address === address && follower.key !== void 0) keys.add(follower.key);
|
|
144
|
+
for (const follower of this.followers) if (follower.address === address || follower.key !== void 0 && keys.has(follower.key)) follower.push({ kind: "restat" });
|
|
145
|
+
}
|
|
146
|
+
async pump() {
|
|
147
|
+
try {
|
|
148
|
+
for await (const item of this.stream) {
|
|
149
|
+
const frame = item.value;
|
|
150
|
+
switch (frame.kind) {
|
|
151
|
+
case "ready":
|
|
152
|
+
item.accept();
|
|
153
|
+
this.started = true;
|
|
154
|
+
for (const follower of this.followers) follower.start();
|
|
155
|
+
break;
|
|
156
|
+
case "change": {
|
|
157
|
+
const key = keyOf(frame.change.absolutePath);
|
|
158
|
+
const notice = editOf(frame.change);
|
|
159
|
+
for (const follower of this.followers) follower.push(notice, key);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
default: assertNever(frame);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch {} finally {
|
|
166
|
+
this.close();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
close() {
|
|
170
|
+
if (this.closed) return;
|
|
171
|
+
this.closed = true;
|
|
172
|
+
const closed = this.stream.dispose();
|
|
173
|
+
for (const follower of this.followers) follower.end();
|
|
174
|
+
this.followers.clear();
|
|
175
|
+
this.onClose(closed);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Open a Host stream once a predecessor has finished closing.
|
|
180
|
+
* @param after - the predecessor's dispose, or nothing to wait for.
|
|
181
|
+
* @param open - opens the stream.
|
|
182
|
+
* @returns the stream's items.
|
|
183
|
+
*/
|
|
184
|
+
async function* openAfter(after, open) {
|
|
185
|
+
await after;
|
|
186
|
+
yield* open();
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The write one Host frame reports.
|
|
190
|
+
* @param frame - the Host frame.
|
|
191
|
+
* @returns the edit notice followers receive.
|
|
192
|
+
*/
|
|
193
|
+
function editOf(frame) {
|
|
194
|
+
return "absent" in frame ? { kind: "absent" } : {
|
|
195
|
+
kind: "changed",
|
|
196
|
+
version: frame.version
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function assertNever(frame) {
|
|
200
|
+
throw new Error(`Unexpected workspace file watch frame: ${JSON.stringify(frame)}`);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Per-session fan-out of the Host's workspace file change stream.
|
|
204
|
+
*
|
|
205
|
+
* Owned by the provider; one instance serves every session of the Client.
|
|
206
|
+
*/
|
|
207
|
+
var ChangeFeed = class {
|
|
208
|
+
remote;
|
|
209
|
+
/** Live feeds only: a feed removes itself when its stream closes. */
|
|
210
|
+
sessions = /* @__PURE__ */ new Map();
|
|
211
|
+
/** Streams still closing, by session: the session's next feed opens after its predecessor has settled. */
|
|
212
|
+
closing = /* @__PURE__ */ new Map();
|
|
213
|
+
/**
|
|
214
|
+
* @param remote - the Remote face carrying `$stream` and `workspaceFiles.changes`.
|
|
215
|
+
*/
|
|
216
|
+
constructor(remote) {
|
|
217
|
+
this.remote = remote;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Follow one resource address in one session before its Host path is known.
|
|
221
|
+
*
|
|
222
|
+
* The follower is registered on call, not on first pull. Changes delivered
|
|
223
|
+
* to this Client are queued while stat is pending. The first follower starts
|
|
224
|
+
* the session's local `changes` call. The iterable ends
|
|
225
|
+
* when `signal` aborts or when the session stream is gone; ending it early
|
|
226
|
+
* (`break`, `return`) unregisters the follower as well, and the last follower
|
|
227
|
+
* of a session disposes its stream. Await a true `ready` result before stat
|
|
228
|
+
* so the Host subscription is active, then bind each stat's absolute path. Until binding,
|
|
229
|
+
* any session write can trigger a retry; after binding, only matching queued
|
|
230
|
+
* and live changes pass.
|
|
231
|
+
* @param sessionId - the session whose workspace holds the file.
|
|
232
|
+
* @param address - the resource address, used only for reload lookup.
|
|
233
|
+
* @param signal - ends the follow.
|
|
234
|
+
* @returns a single-consumer subscription with Host-path binding and explicit disposal.
|
|
235
|
+
*/
|
|
236
|
+
follow(sessionId, address, signal) {
|
|
237
|
+
const feed = signal.aborted ? void 0 : this.feedOf(sessionId);
|
|
238
|
+
const leave = () => {
|
|
239
|
+
signal.removeEventListener("abort", leave);
|
|
240
|
+
follower.end();
|
|
241
|
+
feed?.remove(follower);
|
|
242
|
+
};
|
|
243
|
+
const follower = new Follower(address, leave);
|
|
244
|
+
if (feed === void 0) follower.end();
|
|
245
|
+
else {
|
|
246
|
+
feed.add(follower);
|
|
247
|
+
signal.addEventListener("abort", leave, { once: true });
|
|
248
|
+
}
|
|
249
|
+
return follower;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Ask an address and its same-session Host-path peers to `stat` again.
|
|
253
|
+
* @param sessionId - the session whose workspace holds the file.
|
|
254
|
+
* @param address - the resource address requesting a reload.
|
|
255
|
+
*/
|
|
256
|
+
requestRestat(sessionId, address) {
|
|
257
|
+
this.sessions.get(sessionId)?.requestRestat(address);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Wait for every stream that is still closing, so an owner tearing down
|
|
261
|
+
* leaves no Host stream behind.
|
|
262
|
+
* @returns resolves once no stream of this feed is closing.
|
|
263
|
+
*/
|
|
264
|
+
async settle() {
|
|
265
|
+
await Promise.all(this.closing.values());
|
|
266
|
+
}
|
|
267
|
+
feedOf(sessionId) {
|
|
268
|
+
const existing = this.sessions.get(sessionId);
|
|
269
|
+
if (existing !== void 0) return existing;
|
|
270
|
+
const feed = new SessionFeed(this.remote, sessionId, this.closing.get(sessionId), (closed) => {
|
|
271
|
+
this.sessions.delete(sessionId);
|
|
272
|
+
const tracked = closed.then(() => void 0, () => void 0).then(() => {
|
|
273
|
+
if (this.closing.get(sessionId) === tracked) this.closing.delete(sessionId);
|
|
274
|
+
});
|
|
275
|
+
this.closing.set(sessionId, tracked);
|
|
276
|
+
});
|
|
277
|
+
this.sessions.set(sessionId, feed);
|
|
278
|
+
return feed;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region ../../typert/protocol/lib/index.js
|
|
283
|
+
/** The one Remote failure class shared by owners, the Gateway, and consumers. */
|
|
284
|
+
/**
|
|
285
|
+
* One Remote call failure: a real Error carrying its stable code and typed
|
|
286
|
+
* details. Owners throw it at the failure point; the Host Gateway encodes it
|
|
287
|
+
* onto the wire unchanged; the Client face rebuilds an instance for the
|
|
288
|
+
* `RemoteResult` error branch, so `throw result.error` keeps throw semantics.
|
|
289
|
+
* Discrimination is always by `code`, never by instanceof.
|
|
290
|
+
*/
|
|
291
|
+
var RemoteError = class extends Error {
|
|
292
|
+
code;
|
|
293
|
+
details;
|
|
294
|
+
/** Structural marker: cross-realm/bundle identification never uses instanceof. */
|
|
295
|
+
isDSHRemoteError = true;
|
|
296
|
+
/**
|
|
297
|
+
* @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
|
|
298
|
+
* @param message - human diagnostic carried across the wire.
|
|
299
|
+
* @param details - structured payload typed by the code.
|
|
300
|
+
* @param options - standard Error options (`cause` survives in-process only).
|
|
301
|
+
*/
|
|
302
|
+
constructor(code, message, details, options) {
|
|
303
|
+
super(message, options);
|
|
304
|
+
this.code = code;
|
|
305
|
+
this.details = details;
|
|
306
|
+
this.name = "RemoteError";
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region ../../util/workspace-path/lib/index.js
|
|
311
|
+
/** Whether a decoded first path segment is a Windows drive (`C:`). */
|
|
312
|
+
function isDriveSegment(segment) {
|
|
313
|
+
return segment !== void 0 && /^[A-Za-z]:$/.test(segment);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Read a file address back into its parts.
|
|
317
|
+
* @param address - a candidate address.
|
|
318
|
+
* @returns the parts, or `undefined` when the string is not a `dsh-resource://file/` URI in a known scope with a path, or a segment is not validly encoded.
|
|
319
|
+
*/
|
|
320
|
+
function parseFileAddress(address) {
|
|
321
|
+
try {
|
|
322
|
+
const url = new URL(address);
|
|
323
|
+
if (url.protocol !== "dsh-resource:" || url.host !== "file") return void 0;
|
|
324
|
+
const [, scope, ...rest] = url.pathname.split("/");
|
|
325
|
+
if (scope === "session") {
|
|
326
|
+
const [id, ...segments] = rest;
|
|
327
|
+
if (id === void 0 || id === "" || segments.length === 0) return void 0;
|
|
328
|
+
return {
|
|
329
|
+
scope,
|
|
330
|
+
sessionId: decodeURIComponent(id),
|
|
331
|
+
path: segments.map(decodeURIComponent).join("/")
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (scope === "absolute") {
|
|
335
|
+
const unc = rest[0] === "" && rest.length > 1;
|
|
336
|
+
const segments = (unc ? rest.slice(1) : rest).map(decodeURIComponent);
|
|
337
|
+
if (segments.length === 0 || segments[0] === "") return void 0;
|
|
338
|
+
if (unc) return {
|
|
339
|
+
scope,
|
|
340
|
+
path: `//${segments.join("/")}`
|
|
341
|
+
};
|
|
342
|
+
return {
|
|
343
|
+
scope,
|
|
344
|
+
path: isDriveSegment(segments[0]) ? segments.join("/") : `/${segments.join("/")}`
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return;
|
|
348
|
+
} catch {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
//#endregion
|
|
353
|
+
//#region lib/types/client/provider.js
|
|
354
|
+
/**
|
|
355
|
+
* Build the `file` provider over one Remote face, one change feed, and the Client's Session list.
|
|
356
|
+
* @param remote - the Remote face carrying `workspaceFiles.stat`.
|
|
357
|
+
* @param changes - the per-session change fan-out.
|
|
358
|
+
* @param sessions - the current Session, read for absolute addresses on every open and reload.
|
|
359
|
+
* @returns the provider to register into `ctx.resources`.
|
|
360
|
+
*/
|
|
361
|
+
function createFileResourceProvider(remote, changes, sessions) {
|
|
362
|
+
return {
|
|
363
|
+
protocol: "file",
|
|
364
|
+
async *open(address, { signal }) {
|
|
365
|
+
const resolved = resolve(address, sessions);
|
|
366
|
+
if (!resolved.ok) {
|
|
367
|
+
yield resolved;
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const { sessionId, path } = resolved.value;
|
|
371
|
+
const notices = changes.follow(sessionId, address, signal);
|
|
372
|
+
const stat = () => remote.workspaceFiles.stat(sessionId, path, signal);
|
|
373
|
+
const aborted = () => signal.aborted;
|
|
374
|
+
let current;
|
|
375
|
+
try {
|
|
376
|
+
if (!await notices.ready || aborted()) return;
|
|
377
|
+
const first = await stat();
|
|
378
|
+
if (aborted()) return;
|
|
379
|
+
if (first.ok) {
|
|
380
|
+
notices.bind(first.value.absolutePath);
|
|
381
|
+
current = metadataOf(first.value, false);
|
|
382
|
+
yield {
|
|
383
|
+
ok: true,
|
|
384
|
+
value: current
|
|
385
|
+
};
|
|
386
|
+
} else yield first;
|
|
387
|
+
for await (const notice of notices) {
|
|
388
|
+
if (current === void 0) {
|
|
389
|
+
if (notice.kind === "absent") continue;
|
|
390
|
+
} else if (notice.kind === "changed") {
|
|
391
|
+
if (notice.version === current.version) continue;
|
|
392
|
+
current = {
|
|
393
|
+
...current,
|
|
394
|
+
version: notice.version,
|
|
395
|
+
changed: true
|
|
396
|
+
};
|
|
397
|
+
yield {
|
|
398
|
+
ok: true,
|
|
399
|
+
value: current
|
|
400
|
+
};
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const again = await stat();
|
|
404
|
+
if (aborted()) return;
|
|
405
|
+
if (!again.ok) {
|
|
406
|
+
current = void 0;
|
|
407
|
+
yield again;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
notices.bind(again.value.absolutePath);
|
|
411
|
+
current = metadataOf(again.value, notice.kind !== "restat");
|
|
412
|
+
yield {
|
|
413
|
+
ok: true,
|
|
414
|
+
value: current
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
} finally {
|
|
418
|
+
notices.dispose();
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
reload(address) {
|
|
422
|
+
const resolved = resolve(address, sessions);
|
|
423
|
+
if (resolved.ok) changes.requestRestat(resolved.value.sessionId, address);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Resolve one address to the Host call it stands for, or to the failure frame it earns.
|
|
429
|
+
* @param address - the full address, scheme included.
|
|
430
|
+
* @param sessions - the Client's Session list.
|
|
431
|
+
* @returns the Host file, or the `unsupported-address` / `unknown-workspace` failure.
|
|
432
|
+
*/
|
|
433
|
+
function resolve(address, sessions) {
|
|
434
|
+
const parsed = parseFileAddress(address);
|
|
435
|
+
if (parsed === void 0) return {
|
|
436
|
+
ok: false,
|
|
437
|
+
error: unsupportedAddress(address)
|
|
438
|
+
};
|
|
439
|
+
if (parsed.scope === "session") return {
|
|
440
|
+
ok: true,
|
|
441
|
+
value: {
|
|
442
|
+
sessionId: parsed.sessionId,
|
|
443
|
+
path: parsed.path
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
const sessionId = sessions.current();
|
|
447
|
+
if (sessionId === void 0) return {
|
|
448
|
+
ok: false,
|
|
449
|
+
error: unknownWorkspace(address)
|
|
450
|
+
};
|
|
451
|
+
return {
|
|
452
|
+
ok: true,
|
|
453
|
+
value: {
|
|
454
|
+
sessionId,
|
|
455
|
+
path: parsed.path
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* The failure frame's error for an address this provider does not serve.
|
|
461
|
+
* @param address - the offending address.
|
|
462
|
+
* @returns the typed error.
|
|
463
|
+
*/
|
|
464
|
+
function unsupportedAddress(address) {
|
|
465
|
+
return new RemoteError("workspace-file/unsupported-address", `${address} is not a dsh-resource://file/session/<sessionId>/<path> or dsh-resource://file/absolute/<path> address`, { address });
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* The failure frame's error for an absolute address with no current Session.
|
|
469
|
+
* @param address - the offending address.
|
|
470
|
+
* @returns the typed error.
|
|
471
|
+
*/
|
|
472
|
+
function unknownWorkspace(address) {
|
|
473
|
+
return new RemoteError("workspace-file/unknown-workspace", `${address} requires a current Session`, { address });
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* The resource value one `stat` result amounts to.
|
|
477
|
+
* @param stat - what the Host reported.
|
|
478
|
+
* @param changed - whether the consumer's content may be stale: `true` after a
|
|
479
|
+
* Host notice prompted the stat, `false` for the opening stat and a reload's.
|
|
480
|
+
* @returns the metadata frame value.
|
|
481
|
+
*/
|
|
482
|
+
function metadataOf(stat, changed) {
|
|
483
|
+
return {
|
|
484
|
+
absolutePath: stat.absolutePath,
|
|
485
|
+
version: stat.version,
|
|
486
|
+
changed,
|
|
487
|
+
...stat.bytes === void 0 ? {} : { bytes: stat.bytes }
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region lib/types/client/index.js
|
|
492
|
+
/** Required browser services: the resource model, the Remote carrier and its namespace, and the Session list. */
|
|
493
|
+
const inject = [
|
|
494
|
+
"resources",
|
|
495
|
+
"remote",
|
|
496
|
+
"remote.workspaceFiles",
|
|
497
|
+
"sessions"
|
|
498
|
+
];
|
|
499
|
+
/**
|
|
500
|
+
* Client plugin body: register the `file` provider for this plugin's lifetime.
|
|
501
|
+
* @param ctx - client root context carrying `resources`, the Remote face, and `sessions`.
|
|
502
|
+
*/
|
|
503
|
+
function apply(ctx) {
|
|
504
|
+
const sessions = { current: () => ctx.sessions.list.getSnapshot().current };
|
|
505
|
+
const changes = new ChangeFeed(ctx.remote);
|
|
506
|
+
const provider = createFileResourceProvider(ctx.remote, changes, sessions);
|
|
507
|
+
ctx.effect(() => {
|
|
508
|
+
const release = ctx.resources.register(provider);
|
|
509
|
+
return async () => {
|
|
510
|
+
release();
|
|
511
|
+
await changes.settle();
|
|
512
|
+
};
|
|
513
|
+
}, "workspace-files: file resource provider");
|
|
514
|
+
}
|
|
515
|
+
//#endregion
|
|
516
|
+
exports.apply = apply;
|
|
517
|
+
exports.inject = inject;
|
|
518
|
+
return module.exports;
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
//# sourceMappingURL=client.js.map
|