toolcraft 0.0.771 → 0.0.773
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/composition.json +13 -3
- package/dist/composition.json +13 -3
- package/node_modules/@poe-code/safe-fs/dist/contracts/filesystem.d.ts +8 -1
- package/node_modules/@poe-code/safe-fs/dist/core.d.ts +1 -1
- package/node_modules/@poe-code/safe-fs/dist/core.js +1 -1
- package/node_modules/@poe-code/safe-fs/dist/fs/capabilities.js +4 -2
- package/node_modules/@poe-code/safe-fs/dist/fs/devices/index.d.ts +2 -2
- package/node_modules/@poe-code/safe-fs/dist/fs/devices/index.js +9 -6
- package/node_modules/@poe-code/safe-fs/dist/fs/memory/index.d.ts +2 -2
- package/node_modules/@poe-code/safe-fs/dist/fs/memory/index.js +4 -3
- package/node_modules/@poe-code/safe-fs/dist/fs/mount/index.d.ts +2 -2
- package/node_modules/@poe-code/safe-fs/dist/fs/mount/index.js +12 -7
- package/node_modules/@poe-code/safe-fs/dist/fs/overlay/index.d.ts +1 -0
- package/node_modules/@poe-code/safe-fs/dist/fs/overlay/index.js +10 -0
- package/node_modules/@poe-code/safe-fs/dist/fs/overlay/memory-publication.js +18 -0
- package/node_modules/@poe-code/safe-fs/dist/fs/scoped.js +1 -1
- package/node_modules/@poe-code/safe-fs/dist/storage.d.ts +1 -1
- package/node_modules/@poe-code/safe-fs/dist/storage.js +21 -3
- package/node_modules/@poe-code/xml-ast/README.md +10 -1
- package/node_modules/@poe-code/xml-ast/dist/errors.d.ts +4 -0
- package/node_modules/@poe-code/xml-ast/dist/errors.js +7 -0
- package/node_modules/@poe-code/xml-ast/dist/index.d.ts +2 -4
- package/node_modules/@poe-code/xml-ast/dist/index.js +3 -7
- package/node_modules/@poe-code/xml-ast/dist/stream.d.ts +16 -0
- package/node_modules/@poe-code/xml-ast/dist/stream.js +163 -0
- package/node_modules/saxes/README.md +323 -0
- package/node_modules/saxes/package.json +71 -0
- package/node_modules/saxes/saxes.d.ts +635 -0
- package/node_modules/saxes/saxes.js +2053 -0
- package/node_modules/saxes/saxes.js.map +1 -0
- package/node_modules/tiny-http-mcp-server/dist/composition.json +11 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/README.md +6 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/register-client.d.ts +9 -3
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/register-client.js +22 -7
- package/node_modules/tiny-http-mcp-server/package.json +4 -4
- package/node_modules/tiny-mcp-client/README.md +11 -2
- package/node_modules/tiny-mcp-client/dist/index.browser.d.ts +17 -4
- package/node_modules/tiny-mcp-client/dist/index.browser.js +92 -40
- package/node_modules/tiny-mcp-client/dist/index.d.ts +17 -4
- package/node_modules/tiny-mcp-client/dist/index.js +114 -47
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/node_modules/xmlchars/LICENSE +18 -0
- package/node_modules/xmlchars/README.md +33 -0
- package/node_modules/xmlchars/package.json +51 -0
- package/node_modules/xmlchars/xml/1.0/ed4.d.ts +31 -0
- package/node_modules/xmlchars/xml/1.0/ed4.js +44 -0
- package/node_modules/xmlchars/xml/1.0/ed4.js.map +1 -0
- package/node_modules/xmlchars/xml/1.0/ed5.d.ts +51 -0
- package/node_modules/xmlchars/xml/1.0/ed5.js +105 -0
- package/node_modules/xmlchars/xml/1.0/ed5.js.map +1 -0
- package/node_modules/xmlchars/xml/1.1/ed2.d.ts +73 -0
- package/node_modules/xmlchars/xml/1.1/ed2.js +145 -0
- package/node_modules/xmlchars/xml/1.1/ed2.js.map +1 -0
- package/node_modules/xmlchars/xmlchars.d.ts +170 -0
- package/node_modules/xmlchars/xmlchars.js +191 -0
- package/node_modules/xmlchars/xmlchars.js.map +1 -0
- package/node_modules/xmlchars/xmlns/1.0/ed3.d.ts +28 -0
- package/node_modules/xmlchars/xmlns/1.0/ed3.js +65 -0
- package/node_modules/xmlchars/xmlns/1.0/ed3.js.map +1 -0
- package/package.json +8 -6
package/composition.json
CHANGED
|
@@ -106,6 +106,11 @@
|
|
|
106
106
|
"version": "0.1.0",
|
|
107
107
|
"license": "MIT"
|
|
108
108
|
},
|
|
109
|
+
{
|
|
110
|
+
"name": "saxes",
|
|
111
|
+
"version": "6.0.0",
|
|
112
|
+
"license": "ISC"
|
|
113
|
+
},
|
|
109
114
|
{
|
|
110
115
|
"name": "sisteransi",
|
|
111
116
|
"version": "1.0.5",
|
|
@@ -118,7 +123,7 @@
|
|
|
118
123
|
},
|
|
119
124
|
{
|
|
120
125
|
"name": "tiny-http-mcp-server",
|
|
121
|
-
"version": "0.1.
|
|
126
|
+
"version": "0.1.129",
|
|
122
127
|
"license": "MIT"
|
|
123
128
|
},
|
|
124
129
|
{
|
|
@@ -133,7 +138,7 @@
|
|
|
133
138
|
},
|
|
134
139
|
{
|
|
135
140
|
"name": "toolcraft",
|
|
136
|
-
"version": "0.0.
|
|
141
|
+
"version": "0.0.773",
|
|
137
142
|
"license": "MIT"
|
|
138
143
|
},
|
|
139
144
|
{
|
|
@@ -143,7 +148,12 @@
|
|
|
143
148
|
},
|
|
144
149
|
{
|
|
145
150
|
"name": "toolcraft-schema",
|
|
146
|
-
"version": "0.0.
|
|
151
|
+
"version": "0.0.773",
|
|
152
|
+
"license": "MIT"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "xmlchars",
|
|
156
|
+
"version": "2.2.0",
|
|
147
157
|
"license": "MIT"
|
|
148
158
|
},
|
|
149
159
|
{
|
package/dist/composition.json
CHANGED
|
@@ -106,6 +106,11 @@
|
|
|
106
106
|
"version": "0.1.0",
|
|
107
107
|
"license": "MIT"
|
|
108
108
|
},
|
|
109
|
+
{
|
|
110
|
+
"name": "saxes",
|
|
111
|
+
"version": "6.0.0",
|
|
112
|
+
"license": "ISC"
|
|
113
|
+
},
|
|
109
114
|
{
|
|
110
115
|
"name": "sisteransi",
|
|
111
116
|
"version": "1.0.5",
|
|
@@ -118,7 +123,7 @@
|
|
|
118
123
|
},
|
|
119
124
|
{
|
|
120
125
|
"name": "tiny-http-mcp-server",
|
|
121
|
-
"version": "0.1.
|
|
126
|
+
"version": "0.1.129",
|
|
122
127
|
"license": "MIT"
|
|
123
128
|
},
|
|
124
129
|
{
|
|
@@ -133,7 +138,7 @@
|
|
|
133
138
|
},
|
|
134
139
|
{
|
|
135
140
|
"name": "toolcraft",
|
|
136
|
-
"version": "0.0.
|
|
141
|
+
"version": "0.0.773",
|
|
137
142
|
"license": "MIT"
|
|
138
143
|
},
|
|
139
144
|
{
|
|
@@ -143,7 +148,12 @@
|
|
|
143
148
|
},
|
|
144
149
|
{
|
|
145
150
|
"name": "toolcraft-schema",
|
|
146
|
-
"version": "0.0.
|
|
151
|
+
"version": "0.0.773",
|
|
152
|
+
"license": "MIT"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "xmlchars",
|
|
156
|
+
"version": "2.2.0",
|
|
147
157
|
"license": "MIT"
|
|
148
158
|
},
|
|
149
159
|
{
|
|
@@ -87,6 +87,8 @@ export interface FileSystemCapabilities {
|
|
|
87
87
|
readonly synchronousDirectoryValidation?: boolean;
|
|
88
88
|
/** Captures a followed destination path with a synchronous commit validator. */
|
|
89
89
|
readonly synchronousStagingResolution?: boolean;
|
|
90
|
+
/** Also binds the final symlink and its target in the synchronous validator. */
|
|
91
|
+
readonly synchronousFollowedStagingResolution?: boolean;
|
|
90
92
|
/** Invokes commitGuard in the same synchronous section as staged replacement. */
|
|
91
93
|
readonly guardedStagingPublication?: boolean;
|
|
92
94
|
readonly atomicFilePublication?: boolean;
|
|
@@ -129,6 +131,7 @@ export interface CapabilityQueryOptions extends OpenReadFileOptions {
|
|
|
129
131
|
* Omission retains the ordinary per-target query and its acquisition intent. */
|
|
130
132
|
readonly stagingAncestry?: boolean;
|
|
131
133
|
readonly stagingResolution?: boolean;
|
|
134
|
+
readonly followFinalSymlink?: boolean;
|
|
132
135
|
readonly create?: boolean;
|
|
133
136
|
readonly creation?: OpenFileOptions["creation"];
|
|
134
137
|
}
|
|
@@ -233,6 +236,10 @@ export interface FileStagingEntry {
|
|
|
233
236
|
export interface FileResolutionStep extends FileStagingEntry {
|
|
234
237
|
readonly linkTarget?: string;
|
|
235
238
|
}
|
|
239
|
+
export interface PrepareStagingResolutionOptions extends FsOptions {
|
|
240
|
+
/** Requires synchronousFollowedStagingResolution; omission preserves final-entry resolution. */
|
|
241
|
+
readonly followFinalSymlink?: boolean;
|
|
242
|
+
}
|
|
236
243
|
export interface FileStagingResolution {
|
|
237
244
|
readonly path: string;
|
|
238
245
|
readonly parent: FileStat;
|
|
@@ -344,7 +351,7 @@ export interface FileSystem {
|
|
|
344
351
|
* The returned guard checks current identities/search access synchronously;
|
|
345
352
|
* preparation is not validation, and the guard's result is not an async lease. */
|
|
346
353
|
prepareDirectoryAncestry?(ancestors: readonly FileStagingEntry[], options?: FsOptions): Promise<() => true>;
|
|
347
|
-
prepareStagingResolution?(path: string, options?:
|
|
354
|
+
prepareStagingResolution?(path: string, options?: PrepareStagingResolutionOptions): Promise<FileStagingResolution>;
|
|
348
355
|
removeStagedFile?(staging: FileStaging, options?: FsOptions): Promise<void>;
|
|
349
356
|
openReadFile?(path: string, options?: OpenReadFileOptions): Promise<FileReadHandle>;
|
|
350
357
|
openResizeFile?(path: string, options?: OpenResizeFileOptions): Promise<FileResizeHandle>;
|
|
@@ -8,7 +8,7 @@ export * from "./fs/object-publication/index.js";
|
|
|
8
8
|
export * from "./fs/webdav/index.js";
|
|
9
9
|
export * from "./bridge/index.js";
|
|
10
10
|
export * from "./python/index.js";
|
|
11
|
-
export { parseXml, parseXmlSteps, XmlLimitError } from "./xml.js";
|
|
11
|
+
export { parseXml, parseXmlSteps, parseXmlStream, XmlLimitError } from "./xml.js";
|
|
12
12
|
export type { XmlName, XmlAttribute, XmlContent, XmlElement, XmlLimits } from "./xml.js";
|
|
13
13
|
export * from "./contracts/object.js";
|
|
14
14
|
export { ObjectAuthority } from "./fs/object-authority.js";
|
|
@@ -8,7 +8,7 @@ export * from "./fs/object-publication/index.js";
|
|
|
8
8
|
export * from "./fs/webdav/index.js";
|
|
9
9
|
export * from "./bridge/index.js";
|
|
10
10
|
export * from "./python/index.js";
|
|
11
|
-
export { parseXml, parseXmlSteps, XmlLimitError } from "./xml.js";
|
|
11
|
+
export { parseXml, parseXmlSteps, parseXmlStream, XmlLimitError } from "./xml.js";
|
|
12
12
|
export * from "./contracts/object.js";
|
|
13
13
|
export { ObjectAuthority } from "./fs/object-authority.js";
|
|
14
14
|
export * from "./fs/s3/index.js";
|
|
@@ -132,7 +132,7 @@ export function readOnlyCapabilities(capabilities) {
|
|
|
132
132
|
].filter(name => capabilities[name] !== undefined).map(name => [name, capabilities[name]]));
|
|
133
133
|
return Object.freeze({
|
|
134
134
|
...inspection, retainedStagingCleanup: false, retainedStagingWrite: false, readOnly: true, write: false, append: false, exclusiveCreate: false,
|
|
135
|
-
synchronousStagingResolution: false,
|
|
135
|
+
synchronousStagingResolution: false, synchronousFollowedStagingResolution: false,
|
|
136
136
|
mkdir: false, recursiveMkdir: false, remove: false, removeDirectory: false, recursiveRemove: false,
|
|
137
137
|
rename: false, copy: false, exclusiveCopy: false, truncate: false, streamingAppend: false,
|
|
138
138
|
randomAccessWrite: false, hardlinks: false, permissions: false, conditionalChmod: false, timestamps: false,
|
|
@@ -144,7 +144,7 @@ export function quotaCapabilities(capabilities) {
|
|
|
144
144
|
const streamingWrite = requireCapabilities(capabilities.write, capabilities.append, capabilities.streamingAppend, !capabilities.readOnly);
|
|
145
145
|
const streamingAppend = requireCapabilities(capabilities.append, capabilities.streamingAppend, !capabilities.readOnly);
|
|
146
146
|
const { streamingWrite: ignoredWrite, streamingAppend: ignoredAppend, ...rest } = capabilities;
|
|
147
|
-
return Object.freeze({ ...rest, synchronousStagingResolution: false, retainedStagingCleanup: false, retainedStagingWrite: false, atomicStagingAncestry: false, synchronousDirectoryValidation: false, guardedStagingPublication: false, atomicFilePublication: false, descriptorWriteStream: false, atomicResize: false, atomicFileMutation: false, atomicEntryRemoval: false, atomicEntryRemovalReceipt: false, atomicStagedFileMutation: false, atomicFileStaging: false, atomicDirectoryMetadata: false, trustedOwnedStaging: false,
|
|
147
|
+
return Object.freeze({ ...rest, synchronousStagingResolution: false, synchronousFollowedStagingResolution: false, retainedStagingCleanup: false, retainedStagingWrite: false, atomicStagingAncestry: false, synchronousDirectoryValidation: false, guardedStagingPublication: false, atomicFilePublication: false, descriptorWriteStream: false, atomicResize: false, atomicFileMutation: false, atomicEntryRemoval: false, atomicEntryRemovalReceipt: false, atomicStagedFileMutation: false, atomicFileStaging: false, atomicDirectoryMetadata: false, trustedOwnedStaging: false,
|
|
148
148
|
...(streamingWrite === undefined ? {} : { streamingWrite }),
|
|
149
149
|
...(streamingAppend === undefined ? {} : { streamingAppend }),
|
|
150
150
|
});
|
|
@@ -179,6 +179,8 @@ export function ownedMutationCapabilities(filesystem, capabilities = filesystem.
|
|
|
179
179
|
(unavailable ??= {}).synchronousDirectoryValidation = false;
|
|
180
180
|
if (capabilities.synchronousStagingResolution === true && (capabilities.readOnly === true || typeof filesystem.prepareStagingResolution !== "function"))
|
|
181
181
|
(unavailable ??= {}).synchronousStagingResolution = false;
|
|
182
|
+
if (capabilities.synchronousFollowedStagingResolution === true && (capabilities.synchronousStagingResolution !== true || unavailable?.synchronousStagingResolution === false))
|
|
183
|
+
(unavailable ??= {}).synchronousFollowedStagingResolution = false;
|
|
182
184
|
if (capabilities.guardedStagingPublication === true && (capabilities.atomicFileStaging !== true || unavailable?.atomicFileStaging === false))
|
|
183
185
|
(unavailable ??= {}).guardedStagingPublication = false;
|
|
184
186
|
if (capabilities.atomicDirectoryMetadata === true && (capabilities.readOnly === true || typeof filesystem.prepareDirectory !== "function"))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FileDescriptor, OpenFileOptions } from "../../contracts/descriptor.js";
|
|
2
|
-
import type { AppendFileOptions, CapabilityQueryOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileResizeOperation, FileResizeOptions, FileStat, FileSystem, OpenReadFileOptions, OpenResizeFileOptions, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions, ConditionalFilePublicationOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, ConditionalRemoveEntryOptions, ConditionalRemoveEntryReceiptOptions, CreateStagedFileOptions, FileStaging, FileStagingEntry, FileStagingResolution, PublishStagedFileOptions, PublishStagedFileSetOptions, PrepareDirectoryOptions, StagedFileContent } from "../../contracts/filesystem.js";
|
|
2
|
+
import type { AppendFileOptions, CapabilityQueryOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileResizeOperation, FileResizeOptions, FileStat, FileSystem, OpenReadFileOptions, OpenResizeFileOptions, FileSystemCapabilities, FsOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions, ConditionalFilePublicationOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, ConditionalRemoveEntryOptions, ConditionalRemoveEntryReceiptOptions, CreateStagedFileOptions, FileStaging, FileStagingEntry, FileStagingResolution, PrepareStagingResolutionOptions, PublishStagedFileOptions, PublishStagedFileSetOptions, PrepareDirectoryOptions, StagedFileContent } from "../../contracts/filesystem.js";
|
|
3
3
|
import type { ByteSource } from "../../contracts/io.js";
|
|
4
4
|
import { type EntryLocation } from "../mount/comparison.js";
|
|
5
5
|
import { pathNamespace } from "../path-namespace.js";
|
|
@@ -43,7 +43,7 @@ export declare class DeviceFileSystem implements FileSystem {
|
|
|
43
43
|
publishStagedFile(staging: FileStaging, destination: string, options: PublishStagedFileOptions): Promise<void>;
|
|
44
44
|
publishStagedFileSet(receipt: FileStaging, destination: string, supplied: PublishStagedFileSetOptions): Promise<FileStat>;
|
|
45
45
|
prepareDirectoryAncestry(ancestors: readonly FileStagingEntry[], options?: FsOptions): Promise<() => true>;
|
|
46
|
-
prepareStagingResolution(path: string, options?:
|
|
46
|
+
prepareStagingResolution(path: string, options?: PrepareStagingResolutionOptions): Promise<FileStagingResolution>;
|
|
47
47
|
removeStagedFile(staging: FileStaging, options?: FsOptions): Promise<void>;
|
|
48
48
|
prepareDirectory(path: string, options: PrepareDirectoryOptions): Promise<FileStat>;
|
|
49
49
|
rename(source: string, destination: string, options?: RenameOptions): Promise<void>;
|
|
@@ -13,7 +13,7 @@ import { openFileDescriptor } from "../descriptor.js";
|
|
|
13
13
|
import { normalizePath } from "../../contracts/virtual-path.js";
|
|
14
14
|
const views = new WeakMap();
|
|
15
15
|
const deviceCapabilities = Object.freeze({
|
|
16
|
-
synchronousStagingResolution: false,
|
|
16
|
+
synchronousStagingResolution: false, synchronousFollowedStagingResolution: false,
|
|
17
17
|
readOnly: false, read: true, stat: true, realpath: true, access: true, readdir: false,
|
|
18
18
|
write: true, append: true, exclusiveCreate: true, streamingRead: true, retainedRead: true,
|
|
19
19
|
streamingWrite: true, streamingAppend: true, independentWriteStreams: true, copy: true, exclusiveCopy: true,
|
|
@@ -179,7 +179,7 @@ export class DeviceFileSystem {
|
|
|
179
179
|
let selected;
|
|
180
180
|
const traversal = options.stagingResolution === true ? { virtual: false } : undefined;
|
|
181
181
|
try {
|
|
182
|
-
resolved = await this.#resolve(path, options, options.stagingResolution
|
|
182
|
+
resolved = await this.#resolve(path, options, options.stagingResolution === true ? options.followFinalSymlink === true : options.create !== undefined || options.creation !== "exclusive", options.create, traversal);
|
|
183
183
|
}
|
|
184
184
|
catch (error) {
|
|
185
185
|
options.signal?.throwIfAborted();
|
|
@@ -210,7 +210,7 @@ export class DeviceFileSystem {
|
|
|
210
210
|
// This intent cannot bind virtual traversal. Do not infer other path
|
|
211
211
|
// capabilities by resolving the shadowed entry in the backing filesystem.
|
|
212
212
|
if (traversal?.virtual)
|
|
213
|
-
return Object.freeze({ synchronousStagingResolution: false });
|
|
213
|
+
return Object.freeze({ synchronousStagingResolution: false, synchronousFollowedStagingResolution: false });
|
|
214
214
|
if (selected === undefined) {
|
|
215
215
|
const query = this.#filesystem.capabilitiesFor;
|
|
216
216
|
options.signal?.throwIfAborted();
|
|
@@ -229,6 +229,7 @@ export class DeviceFileSystem {
|
|
|
229
229
|
throw new FsError("EAGAIN", { path });
|
|
230
230
|
if ([resolution.path, ...resolution.traversed.map(entry => entry.path)].some(entry => entry === deviceDirectory || entry.startsWith(`${deviceDirectory}/`))) {
|
|
231
231
|
(unavailable ??= {}).synchronousStagingResolution = false;
|
|
232
|
+
unavailable.synchronousFollowedStagingResolution = false;
|
|
232
233
|
}
|
|
233
234
|
runStagingGuard(resolution.validate);
|
|
234
235
|
}
|
|
@@ -237,6 +238,7 @@ export class DeviceFileSystem {
|
|
|
237
238
|
if (!isFsError(error, "ENOTSUP"))
|
|
238
239
|
throw error;
|
|
239
240
|
(unavailable ??= {}).synchronousStagingResolution = false;
|
|
241
|
+
unavailable.synchronousFollowedStagingResolution = false;
|
|
240
242
|
}
|
|
241
243
|
}
|
|
242
244
|
if (resolved.startsWith(`${deviceDirectory}/`)) {
|
|
@@ -244,6 +246,7 @@ export class DeviceFileSystem {
|
|
|
244
246
|
u.atomicStagingAncestry = false;
|
|
245
247
|
u.synchronousDirectoryValidation = false;
|
|
246
248
|
u.synchronousStagingResolution = false;
|
|
249
|
+
u.synchronousFollowedStagingResolution = false;
|
|
247
250
|
}
|
|
248
251
|
if (typeof this.#filesystem.open !== "function" && capabilities.open !== false)
|
|
249
252
|
(unavailable ??= {}).open = false;
|
|
@@ -753,19 +756,19 @@ export class DeviceFileSystem {
|
|
|
753
756
|
}
|
|
754
757
|
}
|
|
755
758
|
async prepareStagingResolution(path, options = {}) {
|
|
756
|
-
const controls = options.signal === undefined ? {} : { signal: options.signal };
|
|
759
|
+
const controls = { ...(options.signal === undefined ? {} : { signal: options.signal }), ...(options.followFinalSymlink === true ? { followFinalSymlink: true } : {}) };
|
|
757
760
|
controls.signal?.throwIfAborted();
|
|
758
761
|
if (normalizePath(path) !== path || !path.startsWith("/"))
|
|
759
762
|
throw new FsError("EINVAL", { path });
|
|
760
763
|
try {
|
|
761
764
|
const traversal = { virtual: false };
|
|
762
|
-
const resolved = await this.#resolve(path, controls,
|
|
765
|
+
const resolved = await this.#resolve(path, controls, controls.followFinalSymlink === true, undefined, traversal);
|
|
763
766
|
controls.signal?.throwIfAborted();
|
|
764
767
|
if (traversal.virtual)
|
|
765
768
|
throw new FsError("ENOTSUP", { path });
|
|
766
769
|
const declared = ownedMutationCapabilities(this.#filesystem, await this.#filesystem.capabilitiesFor?.(path, { ...controls, stagingResolution: true }) ?? this.#filesystem.capabilities);
|
|
767
770
|
controls.signal?.throwIfAborted();
|
|
768
|
-
if (declared.synchronousStagingResolution !== true || !this.#filesystem.prepareStagingResolution)
|
|
771
|
+
if (declared.synchronousStagingResolution !== true || controls.followFinalSymlink === true && declared.synchronousFollowedStagingResolution !== true || !this.#filesystem.prepareStagingResolution)
|
|
769
772
|
throw new FsError("ENOTSUP", { path });
|
|
770
773
|
const receipt = snapshotStagingResolution(await this.#filesystem.prepareStagingResolution(path, controls));
|
|
771
774
|
controls.signal?.throwIfAborted();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileReadHandle, FileResizeHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, ChmodOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, FileDescriptor, OpenFileOptions, OpenReadFileOptions, OpenResizeFileOptions, WriteFileOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, ConditionalRemoveEntryOptions, ConditionalRemoveEntryReceiptOptions, CreateStagedFileOptions, FileStaging, FileStagingEntry, FileStagingResolution, PublishStagedFileOptions, PublishStagedFileSetOptions, PrepareDirectoryOptions, StagedFileContent } from "../../contracts/filesystem.js";
|
|
1
|
+
import type { AppendFileOptions, CopyFileOptions, DirectoryEntry, EntryComparison, FileReadHandle, FileResizeHandle, FileStat, FileSystem, FileSystemCapabilities, FsOptions, ChmodOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, FileDescriptor, OpenFileOptions, OpenReadFileOptions, OpenResizeFileOptions, WriteFileOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, ConditionalRemoveEntryOptions, ConditionalRemoveEntryReceiptOptions, CreateStagedFileOptions, FileStaging, FileStagingEntry, FileStagingResolution, PrepareStagingResolutionOptions, PublishStagedFileOptions, PublishStagedFileSetOptions, PrepareDirectoryOptions, StagedFileContent } from "../../contracts/filesystem.js";
|
|
2
2
|
import type { ByteSource } from "../../contracts/io.js";
|
|
3
3
|
import { type MemoryAtomicView } from "./atomic-view.js";
|
|
4
4
|
import { MemoryAllocation, MemoryLedger } from "./ledger.js";
|
|
@@ -116,7 +116,7 @@ export declare class MemoryFileSystem implements FileSystem {
|
|
|
116
116
|
createStagedFile(directoryPath: string, name: string, content: StagedFileContent, options: CreateStagedFileOptions): Promise<FileStaging>;
|
|
117
117
|
private retainStagingCleanup;
|
|
118
118
|
prepareDirectoryAncestry(ancestors: readonly FileStagingEntry[], options?: FsOptions): Promise<() => true>;
|
|
119
|
-
prepareStagingResolution(path: string, options?:
|
|
119
|
+
prepareStagingResolution(path: string, options?: PrepareStagingResolutionOptions): Promise<FileStagingResolution>;
|
|
120
120
|
private verifyDirectoryAncestry;
|
|
121
121
|
publishStagedFile(receipt: FileStaging, destination: string, options: PublishStagedFileOptions): Promise<void>;
|
|
122
122
|
publishStagedFileSet(receipt: FileStaging, destination: string, options: PublishStagedFileSetOptions): Promise<FileStat>;
|
|
@@ -585,7 +585,7 @@ const SHARED_STOCK_MEMORY_CAPABILITIES = Object.freeze({
|
|
|
585
585
|
timestamps: true,
|
|
586
586
|
atomicRename: true,
|
|
587
587
|
atomicStagedFileMutation: true, atomicFileStaging: true, retainedStagingCleanup: true, retainedStagingWrite: true, atomicStagingAncestry: true, atomicFileMutation: true, atomicEntryRemoval: true, atomicEntryRemovalReceipt: true, atomicTreeRemoval: true,
|
|
588
|
-
synchronousDirectoryValidation: true, synchronousStagingResolution: true, guardedStagingPublication: true,
|
|
588
|
+
synchronousDirectoryValidation: true, synchronousStagingResolution: true, synchronousFollowedStagingResolution: true, guardedStagingPublication: true,
|
|
589
589
|
atomicDirectoryMetadata: true,
|
|
590
590
|
streamingRead: true,
|
|
591
591
|
retainedRead: true,
|
|
@@ -2186,13 +2186,14 @@ export class MemoryFileSystem {
|
|
|
2186
2186
|
return () => this.verifyDirectoryAncestry(entries, controls);
|
|
2187
2187
|
}
|
|
2188
2188
|
async prepareStagingResolution(path, options = {}) {
|
|
2189
|
+
const followFinal = options.followFinalSymlink === true;
|
|
2189
2190
|
const controls = options.signal === undefined ? {} : { signal: options.signal };
|
|
2190
2191
|
controls.signal?.throwIfAborted();
|
|
2191
2192
|
this.validatePath(path, "prepareStagingResolution");
|
|
2192
2193
|
if (!path.startsWith("/") || normalizePath(path) !== path)
|
|
2193
2194
|
this.fail("EINVAL", "prepareStagingResolution", path);
|
|
2194
2195
|
const traversed = [];
|
|
2195
|
-
const location = this.resolve(path, "prepareStagingResolution", { followFinal
|
|
2196
|
+
const location = this.resolve(path, "prepareStagingResolution", { followFinal, allowMissing: true, resolutionSteps: traversed });
|
|
2196
2197
|
if (location.node && location.node.type !== "file")
|
|
2197
2198
|
this.fail("ENOTSUP", "prepareStagingResolution", path);
|
|
2198
2199
|
const parentPath = location.path.slice(0, location.path.lastIndexOf("/")) || "/";
|
|
@@ -2207,7 +2208,7 @@ export class MemoryFileSystem {
|
|
|
2207
2208
|
const currentSteps = [];
|
|
2208
2209
|
let current;
|
|
2209
2210
|
try {
|
|
2210
|
-
current = this.resolve(path, "verifyStagingResolution", { followFinal
|
|
2211
|
+
current = this.resolve(path, "verifyStagingResolution", { followFinal, allowMissing: true, resolutionSteps: currentSteps });
|
|
2211
2212
|
}
|
|
2212
2213
|
catch (error) {
|
|
2213
2214
|
if (error instanceof FsError && ["ENOENT", "ENOTDIR", "ELOOP"].includes(error.code))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppendFileOptions, CapabilityQueryOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileResizeOperation, FileResizeOptions, FileStat, FileSystem, OpenReadFileOptions, OpenResizeFileOptions, FileSystemCapabilities, FsOptions, ChmodOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions, ConditionalFilePublicationOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, ConditionalRemoveEntryOptions, ConditionalRemoveEntryReceiptOptions, CreateStagedFileOptions, FileStaging, FileStagingEntry, FileStagingResolution, PublishStagedFileOptions, PublishStagedFileSetOptions, PrepareDirectoryOptions, StagedFileContent } from "../../contracts/filesystem.js";
|
|
1
|
+
import type { AppendFileOptions, CapabilityQueryOptions, CopyFileOptions, DirectoryEntry, FileReadHandle, FileResizeHandle, FileResizeOperation, FileResizeOptions, FileStat, FileSystem, OpenReadFileOptions, OpenResizeFileOptions, FileSystemCapabilities, FsOptions, ChmodOptions, RenameOptions, MkdirOptions, ReadDirectoryOptions, ReadFileOptions, ReadStreamOptions, RemoveOptions, WriteFileOptions, ConditionalFilePublicationOptions, ConditionalWriteFileOptions, ConditionalRemoveFileOptions, ConditionalRemoveEntryOptions, ConditionalRemoveEntryReceiptOptions, CreateStagedFileOptions, FileStaging, FileStagingEntry, FileStagingResolution, PrepareStagingResolutionOptions, PublishStagedFileOptions, PublishStagedFileSetOptions, PrepareDirectoryOptions, StagedFileContent } from "../../contracts/filesystem.js";
|
|
2
2
|
import type { ByteSource } from "../../contracts/io.js";
|
|
3
3
|
import type { FileDescriptor, OpenFileOptions } from "../../contracts/descriptor.js";
|
|
4
4
|
export interface MountFileSystemOptions {
|
|
@@ -18,7 +18,7 @@ export declare class MountFileSystem implements FileSystem {
|
|
|
18
18
|
private supportsStagingAncestry;
|
|
19
19
|
private supportsDirectoryValidation;
|
|
20
20
|
prepareDirectoryAncestry(ancestors: readonly FileStagingEntry[], options?: FsOptions): Promise<() => true>;
|
|
21
|
-
prepareStagingResolution(path: string, options?:
|
|
21
|
+
prepareStagingResolution(path: string, options?: PrepareStagingResolutionOptions): Promise<FileStagingResolution>;
|
|
22
22
|
private prepareAncestry;
|
|
23
23
|
private protected;
|
|
24
24
|
openReadFile(path: string, options?: OpenReadFileOptions): Promise<FileReadHandle>;
|
|
@@ -191,7 +191,8 @@ export class MountFileSystem {
|
|
|
191
191
|
fail("EINVAL");
|
|
192
192
|
const location = await this.resolve(path, options, {
|
|
193
193
|
allowMissing: options.create ?? true,
|
|
194
|
-
|
|
194
|
+
retainedResolution: options.stagingResolution === true && options.followFinalSymlink === true,
|
|
195
|
+
followFinal: options.stagingResolution === true ? options.followFinalSymlink === true : options.create !== undefined || options.creation !== "exclusive",
|
|
195
196
|
...(options.create === undefined ? {} : { resizeCreate: options.create }),
|
|
196
197
|
});
|
|
197
198
|
const observed = ownedMutationCapabilities(location.mount.backend, await location.mount.backend.capabilitiesFor?.(location.local, options)
|
|
@@ -212,15 +213,15 @@ export class MountFileSystem {
|
|
|
212
213
|
const input = location.mount.path === "/" ? path : path.slice(location.mount.path.length) || "/";
|
|
213
214
|
const declared = input === location.local ? resize : ownedMutationCapabilities(location.mount.backend, await location.mount.backend.capabilitiesFor?.(input, options) ?? location.mount.backend.capabilities);
|
|
214
215
|
options.signal?.throwIfAborted();
|
|
215
|
-
resolution = declared.synchronousStagingResolution === true && await this.supportsStagingAncestry(location, resize, options);
|
|
216
|
+
resolution = declared.synchronousStagingResolution === true && (options.followFinalSymlink !== true || declared.synchronousFollowedStagingResolution === true) && await this.supportsStagingAncestry(location, resize, options);
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
219
|
const conditionalChmod = options.conditionalChmod === true
|
|
219
220
|
? resize.conditionalChmod === true && typeof location.mount.backend.chmod === "function"
|
|
220
221
|
&& await this.supportsDirectoryValidation(location.path.slice(0, location.path.lastIndexOf("/")) || "/", options, true)
|
|
221
222
|
: this.capabilities.conditionalChmod;
|
|
222
|
-
const { synchronousDirectoryValidation: ignoredValidation, synchronousStagingResolution: ignoredResolution, ...ordinary } = resize;
|
|
223
|
-
const withOpen = { ...ordinary, ...(conditionalChmod === undefined ? {} : { conditionalChmod }), ...(resolution === undefined ? {} : { synchronousStagingResolution: resolution }), ...(validation === undefined ? {} : { synchronousDirectoryValidation: validation }), atomicStagingAncestry: ancestry, ...(typeof location.mount.backend.open === "function" ? {} : { open: false }) };
|
|
223
|
+
const { synchronousDirectoryValidation: ignoredValidation, synchronousStagingResolution: ignoredResolution, synchronousFollowedStagingResolution: ignoredFollowedResolution, ...ordinary } = resize;
|
|
224
|
+
const withOpen = { ...ordinary, ...(conditionalChmod === undefined ? {} : { conditionalChmod }), ...(resolution === undefined ? {} : { synchronousStagingResolution: resolution, ...(options.followFinalSymlink === true ? { synchronousFollowedStagingResolution: resolution } : {}) }), ...(validation === undefined ? {} : { synchronousDirectoryValidation: validation }), atomicStagingAncestry: ancestry, ...(typeof location.mount.backend.open === "function" ? {} : { open: false }) };
|
|
224
225
|
const capabilities = location.synthetic ? { ...withOpen, open: false, retainedRead: false }
|
|
225
226
|
: retainedResizeCapabilities(location.mount.backend, retainedReadCapabilities(location.mount.backend, withOpen));
|
|
226
227
|
if (location.synthetic)
|
|
@@ -264,19 +265,19 @@ export class MountFileSystem {
|
|
|
264
265
|
return this.operation("prepareDirectoryAncestry", ancestors.at(-1)?.path ?? "/", options, () => this.prepareAncestry(ancestors, options));
|
|
265
266
|
}
|
|
266
267
|
prepareStagingResolution(path, options = {}) {
|
|
267
|
-
const controls = options.signal === undefined ? {} : { signal: options.signal };
|
|
268
|
+
const controls = { ...(options.signal === undefined ? {} : { signal: options.signal }), ...(options.followFinalSymlink === true ? { followFinalSymlink: true } : {}) };
|
|
268
269
|
return this.operation("prepareStagingResolution", path, controls, async () => {
|
|
269
270
|
if (normalizePath(globalPath(path)) !== path)
|
|
270
271
|
fail("EINVAL");
|
|
271
272
|
const observed = [];
|
|
272
|
-
const location = await this.resolve(path, controls, { followFinal:
|
|
273
|
+
const location = await this.resolve(path, controls, { followFinal: controls.followFinalSymlink === true, retainedResolution: controls.followFinalSymlink === true, allowMissing: true, resolutionSteps: observed });
|
|
273
274
|
if (location.synthetic || !within(location.mount.path, path))
|
|
274
275
|
fail("ENOTSUP");
|
|
275
276
|
const backend = location.mount.backend;
|
|
276
277
|
const input = location.mount.path === "/" ? path : path.slice(location.mount.path.length) || "/";
|
|
277
278
|
const declared = ownedMutationCapabilities(backend, await backend.capabilitiesFor?.(input, { ...controls, stagingResolution: true }) ?? backend.capabilities);
|
|
278
279
|
controls.signal?.throwIfAborted();
|
|
279
|
-
if (declared.synchronousStagingResolution !== true || !backend.prepareStagingResolution)
|
|
280
|
+
if (declared.synchronousStagingResolution !== true || controls.followFinalSymlink === true && declared.synchronousFollowedStagingResolution !== true || !backend.prepareStagingResolution)
|
|
280
281
|
fail("ENOTSUP");
|
|
281
282
|
const prefix = directoryAncestryPaths(location.mount.path).map(ancestor => {
|
|
282
283
|
const entry = observed.find(step => step.path === ancestor);
|
|
@@ -546,6 +547,10 @@ export class MountFileSystem {
|
|
|
546
547
|
let followedFinalLink = false;
|
|
547
548
|
const verified = async (location) => {
|
|
548
549
|
let inspection = !location.stat && settings.resizeCreate === true ? creationVerification : verification;
|
|
550
|
+
// A guarded resolution binds the original link chain and missing target
|
|
551
|
+
// through the authoritative backend receipt; realpath cannot name absence.
|
|
552
|
+
if (settings.retainedResolution && !location.stat && followedFinalLink)
|
|
553
|
+
inspection = undefined;
|
|
549
554
|
// A missing entry has no realpath. Verify its existing parent while
|
|
550
555
|
// leaving dangling final symlinks on the ordinary following path.
|
|
551
556
|
if (inspection && !location.stat && settings.allowMissing && !followedFinalLink && inspection.finalName === undefined) {
|
|
@@ -554,7 +554,12 @@ export class OverlayFileSystem {
|
|
|
554
554
|
async cleanup(options = {}) {
|
|
555
555
|
return this.run(options, () => this.cleanGarbage(true));
|
|
556
556
|
}
|
|
557
|
+
// Receipts describe overlay paths and identities, never backing-layer staging paths.
|
|
558
|
+
stagingOptions({ signal }) {
|
|
559
|
+
return signal === undefined ? {} : { signal };
|
|
560
|
+
}
|
|
557
561
|
async staged(options, operation) {
|
|
562
|
+
options = this.stagingOptions(options);
|
|
558
563
|
const root = `/.virtual-bash-overlay-${platform.randomUUID()}`;
|
|
559
564
|
if (await this.maybeStat(this.#upper, root, options) || await this.maybeStat(this.#lower, root, options))
|
|
560
565
|
fail("EEXIST", root);
|
|
@@ -570,6 +575,7 @@ export class OverlayFileSystem {
|
|
|
570
575
|
}
|
|
571
576
|
}
|
|
572
577
|
async preserve(path, stat, options) {
|
|
578
|
+
options = this.stagingOptions(options);
|
|
573
579
|
if (this.#upper.chmod)
|
|
574
580
|
await this.#upper.chmod(path, stat.mode & 0o7777, options);
|
|
575
581
|
else if (((await this.#upper.lstat(path, options)).mode & 0o7777) !== (stat.mode & 0o7777))
|
|
@@ -579,6 +585,7 @@ export class OverlayFileSystem {
|
|
|
579
585
|
await this.#upper.utimes(path, stat.atimeMs, stat.mtimeMs, options);
|
|
580
586
|
}
|
|
581
587
|
async clone(entry, destination, options) {
|
|
588
|
+
options = this.stagingOptions(options);
|
|
582
589
|
if (entry.stat.type !== "directory" && ((entry.stat.nlink ?? 1) > 1
|
|
583
590
|
|| (entry.backend.capabilities.hardlinks === true && entry.stat.nlink === undefined))) {
|
|
584
591
|
fail("ENOTSUP", entry.path, "copy-up cannot preserve hardlink identity");
|
|
@@ -608,6 +615,7 @@ export class OverlayFileSystem {
|
|
|
608
615
|
}
|
|
609
616
|
}
|
|
610
617
|
async ensureDirectory(path, options) {
|
|
618
|
+
options = this.stagingOptions(options);
|
|
611
619
|
const entry = await this.lookup(path, options);
|
|
612
620
|
if (!entry)
|
|
613
621
|
fail("ENOENT", path);
|
|
@@ -618,6 +626,7 @@ export class OverlayFileSystem {
|
|
|
618
626
|
await this.copyUp(entry, options);
|
|
619
627
|
}
|
|
620
628
|
async copyUp(entry, options) {
|
|
629
|
+
options = this.stagingOptions(options);
|
|
621
630
|
if (entry.backend === this.#upper)
|
|
622
631
|
return;
|
|
623
632
|
if (entry.path === "/")
|
|
@@ -640,6 +649,7 @@ export class OverlayFileSystem {
|
|
|
640
649
|
});
|
|
641
650
|
}
|
|
642
651
|
async parent(path, options) {
|
|
652
|
+
options = this.stagingOptions(options);
|
|
643
653
|
const parent = await this.lookup(dirname(path), options);
|
|
644
654
|
if (!parent)
|
|
645
655
|
fail("ENOENT", dirname(path));
|
|
@@ -376,6 +376,24 @@ export class OverlayMemoryPublication {
|
|
|
376
376
|
const owned = await this.upper.createStagedFile(directory, name, content, { ...options, parent });
|
|
377
377
|
const staging = Object.freeze({
|
|
378
378
|
parent: owned.parent, directory: owned.directory, file: owned.file,
|
|
379
|
+
...(owned.writer ? { writer: Object.freeze({
|
|
380
|
+
write: async (bytes, controls = {}) => {
|
|
381
|
+
controls.signal?.throwIfAborted();
|
|
382
|
+
this.stores();
|
|
383
|
+
guard?.();
|
|
384
|
+
this.check(ancestors);
|
|
385
|
+
await owned.writer.write(bytes, controls);
|
|
386
|
+
},
|
|
387
|
+
finish: async (controls = {}) => {
|
|
388
|
+
controls.signal?.throwIfAborted();
|
|
389
|
+
this.stores();
|
|
390
|
+
guard?.();
|
|
391
|
+
this.check(ancestors);
|
|
392
|
+
const stat = await owned.writer.finish(controls);
|
|
393
|
+
this.stages.set(staging.directory, { ...owned, file: { ...owned.file, stat } });
|
|
394
|
+
return stat;
|
|
395
|
+
},
|
|
396
|
+
}) } : {}),
|
|
379
397
|
...(owned.cleanup ? { cleanup: createStagingCleanup(directory, controls => this.cleanup(staging, controls, true), () => owned.cleanup.close()) } : {}),
|
|
380
398
|
});
|
|
381
399
|
this.stages.set(staging.directory, owned);
|
|
@@ -459,7 +459,7 @@ export function scopeFileSystem(filesystem, charge, signal, cleanupCharge = char
|
|
|
459
459
|
try {
|
|
460
460
|
const declared = ownedMutationCapabilities(original, await original.capabilitiesFor?.(path, { ...controls, stagingResolution: true }) ?? original.capabilities);
|
|
461
461
|
controls.signal?.throwIfAborted();
|
|
462
|
-
if (declared.synchronousStagingResolution !== true)
|
|
462
|
+
if (declared.synchronousStagingResolution !== true || controls.followFinalSymlink === true && declared.synchronousFollowedStagingResolution !== true)
|
|
463
463
|
throw new FsError("ENOTSUP", { path });
|
|
464
464
|
const resolution = snapshotStagingResolution(await Reflect.apply(method, original, [path, controls]));
|
|
465
465
|
controls.signal?.throwIfAborted();
|
|
@@ -49,7 +49,7 @@ export declare class IntegerTable {
|
|
|
49
49
|
/** Ordered replay with a fixed 16-level radix stack (at most 2 KiB of
|
|
50
50
|
* owned node bytes) and 128 output records. Batches avoid alternating sparse
|
|
51
51
|
* index reads with consumer writes on every record. Mutation is not permitted. */
|
|
52
|
-
entries(): AsyncGenerator<readonly [bigint, bigint]>;
|
|
52
|
+
entries(start?: bigint, end?: bigint): AsyncGenerator<readonly [bigint, bigint]>;
|
|
53
53
|
/** Hot radix paths must not evict dirty leaf pages in a one-page backend. */
|
|
54
54
|
private branch;
|
|
55
55
|
private lookup;
|
|
@@ -103,14 +103,20 @@ export class PagedStorage {
|
|
|
103
103
|
this.pages.set(number, cached);
|
|
104
104
|
return cached;
|
|
105
105
|
}
|
|
106
|
+
let reusable;
|
|
106
107
|
if (this.pages.size === this.maxPages) {
|
|
107
108
|
if (!this.descriptor)
|
|
108
109
|
await this.spill();
|
|
109
110
|
const [oldNumber, oldPage] = this.pages.entries().next().value;
|
|
110
111
|
await this.flush(oldNumber, oldPage);
|
|
111
112
|
this.pages.delete(oldNumber);
|
|
113
|
+
reusable = oldPage;
|
|
112
114
|
}
|
|
113
|
-
|
|
115
|
+
// Flush owns the bytes until its awaited write completes. Reuse only then;
|
|
116
|
+
// zero new/unwritten ranges so the previous page cannot leak into them.
|
|
117
|
+
const page = reusable ?? { bytes: new Uint8Array(pageBytes), dirty: false };
|
|
118
|
+
if (reusable)
|
|
119
|
+
page.bytes.fill(0);
|
|
114
120
|
if (number * pageBytes < this.diskLength) {
|
|
115
121
|
let offset = 0;
|
|
116
122
|
while (offset < pageBytes) {
|
|
@@ -204,8 +210,14 @@ export class IntegerTable {
|
|
|
204
210
|
async retain(key, value, dirty) {
|
|
205
211
|
if (this.cache.size === this.maxEntries && !this.cache.has(key)) {
|
|
206
212
|
const [oldKey, old] = this.cache.entries().next().value;
|
|
213
|
+
// Flush the bounded dirty window together, before the caller touches other
|
|
214
|
+
// stored data. One dirty leaf per insertion otherwise thrashes a page cache.
|
|
207
215
|
if (old.dirty)
|
|
208
|
-
|
|
216
|
+
for (const [key, entry] of this.cache)
|
|
217
|
+
if (entry.dirty) {
|
|
218
|
+
await this.persist(key, entry.value);
|
|
219
|
+
entry.dirty = false;
|
|
220
|
+
}
|
|
209
221
|
this.cache.delete(oldKey);
|
|
210
222
|
}
|
|
211
223
|
this.cache.delete(key);
|
|
@@ -232,7 +244,11 @@ export class IntegerTable {
|
|
|
232
244
|
/** Ordered replay with a fixed 16-level radix stack (at most 2 KiB of
|
|
233
245
|
* owned node bytes) and 128 output records. Batches avoid alternating sparse
|
|
234
246
|
* index reads with consumer writes on every record. Mutation is not permitted. */
|
|
235
|
-
async *entries() {
|
|
247
|
+
async *entries(start = 0n, end = 0x10000000000000000n) {
|
|
248
|
+
if (start < 0n || start > end || end > 0x10000000000000000n)
|
|
249
|
+
throw new RangeError("Invalid integer table range");
|
|
250
|
+
if (start === end)
|
|
251
|
+
return;
|
|
236
252
|
const revision = this.revision;
|
|
237
253
|
const check = () => { if (revision !== this.revision)
|
|
238
254
|
throw new Error("Integer table changed during iteration"); };
|
|
@@ -259,6 +275,8 @@ export class IntegerTable {
|
|
|
259
275
|
if (!value)
|
|
260
276
|
continue;
|
|
261
277
|
const key = prefix | BigInt(slot) << shift;
|
|
278
|
+
if (key >= end || key + (1n << shift) <= start)
|
|
279
|
+
continue;
|
|
262
280
|
if (shift === 0n)
|
|
263
281
|
yield [key, value - 1n];
|
|
264
282
|
else
|
|
@@ -16,9 +16,10 @@ console.log(document.children[0].text); // A & B
|
|
|
16
16
|
| API | Use |
|
|
17
17
|
| --- | --- |
|
|
18
18
|
| `parseXml(source, limits?)` | Parse a string into an `XmlElement`. |
|
|
19
|
+
| `parseXmlStream(chunks, limits?, checkpoint?)` | Consume synchronous or asynchronous string chunks into an `XmlElement`. |
|
|
19
20
|
| `parseXmlSteps(source, limits?)` | Advance a parser through work checkpoints; the generator returns the completed element. |
|
|
20
21
|
| `XmlLimitError` | Inspect which configured resource limit was exceeded through its `limit` property. |
|
|
21
|
-
| `XmlElement`, `XmlContent`, `XmlAttribute`, `XmlName`, `XmlLimits` | Describe and consume the XML tree without Node.js types. |
|
|
22
|
+
| `XmlElement`, `XmlContent`, `XmlAttribute`, `XmlName`, `XmlLimits`, `XmlStreamLimits` | Describe and consume the XML tree without Node.js types. |
|
|
22
23
|
|
|
23
24
|
Pass positive integer limits for depth, element and content counts, attributes, namespaces, and text. Omitted limits are unlimited. `retainContent` preserves ordered mixed content; `onElement` observes element names, parents, and depths during parsing. `expectedEncoding` checks an XML declaration against the caller's decoded input.
|
|
24
25
|
|
|
@@ -26,3 +27,11 @@ Pass positive integer limits for depth, element and content counts, attributes,
|
|
|
26
27
|
Pass `recover: message => report(message)` to repair truncated elements,
|
|
27
28
|
mismatched closing tags, and undeclared entities. Each repair calls the callback.
|
|
28
29
|
Resource limits and the prohibition on DTDs remain in force.
|
|
30
|
+
|
|
31
|
+
`parseXmlStream` consumes each chunk before requesting the next and calls the optional asynchronous checkpoint with at most 512 UTF-16 units. It preserves the buffered parser’s tree shape, namespaces and retained content. The source is closed on parsing or checkpoint failure. Decode byte sources incrementally before passing their strings. This avoids a full source string; the resulting tree and individual XML tokens still reside in memory. Recovery remains available through the buffered parser.
|
|
32
|
+
|
|
33
|
+
For validation or root-name detection, pass `retainTree: false` to `parseXmlStream`. It validates the complete document and enforces the same limits, but returns a root element with empty data fields. Only open element frames, namespace scopes and current parser tokens remain resident; completed descendants, text, attributes and document content are not retained. `onElement` still observes each element.
|
|
34
|
+
|
|
35
|
+
Use `streamElements: { matches, consume }` to process selected complete subtrees asynchronously. Matching non-root elements are detached from their parent; nested matches remain inside the selected ancestor. `consume(element, parent, before?)` runs in source order and is awaited before the next 512-unit parse window. The producer closes on consumer failure. This mode requires normal tree/content retention and retains each selected subtree until consumption; individual large subtrees and XML tokens still need their own storage strategy.
|
|
36
|
+
|
|
37
|
+
Set `streamElements.captureBefore: true` to detach preceding sibling content when each subtree is selected and receive it as `before`. This preserves text, CDATA, comments and sibling order even when multiple subtrees share one parse window. The remaining trailing content stays in the parent. Store both `before` and the subtree when you need to reconstruct the original mixed content.
|
|
@@ -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 } from "./stream.js";
|