@poe-platform/safe-fs 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 +290 -0
- package/dist/safe-fs/bridge/confinement.d.ts +3 -0
- package/dist/safe-fs/bridge/confinement.js +171 -0
- package/dist/safe-fs/bridge/filesystem.d.ts +88 -0
- package/dist/safe-fs/bridge/filesystem.js +411 -0
- package/dist/safe-fs/bridge/index.d.ts +6 -0
- package/dist/safe-fs/bridge/index.js +18 -0
- package/dist/safe-fs/bridge/stats.d.ts +4 -0
- package/dist/safe-fs/bridge/stats.js +37 -0
- package/dist/safe-fs/bridge/types.d.ts +75 -0
- package/dist/safe-fs/bridge/types.js +1 -0
- package/dist/safe-fs/bridge/values.d.ts +6 -0
- package/dist/safe-fs/bridge/values.js +65 -0
- package/dist/safe-fs/config/memory.d.ts +3 -0
- package/dist/safe-fs/config/memory.js +9 -0
- package/dist/safe-fs/config/real.d.ts +5 -0
- package/dist/safe-fs/config/real.js +16 -0
- package/dist/safe-fs/config.d.ts +15 -0
- package/dist/safe-fs/config.js +46 -0
- package/dist/safe-fs/config.node.d.ts +2 -0
- package/dist/safe-fs/config.node.js +22 -0
- package/dist/safe-fs/contracts/abort.d.ts +5 -0
- package/dist/safe-fs/contracts/abort.js +34 -0
- package/dist/safe-fs/contracts/cleanup.d.ts +1 -0
- package/dist/safe-fs/contracts/cleanup.js +9 -0
- package/dist/safe-fs/contracts/errors.d.ts +50 -0
- package/dist/safe-fs/contracts/errors.js +77 -0
- package/dist/safe-fs/contracts/filesystem.d.ts +96 -0
- package/dist/safe-fs/contracts/filesystem.js +1 -0
- package/dist/safe-fs/contracts/index.d.ts +4 -0
- package/dist/safe-fs/contracts/index.js +4 -0
- package/dist/safe-fs/contracts/io.d.ts +8 -0
- package/dist/safe-fs/contracts/io.js +92 -0
- package/dist/safe-fs/contracts/path.d.ts +8 -0
- package/dist/safe-fs/contracts/path.js +8 -0
- package/dist/safe-fs/contracts/portable-path.d.ts +13 -0
- package/dist/safe-fs/contracts/portable-path.js +57 -0
- package/dist/safe-fs/contracts/virtual-path.d.ts +7 -0
- package/dist/safe-fs/contracts/virtual-path.js +66 -0
- package/dist/safe-fs/core.d.ts +12 -0
- package/dist/safe-fs/core.js +12 -0
- package/dist/safe-fs/fs/memory/index.d.ts +58 -0
- package/dist/safe-fs/fs/memory/index.js +532 -0
- package/dist/safe-fs/fs/mount/comparison.d.ts +19 -0
- package/dist/safe-fs/fs/mount/comparison.js +118 -0
- package/dist/safe-fs/fs/mount/identity.d.ts +2 -0
- package/dist/safe-fs/fs/mount/identity.js +12 -0
- package/dist/safe-fs/fs/mount/index.d.ts +44 -0
- package/dist/safe-fs/fs/mount/index.js +546 -0
- package/dist/safe-fs/fs/overlay/index.d.ts +73 -0
- package/dist/safe-fs/fs/overlay/index.js +927 -0
- package/dist/safe-fs/fs/readonly/index.d.ts +31 -0
- package/dist/safe-fs/fs/readonly/index.js +128 -0
- package/dist/safe-fs/fs/real/allocation.d.ts +1 -0
- package/dist/safe-fs/fs/real/allocation.js +8 -0
- package/dist/safe-fs/fs/real/index.d.ts +76 -0
- package/dist/safe-fs/fs/real/index.js +543 -0
- package/dist/safe-fs/fs/s3/authority.d.ts +7 -0
- package/dist/safe-fs/fs/s3/authority.js +31 -0
- package/dist/safe-fs/fs/s3/filesystem.d.ts +93 -0
- package/dist/safe-fs/fs/s3/filesystem.js +1024 -0
- package/dist/safe-fs/fs/s3/http/index.d.ts +2 -0
- package/dist/safe-fs/fs/s3/http/index.js +1 -0
- package/dist/safe-fs/fs/s3/http/request.d.ts +16 -0
- package/dist/safe-fs/fs/s3/http/request.js +196 -0
- package/dist/safe-fs/fs/s3/http/signature.d.ts +20 -0
- package/dist/safe-fs/fs/s3/http/signature.js +74 -0
- package/dist/safe-fs/fs/s3/http/transport.d.ts +3 -0
- package/dist/safe-fs/fs/s3/http/transport.js +455 -0
- package/dist/safe-fs/fs/s3/http/types.d.ts +30 -0
- package/dist/safe-fs/fs/s3/http/types.js +1 -0
- package/dist/safe-fs/fs/s3/http/xml.d.ts +11 -0
- package/dist/safe-fs/fs/s3/http/xml.js +188 -0
- package/dist/safe-fs/fs/s3/index.d.ts +6 -0
- package/dist/safe-fs/fs/s3/index.js +3 -0
- package/dist/safe-fs/fs/s3/mock.d.ts +48 -0
- package/dist/safe-fs/fs/s3/mock.js +233 -0
- package/dist/safe-fs/fs/s3/registry.d.ts +10 -0
- package/dist/safe-fs/fs/s3/registry.js +71 -0
- package/dist/safe-fs/fs/s3/transport.d.ts +103 -0
- package/dist/safe-fs/fs/s3/transport.js +28 -0
- package/dist/safe-fs/fs/webdav/index.d.ts +2 -0
- package/dist/safe-fs/fs/webdav/index.js +1 -0
- package/dist/safe-fs/fs/webdav/resource-id.d.ts +18 -0
- package/dist/safe-fs/fs/webdav/resource-id.js +109 -0
- package/dist/safe-fs/fs/webdav/webdav.d.ts +101 -0
- package/dist/safe-fs/fs/webdav/webdav.js +1226 -0
- package/dist/safe-fs/fs/webdav/xml.d.ts +15 -0
- package/dist/safe-fs/fs/webdav/xml.js +250 -0
- package/dist/safe-fs/index.d.ts +14 -0
- package/dist/safe-fs/index.js +12 -0
- package/dist/safe-fs/node/filesystem.d.ts +11 -0
- package/dist/safe-fs/node/filesystem.js +28 -0
- package/dist/safe-fs/node/index.d.ts +2 -0
- package/dist/safe-fs/node/index.js +1 -0
- package/dist/safe-fs/node/stats.d.ts +1 -0
- package/dist/safe-fs/node/stats.js +1 -0
- package/dist/safe-fs/node/values.d.ts +1 -0
- package/dist/safe-fs/node/values.js +1 -0
- package/dist/safe-fs/node-host.d.ts +1 -0
- package/dist/safe-fs/node-host.js +1 -0
- package/dist/safe-fs/node-unavailable.d.ts +1 -0
- package/dist/safe-fs/node-unavailable.js +1 -0
- package/dist/safe-fs/platform/browser.d.ts +12 -0
- package/dist/safe-fs/platform/browser.js +39 -0
- package/dist/safe-fs/platform/node.d.ts +13 -0
- package/dist/safe-fs/platform/node.js +26 -0
- package/package.json +108 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface XmlElement {
|
|
2
|
+
readonly namespace: string;
|
|
3
|
+
readonly localName: string;
|
|
4
|
+
readonly children: XmlElement[];
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
export interface XmlLimits {
|
|
8
|
+
readonly maxDepth?: number;
|
|
9
|
+
readonly maxNodes?: number;
|
|
10
|
+
readonly maxAttributes?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function parseXml(input: string, limits?: XmlLimits): XmlElement;
|
|
13
|
+
export declare function davChildren(element: XmlElement, localName: string): XmlElement[];
|
|
14
|
+
export declare function davChild(element: XmlElement, localName: string): XmlElement | undefined;
|
|
15
|
+
export declare function scalar(element: XmlElement): string;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
const xmlNamespace = "http://www.w3.org/XML/1998/namespace";
|
|
2
|
+
const xmlnsNamespace = "http://www.w3.org/2000/xmlns/";
|
|
3
|
+
function invalid(message) {
|
|
4
|
+
throw new SyntaxError(`Invalid WebDAV XML: ${message}`);
|
|
5
|
+
}
|
|
6
|
+
function validCharacter(point) {
|
|
7
|
+
return point === 9 || point === 10 || point === 13
|
|
8
|
+
|| (point >= 0x20 && point <= 0xd7ff)
|
|
9
|
+
|| (point >= 0xe000 && point <= 0xfffd)
|
|
10
|
+
|| (point >= 0x10000 && point <= 0x10ffff);
|
|
11
|
+
}
|
|
12
|
+
function nameStart(point) {
|
|
13
|
+
return point === 95 || (point >= 65 && point <= 90) || (point >= 97 && point <= 122)
|
|
14
|
+
|| (point >= 0xc0 && point <= 0xd6) || (point >= 0xd8 && point <= 0xf6)
|
|
15
|
+
|| (point >= 0xf8 && point <= 0x2ff) || (point >= 0x370 && point <= 0x37d)
|
|
16
|
+
|| (point >= 0x37f && point <= 0x1fff) || (point >= 0x200c && point <= 0x200d)
|
|
17
|
+
|| (point >= 0x2070 && point <= 0x218f) || (point >= 0x2c00 && point <= 0x2fef)
|
|
18
|
+
|| (point >= 0x3001 && point <= 0xd7ff) || (point >= 0xf900 && point <= 0xfdcf)
|
|
19
|
+
|| (point >= 0xfdf0 && point <= 0xfffd) || (point >= 0x10000 && point <= 0xeffff);
|
|
20
|
+
}
|
|
21
|
+
function namePart(point) {
|
|
22
|
+
return nameStart(point) || point === 45 || point === 46 || point === 0xb7
|
|
23
|
+
|| (point >= 48 && point <= 57) || (point >= 0x300 && point <= 0x36f)
|
|
24
|
+
|| (point >= 0x203f && point <= 0x2040);
|
|
25
|
+
}
|
|
26
|
+
function qualifiedName(name) {
|
|
27
|
+
const parts = name.split(":");
|
|
28
|
+
if (parts.length > 2 || parts.some((part) => {
|
|
29
|
+
const points = [...part].map((character) => character.codePointAt(0));
|
|
30
|
+
return points.length === 0 || !nameStart(points[0]) || points.slice(1).some((point) => !namePart(point));
|
|
31
|
+
}))
|
|
32
|
+
invalid("invalid qualified name");
|
|
33
|
+
return parts.length === 1 ? ["", parts[0]] : [parts[0], parts[1]];
|
|
34
|
+
}
|
|
35
|
+
function entities(text) {
|
|
36
|
+
let result = "";
|
|
37
|
+
let offset = 0;
|
|
38
|
+
while (offset < text.length) {
|
|
39
|
+
const start = text.indexOf("&", offset);
|
|
40
|
+
if (start < 0)
|
|
41
|
+
return result + text.slice(offset);
|
|
42
|
+
result += text.slice(offset, start);
|
|
43
|
+
const end = text.indexOf(";", start + 1);
|
|
44
|
+
if (end < 0)
|
|
45
|
+
invalid("unterminated entity");
|
|
46
|
+
const entity = text.slice(start + 1, end);
|
|
47
|
+
const predefined = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
48
|
+
if (Object.hasOwn(predefined, entity))
|
|
49
|
+
result += predefined[entity];
|
|
50
|
+
else {
|
|
51
|
+
const hexadecimal = entity.startsWith("#x");
|
|
52
|
+
const digits = entity.slice(hexadecimal ? 2 : 1);
|
|
53
|
+
if (!entity.startsWith("#") || !(hexadecimal ? /^[0-9a-fA-F]+$/ : /^[0-9]+$/).test(digits)) {
|
|
54
|
+
invalid("undeclared entity");
|
|
55
|
+
}
|
|
56
|
+
const point = Number.parseInt(digits, hexadecimal ? 16 : 10);
|
|
57
|
+
if (!validCharacter(point))
|
|
58
|
+
invalid("invalid character reference");
|
|
59
|
+
result += String.fromCodePoint(point);
|
|
60
|
+
}
|
|
61
|
+
offset = end + 1;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
export function parseXml(input, limits = {}) {
|
|
66
|
+
const maxDepth = limits.maxDepth ?? 64;
|
|
67
|
+
const maxNodes = limits.maxNodes ?? 100_000;
|
|
68
|
+
const maxAttributes = limits.maxAttributes ?? 10_000;
|
|
69
|
+
for (const limit of [maxDepth, maxNodes, maxAttributes]) {
|
|
70
|
+
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
71
|
+
throw new RangeError("XML limits must be positive integers");
|
|
72
|
+
}
|
|
73
|
+
for (const character of input) {
|
|
74
|
+
if (!validCharacter(character.codePointAt(0)))
|
|
75
|
+
invalid("invalid character");
|
|
76
|
+
}
|
|
77
|
+
const source = input.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
|
|
78
|
+
const stack = [];
|
|
79
|
+
let root;
|
|
80
|
+
let offset = 0;
|
|
81
|
+
let nodes = 0;
|
|
82
|
+
let attributeCount = 0;
|
|
83
|
+
const whitespace = () => {
|
|
84
|
+
while (offset < source.length && " \t\n\r".includes(source[offset]))
|
|
85
|
+
offset++;
|
|
86
|
+
};
|
|
87
|
+
const readName = () => {
|
|
88
|
+
const start = offset;
|
|
89
|
+
while (offset < source.length && !" \t\r\n/=>?".includes(source[offset]))
|
|
90
|
+
offset++;
|
|
91
|
+
const name = source.slice(start, offset);
|
|
92
|
+
qualifiedName(name);
|
|
93
|
+
return name;
|
|
94
|
+
};
|
|
95
|
+
const appendText = (text) => {
|
|
96
|
+
const parent = stack.at(-1);
|
|
97
|
+
if (parent)
|
|
98
|
+
parent.element.text += text;
|
|
99
|
+
else if (!/^[ \t\r\n]*$/.test(text))
|
|
100
|
+
invalid("text outside the root");
|
|
101
|
+
};
|
|
102
|
+
while (offset < source.length) {
|
|
103
|
+
if (source[offset] !== "<") {
|
|
104
|
+
const next = source.indexOf("<", offset);
|
|
105
|
+
const text = source.slice(offset, next < 0 ? source.length : next);
|
|
106
|
+
if (text.includes("]]>"))
|
|
107
|
+
invalid("CDATA terminator in text");
|
|
108
|
+
appendText(entities(text));
|
|
109
|
+
offset += text.length;
|
|
110
|
+
}
|
|
111
|
+
else if (source.startsWith("<!--", offset)) {
|
|
112
|
+
const end = source.indexOf("-->", offset + 4);
|
|
113
|
+
if (end < 0 || source.slice(offset + 4, end).includes("--") || source.slice(offset + 4, end).endsWith("-")) {
|
|
114
|
+
invalid("malformed comment");
|
|
115
|
+
}
|
|
116
|
+
offset = end + 3;
|
|
117
|
+
}
|
|
118
|
+
else if (source.startsWith("<![CDATA[", offset)) {
|
|
119
|
+
if (!stack.length)
|
|
120
|
+
invalid("CDATA outside root");
|
|
121
|
+
const end = source.indexOf("]]>", offset + 9);
|
|
122
|
+
if (end < 0)
|
|
123
|
+
invalid("unterminated CDATA");
|
|
124
|
+
appendText(source.slice(offset + 9, end));
|
|
125
|
+
offset = end + 3;
|
|
126
|
+
}
|
|
127
|
+
else if (source.startsWith("<?", offset)) {
|
|
128
|
+
const start = offset;
|
|
129
|
+
offset += 2;
|
|
130
|
+
const target = readName();
|
|
131
|
+
const end = source.indexOf("?>", offset);
|
|
132
|
+
if (end < 0)
|
|
133
|
+
invalid("unterminated processing instruction");
|
|
134
|
+
const content = source.slice(offset, end);
|
|
135
|
+
if (target.toLowerCase() === "xml") {
|
|
136
|
+
if (start !== 0 || target !== "xml"
|
|
137
|
+
|| !/^\s+version\s*=\s*(['"])1\.0\1(?:\s+encoding\s*=\s*(['"])(?:UTF-8|UTF-16|UTF-16LE|UTF-16BE)\2)?(?:\s+standalone\s*=\s*(['"])(?:yes|no)\3)?\s*$/i.test(content)) {
|
|
138
|
+
invalid("unsupported XML declaration");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (content && !" \t\n\r".includes(content[0]))
|
|
142
|
+
invalid("invalid processing instruction");
|
|
143
|
+
offset = end + 2;
|
|
144
|
+
}
|
|
145
|
+
else if (source.startsWith("<!", offset)) {
|
|
146
|
+
invalid("DTD and entity declarations are forbidden");
|
|
147
|
+
}
|
|
148
|
+
else if (source.startsWith("</", offset)) {
|
|
149
|
+
offset += 2;
|
|
150
|
+
const name = readName();
|
|
151
|
+
whitespace();
|
|
152
|
+
if (source[offset++] !== ">" || stack.pop()?.name !== name)
|
|
153
|
+
invalid("mismatched closing tag");
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
offset++;
|
|
157
|
+
const name = readName();
|
|
158
|
+
const attributes = new Map();
|
|
159
|
+
const namespaces = new Map(stack.at(-1)?.namespaces ?? [["xml", xmlNamespace]]);
|
|
160
|
+
while (true) {
|
|
161
|
+
const beforeSpace = offset;
|
|
162
|
+
whitespace();
|
|
163
|
+
if (source[offset] === "/" || source[offset] === ">")
|
|
164
|
+
break;
|
|
165
|
+
if (offset === beforeSpace)
|
|
166
|
+
invalid("attributes require whitespace");
|
|
167
|
+
const attribute = readName();
|
|
168
|
+
if (attributes.has(attribute))
|
|
169
|
+
invalid("duplicate attribute");
|
|
170
|
+
if (++attributeCount > maxAttributes || attributes.size >= 128)
|
|
171
|
+
invalid("XML attribute limit exceeded");
|
|
172
|
+
whitespace();
|
|
173
|
+
if (source[offset++] !== "=")
|
|
174
|
+
invalid("missing attribute equals");
|
|
175
|
+
whitespace();
|
|
176
|
+
const quote = source[offset++];
|
|
177
|
+
if (quote !== '"' && quote !== "'")
|
|
178
|
+
invalid("unquoted attribute");
|
|
179
|
+
const end = source.indexOf(quote, offset);
|
|
180
|
+
if (end < 0)
|
|
181
|
+
invalid("unterminated attribute");
|
|
182
|
+
const raw = source.slice(offset, end);
|
|
183
|
+
if (raw.includes("<"))
|
|
184
|
+
invalid("less-than in attribute");
|
|
185
|
+
const value = entities(raw.replace(/[\t\n\r]/g, " "));
|
|
186
|
+
attributes.set(attribute, value);
|
|
187
|
+
offset = end + 1;
|
|
188
|
+
if (attribute === "xmlns" || attribute.startsWith("xmlns:")) {
|
|
189
|
+
const prefix = attribute === "xmlns" ? "" : attribute.slice(6);
|
|
190
|
+
if (prefix === "xmlns" || value === xmlnsNamespace
|
|
191
|
+
|| (prefix === "xml") !== (value === xmlNamespace)
|
|
192
|
+
|| (prefix !== "" && value === ""))
|
|
193
|
+
invalid("invalid namespace binding");
|
|
194
|
+
namespaces.set(prefix, value);
|
|
195
|
+
if (namespaces.size > 256)
|
|
196
|
+
invalid("XML namespace scope limit exceeded");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const expanded = new Set();
|
|
200
|
+
for (const attribute of attributes.keys()) {
|
|
201
|
+
if (attribute === "xmlns" || attribute.startsWith("xmlns:"))
|
|
202
|
+
continue;
|
|
203
|
+
const [prefix, localName] = qualifiedName(attribute);
|
|
204
|
+
if (prefix && !namespaces.has(prefix))
|
|
205
|
+
invalid("unbound attribute prefix");
|
|
206
|
+
const key = JSON.stringify([prefix ? namespaces.get(prefix) : "", localName]);
|
|
207
|
+
if (expanded.has(key))
|
|
208
|
+
invalid("duplicate expanded attribute");
|
|
209
|
+
expanded.add(key);
|
|
210
|
+
}
|
|
211
|
+
const [prefix, localName] = qualifiedName(name);
|
|
212
|
+
if (prefix === "xmlns" || (prefix && !namespaces.has(prefix)))
|
|
213
|
+
invalid("unbound element prefix");
|
|
214
|
+
const element = { namespace: namespaces.get(prefix) ?? "", localName, children: [], text: "" };
|
|
215
|
+
if (++nodes > maxNodes || stack.length + 1 > maxDepth)
|
|
216
|
+
invalid("XML resource limit exceeded");
|
|
217
|
+
const parent = stack.at(-1);
|
|
218
|
+
if (parent)
|
|
219
|
+
parent.element.children.push(element);
|
|
220
|
+
else if (root)
|
|
221
|
+
invalid("multiple root elements");
|
|
222
|
+
else
|
|
223
|
+
root = element;
|
|
224
|
+
const empty = source[offset] === "/";
|
|
225
|
+
if (empty)
|
|
226
|
+
offset++;
|
|
227
|
+
if (source[offset++] !== ">")
|
|
228
|
+
invalid("unterminated start tag");
|
|
229
|
+
if (!empty)
|
|
230
|
+
stack.push({ element, name, namespaces });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (stack.length || !root)
|
|
234
|
+
invalid("incomplete document");
|
|
235
|
+
return root;
|
|
236
|
+
}
|
|
237
|
+
export function davChildren(element, localName) {
|
|
238
|
+
return element.children.filter((child) => child.namespace === "DAV:" && child.localName === localName);
|
|
239
|
+
}
|
|
240
|
+
export function davChild(element, localName) {
|
|
241
|
+
const children = davChildren(element, localName);
|
|
242
|
+
if (children.length > 1)
|
|
243
|
+
invalid(`duplicate DAV:${localName}`);
|
|
244
|
+
return children[0];
|
|
245
|
+
}
|
|
246
|
+
export function scalar(element) {
|
|
247
|
+
if (element.children.length)
|
|
248
|
+
invalid("expected text-only element");
|
|
249
|
+
return element.text.trim();
|
|
250
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./contracts/index.js";
|
|
2
|
+
export * from "./fs/memory/index.js";
|
|
3
|
+
export * from "./fs/real/index.js";
|
|
4
|
+
export * from "./fs/s3/index.js";
|
|
5
|
+
export type { S3StreamGetInput, S3StreamGetOutput, S3StreamPutInput } from "./fs/s3/transport.js";
|
|
6
|
+
export * from "./fs/s3/http/index.js";
|
|
7
|
+
export * from "./fs/webdav/index.js";
|
|
8
|
+
export * from "./fs/readonly/index.js";
|
|
9
|
+
export * from "./fs/mount/index.js";
|
|
10
|
+
export * from "./fs/overlay/index.js";
|
|
11
|
+
export * from "./node/index.js";
|
|
12
|
+
export { createFileSystem, readConfigRecord, validateFileSystemConfig } from "./config.js";
|
|
13
|
+
export type { FileSystemConfig, FileSystemAdapterDescriptor, FileSystemAdapterRegistry } from "./config.js";
|
|
14
|
+
export { createNodeFileSystemAdapterRegistry } from "./config.node.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./contracts/index.js";
|
|
2
|
+
export * from "./fs/memory/index.js";
|
|
3
|
+
export * from "./fs/real/index.js";
|
|
4
|
+
export * from "./fs/s3/index.js";
|
|
5
|
+
export * from "./fs/s3/http/index.js";
|
|
6
|
+
export * from "./fs/webdav/index.js";
|
|
7
|
+
export * from "./fs/readonly/index.js";
|
|
8
|
+
export * from "./fs/mount/index.js";
|
|
9
|
+
export * from "./fs/overlay/index.js";
|
|
10
|
+
export * from "./node/index.js";
|
|
11
|
+
export { createFileSystem, readConfigRecord, validateFileSystemConfig } from "./config.js";
|
|
12
|
+
export { createNodeFileSystemAdapterRegistry } from "./config.node.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type * as FsPromises from "node:fs/promises";
|
|
2
|
+
import type { FileSystem, FsOptions } from "../contracts/filesystem.js";
|
|
3
|
+
export type NodeFsImplementation = Pick<typeof FsPromises, "access" | "appendFile" | "chmod" | "copyFile" | "cp" | "link" | "lstat" | "mkdir" | "mkdtemp" | "readFile" | "readdir" | "readlink" | "realpath" | "rename" | "rm" | "rmdir" | "stat" | "symlink" | "truncate" | "utimes" | "writeFile">;
|
|
4
|
+
export interface NodeFsBridgeOptions {
|
|
5
|
+
readonly cwd?: string;
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export interface NodeFsBridgeFileSystem extends FileSystem {
|
|
9
|
+
rmdir?(path: string, options?: FsOptions): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createNodeFsBridge(fs: FileSystem, options?: NodeFsBridgeOptions): NodeFsImplementation;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { posix } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { FileSystemBridge } from "../bridge/filesystem.js";
|
|
6
|
+
export function createNodeFsBridge(fs, options = {}) {
|
|
7
|
+
return new FileSystemBridge(fs, options, {
|
|
8
|
+
codec: {
|
|
9
|
+
isEncoding: Buffer.isEncoding,
|
|
10
|
+
encode(text, encoding) {
|
|
11
|
+
if (!Buffer.isEncoding(encoding))
|
|
12
|
+
throw new TypeError("Invalid encoding");
|
|
13
|
+
return Buffer.from(text, encoding);
|
|
14
|
+
},
|
|
15
|
+
decode(bytes, encoding) {
|
|
16
|
+
if (!Buffer.isEncoding(encoding))
|
|
17
|
+
throw new TypeError("Invalid encoding");
|
|
18
|
+
return Buffer.from(bytes).toString(encoding);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
copyBytes: Buffer.from,
|
|
22
|
+
pathValue(value) {
|
|
23
|
+
return value instanceof URL ? fileURLToPath(value) : Buffer.isBuffer(value) ? value.toString("utf8") : value;
|
|
24
|
+
},
|
|
25
|
+
randomSuffix() { return randomBytes(6).toString("hex").slice(0, 6); },
|
|
26
|
+
paths: posix
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createNodeFsBridge } from "./filesystem.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { bridgeDirent as nodeDirent, bridgeStats as nodeStats } from "../bridge/stats.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { bridgeDirent as nodeDirent, bridgeStats as nodeStats } from "../bridge/stats.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { abortError, booleanValue, checkSignal, onlyKeys, record, withSignal } from "../bridge/values.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { abortError, booleanValue, checkSignal, onlyKeys, record, withSignal } from "../bridge/values.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FsOptions } from "../contracts/filesystem.js";
|
|
2
|
+
export type PlatformErrno = number | undefined;
|
|
3
|
+
export type PlatformComparisonCallback<Callback> = Callback & never;
|
|
4
|
+
export declare const platform: Readonly<{
|
|
5
|
+
errno(_code: string): PlatformErrno;
|
|
6
|
+
callbackAuthorities: false;
|
|
7
|
+
randomUUID(): string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const comparisonContext: Readonly<{
|
|
10
|
+
active(options: FsOptions): boolean;
|
|
11
|
+
run<Result>(options: FsOptions, action: (nested: FsOptions) => Promise<Result>): Promise<Result>;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FsError } from "../contracts/errors.js";
|
|
2
|
+
export const platform = Object.freeze({
|
|
3
|
+
errno(_code) {
|
|
4
|
+
return undefined;
|
|
5
|
+
},
|
|
6
|
+
callbackAuthorities: false,
|
|
7
|
+
randomUUID() {
|
|
8
|
+
const crypto = globalThis.crypto;
|
|
9
|
+
if (typeof crypto?.randomUUID === "function")
|
|
10
|
+
return crypto.randomUUID();
|
|
11
|
+
if (typeof crypto?.getRandomValues !== "function") {
|
|
12
|
+
throw new FsError("ENOTSUP", { message: "secure crypto is required for overlay staging" });
|
|
13
|
+
}
|
|
14
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
15
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
16
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
17
|
+
const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, "0")).join("");
|
|
18
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
const operationKey = Symbol("filesystem-comparison");
|
|
22
|
+
const contexts = new WeakSet();
|
|
23
|
+
export const comparisonContext = Object.freeze({
|
|
24
|
+
active(options) {
|
|
25
|
+
const context = options[operationKey];
|
|
26
|
+
return context !== undefined && contexts.has(context);
|
|
27
|
+
},
|
|
28
|
+
async run(options, action) {
|
|
29
|
+
const context = Object.freeze({});
|
|
30
|
+
contexts.add(context);
|
|
31
|
+
const nested = { ...options, [operationKey]: context };
|
|
32
|
+
try {
|
|
33
|
+
return await action(nested);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
contexts.delete(context);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { FsOptions } from "../contracts/filesystem.js";
|
|
3
|
+
export type PlatformErrno = number;
|
|
4
|
+
export type PlatformComparisonCallback<Callback> = Callback;
|
|
5
|
+
export declare const platform: Readonly<{
|
|
6
|
+
errno(code: string): PlatformErrno;
|
|
7
|
+
callbackAuthorities: true;
|
|
8
|
+
randomUUID: typeof randomUUID;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const comparisonContext: Readonly<{
|
|
11
|
+
active(_options: FsOptions): boolean;
|
|
12
|
+
run<Result>(options: FsOptions, action: (nested: FsOptions) => Promise<Result>): Promise<Result>;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import * as util from "node:util";
|
|
4
|
+
import { constants } from "node:os";
|
|
5
|
+
const systemErrnos = typeof util.getSystemErrorMap === "function"
|
|
6
|
+
? new Map([...util.getSystemErrorMap()].map(([errno, [name]]) => [name, errno]))
|
|
7
|
+
: new Map(Object.entries(constants.errno).map(([name, errno]) => [name, -Math.abs(errno)]));
|
|
8
|
+
const negotiating = new AsyncLocalStorage();
|
|
9
|
+
export const platform = Object.freeze({
|
|
10
|
+
errno(code) {
|
|
11
|
+
const errno = systemErrnos.get(code === "EOPNOTSUPP" ? "ENOTSUP" : code);
|
|
12
|
+
if (errno === undefined)
|
|
13
|
+
throw new TypeError(`Unsupported platform errno code: ${code}`);
|
|
14
|
+
return errno;
|
|
15
|
+
},
|
|
16
|
+
callbackAuthorities: true,
|
|
17
|
+
randomUUID
|
|
18
|
+
});
|
|
19
|
+
export const comparisonContext = Object.freeze({
|
|
20
|
+
active(_options) {
|
|
21
|
+
return negotiating.getStore() === true;
|
|
22
|
+
},
|
|
23
|
+
run(options, action) {
|
|
24
|
+
return negotiating.run(true, () => action(options));
|
|
25
|
+
}
|
|
26
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@poe-platform/safe-fs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Composable filesystem with a portable core and explicit Node adapters",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18.18"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": {
|
|
16
|
+
"browser": "./dist/safe-fs/core.d.ts",
|
|
17
|
+
"default": "./dist/safe-fs/index.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"browser": "./dist/safe-fs/core.js",
|
|
20
|
+
"import": "./dist/safe-fs/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./contracts": {
|
|
23
|
+
"types": {
|
|
24
|
+
"browser": "./dist/safe-fs/core.d.ts",
|
|
25
|
+
"default": "./dist/safe-fs/contracts/index.d.ts"
|
|
26
|
+
},
|
|
27
|
+
"browser": "./dist/safe-fs/core.js",
|
|
28
|
+
"import": "./dist/safe-fs/contracts/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./core": {
|
|
31
|
+
"types": "./dist/safe-fs/core.d.ts",
|
|
32
|
+
"import": "./dist/safe-fs/core.js"
|
|
33
|
+
},
|
|
34
|
+
"./node": {
|
|
35
|
+
"types": {
|
|
36
|
+
"browser": "./dist/safe-fs/node-unavailable.d.ts",
|
|
37
|
+
"default": "./dist/safe-fs/node-host.d.ts"
|
|
38
|
+
},
|
|
39
|
+
"browser": null,
|
|
40
|
+
"import": "./dist/safe-fs/node-host.js"
|
|
41
|
+
},
|
|
42
|
+
"./fs/memory": {
|
|
43
|
+
"types": "./dist/safe-fs/fs/memory/index.d.ts",
|
|
44
|
+
"import": "./dist/safe-fs/fs/memory/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./fs/real": {
|
|
47
|
+
"types": {
|
|
48
|
+
"browser": "./dist/safe-fs/node-unavailable.d.ts",
|
|
49
|
+
"default": "./dist/safe-fs/fs/real/index.d.ts"
|
|
50
|
+
},
|
|
51
|
+
"browser": null,
|
|
52
|
+
"import": "./dist/safe-fs/fs/real/index.js"
|
|
53
|
+
},
|
|
54
|
+
"./fs/s3/http": {
|
|
55
|
+
"types": {
|
|
56
|
+
"browser": "./dist/safe-fs/node-unavailable.d.ts",
|
|
57
|
+
"default": "./dist/safe-fs/fs/s3/http/index.d.ts"
|
|
58
|
+
},
|
|
59
|
+
"browser": null,
|
|
60
|
+
"import": "./dist/safe-fs/fs/s3/http/index.js"
|
|
61
|
+
},
|
|
62
|
+
"./fs/s3": {
|
|
63
|
+
"types": {
|
|
64
|
+
"browser": "./dist/safe-fs/node-unavailable.d.ts",
|
|
65
|
+
"default": "./dist/safe-fs/fs/s3/index.d.ts"
|
|
66
|
+
},
|
|
67
|
+
"browser": null,
|
|
68
|
+
"import": "./dist/safe-fs/fs/s3/index.js"
|
|
69
|
+
},
|
|
70
|
+
"./fs/webdav": {
|
|
71
|
+
"types": "./dist/safe-fs/fs/webdav/index.d.ts",
|
|
72
|
+
"import": "./dist/safe-fs/fs/webdav/index.js"
|
|
73
|
+
},
|
|
74
|
+
"./fs/readonly": {
|
|
75
|
+
"types": "./dist/safe-fs/fs/readonly/index.d.ts",
|
|
76
|
+
"import": "./dist/safe-fs/fs/readonly/index.js"
|
|
77
|
+
},
|
|
78
|
+
"./fs/mount": {
|
|
79
|
+
"types": "./dist/safe-fs/fs/mount/index.d.ts",
|
|
80
|
+
"import": "./dist/safe-fs/fs/mount/index.js"
|
|
81
|
+
},
|
|
82
|
+
"./fs/overlay": {
|
|
83
|
+
"types": "./dist/safe-fs/fs/overlay/index.d.ts",
|
|
84
|
+
"import": "./dist/safe-fs/fs/overlay/index.js"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"repository": {
|
|
88
|
+
"type": "git",
|
|
89
|
+
"url": "git+https://github.com/poe-platform/poe-code.git",
|
|
90
|
+
"directory": "packages/safe-fs"
|
|
91
|
+
},
|
|
92
|
+
"publishConfig": {
|
|
93
|
+
"access": "public"
|
|
94
|
+
},
|
|
95
|
+
"dependencies": {
|
|
96
|
+
"@types/node": "^25.2.2"
|
|
97
|
+
},
|
|
98
|
+
"imports": {
|
|
99
|
+
"#safe-fs-platform": {
|
|
100
|
+
"types": {
|
|
101
|
+
"browser": "./dist/safe-fs/platform/browser.d.ts",
|
|
102
|
+
"default": "./dist/safe-fs/platform/node.d.ts"
|
|
103
|
+
},
|
|
104
|
+
"browser": "./dist/safe-fs/platform/browser.js",
|
|
105
|
+
"default": "./dist/safe-fs/platform/node.js"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|