@ai-switch/tauri-plugin-runtime 0.1.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.
- package/LICENSE +21 -0
- package/README.md +327 -0
- package/THIRD_PARTY_NOTICES.md +210 -0
- package/dist/bridge/handshake.d.ts +11 -0
- package/dist/bridge/json-codec.d.ts +4 -0
- package/dist/bridge/rpc-peer.d.ts +7 -0
- package/dist/bridge/rpc-types.d.ts +20 -0
- package/dist/chunk-CFQSUF5W.js +1794 -0
- package/dist/chunk-JSBRDJBE.js +30 -0
- package/dist/chunk-K5YWIJN6.js +389 -0
- package/dist/chunk-PW7XHHWG.js +403 -0
- package/dist/chunk-RKQEK7DY.js +13879 -0
- package/dist/chunk-WT62GTMC.js +18 -0
- package/dist/chunk-YI6NQ6WR.js +513 -0
- package/dist/host/event-router.d.ts +19 -0
- package/dist/host/file-handles.d.ts +15 -0
- package/dist/host/host.d.ts +2 -0
- package/dist/host/index.d.ts +4 -0
- package/dist/host/index.js +791 -0
- package/dist/host/policy.d.ts +7 -0
- package/dist/host/requests.d.ts +13 -0
- package/dist/host/types.d.ts +19 -0
- package/dist/host/view.d.ts +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -0
- package/dist/node/buffer.d.ts +7 -0
- package/dist/node/buffer.js +8 -0
- package/dist/node/events.d.ts +12 -0
- package/dist/node/events.js +458 -0
- package/dist/node/fs/backend.d.ts +8 -0
- package/dist/node/fs/callback-types.d.ts +43 -0
- package/dist/node/fs/callbacks.d.ts +3 -0
- package/dist/node/fs/client.d.ts +3 -0
- package/dist/node/fs/errors.d.ts +5 -0
- package/dist/node/fs/lifecycle.d.ts +11 -0
- package/dist/node/fs/options.d.ts +7 -0
- package/dist/node/fs/promises.d.ts +33 -0
- package/dist/node/fs/promises.js +30 -0
- package/dist/node/fs/transfer.d.ts +7 -0
- package/dist/node/fs/types.d.ts +86 -0
- package/dist/node/fs.d.ts +36 -0
- package/dist/node/fs.js +92 -0
- package/dist/node/path.d.ts +20 -0
- package/dist/node/path.js +434 -0
- package/dist/plugin/client.d.ts +4 -0
- package/dist/plugin/connection.d.ts +6 -0
- package/dist/plugin/errors.d.ts +3 -0
- package/dist/plugin/index.d.ts +6 -0
- package/dist/plugin/index.js +12 -0
- package/dist/plugin/types.d.ts +28 -0
- package/dist/protocol/capabilities.d.ts +8 -0
- package/dist/protocol/errors.d.ts +14 -0
- package/dist/protocol/generated/capabilities.generated.d.ts +56 -0
- package/dist/protocol/generated/fs.generated.d.ts +178 -0
- package/dist/protocol/generated/host-event.generated.d.ts +17 -0
- package/dist/protocol/generated/manifest.generated.d.ts +64 -0
- package/dist/protocol/generated/session.generated.d.ts +93 -0
- package/dist/protocol/generated/types.generated.d.ts +6 -0
- package/dist/protocol/generated/validators.generated.d.mts +24 -0
- package/dist/protocol/generated/wire.generated.d.ts +63 -0
- package/dist/protocol/index.d.ts +18 -0
- package/dist/protocol/index.js +746 -0
- package/dist/protocol/json-safety.d.ts +3 -0
- package/dist/protocol/limits.d.ts +11 -0
- package/dist/protocol/manifest.d.ts +5 -0
- package/dist/protocol/path-policy.d.ts +2 -0
- package/dist/protocol/schema/capabilities.schema.json +301 -0
- package/dist/protocol/schema/fs.schema.json +900 -0
- package/dist/protocol/schema/host-event.schema.json +116 -0
- package/dist/protocol/schema/manifest.schema.json +60 -0
- package/dist/protocol/schema/session.schema.json +145 -0
- package/dist/protocol/schema/wire.schema.json +358 -0
- package/dist/protocol/session.d.ts +3 -0
- package/dist/protocol/types.d.ts +27 -0
- package/dist/protocol/validation.d.ts +2 -0
- package/dist/protocol/wire.d.ts +16 -0
- package/package.json +95 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/protocol/limits.ts
|
|
2
|
+
var protocolVersion = "aplg/1";
|
|
3
|
+
var apiVersion = "1.0.0";
|
|
4
|
+
var limits = Object.freeze({
|
|
5
|
+
controlBytes: 1048576,
|
|
6
|
+
fileChunkBytes: 262144,
|
|
7
|
+
fileBytes: 8388608,
|
|
8
|
+
fileTransfers: 2,
|
|
9
|
+
archiveBytes: 134217728,
|
|
10
|
+
archiveExtractedBytes: 536870912,
|
|
11
|
+
archiveEntries: 1e4
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
protocolVersion,
|
|
16
|
+
apiVersion,
|
|
17
|
+
limits
|
|
18
|
+
};
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aplg
|
|
3
|
+
} from "./chunk-K5YWIJN6.js";
|
|
4
|
+
import {
|
|
5
|
+
validatePublicSessionInfo
|
|
6
|
+
} from "./chunk-PW7XHHWG.js";
|
|
7
|
+
import {
|
|
8
|
+
AplgError,
|
|
9
|
+
errorCodes,
|
|
10
|
+
validateCapabilityRequest,
|
|
11
|
+
validateCapabilityResult,
|
|
12
|
+
validateJson,
|
|
13
|
+
validateVirtualPath
|
|
14
|
+
} from "./chunk-RKQEK7DY.js";
|
|
15
|
+
import {
|
|
16
|
+
limits
|
|
17
|
+
} from "./chunk-WT62GTMC.js";
|
|
18
|
+
import {
|
|
19
|
+
import_buffer
|
|
20
|
+
} from "./chunk-CFQSUF5W.js";
|
|
21
|
+
|
|
22
|
+
// src/node/fs/lifecycle.ts
|
|
23
|
+
function checkSignal(signal) {
|
|
24
|
+
if (signal.aborted) throw signal.reason;
|
|
25
|
+
}
|
|
26
|
+
function boundedOperation(action, signal, timeoutMs = 3e4, onLate) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
let settled = false;
|
|
30
|
+
const finish = () => {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
signal?.removeEventListener("abort", abort);
|
|
33
|
+
};
|
|
34
|
+
const stop = (error) => {
|
|
35
|
+
if (!settled) {
|
|
36
|
+
settled = true;
|
|
37
|
+
finish();
|
|
38
|
+
controller.abort(error);
|
|
39
|
+
reject(error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const abort = () => stop(signal.reason);
|
|
43
|
+
const timer = setTimeout(() => stop(new AplgError("E_TIMEOUT", "The filesystem operation timed out.")), timeoutMs);
|
|
44
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
45
|
+
if (signal?.aborted) {
|
|
46
|
+
abort();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
void Promise.resolve().then(() => {
|
|
50
|
+
checkSignal(controller.signal);
|
|
51
|
+
return action(controller.signal);
|
|
52
|
+
}).then((value) => {
|
|
53
|
+
if (settled) {
|
|
54
|
+
try {
|
|
55
|
+
onLate?.(value);
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
settled = true;
|
|
61
|
+
finish();
|
|
62
|
+
resolve(value);
|
|
63
|
+
}, (error) => {
|
|
64
|
+
if (!settled) {
|
|
65
|
+
settled = true;
|
|
66
|
+
finish();
|
|
67
|
+
reject(error);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function createLifecycle(session, options) {
|
|
73
|
+
const active = /* @__PURE__ */ new Set();
|
|
74
|
+
let remove;
|
|
75
|
+
let closed = false;
|
|
76
|
+
return {
|
|
77
|
+
async run(action) {
|
|
78
|
+
if (closed) throw new AplgError("E_SESSION_CLOSED", "The filesystem session is closed.");
|
|
79
|
+
if (active.size >= 64) throw new AplgError("E_LIMIT_EXCEEDED", "Too many queued filesystem operations.");
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
active.add(controller);
|
|
82
|
+
try {
|
|
83
|
+
const info = validatePublicSessionInfo(await boundedOperation(() => session(), controller.signal));
|
|
84
|
+
if (!Object.hasOwn(info.capabilities, "aplg.fs")) throw new AplgError("E_CAPABILITY_UNAVAILABLE", "The host does not provide filesystem access.");
|
|
85
|
+
if (!remove && options.onConnectionChange) remove = options.onConnectionChange((state) => {
|
|
86
|
+
if (state === "connected") return;
|
|
87
|
+
if (state === "closed") closed = true;
|
|
88
|
+
for (const pending of [...active]) pending.abort(new AplgError(state === "closed" ? "E_SESSION_CLOSED" : "E_HOST_UNAVAILABLE", "The filesystem connection is unavailable."));
|
|
89
|
+
});
|
|
90
|
+
checkSignal(controller.signal);
|
|
91
|
+
return await action({ info, signal: controller.signal });
|
|
92
|
+
} finally {
|
|
93
|
+
active.delete(controller);
|
|
94
|
+
if (!active.size) {
|
|
95
|
+
const unsubscribe = remove;
|
|
96
|
+
remove = void 0;
|
|
97
|
+
try {
|
|
98
|
+
unsubscribe?.();
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/node/fs/backend.ts
|
|
108
|
+
function createBackend(call) {
|
|
109
|
+
return {
|
|
110
|
+
async invoke(context, method, params, observe) {
|
|
111
|
+
if (!context.info.capabilities["aplg.fs"].methods.includes(method)) throw new AplgError("E_CAPABILITY_UNAVAILABLE", "The filesystem method is unavailable.");
|
|
112
|
+
const checked = validateCapabilityRequest("aplg.fs", method, params);
|
|
113
|
+
if (!checked.ok) throw Object.assign(new Error("Invalid filesystem request."), { code: checked.diagnostics[0]?.code ?? "E_INVALID_ARGUMENT" });
|
|
114
|
+
const raw = await boundedOperation(
|
|
115
|
+
(signal) => call("aplg.fs", method, params, { signal, timeoutMs: 3e4 }),
|
|
116
|
+
context.signal,
|
|
117
|
+
3e4,
|
|
118
|
+
(result2) => observe?.(result2, true)
|
|
119
|
+
);
|
|
120
|
+
observe?.(raw, false);
|
|
121
|
+
const result = validateCapabilityResult("aplg.fs", method, raw);
|
|
122
|
+
if (!result.ok) throw new AplgError("E_INVALID_MESSAGE", "The filesystem backend returned an invalid result.");
|
|
123
|
+
return raw;
|
|
124
|
+
},
|
|
125
|
+
async abort(handle) {
|
|
126
|
+
try {
|
|
127
|
+
const result = await boundedOperation((signal) => call("aplg.fs", "transfer.abort", { handle }, { signal, timeoutMs: 1e3 }), void 0, 1e3);
|
|
128
|
+
return result === null;
|
|
129
|
+
} catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function responseHandle(value) {
|
|
136
|
+
if (!validateJson(value).ok || value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
137
|
+
const handle = value.handle;
|
|
138
|
+
return typeof handle === "string" && /^[\x21-\x7e]{1,128}$/.test(handle) ? handle : void 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/node/fs/transfer.ts
|
|
142
|
+
function createTransferClient(backend) {
|
|
143
|
+
let active = 0;
|
|
144
|
+
let unsafe = false;
|
|
145
|
+
const waiters = [];
|
|
146
|
+
const handles = /* @__PURE__ */ new Set();
|
|
147
|
+
function acquire(context) {
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
let settled = false;
|
|
150
|
+
const finish = () => {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
context.signal.removeEventListener("abort", abort);
|
|
153
|
+
const index = waiters.indexOf(wake);
|
|
154
|
+
if (index >= 0) waiters.splice(index, 1);
|
|
155
|
+
};
|
|
156
|
+
const abort = () => {
|
|
157
|
+
if (!settled) {
|
|
158
|
+
settled = true;
|
|
159
|
+
finish();
|
|
160
|
+
reject(context.signal.reason);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const timer = setTimeout(() => {
|
|
164
|
+
if (!settled) {
|
|
165
|
+
settled = true;
|
|
166
|
+
finish();
|
|
167
|
+
reject(new AplgError("E_TIMEOUT", "The filesystem transfer queue timed out."));
|
|
168
|
+
}
|
|
169
|
+
}, 3e4);
|
|
170
|
+
const wake = () => {
|
|
171
|
+
if (settled) return;
|
|
172
|
+
if (unsafe) {
|
|
173
|
+
settled = true;
|
|
174
|
+
finish();
|
|
175
|
+
reject(new AplgError("E_SESSION_CLOSED", "File transfer cleanup could not be confirmed."));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (active >= context.info.limits.fileTransfers) return;
|
|
179
|
+
if (context.signal.aborted) {
|
|
180
|
+
abort();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
settled = true;
|
|
184
|
+
finish();
|
|
185
|
+
active++;
|
|
186
|
+
let released = false;
|
|
187
|
+
resolve(() => {
|
|
188
|
+
if (released) return;
|
|
189
|
+
released = true;
|
|
190
|
+
active--;
|
|
191
|
+
for (const pending of [...waiters]) pending();
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
waiters.push(wake);
|
|
195
|
+
context.signal.addEventListener("abort", abort, { once: true });
|
|
196
|
+
if (context.signal.aborted) abort();
|
|
197
|
+
else wake();
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function chunkLimit(context) {
|
|
201
|
+
const envelopeBudget = context.info.limits.controlBytes - 768;
|
|
202
|
+
const limit = Math.min(context.info.limits.fileChunkBytes, Math.floor(envelopeBudget / 4) * 3);
|
|
203
|
+
if (limit < 1) throw new AplgError("E_LIMIT_EXCEEDED", "The negotiated message budget is too small for file transfers.");
|
|
204
|
+
return limit;
|
|
205
|
+
}
|
|
206
|
+
async function usingHandle(context, open, perform) {
|
|
207
|
+
const chunk = chunkLimit(context);
|
|
208
|
+
const release = await acquire(context);
|
|
209
|
+
let handle;
|
|
210
|
+
let committed = false;
|
|
211
|
+
const observe = (raw, late) => {
|
|
212
|
+
const candidate = responseHandle(raw);
|
|
213
|
+
if (!candidate) return;
|
|
214
|
+
if (late) {
|
|
215
|
+
if (!handles.has(candidate)) void backend.abort(candidate).then((confirmed) => {
|
|
216
|
+
if (!confirmed) {
|
|
217
|
+
unsafe = true;
|
|
218
|
+
for (const pending of [...waiters]) pending();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (handles.has(candidate)) throw new AplgError("E_INVALID_MESSAGE", "The host reused an active file transfer handle.");
|
|
224
|
+
handles.add(candidate);
|
|
225
|
+
handle = candidate;
|
|
226
|
+
};
|
|
227
|
+
try {
|
|
228
|
+
checkSignal(context.signal);
|
|
229
|
+
const opened = await open(observe);
|
|
230
|
+
if (!handle) throw new AplgError("E_INVALID_MESSAGE", "The host did not return a file transfer handle.");
|
|
231
|
+
checkSignal(context.signal);
|
|
232
|
+
const result = await perform(handle, opened, chunk);
|
|
233
|
+
await backend.invoke(context, "transfer.finish", { handle });
|
|
234
|
+
committed = true;
|
|
235
|
+
return result;
|
|
236
|
+
} finally {
|
|
237
|
+
if (handle) {
|
|
238
|
+
if (!committed && !await backend.abort(handle) && !context.signal.aborted) unsafe = true;
|
|
239
|
+
handles.delete(handle);
|
|
240
|
+
}
|
|
241
|
+
release();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
read(context, path) {
|
|
246
|
+
return usingHandle(context, (observe) => backend.invoke(context, "transfer.openRead", { path }, observe), async (handle, opened, chunk) => {
|
|
247
|
+
const size = opened.size;
|
|
248
|
+
if (size > context.info.limits.fileBytes) throw new AplgError("E_LIMIT_EXCEEDED", "The file exceeds the negotiated size limit.");
|
|
249
|
+
const output = import_buffer.Buffer.alloc(size);
|
|
250
|
+
for (let offset = 0; offset < size; ) {
|
|
251
|
+
checkSignal(context.signal);
|
|
252
|
+
const length = Math.min(chunk, size - offset);
|
|
253
|
+
const result = await backend.invoke(context, "transfer.pull", { handle, offset, length });
|
|
254
|
+
const bytes = import_buffer.Buffer.from(result.dataBase64, "base64");
|
|
255
|
+
if (result.offset !== offset || bytes.length !== length || bytes.toString("base64") !== result.dataBase64) {
|
|
256
|
+
throw new AplgError("E_INVALID_MESSAGE", "The host returned an incomplete or uncorrelated file chunk.");
|
|
257
|
+
}
|
|
258
|
+
output.set(bytes, offset);
|
|
259
|
+
offset += bytes.length;
|
|
260
|
+
}
|
|
261
|
+
return output;
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
async write(context, path, data, mode) {
|
|
265
|
+
if (data.length > context.info.limits.fileBytes) throw new AplgError("E_LIMIT_EXCEEDED", "The file exceeds the negotiated size limit.");
|
|
266
|
+
await usingHandle(context, (observe) => backend.invoke(context, "transfer.openWrite", { path, size: data.length, mode }, observe), async (handle, _opened, chunk) => {
|
|
267
|
+
for (let offset = 0; offset < data.length; ) {
|
|
268
|
+
checkSignal(context.signal);
|
|
269
|
+
const length = Math.min(chunk, data.length - offset);
|
|
270
|
+
const result = await backend.invoke(context, "transfer.push", { handle, offset, dataBase64: data.subarray(offset, offset + length).toString("base64") });
|
|
271
|
+
if (result.written !== length) throw new AplgError("E_INVALID_MESSAGE", "The host did not acknowledge the complete file chunk.");
|
|
272
|
+
offset += length;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/node/fs/errors.ts
|
|
280
|
+
var known = /* @__PURE__ */ new Set([...errorCodes, "ENOENT", "EACCES", "EPERM", "EEXIST", "ENOTDIR", "EISDIR", "ENOTEMPTY", "EXDEV", "EINVAL", "ENOSPC", "EIO", "EBUSY", "EROFS", "EBADF", "ENAMETOOLONG", "EMFILE", "ELOOP", "ERR_APLG_SYNC_IO_UNSUPPORTED"]);
|
|
281
|
+
function invalidArgument() {
|
|
282
|
+
throw new AplgError("E_INVALID_ARGUMENT", "Unsupported filesystem argument or option.");
|
|
283
|
+
}
|
|
284
|
+
function checkArity(count, min, max) {
|
|
285
|
+
if (count < min || count > max) invalidArgument();
|
|
286
|
+
}
|
|
287
|
+
function toFsError(error, syscall, path, dest) {
|
|
288
|
+
let code = "EIO";
|
|
289
|
+
try {
|
|
290
|
+
if (error && typeof error === "object") {
|
|
291
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "code");
|
|
292
|
+
if (descriptor && "value" in descriptor && typeof descriptor.value === "string" && known.has(descriptor.value)) code = descriptor.value;
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
const result = Object.assign(new Error(`Filesystem operation failed: ${syscall} (${code}).`), { name: "FileSystemError", code, syscall });
|
|
297
|
+
try {
|
|
298
|
+
if (typeof path === "string") result.path = validateVirtualPath(path);
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
if (typeof dest === "string") result.dest = validateVirtualPath(dest);
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
function unsupportedSync() {
|
|
308
|
+
throw toFsError({ code: "ERR_APLG_SYNC_IO_UNSUPPORTED" }, "sync");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/node/fs/options.ts
|
|
312
|
+
function filePath(value) {
|
|
313
|
+
if (typeof value !== "string") invalidArgument();
|
|
314
|
+
return validateVirtualPath(value);
|
|
315
|
+
}
|
|
316
|
+
function objectOptions(value, allowed) {
|
|
317
|
+
if (value === void 0 || value === null) return {};
|
|
318
|
+
if (typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) invalidArgument();
|
|
319
|
+
const result = {};
|
|
320
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
321
|
+
if (typeof key !== "string" || !allowed.includes(key)) invalidArgument();
|
|
322
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
323
|
+
if (!("value" in descriptor) || !descriptor.enumerable) invalidArgument();
|
|
324
|
+
if (descriptor.value !== void 0) result[key] = descriptor.value;
|
|
325
|
+
}
|
|
326
|
+
if (!validateJson(result, 4096).ok) invalidArgument();
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
function readEncoding(value) {
|
|
330
|
+
if (value === "utf8") return "utf8";
|
|
331
|
+
const options = objectOptions(value, ["encoding"]);
|
|
332
|
+
if (options.encoding !== void 0 && options.encoding !== null && options.encoding !== "utf8") invalidArgument();
|
|
333
|
+
return options.encoding === "utf8" ? "utf8" : null;
|
|
334
|
+
}
|
|
335
|
+
function writeMode(value, append) {
|
|
336
|
+
const options = value === "utf8" ? {} : objectOptions(value, ["encoding", "flag"]);
|
|
337
|
+
if (options.encoding !== void 0 && options.encoding !== "utf8") invalidArgument();
|
|
338
|
+
const mode = options.flag ?? (append ? "a" : "w");
|
|
339
|
+
if (!(append ? ["a", "ax"] : ["w", "wx"]).includes(mode)) invalidArgument();
|
|
340
|
+
return mode;
|
|
341
|
+
}
|
|
342
|
+
function boolOptions(value, allowed) {
|
|
343
|
+
const options = objectOptions(value, allowed);
|
|
344
|
+
const result = {};
|
|
345
|
+
for (const key of allowed) {
|
|
346
|
+
if (options[key] !== void 0 && typeof options[key] !== "boolean") invalidArgument();
|
|
347
|
+
result[key] = options[key] === true;
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
function snapshotData(data) {
|
|
352
|
+
if (typeof data === "string") {
|
|
353
|
+
if (data.length > limits.fileBytes || import_buffer.Buffer.byteLength(data, "utf8") > limits.fileBytes) throw new AplgError("E_LIMIT_EXCEEDED", "File data exceeds the supported size.");
|
|
354
|
+
return import_buffer.Buffer.from(data, "utf8");
|
|
355
|
+
}
|
|
356
|
+
if (!(data instanceof Uint8Array)) invalidArgument();
|
|
357
|
+
if (data.byteLength > limits.fileBytes) throw new AplgError("E_LIMIT_EXCEEDED", "File data exceeds the supported size.");
|
|
358
|
+
return import_buffer.Buffer.from(data);
|
|
359
|
+
}
|
|
360
|
+
function virtualRoot(path) {
|
|
361
|
+
return path.startsWith("/mounts/") ? path.split("/").slice(0, 3).join("/") : path.split("/").slice(0, 2).join("/");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/node/fs/client.ts
|
|
365
|
+
function createFsClient(call, session, options = {}) {
|
|
366
|
+
const lifecycle = createLifecycle(session, options);
|
|
367
|
+
const backend = createBackend(call);
|
|
368
|
+
const transfers = createTransferClient(backend);
|
|
369
|
+
const api = {
|
|
370
|
+
async readFile(path, options2) {
|
|
371
|
+
try {
|
|
372
|
+
checkArity(arguments.length, 1, 2);
|
|
373
|
+
const normalized = filePath(path);
|
|
374
|
+
const encoding = readEncoding(options2);
|
|
375
|
+
const data = await lifecycle.run((context) => transfers.read(context, normalized));
|
|
376
|
+
return encoding === "utf8" ? data.toString("utf8") : data;
|
|
377
|
+
} catch (error) {
|
|
378
|
+
throw toFsError(error, "readFile", path);
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
async writeFile(path, data, options2) {
|
|
382
|
+
try {
|
|
383
|
+
checkArity(arguments.length, 2, 3);
|
|
384
|
+
const normalized = filePath(path);
|
|
385
|
+
const mode = writeMode(options2, false);
|
|
386
|
+
const copy = snapshotData(data);
|
|
387
|
+
await lifecycle.run((context) => transfers.write(context, normalized, copy, mode));
|
|
388
|
+
} catch (error) {
|
|
389
|
+
throw toFsError(error, "writeFile", path);
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
async appendFile(path, data, options2) {
|
|
393
|
+
try {
|
|
394
|
+
checkArity(arguments.length, 2, 3);
|
|
395
|
+
const normalized = filePath(path);
|
|
396
|
+
const mode = writeMode(options2, true);
|
|
397
|
+
const copy = snapshotData(data);
|
|
398
|
+
await lifecycle.run((context) => transfers.write(context, normalized, copy, mode));
|
|
399
|
+
} catch (error) {
|
|
400
|
+
throw toFsError(error, "appendFile", path);
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
async readdir(path, options2) {
|
|
404
|
+
try {
|
|
405
|
+
checkArity(arguments.length, 1, 2);
|
|
406
|
+
const normalized = filePath(path);
|
|
407
|
+
const parsed = boolOptions(options2, ["withFileTypes"]);
|
|
408
|
+
const entries = await lifecycle.run((context) => backend.invoke(context, "readdir", { path: normalized }));
|
|
409
|
+
return parsed.withFileTypes ? entries.map(({ name, kind }) => Object.freeze({ name, isFile: () => kind === "file", isDirectory: () => kind === "directory" })) : entries.map((entry) => entry.name);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
throw toFsError(error, "readdir", path);
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
async stat(path) {
|
|
415
|
+
try {
|
|
416
|
+
checkArity(arguments.length, 1, 1);
|
|
417
|
+
const normalized = filePath(path);
|
|
418
|
+
const entry = await lifecycle.run((context) => backend.invoke(context, "stat", { path: normalized }));
|
|
419
|
+
const { kind, size, mtimeMs } = entry;
|
|
420
|
+
return Object.freeze({ size, mtimeMs, isFile: () => kind === "file", isDirectory: () => kind === "directory" });
|
|
421
|
+
} catch (error) {
|
|
422
|
+
throw toFsError(error, "stat", path);
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
async mkdir(path, options2) {
|
|
426
|
+
try {
|
|
427
|
+
checkArity(arguments.length, 1, 2);
|
|
428
|
+
const normalized = filePath(path);
|
|
429
|
+
const parsed = boolOptions(options2, ["recursive"]);
|
|
430
|
+
const result = await lifecycle.run((context) => backend.invoke(context, "mkdir", { path: normalized, recursive: parsed.recursive }));
|
|
431
|
+
if (result.createdPath !== null) {
|
|
432
|
+
const created = filePath(result.createdPath);
|
|
433
|
+
if (virtualRoot(created) !== virtualRoot(normalized) || !(created === normalized || normalized.startsWith(created + "/"))) {
|
|
434
|
+
throw new AplgError("E_INVALID_MESSAGE", "The created directory path does not match the request.");
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return parsed.recursive ? result.createdPath ?? void 0 : void 0;
|
|
438
|
+
} catch (error) {
|
|
439
|
+
throw toFsError(error, "mkdir", path);
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
async rename(from, to) {
|
|
443
|
+
try {
|
|
444
|
+
checkArity(arguments.length, 2, 2);
|
|
445
|
+
const source = filePath(from);
|
|
446
|
+
const destination = filePath(to);
|
|
447
|
+
if (virtualRoot(source) !== virtualRoot(destination)) throw Object.assign(new Error("Cross-grant rename is unsupported."), { code: "EXDEV" });
|
|
448
|
+
await lifecycle.run((context) => backend.invoke(context, "rename", { from: source, to: destination }));
|
|
449
|
+
} catch (error) {
|
|
450
|
+
throw toFsError(error, "rename", from, to);
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
async copyFile(from, to) {
|
|
454
|
+
try {
|
|
455
|
+
checkArity(arguments.length, 2, 2);
|
|
456
|
+
const source = filePath(from);
|
|
457
|
+
const destination = filePath(to);
|
|
458
|
+
await lifecycle.run((context) => backend.invoke(context, "copyFile", { from: source, to: destination }));
|
|
459
|
+
} catch (error) {
|
|
460
|
+
throw toFsError(error, "copyFile", from, to);
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
async rm(path, options2) {
|
|
464
|
+
try {
|
|
465
|
+
checkArity(arguments.length, 1, 2);
|
|
466
|
+
const normalized = filePath(path);
|
|
467
|
+
const parsed = boolOptions(options2, ["recursive", "force"]);
|
|
468
|
+
await lifecycle.run((context) => backend.invoke(context, "rm", { path: normalized, recursive: parsed.recursive, force: parsed.force }));
|
|
469
|
+
} catch (error) {
|
|
470
|
+
throw toFsError(error, "rm", path);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
return Object.freeze(api);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/node/fs/promises.ts
|
|
478
|
+
var client;
|
|
479
|
+
function getClient() {
|
|
480
|
+
client ??= createFsClient(aplg.call, aplg.ready, { onConnectionChange: aplg.onConnectionChange });
|
|
481
|
+
return client;
|
|
482
|
+
}
|
|
483
|
+
function lazy(method) {
|
|
484
|
+
return ((...args) => {
|
|
485
|
+
const current = getClient();
|
|
486
|
+
return Reflect.apply(current[method], current, args);
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
var readFile = lazy("readFile");
|
|
490
|
+
var writeFile = lazy("writeFile");
|
|
491
|
+
var appendFile = lazy("appendFile");
|
|
492
|
+
var readdir = lazy("readdir");
|
|
493
|
+
var stat = lazy("stat");
|
|
494
|
+
var mkdir = lazy("mkdir");
|
|
495
|
+
var rename = lazy("rename");
|
|
496
|
+
var copyFile = lazy("copyFile");
|
|
497
|
+
var rm = lazy("rm");
|
|
498
|
+
var promises = Object.freeze({ readFile, writeFile, appendFile, readdir, stat, mkdir, rename, copyFile, rm });
|
|
499
|
+
var promises_default = promises;
|
|
500
|
+
|
|
501
|
+
export {
|
|
502
|
+
unsupportedSync,
|
|
503
|
+
readFile,
|
|
504
|
+
writeFile,
|
|
505
|
+
appendFile,
|
|
506
|
+
readdir,
|
|
507
|
+
stat,
|
|
508
|
+
mkdir,
|
|
509
|
+
rename,
|
|
510
|
+
copyFile,
|
|
511
|
+
rm,
|
|
512
|
+
promises_default
|
|
513
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { HostEvent } from "../protocol/generated/types.generated.js";
|
|
2
|
+
import type { HostTransport } from "../protocol/wire.js";
|
|
3
|
+
import type { PortEvent } from "../bridge/rpc-types.js";
|
|
4
|
+
import type { SessionRequests } from "./requests.js";
|
|
5
|
+
export interface EventRouter {
|
|
6
|
+
open(capability: string, topic: string, signal?: AbortSignal): Promise<string>;
|
|
7
|
+
close(id: string): Promise<void>;
|
|
8
|
+
accept(event: HostEvent): void;
|
|
9
|
+
isConnected(): boolean;
|
|
10
|
+
setConnected(connected: boolean): Promise<void>;
|
|
11
|
+
dispose(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function createEventRouter(options: {
|
|
14
|
+
transport: HostTransport;
|
|
15
|
+
requests: SessionRequests;
|
|
16
|
+
sessionId: string;
|
|
17
|
+
timeoutMs: number;
|
|
18
|
+
send(event: PortEvent): void;
|
|
19
|
+
}): EventRouter;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JsonValue } from "../protocol/types.js";
|
|
2
|
+
import type { HostTransport } from "../protocol/wire.js";
|
|
3
|
+
import type { SessionRequests } from "./requests.js";
|
|
4
|
+
/** Owns file handles at the host boundary, including replies the plugin no longer sees. */
|
|
5
|
+
export declare function createFileHandleGuard(options: {
|
|
6
|
+
transport: HostTransport;
|
|
7
|
+
requests: SessionRequests;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
maxTransfers: number;
|
|
11
|
+
}): {
|
|
12
|
+
call(method: string, params: JsonValue, signal: AbortSignal): Promise<JsonValue>;
|
|
13
|
+
restore(): Promise<void>;
|
|
14
|
+
dispose(): Promise<void>;
|
|
15
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createPluginHost } from "./host.js";
|
|
2
|
+
export type { PluginHost, PluginHostOptions, PluginView } from "./types.js";
|
|
3
|
+
export type { HostTransport, HostOperation, Unsubscribe } from "../protocol/wire.js";
|
|
4
|
+
export type { HostEvent } from "../protocol/generated/types.generated.js";
|