toolcraft 0.0.881 → 0.0.882

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 CHANGED
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "name": "toolcraft",
141
- "version": "0.0.881",
141
+ "version": "0.0.882",
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.881",
151
+ "version": "0.0.882",
152
152
  "license": "MIT"
153
153
  },
154
154
  {
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "name": "toolcraft",
141
- "version": "0.0.881",
141
+ "version": "0.0.882",
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.881",
151
+ "version": "0.0.882",
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. The budget excludes caller buffers and backend storage.
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.
@@ -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 pageBytes = 16 * 1024;
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 > pageBytes || position + length > this.end)
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;
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.881",
11
+ "version": "0.0.882",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.881",
3
+ "version": "0.0.882",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.881",
3
+ "version": "0.0.882",
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.881",
180
+ "toolcraft-schema": "0.0.882",
181
181
  "toolcraft-design": "*",
182
182
  "@poe-code/frontmatter": "*",
183
183
  "@poe-code/agent-mcp-config": "*",