toolcraft 0.0.881 → 0.0.883
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 +2 -2
- package/dist/composition.json +2 -2
- package/node_modules/@poe-code/safe-fs/README.md +4 -1
- package/node_modules/@poe-code/safe-fs/dist/fs/object-publication/index.d.ts +1 -0
- package/node_modules/@poe-code/safe-fs/dist/fs/object-publication/index.js +7 -4
- package/node_modules/@poe-code/safe-fs/dist/storage.d.ts +3 -1
- package/node_modules/@poe-code/safe-fs/dist/storage.js +26 -20
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
package/composition.json
CHANGED
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"name": "toolcraft",
|
|
141
|
-
"version": "0.0.
|
|
141
|
+
"version": "0.0.883",
|
|
142
142
|
"license": "MIT"
|
|
143
143
|
},
|
|
144
144
|
{
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"name": "toolcraft-schema",
|
|
151
|
-
"version": "0.0.
|
|
151
|
+
"version": "0.0.883",
|
|
152
152
|
"license": "MIT"
|
|
153
153
|
},
|
|
154
154
|
{
|
package/dist/composition.json
CHANGED
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"name": "toolcraft",
|
|
141
|
-
"version": "0.0.
|
|
141
|
+
"version": "0.0.883",
|
|
142
142
|
"license": "MIT"
|
|
143
143
|
},
|
|
144
144
|
{
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"name": "toolcraft-schema",
|
|
151
|
-
"version": "0.0.
|
|
151
|
+
"version": "0.0.883",
|
|
152
152
|
"license": "MIT"
|
|
153
153
|
},
|
|
154
154
|
{
|
|
@@ -14,7 +14,10 @@ To bound aggregate pages across multiple live stores, pass the same
|
|
|
14
14
|
`new PagedStorageCache(16)` as the third argument to each `PagedStorage` constructor.
|
|
15
15
|
This shares a 256 KiB resident-page budget; `cache.residentBytes` reports current
|
|
16
16
|
page retention. Eviction uses each store's filesystem, and each store still owns
|
|
17
|
-
its `close()` cleanup.
|
|
17
|
+
its `close()` cleanup. Use `new PagedStorageCache(16, 65536)` to coalesce scratch
|
|
18
|
+
I/O into 64 KiB pages within a 1 MiB budget, for example with object-backed files.
|
|
19
|
+
Page sizes must be multiples of 16 KiB, up to 1 MiB; individual `read()` calls
|
|
20
|
+
remain limited to 16 KiB. The budget excludes caller buffers and backend storage.
|
|
18
21
|
Call `await storage.prepare()` before opening a directory iterator when its
|
|
19
22
|
snapshot includes the scratch directory. It acquires and unlinks the backing
|
|
20
23
|
handle up front, so later spills do not mutate that directory's namespace.
|
|
@@ -20,6 +20,7 @@ export interface ObjectFileAcquireOptions extends FsOptions {
|
|
|
20
20
|
}
|
|
21
21
|
export interface ObjectFileStaging {
|
|
22
22
|
readPage(index: number, options?: FsOptions): Promise<Uint8Array | undefined>;
|
|
23
|
+
/** Own acknowledged bytes; the caller may reuse its buffer after settlement. */
|
|
23
24
|
writePage(index: number, bytes: Uint8Array, options?: FsOptions): Promise<void>;
|
|
24
25
|
truncate(size: number, options?: FsOptions): Promise<void>;
|
|
25
26
|
close(): Promise<void>;
|
|
@@ -296,13 +296,16 @@ export function withObjectFileDescriptors(filesystem, store, options = {}) {
|
|
|
296
296
|
const page = Math.floor(offset / chunkBytes);
|
|
297
297
|
const within = offset % chunkBytes;
|
|
298
298
|
const length = Math.min(buffer.byteLength - copied, chunkBytes - within);
|
|
299
|
-
|
|
299
|
+
const complete = within === 0 && length === chunkBytes;
|
|
300
|
+
// Staging owns acknowledged bytes. Borrow the caller's complete
|
|
301
|
+
// page until writePage settles instead of allocating another copy.
|
|
302
|
+
let bytes = complete ? buffer.subarray(copied, copied + length) : await readStagedPage(page, forwarded);
|
|
300
303
|
if (!bytes) {
|
|
301
304
|
bytes = new Uint8Array(chunkBytes);
|
|
302
|
-
|
|
303
|
-
bytes.set(await readBase(page * chunkBytes, chunkBytes, forwarded));
|
|
305
|
+
bytes.set(await readBase(page * chunkBytes, chunkBytes, forwarded));
|
|
304
306
|
}
|
|
305
|
-
|
|
307
|
+
if (!complete)
|
|
308
|
+
bytes.set(buffer.subarray(copied, copied + length), within);
|
|
306
309
|
await perform(forwarded, selected => state.staging.writePage(page, bytes, selected));
|
|
307
310
|
copied += length;
|
|
308
311
|
}
|
|
@@ -13,9 +13,10 @@ type Page = {
|
|
|
13
13
|
* filesystem and cleanup ownership. Serializes cache users through eviction IO. */
|
|
14
14
|
export declare class PagedStorageCache {
|
|
15
15
|
private readonly maxPages;
|
|
16
|
+
readonly pageBytes: number;
|
|
16
17
|
private readonly pages;
|
|
17
18
|
private active;
|
|
18
|
-
constructor(maxPages: number);
|
|
19
|
+
constructor(maxPages: number, pageBytes?: number);
|
|
19
20
|
get residentBytes(): number;
|
|
20
21
|
run<T>(action: () => Promise<T>): Promise<T>;
|
|
21
22
|
acquire(): Promise<Page>;
|
|
@@ -31,6 +32,7 @@ export declare class PagedStorage {
|
|
|
31
32
|
private readonly maxPages;
|
|
32
33
|
private readonly cache?;
|
|
33
34
|
private readonly pages;
|
|
35
|
+
private readonly pageBytes;
|
|
34
36
|
private readonly controller;
|
|
35
37
|
private readonly signal;
|
|
36
38
|
private descriptor;
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { FsError, isFsError } from "./contracts/errors.js";
|
|
2
|
-
const
|
|
2
|
+
const defaultPageBytes = 16 * 1024;
|
|
3
3
|
let serial = 0;
|
|
4
4
|
/** Shares one resident-page limit across stores, retaining each store's caller
|
|
5
5
|
* filesystem and cleanup ownership. Serializes cache users through eviction IO. */
|
|
6
6
|
export class PagedStorageCache {
|
|
7
7
|
maxPages;
|
|
8
|
+
pageBytes;
|
|
8
9
|
pages = new Map();
|
|
9
10
|
active = Promise.resolve();
|
|
10
|
-
constructor(maxPages) {
|
|
11
|
+
constructor(maxPages, pageBytes = defaultPageBytes) {
|
|
11
12
|
this.maxPages = maxPages;
|
|
13
|
+
this.pageBytes = pageBytes;
|
|
14
|
+
if (!Number.isSafeInteger(pageBytes) || pageBytes < defaultPageBytes || pageBytes > 1024 * 1024 || pageBytes % defaultPageBytes !== 0)
|
|
15
|
+
throw new RangeError("Invalid storage page size");
|
|
12
16
|
if (!Number.isSafeInteger(maxPages) || maxPages < 1)
|
|
13
17
|
throw new RangeError("Invalid storage page cache size");
|
|
14
18
|
}
|
|
15
|
-
get residentBytes() { return this.pages.size * pageBytes; }
|
|
19
|
+
get residentBytes() { return this.pages.size * this.pageBytes; }
|
|
16
20
|
run(action) {
|
|
17
21
|
const work = this.active.then(action);
|
|
18
22
|
this.active = work.then(() => undefined, () => undefined);
|
|
@@ -20,7 +24,7 @@ export class PagedStorageCache {
|
|
|
20
24
|
}
|
|
21
25
|
async acquire() {
|
|
22
26
|
if (this.pages.size < this.maxPages)
|
|
23
|
-
return { bytes: new Uint8Array(pageBytes), dirty: false };
|
|
27
|
+
return { bytes: new Uint8Array(this.pageBytes), dirty: false };
|
|
24
28
|
const [page, release] = this.pages.entries().next().value;
|
|
25
29
|
await release();
|
|
26
30
|
this.pages.delete(page);
|
|
@@ -46,6 +50,7 @@ export class PagedStorage {
|
|
|
46
50
|
maxPages;
|
|
47
51
|
cache;
|
|
48
52
|
pages = new Map();
|
|
53
|
+
pageBytes;
|
|
49
54
|
controller = new AbortController();
|
|
50
55
|
signal;
|
|
51
56
|
descriptor;
|
|
@@ -63,6 +68,7 @@ export class PagedStorage {
|
|
|
63
68
|
this.cache = cache;
|
|
64
69
|
if (!Number.isSafeInteger(maxPages) || maxPages < 1)
|
|
65
70
|
throw new RangeError("Invalid storage page cache size");
|
|
71
|
+
this.pageBytes = cache?.pageBytes ?? defaultPageBytes;
|
|
66
72
|
this.signal = AbortSignal.any([context.signal, this.controller.signal]);
|
|
67
73
|
}
|
|
68
74
|
allocate(length) {
|
|
@@ -129,15 +135,15 @@ export class PagedStorage {
|
|
|
129
135
|
if (!page.dirty)
|
|
130
136
|
return;
|
|
131
137
|
let offset = 0;
|
|
132
|
-
while (offset < pageBytes) {
|
|
138
|
+
while (offset < this.pageBytes) {
|
|
133
139
|
this.signal.throwIfAborted();
|
|
134
|
-
const count = await this.descriptor.write(page.bytes.subarray(offset), number * pageBytes + offset, { signal: this.signal });
|
|
140
|
+
const count = await this.descriptor.write(page.bytes.subarray(offset), number * this.pageBytes + offset, { signal: this.signal });
|
|
135
141
|
this.signal.throwIfAborted();
|
|
136
|
-
if (!Number.isSafeInteger(count) || count <= 0 || count > pageBytes - offset)
|
|
142
|
+
if (!Number.isSafeInteger(count) || count <= 0 || count > this.pageBytes - offset)
|
|
137
143
|
throw new FsError("EIO");
|
|
138
144
|
offset += count;
|
|
139
145
|
}
|
|
140
|
-
this.diskLength = Math.max(this.diskLength, (number + 1) * pageBytes);
|
|
146
|
+
this.diskLength = Math.max(this.diskLength, (number + 1) * this.pageBytes);
|
|
141
147
|
page.dirty = false;
|
|
142
148
|
}
|
|
143
149
|
async page(number) {
|
|
@@ -161,17 +167,17 @@ export class PagedStorage {
|
|
|
161
167
|
}
|
|
162
168
|
// Flush owns the bytes until its awaited write completes. Reuse only then;
|
|
163
169
|
// zero new/unwritten ranges so the previous page cannot leak into them.
|
|
164
|
-
const page = reusable ?? (this.cache ? await this.cache.acquire() : { bytes: new Uint8Array(pageBytes), dirty: false });
|
|
170
|
+
const page = reusable ?? (this.cache ? await this.cache.acquire() : { bytes: new Uint8Array(this.pageBytes), dirty: false });
|
|
165
171
|
// Shared eviction may await IO owned by a different, still-live signal.
|
|
166
172
|
this.signal.throwIfAborted();
|
|
167
173
|
if (reusable)
|
|
168
174
|
page.bytes.fill(0);
|
|
169
|
-
if (number * pageBytes < this.diskLength) {
|
|
175
|
+
if (number * this.pageBytes < this.diskLength) {
|
|
170
176
|
let offset = 0;
|
|
171
|
-
while (offset < pageBytes) {
|
|
172
|
-
const count = await this.descriptor.read(page.bytes.subarray(offset), number * pageBytes + offset, { signal: this.signal });
|
|
177
|
+
while (offset < this.pageBytes) {
|
|
178
|
+
const count = await this.descriptor.read(page.bytes.subarray(offset), number * this.pageBytes + offset, { signal: this.signal });
|
|
173
179
|
this.signal.throwIfAborted();
|
|
174
|
-
if (!Number.isSafeInteger(count) || count <= 0 || count > pageBytes - offset)
|
|
180
|
+
if (!Number.isSafeInteger(count) || count <= 0 || count > this.pageBytes - offset)
|
|
175
181
|
throw new FsError("EIO");
|
|
176
182
|
offset += count;
|
|
177
183
|
}
|
|
@@ -191,9 +197,9 @@ export class PagedStorage {
|
|
|
191
197
|
return this.operation(async () => {
|
|
192
198
|
for (let offset = 0; offset < bytes.length;) {
|
|
193
199
|
const target = position + offset;
|
|
194
|
-
const page = await this.page(Math.floor(target / pageBytes));
|
|
195
|
-
const count = Math.min(bytes.length - offset, pageBytes - target % pageBytes);
|
|
196
|
-
page.bytes.set(bytes.subarray(offset, offset + count), target % pageBytes);
|
|
200
|
+
const page = await this.page(Math.floor(target / this.pageBytes));
|
|
201
|
+
const count = Math.min(bytes.length - offset, this.pageBytes - target % this.pageBytes);
|
|
202
|
+
page.bytes.set(bytes.subarray(offset, offset + count), target % this.pageBytes);
|
|
197
203
|
page.dirty = true;
|
|
198
204
|
offset += count;
|
|
199
205
|
}
|
|
@@ -205,15 +211,15 @@ export class PagedStorage {
|
|
|
205
211
|
return position;
|
|
206
212
|
}
|
|
207
213
|
read(position, length) {
|
|
208
|
-
if (!Number.isSafeInteger(position) || position < 8 || !Number.isSafeInteger(length) || length < 0 || length >
|
|
214
|
+
if (!Number.isSafeInteger(position) || position < 8 || !Number.isSafeInteger(length) || length < 0 || length > defaultPageBytes || position + length > this.end)
|
|
209
215
|
throw new RangeError("Invalid paged storage range");
|
|
210
216
|
return this.operation(async () => {
|
|
211
217
|
const bytes = new Uint8Array(length);
|
|
212
218
|
for (let offset = 0; offset < length;) {
|
|
213
219
|
const source = position + offset;
|
|
214
|
-
const page = await this.page(Math.floor(source / pageBytes));
|
|
215
|
-
const count = Math.min(length - offset, pageBytes - source % pageBytes);
|
|
216
|
-
bytes.set(page.bytes.subarray(source % pageBytes, source % pageBytes + count), offset);
|
|
220
|
+
const page = await this.page(Math.floor(source / this.pageBytes));
|
|
221
|
+
const count = Math.min(length - offset, this.pageBytes - source % this.pageBytes);
|
|
222
|
+
bytes.set(page.bytes.subarray(source % this.pageBytes, source % this.pageBytes + count), offset);
|
|
217
223
|
offset += count;
|
|
218
224
|
}
|
|
219
225
|
return bytes;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.883",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
"saxes"
|
|
178
178
|
],
|
|
179
179
|
"optionalDependencies": {
|
|
180
|
-
"toolcraft-schema": "0.0.
|
|
180
|
+
"toolcraft-schema": "0.0.883",
|
|
181
181
|
"toolcraft-design": "*",
|
|
182
182
|
"@poe-code/frontmatter": "*",
|
|
183
183
|
"@poe-code/agent-mcp-config": "*",
|