@poe-platform/safe-fs 0.1.740 → 0.1.741
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 +5 -0
- package/dist/safe-fs/fs/scoped.js +11 -0
- package/dist/safe-fs/xml.js +16 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,6 +113,11 @@ call `staging.cleanup.close()` in `finally`. Memory and its supported wrappers
|
|
|
113
113
|
retain only the owned staging entries; replacement entries remain protected.
|
|
114
114
|
See the [staging contract](src/contracts/filesystem.md#atomic-owned-staging).
|
|
115
115
|
|
|
116
|
+
Capture `retainFileSystemCleanup(scoped, callback, { maxOperations })` before
|
|
117
|
+
cancellation to clean up through a bounded view after ordinary scoped operations
|
|
118
|
+
close. Cleanup preserves its owning scope and limits across separately bundled
|
|
119
|
+
command runtimes, including restrictions added by filesystem wrappers.
|
|
120
|
+
|
|
116
121
|
| Backend or wrapper | Use it for |
|
|
117
122
|
| --- | --- |
|
|
118
123
|
| `createMemoryFileSystem()` | Isolated, nonpersistent storage with links, permissions, timestamps, and streams; each path resolution admits at most 65,536 cumulative UTF-16 code units across the input and followed symlink targets, rejecting excess with `ENAMETOOLONG` before component allocation |
|
|
@@ -8,6 +8,7 @@ import { openRetainedResizeFile, retainedResizeCapabilities, ownedMutationCapabi
|
|
|
8
8
|
import { createStagingCleanup, snapshotStagingCreation } from "./staging-cleanup.js";
|
|
9
9
|
import { inspectStagingBindings, runStagingGuard, snapshotDirectoryAncestry, snapshotStagingResolution } from "./staging-ancestry.js";
|
|
10
10
|
const originals = new WeakMap();
|
|
11
|
+
const retainedCleanupSymbol = Symbol.for("safe-fs.retainedCleanup");
|
|
11
12
|
const retargeters = new WeakMap();
|
|
12
13
|
export function retargetScopedFileSystem(scoped, charge, signal, cleanupCharge = charge, maxPathComponents) {
|
|
13
14
|
const fn = retargeters.get(scoped);
|
|
@@ -233,6 +234,8 @@ export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = char
|
|
|
233
234
|
return Reflect.set(original, property, value, original);
|
|
234
235
|
},
|
|
235
236
|
get(_target, property) {
|
|
237
|
+
if (property === retainedCleanupSymbol)
|
|
238
|
+
return cleanupBinding;
|
|
236
239
|
if (property === "capabilities") {
|
|
237
240
|
const rawCaps = original.capabilities;
|
|
238
241
|
if (cachedComputedCaps !== undefined &&
|
|
@@ -510,6 +513,7 @@ export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = char
|
|
|
510
513
|
return scoped;
|
|
511
514
|
},
|
|
512
515
|
});
|
|
516
|
+
const cleanupBinding = Object.freeze({ filesystem: view, retain: retainFileSystemCleanup });
|
|
513
517
|
const originalRecord = { filesystem: original, signal, cleanupCharge, creationMask, maxPathComponents };
|
|
514
518
|
originals.set(view, originalRecord);
|
|
515
519
|
retargeters.set(view, (nextCharge, nextSignal, nextCleanupCharge, nextMaxPathComponents) => {
|
|
@@ -538,6 +542,13 @@ export function retainFileSystemCleanup(filesystem, cleanupCallback, options) {
|
|
|
538
542
|
if (typeof cleanupCallback !== "function")
|
|
539
543
|
throw new TypeError("cleanup callback must be a function");
|
|
540
544
|
const scope = originals.get(filesystem);
|
|
545
|
+
if (!scope) {
|
|
546
|
+
// Bundled commands can hold another module copy. Delegate only for this
|
|
547
|
+
// exact view, never through a wrapper that happens to forward its symbols.
|
|
548
|
+
const binding = Reflect.get(filesystem, retainedCleanupSymbol);
|
|
549
|
+
if (binding?.filesystem === filesystem && typeof binding.retain === "function" && binding.retain !== retainFileSystemCleanup)
|
|
550
|
+
return binding.retain(filesystem, cleanupCallback, options);
|
|
551
|
+
}
|
|
541
552
|
scope?.signal.throwIfAborted();
|
|
542
553
|
const backing = scope?.filesystem ?? filesystem;
|
|
543
554
|
const pending = new Set();
|
package/dist/safe-fs/xml.js
CHANGED
|
@@ -273,6 +273,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
273
273
|
let nodes = 0;
|
|
274
274
|
let attributeCount = 0;
|
|
275
275
|
let contentNodes = 0;
|
|
276
|
+
let previousEmpty;
|
|
276
277
|
const admitContent = () => {
|
|
277
278
|
if (++contentNodes > maxContentNodes)
|
|
278
279
|
throw new XmlLimitError("maxContentNodes", "XML content node limit exceeded");
|
|
@@ -440,17 +441,23 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
440
441
|
}
|
|
441
442
|
else {
|
|
442
443
|
offset++;
|
|
443
|
-
const
|
|
444
|
+
const repeated = previousEmpty && source.startsWith(previousEmpty.suffix, offset) ? previousEmpty : undefined;
|
|
445
|
+
const [name, prefix, localName, wName] = repeated
|
|
446
|
+
? [repeated.name, repeated.prefix, repeated.localName, repeated.name.length * 2]
|
|
447
|
+
: scanName();
|
|
448
|
+
if (repeated)
|
|
449
|
+
offset += name.length;
|
|
450
|
+
// Preserve scan/validation work while reusing the admitted spelling.
|
|
444
451
|
pendingWork += wName;
|
|
445
452
|
while (pendingWork >= 512) {
|
|
446
453
|
yield 512;
|
|
447
454
|
pendingWork -= 512;
|
|
448
455
|
}
|
|
449
|
-
|
|
456
|
+
let attributes;
|
|
450
457
|
const attrMeta = [];
|
|
451
458
|
let namespaces = stack.at(-1)?.namespaces ?? new Map([["xml", xmlNamespace]]);
|
|
452
459
|
let ownsNamespaces = stack.length === 0;
|
|
453
|
-
while (
|
|
460
|
+
while (!repeated) {
|
|
454
461
|
const ws = skipWhitespace();
|
|
455
462
|
pendingWork += ws;
|
|
456
463
|
while (pendingWork >= 512) {
|
|
@@ -468,6 +475,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
468
475
|
yield 512;
|
|
469
476
|
pendingWork -= 512;
|
|
470
477
|
}
|
|
478
|
+
attributes ??= new Map();
|
|
471
479
|
if (attributes.has(attribute))
|
|
472
480
|
invalid("duplicate attribute");
|
|
473
481
|
if (++attributeCount > maxAttributes)
|
|
@@ -560,7 +568,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
560
568
|
limits.onElement?.({ name, namespace, localName }, stack.at(-1)?.element, stack.length + 1);
|
|
561
569
|
admitContent();
|
|
562
570
|
const retainedAttributes = [];
|
|
563
|
-
if (retainContent) {
|
|
571
|
+
if (retainContent && attributes) {
|
|
564
572
|
for (let i = 0; i < attrMeta.length; i++) {
|
|
565
573
|
const [attribute, attrPrefix, attrLocal] = attrMeta[i];
|
|
566
574
|
const value = attributes.get(attribute);
|
|
@@ -590,6 +598,10 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
590
598
|
offset++;
|
|
591
599
|
if (source[offset++] !== ">")
|
|
592
600
|
invalid("unterminated start tag");
|
|
601
|
+
// Only cache compact, attribute-free spellings. Namespace resolution and
|
|
602
|
+
// every admission counter still run for each distinct physical element.
|
|
603
|
+
if (empty && !attributes && name.length <= 512 && source.slice(offset - name.length - 3, offset) === `<${name}/>`)
|
|
604
|
+
previousEmpty = { suffix: name + "/>", name, prefix, localName };
|
|
593
605
|
if (!empty)
|
|
594
606
|
stack.push({ element, content, name, namespaces });
|
|
595
607
|
}
|