@poe-platform/safe-fs 0.1.630 → 0.1.631

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.
Files changed (49) hide show
  1. package/dist/safe-fs/bridge/stats.js +1 -1
  2. package/dist/safe-fs/contracts/descriptor.d.ts +31 -0
  3. package/dist/safe-fs/contracts/descriptor.js +1 -0
  4. package/dist/safe-fs/contracts/errors.d.ts +1 -1
  5. package/dist/safe-fs/contracts/errors.js +1 -1
  6. package/dist/safe-fs/contracts/filesystem.d.ts +14 -1
  7. package/dist/safe-fs/core.d.ts +3 -0
  8. package/dist/safe-fs/core.js +2 -0
  9. package/dist/safe-fs/fs/capabilities.d.ts +1 -1
  10. package/dist/safe-fs/fs/capabilities.js +5 -3
  11. package/dist/safe-fs/fs/descriptor.d.ts +21 -0
  12. package/dist/safe-fs/fs/descriptor.js +244 -0
  13. package/dist/safe-fs/fs/devices/index.d.ts +5 -1
  14. package/dist/safe-fs/fs/devices/index.js +53 -4
  15. package/dist/safe-fs/fs/memory/index.d.ts +8 -1
  16. package/dist/safe-fs/fs/memory/index.js +125 -8
  17. package/dist/safe-fs/fs/memory/limits.d.ts +1 -0
  18. package/dist/safe-fs/fs/memory/limits.js +7 -1
  19. package/dist/safe-fs/fs/mount/index.d.ts +5 -1
  20. package/dist/safe-fs/fs/mount/index.js +69 -6
  21. package/dist/safe-fs/fs/overlay/index.d.ts +2 -0
  22. package/dist/safe-fs/fs/overlay/index.js +9 -1
  23. package/dist/safe-fs/fs/quota/index.js +5 -1
  24. package/dist/safe-fs/fs/readonly/index.d.ts +2 -0
  25. package/dist/safe-fs/fs/readonly/index.js +35 -2
  26. package/dist/safe-fs/fs/real/index.d.ts +3 -0
  27. package/dist/safe-fs/fs/real/index.js +73 -3
  28. package/dist/safe-fs/fs/s3/filesystem.d.ts +3 -0
  29. package/dist/safe-fs/fs/s3/filesystem.js +5 -0
  30. package/dist/safe-fs/fs/scoped.js +68 -17
  31. package/dist/safe-fs/fs/webdav/webdav.d.ts +3 -0
  32. package/dist/safe-fs/fs/webdav/webdav.js +5 -0
  33. package/dist/safe-fs/index.d.ts +3 -0
  34. package/dist/safe-fs/index.js +2 -0
  35. package/dist/safe-fs/python/emscripten.d.ts +14 -0
  36. package/dist/safe-fs/python/emscripten.js +354 -0
  37. package/dist/safe-fs/python/filesystem.d.ts +51 -0
  38. package/dist/safe-fs/python/filesystem.js +216 -0
  39. package/dist/safe-fs/python/flags.d.ts +3 -0
  40. package/dist/safe-fs/python/flags.js +14 -0
  41. package/dist/safe-fs/python/index.d.ts +7 -0
  42. package/dist/safe-fs/python/index.js +4 -0
  43. package/dist/safe-fs/python/request.d.ts +2 -0
  44. package/dist/safe-fs/python/request.js +61 -0
  45. package/dist/safe-fs/python/stat.d.ts +8 -0
  46. package/dist/safe-fs/python/stat.js +31 -0
  47. package/dist/safe-fs/xml.d.ts +3 -0
  48. package/dist/safe-fs/xml.js +41 -19
  49. package/package.json +5 -1
@@ -0,0 +1,61 @@
1
+ import { FsError } from "../contracts/errors.js";
2
+ const argumentKinds = {
3
+ open: ["string", "object"], read: ["number", "number", "position"], write: ["number", "bytes", "position"],
4
+ descriptorCapabilities: ["number"], fstat: ["number"], close: ["number"], position: ["number"], ftruncate: ["number", "number"], sync: ["number", "boolean"],
5
+ stat: ["string"], lstat: ["string"], readdir: ["string"], realpath: ["string"], readlink: ["string"], rm: ["string"], rmdir: ["string"],
6
+ rename: ["string", "string"], symlink: ["string", "string"], link: ["string", "string"], mkdir: ["string", "optionalObject"],
7
+ chmod: ["string", "number"], truncate: ["string", "number"], access: ["string", "number"], utimes: ["string", "number", "number"],
8
+ };
9
+ export function parsePythonFsRequest(value, maxTransferBytes) {
10
+ if (!value || typeof value !== "object" || Reflect.ownKeys(value).some(key => key !== "op" && key !== "args"))
11
+ throw new FsError("EINVAL");
12
+ const op = Object.getOwnPropertyDescriptor(value, "op")?.value;
13
+ const args = Object.getOwnPropertyDescriptor(value, "args")?.value;
14
+ if (typeof op !== "string" || !Object.hasOwn(argumentKinds, op))
15
+ throw new FsError("ENOTSUP");
16
+ const kinds = argumentKinds[op];
17
+ if (!Array.isArray(args) || args.length > kinds.length || args.length < kinds.length - (kinds.at(-1) === "optionalObject" ? 1 : 0))
18
+ throw new FsError("EINVAL", { syscall: op });
19
+ const selected = [];
20
+ for (let index = 0; index < kinds.length; index++) {
21
+ const kind = kinds[index];
22
+ const descriptor = Object.getOwnPropertyDescriptor(args, String(index));
23
+ if (descriptor && !Object.hasOwn(descriptor, "value"))
24
+ throw new FsError("EINVAL", { syscall: op });
25
+ const argument = descriptor?.value;
26
+ if (kind === "bytes") {
27
+ if (!(argument instanceof Uint8Array) && !Array.isArray(argument))
28
+ throw new FsError("EINVAL", { syscall: op });
29
+ if (argument.length > maxTransferBytes)
30
+ throw new FsError("EFBIG", { syscall: op });
31
+ const owned = new Uint8Array(argument.length);
32
+ for (let byteIndex = 0; byteIndex < argument.length; byteIndex++) {
33
+ const entry = Object.getOwnPropertyDescriptor(argument, String(byteIndex));
34
+ const byte = entry?.value;
35
+ if (!entry || !Object.hasOwn(entry, "value") || typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255)
36
+ throw new FsError("EINVAL", { syscall: op });
37
+ owned[byteIndex] = byte;
38
+ }
39
+ selected.push(owned);
40
+ continue;
41
+ }
42
+ if (kind === "position" ? argument !== null && typeof argument !== "number"
43
+ : kind === "optionalObject" ? argument !== undefined && (typeof argument !== "object" || argument === null || Array.isArray(argument))
44
+ : kind === "object" ? typeof argument !== "object" || argument === null || Array.isArray(argument)
45
+ : typeof argument !== kind)
46
+ throw new FsError("EINVAL", { syscall: op });
47
+ if (argument !== null && typeof argument === "object") {
48
+ const snapshot = {};
49
+ for (const key of Reflect.ownKeys(argument)) {
50
+ const field = Object.getOwnPropertyDescriptor(argument, key);
51
+ if (typeof key !== "string" || !field || !Object.hasOwn(field, "value"))
52
+ throw new FsError("EINVAL", { syscall: op });
53
+ Object.defineProperty(snapshot, key, { value: field.value, enumerable: true });
54
+ }
55
+ selected.push(snapshot);
56
+ }
57
+ else
58
+ selected.push(argument);
59
+ }
60
+ return { op, args: selected };
61
+ }
@@ -0,0 +1,8 @@
1
+ import type { FileStat } from "../contracts/filesystem.js";
2
+ export type PythonWireStat = Omit<FileStat, "identityScope">;
3
+ /** Invocation-local device numbers qualify canonical scope/dev without serializing opaque authority. */
4
+ export declare class PythonStatTranslator {
5
+ #private;
6
+ constructor(maxDevices?: number);
7
+ translate(stat: FileStat): PythonWireStat;
8
+ }
@@ -0,0 +1,31 @@
1
+ import { FsError } from "../contracts/errors.js";
2
+ /** Invocation-local device numbers qualify canonical scope/dev without serializing opaque authority. */
3
+ export class PythonStatTranslator {
4
+ #devices = new Map();
5
+ #maxDevices;
6
+ #next = 1;
7
+ constructor(maxDevices = 1024) {
8
+ if (!Number.isSafeInteger(maxDevices) || maxDevices < 1)
9
+ throw new FsError("EINVAL");
10
+ this.#maxDevices = maxDevices;
11
+ }
12
+ translate(stat) {
13
+ const { identityScope, dev, ino, ...rest } = stat;
14
+ if (!(typeof identityScope === "symbol" || typeof identityScope === "object" && identityScope !== null)
15
+ || !Number.isSafeInteger(dev) || dev < 0 || !Number.isSafeInteger(ino) || ino < 0)
16
+ return rest;
17
+ let devices = this.#devices.get(identityScope);
18
+ let guestDevice = devices?.get(dev);
19
+ if (guestDevice === undefined) {
20
+ if (this.#next > this.#maxDevices)
21
+ throw new FsError("EFBIG", { syscall: "stat", message: "Python device identity limit exceeded" });
22
+ if (!devices) {
23
+ devices = new Map();
24
+ this.#devices.set(identityScope, devices);
25
+ }
26
+ guestDevice = this.#next++;
27
+ devices.set(dev, guestDevice);
28
+ }
29
+ return { ...rest, dev: guestDevice, ino: ino };
30
+ }
31
+ }
@@ -22,6 +22,8 @@ export interface XmlElement extends XmlName {
22
22
  readonly namespaces: ReadonlyMap<string, string>;
23
23
  text: string;
24
24
  readonly declaration?: string;
25
+ readonly prolog?: readonly XmlContent[];
26
+ readonly epilog?: readonly XmlContent[];
25
27
  }
26
28
  export interface XmlLimits {
27
29
  readonly expectedEncoding?: "UTF-8" | "UTF-16" | "UTF-16LE" | "UTF-16BE";
@@ -32,6 +34,7 @@ export interface XmlLimits {
32
34
  readonly maxAttributesPerElement?: number;
33
35
  readonly maxNamespaces?: number;
34
36
  readonly maxContentNodes?: number;
37
+ readonly maxTextLength?: number;
35
38
  readonly onElement?: (element: XmlName, parent: XmlName | undefined, depth: number) => void;
36
39
  }
37
40
  export declare class XmlLimitError extends SyntaxError {
@@ -137,7 +137,7 @@ function* validDeclaration(content, expectedEncoding) {
137
137
  return offset - start;
138
138
  };
139
139
  const field = function* (name) {
140
- if (content.slice(offset, offset + name.length).toLowerCase() !== name)
140
+ if (content.slice(offset, offset + name.length) !== name)
141
141
  return undefined;
142
142
  offset += name.length;
143
143
  yield name.length;
@@ -163,21 +163,22 @@ function* validDeclaration(content, expectedEncoding) {
163
163
  if (!(yield* whitespace()) || (yield* field("version")) !== "1.0")
164
164
  return false;
165
165
  let spacing = yield* whitespace();
166
- if (content.slice(offset, offset + 8).toLowerCase() === "encoding") {
166
+ if (content.slice(offset, offset + 8) === "encoding") {
167
167
  if (!spacing)
168
168
  return false;
169
169
  const encoding = yield* field("encoding");
170
170
  if (encoding === undefined || encoding.length > 8 || !["utf-8", "utf-16", "utf-16le", "utf-16be"].includes(encoding.toLowerCase()))
171
171
  return false;
172
- if (expectedEncoding !== undefined && encoding.toLowerCase() !== expectedEncoding.toLowerCase())
172
+ if (expectedEncoding !== undefined && encoding.toLowerCase() !== expectedEncoding.toLowerCase()
173
+ && !(encoding.toLowerCase() === "utf-16" && (expectedEncoding === "UTF-16LE" || expectedEncoding === "UTF-16BE")))
173
174
  return false;
174
175
  spacing = yield* whitespace();
175
176
  }
176
- if (content.slice(offset, offset + 10).toLowerCase() === "standalone") {
177
+ if (content.slice(offset, offset + 10) === "standalone") {
177
178
  if (!spacing)
178
179
  return false;
179
180
  const standalone = yield* field("standalone");
180
- if (standalone === undefined || standalone.length > 3 || !["yes", "no"].includes(standalone.toLowerCase()))
181
+ if (standalone === undefined || standalone.length > 3 || !["yes", "no"].includes(standalone))
181
182
  return false;
182
183
  yield* whitespace();
183
184
  }
@@ -198,6 +199,15 @@ export function* parseXmlSteps(input, limits = {}) {
198
199
  if (!Number.isSafeInteger(limit) || limit < 1)
199
200
  throw new RangeError("XML limits must be positive integers");
200
201
  }
202
+ const maxTextLength = limits.maxTextLength ?? Number.MAX_SAFE_INTEGER;
203
+ if (!Number.isSafeInteger(maxTextLength) || maxTextLength < 1)
204
+ throw new RangeError("XML limits must be positive integers");
205
+ let textLength = 0;
206
+ const admitText = (text) => {
207
+ if (text.length > maxTextLength - textLength)
208
+ throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
209
+ textLength += text.length;
210
+ };
201
211
  const chunks = [];
202
212
  let chunk = "";
203
213
  for (let index = input.charCodeAt(0) === 0xfeff ? 1 : 0; index < input.length; index++) {
@@ -226,6 +236,8 @@ export function* parseXmlSteps(input, limits = {}) {
226
236
  const source = chunks.join("");
227
237
  const stack = [];
228
238
  let root;
239
+ const prolog = [];
240
+ const epilog = [];
229
241
  let declaration;
230
242
  let offset = 0;
231
243
  let nodes = 0;
@@ -261,6 +273,7 @@ export function* parseXmlSteps(input, limits = {}) {
261
273
  return name;
262
274
  };
263
275
  const appendText = function* (text, kind = "text") {
276
+ admitText(text);
264
277
  const parent = stack.at(-1);
265
278
  if (parent) {
266
279
  parent.element.text += text;
@@ -278,6 +291,10 @@ export function* parseXmlSteps(input, limits = {}) {
278
291
  }
279
292
  if (text.length % 512)
280
293
  yield text.length % 512;
294
+ if (retainContent && text.length) {
295
+ admitContent();
296
+ (root ? epilog : prolog).push({ kind, text });
297
+ }
281
298
  }
282
299
  };
283
300
  while (offset < source.length) {
@@ -287,7 +304,7 @@ export function* parseXmlSteps(input, limits = {}) {
287
304
  const text = source.slice(offset, next < 0 ? source.length : next);
288
305
  if ((yield* find(text, "]]>", 0)) >= 0)
289
306
  invalid("CDATA terminator in text");
290
- yield* appendText(yield* entities(text));
307
+ yield* appendText(stack.length ? yield* entities(text) : text);
291
308
  offset += text.length;
292
309
  }
293
310
  else if (source.startsWith("<!--", offset)) {
@@ -296,9 +313,11 @@ export function* parseXmlSteps(input, limits = {}) {
296
313
  invalid("malformed comment");
297
314
  }
298
315
  const parent = stack.at(-1);
299
- if (retainContent && parent) {
316
+ const text = source.slice(offset + 4, end);
317
+ admitText(text);
318
+ if (retainContent) {
300
319
  admitContent();
301
- parent.content.push({ kind: "comment", text: source.slice(offset + 4, end) });
320
+ (parent?.content ?? (root ? epilog : prolog)).push({ kind: "comment", text });
302
321
  }
303
322
  offset = end + 3;
304
323
  }
@@ -331,17 +350,19 @@ export function* parseXmlSteps(input, limits = {}) {
331
350
  invalid("invalid processing instruction");
332
351
  if (!(target.length === 3 && target.toLowerCase() === "xml")) {
333
352
  const parent = stack.at(-1);
334
- if (retainContent && parent) {
335
- let start = 0;
336
- while (start < content.length && " \t\n\r".includes(content[start])) {
337
- start++;
338
- if (start % 512 === 0)
339
- yield 512;
340
- }
341
- if (start % 512)
342
- yield start % 512;
353
+ let start = 0;
354
+ while (start < content.length && " \t\n\r".includes(content[start])) {
355
+ start++;
356
+ if (start % 512 === 0)
357
+ yield 512;
358
+ }
359
+ if (start % 512)
360
+ yield start % 512;
361
+ const text = content.slice(start);
362
+ admitText(text);
363
+ if (retainContent) {
343
364
  admitContent();
344
- parent.content.push({ kind: "processing-instruction", target, text: content.slice(start) });
365
+ (parent?.content ?? (root ? epilog : prolog)).push({ kind: "processing-instruction", target, text });
345
366
  }
346
367
  }
347
368
  offset = end + 2;
@@ -399,6 +420,7 @@ export function* parseXmlSteps(input, limits = {}) {
399
420
  if (raw.length % 512)
400
421
  yield raw.length % 512;
401
422
  const value = yield* entities(normalized);
423
+ admitText(value);
402
424
  attributes.set(attribute, value);
403
425
  offset = end + 1;
404
426
  if (attribute === "xmlns" || attribute.startsWith("xmlns:")) {
@@ -452,7 +474,7 @@ export function* parseXmlSteps(input, limits = {}) {
452
474
  yield 1;
453
475
  }
454
476
  const content = retainContent ? [] : undefined;
455
- const element = { kind: "element", name, namespace, localName, children: [], text: "", content: content ?? emptyContent, attributes: retainContent ? retainedAttributes : emptyAttributes, namespaces: retainContent ? namespaces : emptyNamespaces, ...(root === undefined && declaration !== undefined ? { declaration } : {}) };
477
+ const element = { kind: "element", name, namespace, localName, children: [], text: "", content: content ?? emptyContent, attributes: retainContent ? retainedAttributes : emptyAttributes, namespaces: retainContent ? namespaces : emptyNamespaces, ...(root === undefined && retainContent ? { prolog, epilog, ...(declaration === undefined ? {} : { declaration }) } : {}) };
456
478
  const parent = stack.at(-1);
457
479
  if (parent) {
458
480
  parent.element.children.push(element);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-fs",
3
- "version": "0.1.630",
3
+ "version": "0.1.631",
4
4
  "description": "Composable filesystem with a portable core and explicit Node adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,6 +27,10 @@
27
27
  "browser": "./dist/safe-fs/core.js",
28
28
  "import": "./dist/safe-fs/contracts/index.js"
29
29
  },
30
+ "./xml": {
31
+ "types": "./dist/safe-fs/xml.d.ts",
32
+ "import": "./dist/safe-fs/xml.js"
33
+ },
30
34
  "./core": {
31
35
  "types": "./dist/safe-fs/core.d.ts",
32
36
  "import": "./dist/safe-fs/core.js"