@poe-platform/safe-fs 0.1.807 → 0.1.809

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 (41) hide show
  1. package/README.md +28 -1
  2. package/dist/safe-fs/contracts/filesystem.d.ts +20 -1
  3. package/dist/safe-fs/core.d.ts +3 -1
  4. package/dist/safe-fs/core.js +3 -1
  5. package/dist/safe-fs/fs/capabilities.d.ts +1 -1
  6. package/dist/safe-fs/fs/capabilities.js +9 -5
  7. package/dist/safe-fs/fs/devices/index.d.ts +3 -2
  8. package/dist/safe-fs/fs/devices/index.js +53 -9
  9. package/dist/safe-fs/fs/memory/atomic-view.d.ts +1 -0
  10. package/dist/safe-fs/fs/memory/index.d.ts +6 -3
  11. package/dist/safe-fs/fs/memory/index.js +101 -13
  12. package/dist/safe-fs/fs/mount/index.d.ts +4 -2
  13. package/dist/safe-fs/fs/mount/index.js +169 -10
  14. package/dist/safe-fs/fs/overlay/index.d.ts +3 -0
  15. package/dist/safe-fs/fs/overlay/index.js +177 -59
  16. package/dist/safe-fs/fs/overlay/memory-publication.d.ts +1 -0
  17. package/dist/safe-fs/fs/overlay/memory-publication.js +25 -0
  18. package/dist/safe-fs/fs/quota/index.js +9 -4
  19. package/dist/safe-fs/fs/read-file-stream.d.ts +7 -0
  20. package/dist/safe-fs/fs/read-file-stream.js +103 -0
  21. package/dist/safe-fs/fs/readonly/index.d.ts +1 -0
  22. package/dist/safe-fs/fs/readonly/index.js +10 -0
  23. package/dist/safe-fs/fs/real/index.d.ts +1 -0
  24. package/dist/safe-fs/fs/real/index.js +29 -0
  25. package/dist/safe-fs/fs/s3/filesystem.d.ts +2 -1
  26. package/dist/safe-fs/fs/s3/filesystem.js +55 -16
  27. package/dist/safe-fs/fs/scoped.js +13 -5
  28. package/dist/safe-fs/index.d.ts +1 -0
  29. package/dist/safe-fs/index.js +1 -0
  30. package/dist/safe-fs/runtime-core.d.ts +1 -0
  31. package/dist/safe-fs/runtime-core.js +1 -0
  32. package/dist/safe-fs/storage.d.ts +57 -0
  33. package/dist/safe-fs/storage.js +364 -0
  34. package/dist/safe-fs/testing/object-io-metrics.js +1 -0
  35. package/dist/xml-ast/errors.d.ts +4 -0
  36. package/dist/xml-ast/errors.js +7 -0
  37. package/dist/xml-ast/index.d.ts +2 -4
  38. package/dist/xml-ast/index.js +3 -7
  39. package/dist/xml-ast/stream.d.ts +31 -0
  40. package/dist/xml-ast/stream.js +179 -0
  41. package/package.json +10 -6
@@ -48,6 +48,7 @@ export function measureObjectIoStore(store, metrics) {
48
48
  const publish = store.publish;
49
49
  const createStaging = store.createStaging;
50
50
  return {
51
+ ...(store.noFollow === undefined ? {} : { noFollow: store.noFollow }),
51
52
  async acquire(...args) {
52
53
  const head = await metrics.measure("store", "acquire", () => store.acquire(...args));
53
54
  return head && version(head);
@@ -0,0 +1,4 @@
1
+ export declare class XmlLimitError extends SyntaxError {
2
+ readonly limit: string;
3
+ constructor(limit: string, message: string);
4
+ }
@@ -0,0 +1,7 @@
1
+ export class XmlLimitError extends SyntaxError {
2
+ limit;
3
+ constructor(limit, message) {
4
+ super(message);
5
+ this.limit = limit;
6
+ }
7
+ }
@@ -1,3 +1,4 @@
1
+ export { XmlLimitError } from "./errors.js";
1
2
  export interface XmlName {
2
3
  readonly name: string;
3
4
  readonly namespace: string;
@@ -39,9 +40,6 @@ export interface XmlLimits {
39
40
  readonly maxTextLength?: number;
40
41
  readonly onElement?: (element: XmlName, parent: XmlName | undefined, depth: number) => void;
41
42
  }
42
- export declare class XmlLimitError extends SyntaxError {
43
- readonly limit: string;
44
- constructor(limit: string, message: string);
45
- }
46
43
  export declare function parseXmlSteps(input: string, limits?: XmlLimits): Generator<number, XmlElement, void>;
47
44
  export declare function parseXml(input: string, limits?: XmlLimits): XmlElement;
45
+ export { parseXmlStream, type XmlStreamLimits, type XmlStreamEvent } from "./stream.js";
@@ -1,10 +1,5 @@
1
- export class XmlLimitError extends SyntaxError {
2
- limit;
3
- constructor(limit, message) {
4
- super(message);
5
- this.limit = limit;
6
- }
7
- }
1
+ import { XmlLimitError } from "./errors.js";
2
+ export { XmlLimitError } from "./errors.js";
8
3
  function* find(source, needle, start) {
9
4
  const found = source.indexOf(needle, start);
10
5
  const end = found < 0 ? source.length : found;
@@ -633,3 +628,4 @@ export function parseXml(input, limits = {}) {
633
628
  result = parser.next();
634
629
  return result.value;
635
630
  }
631
+ export { parseXmlStream } from "./stream.js";
@@ -0,0 +1,31 @@
1
+ import type { XmlContent, XmlElement, XmlLimits, XmlName } from "./index.js";
2
+ export type XmlStreamEvent = {
3
+ readonly type: "open" | "close";
4
+ readonly element: XmlElement;
5
+ readonly parent: XmlElement | undefined;
6
+ } | {
7
+ readonly type: "content";
8
+ readonly content: Exclude<XmlContent, XmlElement>;
9
+ readonly parent: XmlElement | undefined;
10
+ };
11
+ export interface XmlStreamLimits extends XmlLimits {
12
+ /** Validate the full document but return only its root name, without a retained tree. */
13
+ readonly retainTree?: boolean;
14
+ /** Ordered node events, awaited after each parser window. Requires retainTree:
15
+ * false; elements carry metadata without child/content arrays. Individual XML
16
+ * tokens remain resident until the underlying parser completes them. */
17
+ readonly events?: (event: XmlStreamEvent) => void | Promise<void>;
18
+ /** Detach selected non-root subtrees and await their consumer before parsing
19
+ * the next 512-unit window. By default nested matches belong to the selected ancestor. */
20
+ readonly streamElements?: {
21
+ /** Also detach preceding sibling content when selecting a subtree, before
22
+ * the parser advances. The final trailing content remains in the parent. */
23
+ readonly captureBefore?: boolean;
24
+ /** Detach nested matches too, consuming descendants before their ancestors. */
25
+ readonly includeNested?: boolean;
26
+ readonly matches: (element: XmlName, parent: XmlName | undefined, depth: number) => boolean;
27
+ readonly consume: (element: XmlElement, parent: XmlElement, before?: readonly XmlContent[]) => void | Promise<void>;
28
+ };
29
+ }
30
+ /** Incremental source consumption. Individual XML tokens and optional trees remain resident. */
31
+ export declare function parseXmlStream(source: AsyncIterable<string> | Iterable<string>, limits?: XmlStreamLimits, checkpoint?: (units: number) => void | Promise<void>): Promise<XmlElement>;
@@ -0,0 +1,179 @@
1
+ import { SaxesParser } from "saxes/saxes.js";
2
+ import { XmlLimitError } from "./errors.js";
3
+ /** Incremental source consumption. Individual XML tokens and optional trees remain resident. */
4
+ export async function parseXmlStream(source, limits = {}, checkpoint) {
5
+ if (limits.recover)
6
+ throw new TypeError("XML stream recovery is unsupported; use parseXmlSteps");
7
+ for (const key of ["maxDepth", "maxNodes", "maxAttributes", "maxAttributesPerElement", "maxNamespaces", "maxContentNodes", "maxTextLength"]) {
8
+ const value = limits[key];
9
+ if (value !== undefined && value !== Infinity && (!Number.isSafeInteger(value) || value < 1))
10
+ throw new RangeError("XML limits must be positive integers");
11
+ }
12
+ const counters = { maxNodes: 0, maxAttributes: 0, maxContentNodes: 0, maxTextLength: 0 };
13
+ function bound(key, value) {
14
+ if (value > (limits[key] ?? Infinity))
15
+ throw new XmlLimitError(key, "XML resource limit exceeded");
16
+ }
17
+ function charge(key, value = 1) { counters[key] += value; bound(key, counters[key]); }
18
+ const retainContent = limits.retainContent !== false, tree = limits.retainTree !== false, retain = tree && retainContent;
19
+ const events = limits.events;
20
+ if (events && tree)
21
+ throw new TypeError("XML events require retainTree: false");
22
+ const describe = retain || !!events;
23
+ const queued = [];
24
+ const streaming = limits.streamElements;
25
+ if (streaming && !retain)
26
+ throw new TypeError("XML subtree streaming requires retained content and tree mode");
27
+ let selectedCount = 0;
28
+ const completed = [];
29
+ const stack = [];
30
+ const prolog = [], epilog = [];
31
+ const emptyNamespaces = new Map();
32
+ let root, declaration;
33
+ const parser = new SaxesParser({ xmlns: true, defaultXMLVersion: "1.0", forceXMLVersion: true });
34
+ parser.on("error", error => { throw new SyntaxError(`Invalid XML: ${error.message}`); });
35
+ parser.on("doctype", () => { throw new SyntaxError("Invalid XML: DTD and entity declarations are forbidden"); });
36
+ let prefix = "", capturing = describe;
37
+ parser.on("xmldecl", value => {
38
+ const encoding = value.encoding?.toUpperCase();
39
+ if (value.version !== "1.0" || encoding !== undefined &&
40
+ (!["UTF-8", "UTF-16", "UTF-16LE", "UTF-16BE"].includes(encoding) ||
41
+ limits.expectedEncoding !== undefined && encoding !== limits.expectedEncoding &&
42
+ !(encoding === "UTF-16" && ["UTF-16LE", "UTF-16BE"].includes(limits.expectedEncoding))))
43
+ throw new SyntaxError("Invalid XML: unsupported XML declaration");
44
+ if (describe)
45
+ declaration = prefix.slice(0, prefix.indexOf("?>") + 2);
46
+ prefix = "";
47
+ capturing = false;
48
+ });
49
+ parser.on("opentag", tag => {
50
+ charge("maxNodes");
51
+ bound("maxDepth", stack.length + 1);
52
+ const attrs = Object.values(tag.attributes);
53
+ charge("maxAttributes", attrs.length);
54
+ bound("maxAttributesPerElement", attrs.length);
55
+ const parent = stack.at(-1);
56
+ let namespaces = parent?.namespaces ?? new Map([["xml", "http://www.w3.org/XML/1998/namespace"]]);
57
+ const bindings = Object.entries(tag.ns);
58
+ if (bindings.length) {
59
+ namespaces = new Map(namespaces);
60
+ for (const [name, uri] of bindings)
61
+ namespaces.set(name, uri);
62
+ bound("maxNamespaces", namespaces.size);
63
+ }
64
+ for (const attr of attrs)
65
+ charge("maxTextLength", attr.value.length);
66
+ const name = { name: tag.name, namespace: tag.uri, localName: tag.local };
67
+ limits.onElement?.(name, parent?.element, stack.length + 1);
68
+ const selected = (!selectedCount || streaming?.includeNested === true) && !!streaming?.matches(name, parent?.element, stack.length + 1);
69
+ if (selected && !parent)
70
+ throw new TypeError("XML subtree streaming cannot select the root");
71
+ let before = [];
72
+ if (selected) {
73
+ selectedCount++;
74
+ if (streaming?.captureBefore) {
75
+ before = parent.element.content.splice(0);
76
+ parent.element.children.length = 0;
77
+ parent.element.text = '';
78
+ }
79
+ }
80
+ charge("maxContentNodes", 1 + (retainContent || events ? attrs.length : 0));
81
+ const element = { kind: "element", ...name, children: [], content: [], text: "",
82
+ attributes: describe ? attrs.map(attr => ({ name: attr.name, localName: attr.local, namespace: attr.uri, value: attr.value })) : [],
83
+ namespaces: describe ? namespaces : emptyNamespaces,
84
+ ...(!root && describe ? { prolog, epilog, ...(declaration === undefined ? {} : { declaration }) } : {}) };
85
+ if (parent && tree && !selected) {
86
+ parent.element.children.push(element);
87
+ if (retain)
88
+ parent.element.content.push(element);
89
+ }
90
+ else if (!parent)
91
+ root = element;
92
+ stack.push({ element, namespaces, selected, before });
93
+ if (events)
94
+ queued.push({ type: "open", element, parent: parent?.element });
95
+ });
96
+ parser.on("closetag", () => {
97
+ const frame = stack.pop();
98
+ if (events)
99
+ queued.push({ type: "close", element: frame.element, parent: stack.at(-1)?.element });
100
+ if (frame.selected) {
101
+ completed.push({ element: frame.element, parent: stack.at(-1).element, before: frame.before });
102
+ selectedCount--;
103
+ }
104
+ });
105
+ async function drain() {
106
+ for (const entry of completed) {
107
+ await checkpoint?.(0);
108
+ await streaming.consume(entry.element, entry.parent, entry.before);
109
+ }
110
+ completed.length = 0;
111
+ for (const event of queued) {
112
+ await checkpoint?.(0);
113
+ await events(event);
114
+ }
115
+ queued.length = 0;
116
+ }
117
+ function append(content) {
118
+ charge("maxTextLength", content.text.length);
119
+ charge("maxContentNodes");
120
+ const parent = stack.at(-1)?.element;
121
+ if (events)
122
+ queued.push({ type: "content", content, parent });
123
+ if (tree && parent && (content.kind === "text" || content.kind === "cdata"))
124
+ parent.text += content.text;
125
+ if (retain)
126
+ (parent ? parent.content : root ? epilog : prolog).push(content);
127
+ }
128
+ parser.on("text", text => { if (text.length)
129
+ append({ kind: "text", text }); });
130
+ parser.on("cdata", text => append({ kind: "cdata", text }));
131
+ parser.on("comment", text => append({ kind: "comment", text }));
132
+ parser.on("processinginstruction", value => append({ kind: "processing-instruction", target: value.target, text: value.body }));
133
+ let cr = false, first = true;
134
+ function write(text) {
135
+ if (!text)
136
+ return;
137
+ if (first) {
138
+ first = false;
139
+ if (text.charCodeAt(0) === 0xfeff) {
140
+ parser.write(text[0]);
141
+ text = text.slice(1);
142
+ }
143
+ }
144
+ if (cr) {
145
+ if (text.startsWith("\n"))
146
+ text = text.slice(1);
147
+ cr = false;
148
+ }
149
+ cr = text.endsWith("\r");
150
+ text = text.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
151
+ if (capturing) {
152
+ prefix += text;
153
+ if (prefix.length >= 6 && !(prefix.startsWith("<?xml") && " \t\n".includes(prefix[5]))) {
154
+ prefix = "";
155
+ capturing = false;
156
+ }
157
+ }
158
+ parser.write(text);
159
+ }
160
+ await checkpoint?.(0);
161
+ for await (const text of source) {
162
+ if (typeof text !== "string")
163
+ throw new TypeError("XML producer must yield strings");
164
+ for (let i = 0; i < text.length; i += 512) {
165
+ await checkpoint?.(0);
166
+ write(text.slice(i, i + 512));
167
+ if (completed.length || queued.length)
168
+ await drain();
169
+ await checkpoint?.(Math.min(512, text.length - i));
170
+ }
171
+ }
172
+ await checkpoint?.(0);
173
+ parser.close();
174
+ if (completed.length || queued.length)
175
+ await drain();
176
+ if (!root)
177
+ throw new SyntaxError("Invalid XML: incomplete document");
178
+ return root;
179
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-fs",
3
- "version": "0.1.807",
3
+ "version": "0.1.809",
4
4
  "description": "Composable filesystem with a portable core and explicit Node adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -88,11 +88,7 @@
88
88
  "import": "./dist/safe-fs/fs/real/index.js"
89
89
  },
90
90
  "./fs/s3/http": {
91
- "types": {
92
- "browser": "./dist/safe-fs/node-unavailable.d.ts",
93
- "default": "./dist/safe-fs/fs/s3/http/index.d.ts"
94
- },
95
- "browser": null,
91
+ "types": "./dist/safe-fs/fs/s3/http/index.d.ts",
96
92
  "import": "./dist/safe-fs/fs/s3/http/index.js"
97
93
  },
98
94
  "./fs/s3": {
@@ -134,6 +130,13 @@
134
130
  "./bridge": {
135
131
  "types": "./dist/safe-fs/bridge/index.d.ts",
136
132
  "import": "./dist/safe-fs/bridge/index.js"
133
+ },
134
+ "./storage": {
135
+ "types": "./dist/safe-fs/storage.d.ts",
136
+ "workerd": "./dist/safe-fs/storage.js",
137
+ "browser": "./dist/safe-fs/storage.js",
138
+ "worker": "./dist/safe-fs/storage.js",
139
+ "import": "./dist/safe-fs/storage.js"
137
140
  }
138
141
  },
139
142
  "repository": {
@@ -145,6 +148,7 @@
145
148
  "access": "public"
146
149
  },
147
150
  "dependencies": {
151
+ "saxes": "^6.0.0",
148
152
  "@types/node": "^25.2.2",
149
153
  "@noble/hashes": "2.4.0"
150
154
  },