@poe-platform/safe-fs 0.1.814 → 0.1.816
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/README.md +2 -0
- package/dist/safe-fs/fs/mount/index.js +10 -1
- package/dist/xml-ast/index.d.ts +2 -0
- package/dist/xml-ast/index.js +33 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -317,6 +317,8 @@ Every raw filesystem operation accepts an optional `signal`. Additional fields a
|
|
|
317
317
|
|
|
318
318
|
`access` takes a separate mode bitmask from `ACCESS_MODES`. `chmod` takes a mode, `utimes` takes millisecond timestamps, and `truncate` takes a byte length (default 0). For conditional chmod, check `capabilitiesFor(path, { conditionalChmod: true })` (or `capabilities`) and require `conditionalChmod: true`; supply `parent`, `expected`, and complete root-to-parent `ancestors` together. An optional mutation-free `commitGuard` must return literal `true` synchronously. Memory validates at its metadata commit; mount and supported Memory overlays preserve wrapper ancestry. Real uses its existing externally isolated host-tree boundary and does not prevent races with other processes. Backend limits still apply. Node-shaped bridge methods translate their own options rather than accepting these raw option objects; see the [bridge signatures](src/bridge/filesystem.ts).
|
|
319
319
|
|
|
320
|
+
Cross-mount streamed copies start reading the source before opening the destination for writing. An initial read failure leaves the destination unchanged; later read or write failures can leave partial destination bytes.
|
|
321
|
+
|
|
320
322
|
Overlay copy-up, copy, stream replacement/append and resize transfer retained ranges in 64 KiB chunks through the injected upper's stream writer. Resize streams the retained prefix and bounded zero chunks. Stream writes stage input before publication; cancellation or transfer failure leaves the destination unchanged and cleans staging. Stock Memory overlays transfer staged storage into existing upper inodes, preserving hardlinks without an additional whole-file buffer. `readFile` and `writeFile` remain buffering convenience APIs; use `readStream`/`writeStream` for large payloads. `maxBufferBytes` retains its existing optional total-byte admission semantics, not a working-memory guarantee.
|
|
321
323
|
|
|
322
324
|
A streaming overlay needs an upper with `atomicRename`, `streamingWrite`, retained reads (`retainedRead` plus `openReadFile`), exclusive create, directory mutation, permissions and timestamps. Both layers must provide retained, bounded range reads with stable file and directory identities. For example, inject these host-authorized adapters with `createOverlayFileSystem({ upper: externalSpool, lower: retainedSource })`, then use `overlay.writeStream(path, source)`. The upper must await each consumed chunk, keep transport queues bounded, and preserve ownership until publication. Supply bounded chunks at the input boundary; the overlay cannot undo a payload-wide allocation already made by its caller. Whole-file-only uppers explicitly reject transfer operations with `ENOTSUP`; they do not silently collect streams.
|
|
@@ -1308,7 +1308,16 @@ export class MountFileSystem {
|
|
|
1308
1308
|
const source = readBytes(reader.readStream(origin.local, options), options.signal);
|
|
1309
1309
|
let failed = false;
|
|
1310
1310
|
try {
|
|
1311
|
-
|
|
1311
|
+
// Admit lazy reader startup before the writer can truncate its target.
|
|
1312
|
+
// Later transfer failures retain the backend's partial-write semantics.
|
|
1313
|
+
const first = await source.next();
|
|
1314
|
+
await writer.writeStream(target.local, (async function* () {
|
|
1315
|
+
let item = first;
|
|
1316
|
+
while (!item.done) {
|
|
1317
|
+
yield item.value;
|
|
1318
|
+
item = await source.next();
|
|
1319
|
+
}
|
|
1320
|
+
})(), writeOptions);
|
|
1312
1321
|
}
|
|
1313
1322
|
catch (error) {
|
|
1314
1323
|
failed = true;
|
package/dist/xml-ast/index.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export interface XmlStepLimits extends XmlLimits {
|
|
|
54
54
|
readonly events?: (event: XmlStreamEvent) => void;
|
|
55
55
|
}
|
|
56
56
|
export interface XmlSourceLimits extends XmlStepLimits {
|
|
57
|
+
/** Keep normalized declaration fields instead of the complete source spelling. */
|
|
58
|
+
readonly compactDeclaration?: boolean;
|
|
57
59
|
/** Emit content bodies in bounded fragments, marking continuations of the same logical
|
|
58
60
|
* node. Requires retainTree: false; consumers must preserve fragment identity. */
|
|
59
61
|
readonly fragmentContent?: boolean;
|
package/dist/xml-ast/index.js
CHANGED
|
@@ -176,11 +176,10 @@ function* entities(text, recover) {
|
|
|
176
176
|
}
|
|
177
177
|
return result;
|
|
178
178
|
}
|
|
179
|
-
function*
|
|
180
|
-
let offset = 0;
|
|
179
|
+
function* readDeclaration(source, offset, end, expectedEncoding) {
|
|
181
180
|
const whitespace = function* () {
|
|
182
181
|
const start = offset;
|
|
183
|
-
while (offset <
|
|
182
|
+
while (offset < end && " \t\n\r".includes(yield* source.slice(offset, offset + 1))) {
|
|
184
183
|
offset++;
|
|
185
184
|
if ((offset - start) % 512 === 0)
|
|
186
185
|
yield 512;
|
|
@@ -189,53 +188,58 @@ function* validDeclaration(content, expectedEncoding) {
|
|
|
189
188
|
yield (offset - start) % 512;
|
|
190
189
|
return offset - start;
|
|
191
190
|
};
|
|
192
|
-
const field = function* (name) {
|
|
193
|
-
if (
|
|
191
|
+
const field = function* (name, maxLength) {
|
|
192
|
+
if ((yield* source.slice(offset, Math.min(end, offset + name.length))) !== name)
|
|
194
193
|
return undefined;
|
|
195
194
|
offset += name.length;
|
|
196
195
|
yield name.length;
|
|
197
196
|
yield* whitespace();
|
|
198
|
-
if (
|
|
197
|
+
if (offset >= end || (yield* source.charCodeAt(offset++)) !== 61)
|
|
199
198
|
return undefined;
|
|
200
199
|
yield* whitespace();
|
|
201
|
-
|
|
200
|
+
if (offset >= end)
|
|
201
|
+
return undefined;
|
|
202
|
+
const quote = yield* source.slice(offset++, offset);
|
|
202
203
|
if (quote !== "'" && quote !== '"')
|
|
203
204
|
return undefined;
|
|
204
205
|
const start = offset;
|
|
205
|
-
while (offset <
|
|
206
|
+
while (offset < end && (yield* source.slice(offset, offset + 1)) !== quote) {
|
|
206
207
|
offset++;
|
|
207
208
|
if ((offset - start) % 512 === 0)
|
|
208
209
|
yield 512;
|
|
209
210
|
}
|
|
210
211
|
if ((offset - start) % 512)
|
|
211
212
|
yield (offset - start) % 512;
|
|
212
|
-
if (offset >=
|
|
213
|
+
if (offset >= end || offset - start > maxLength)
|
|
213
214
|
return undefined;
|
|
214
|
-
return
|
|
215
|
+
return yield* source.slice(start, offset++);
|
|
215
216
|
};
|
|
216
|
-
if (!(yield* whitespace()) || (yield* field("version")) !== "1.0")
|
|
217
|
-
return
|
|
217
|
+
if (!(yield* whitespace()) || (yield* field("version", 3)) !== "1.0")
|
|
218
|
+
return undefined;
|
|
219
|
+
let result = '<?xml version="1.0"';
|
|
218
220
|
let spacing = yield* whitespace();
|
|
219
|
-
if (
|
|
221
|
+
if ((yield* source.slice(offset, Math.min(end, offset + 8))) === "encoding") {
|
|
220
222
|
if (!spacing)
|
|
221
|
-
return
|
|
222
|
-
const encoding = yield* field("encoding");
|
|
223
|
-
if (encoding === undefined ||
|
|
224
|
-
return
|
|
223
|
+
return undefined;
|
|
224
|
+
const encoding = yield* field("encoding", 8);
|
|
225
|
+
if (encoding === undefined || !["utf-8", "utf-16", "utf-16le", "utf-16be"].includes(encoding.toLowerCase()))
|
|
226
|
+
return undefined;
|
|
225
227
|
if (expectedEncoding !== undefined && encoding.toLowerCase() !== expectedEncoding.toLowerCase()
|
|
226
228
|
&& !(encoding.toLowerCase() === "utf-16" && (expectedEncoding === "UTF-16LE" || expectedEncoding === "UTF-16BE")))
|
|
227
|
-
return
|
|
229
|
+
return undefined;
|
|
230
|
+
result += ` encoding="${encoding}"`;
|
|
228
231
|
spacing = yield* whitespace();
|
|
229
232
|
}
|
|
230
|
-
if (
|
|
233
|
+
if ((yield* source.slice(offset, Math.min(end, offset + 10))) === "standalone") {
|
|
231
234
|
if (!spacing)
|
|
232
|
-
return
|
|
233
|
-
const standalone = yield* field("standalone");
|
|
234
|
-
if (standalone === undefined ||
|
|
235
|
-
return
|
|
235
|
+
return undefined;
|
|
236
|
+
const standalone = yield* field("standalone", 3);
|
|
237
|
+
if (standalone === undefined || !["yes", "no"].includes(standalone))
|
|
238
|
+
return undefined;
|
|
239
|
+
result += ` standalone="${standalone}"`;
|
|
236
240
|
yield* whitespace();
|
|
237
241
|
}
|
|
238
|
-
return offset ===
|
|
242
|
+
return offset === end ? result + "?>" : undefined;
|
|
239
243
|
}
|
|
240
244
|
function validateLimits(limits) {
|
|
241
245
|
for (const limit of [limits.maxDepth, limits.maxNodes, limits.maxAttributes, limits.maxContentNodes, limits.maxAttributesPerElement, limits.maxNamespaces]) {
|
|
@@ -485,12 +489,13 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
485
489
|
if (end < 0)
|
|
486
490
|
invalid("unterminated processing instruction");
|
|
487
491
|
if (target.length === 3 && target.toLowerCase() === "xml") {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
492
|
+
if (start !== 0 || target !== "xml")
|
|
493
|
+
invalid("unsupported XML declaration");
|
|
494
|
+
const parsed = yield* readDeclaration(source, offset, end, limits.expectedEncoding);
|
|
495
|
+
if (parsed === undefined)
|
|
491
496
|
invalid("unsupported XML declaration");
|
|
492
497
|
if (retainContent)
|
|
493
|
-
declaration = yield* source.slice(start, end + 2);
|
|
498
|
+
declaration = limits.compactDeclaration ? parsed : yield* source.slice(start, end + 2);
|
|
494
499
|
}
|
|
495
500
|
else {
|
|
496
501
|
if (offset < end && !" \t\n\r".includes(yield* source.slice(offset, offset + 1)))
|