toolcraft 0.0.789 → 0.0.790

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.789",
141
+ "version": "0.0.790",
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.789",
151
+ "version": "0.0.790",
152
152
  "license": "MIT"
153
153
  },
154
154
  {
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "name": "toolcraft",
141
- "version": "0.0.789",
141
+ "version": "0.0.790",
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.789",
151
+ "version": "0.0.790",
152
152
  "license": "MIT"
153
153
  },
154
154
  {
@@ -9,7 +9,7 @@ export * from "./fs/webdav/index.js";
9
9
  export * from "./bridge/index.js";
10
10
  export * from "./python/index.js";
11
11
  export { parseXml, parseXmlSteps, parseXmlSourceSteps, normalizeXmlChunks, parseXmlStream, XmlLimitError } from "./xml.js";
12
- export type { XmlName, XmlAttribute, XmlContent, XmlElement, XmlLimits } from "./xml.js";
12
+ export type { XmlName, XmlAttribute, XmlContent, XmlElement, XmlLimits, XmlParserFrame, XmlFrameRequest } from "./xml.js";
13
13
  export * from "./contracts/object.js";
14
14
  export { ObjectAuthority } from "./fs/object-authority.js";
15
15
  export * from "./fs/s3/index.js";
@@ -54,3 +54,18 @@ limits)`: numbers are work checkpoints; read requests specify UTF-16 `offset` an
54
54
  iterator. The source must already be validated and normalized. This supports the
55
55
  same repairs and events without a complete source string; tokens and open ancestry
56
56
  still remain resident. Close the iterator and backing on errors or cancellation.
57
+
58
+ For tree-free external parsing, set `storeFrames: true` alongside `retainTree: false`.
59
+ The iterator also yields `frameOperation` requests: `push` saves the supplied frame,
60
+ `pop` removes the latest frame, and `peek` needs its `frame` filled with the saved
61
+ metadata. Keep the host stack in bounded backing storage. The parser caches one
62
+ current frame; individual frame metadata and namespace scopes still have their own
63
+ size cost. The default string and stream APIs retain their existing ancestry behavior.
64
+
65
+ Pass `undefined` as the source length to parse incrementally. Such read requests
66
+ have `streaming: true` and request at most 512 UTF-16 units. Return any nonempty
67
+ prefix available without pulling more input; set `complete: true` only when that
68
+ read reaches EOF (an empty EOF read is valid). Earlier offsets may be requested
69
+ again, so retain source spans in caller-backed storage. With `normalizeXmlChunks`,
70
+ pass `true` as the second argument to flush at input-chunk boundaries and preserve
71
+ early parser failures. The string parser and known-length protocol are unchanged.
@@ -1,3 +1,3 @@
1
1
  export declare function validCharacter(point: number): boolean;
2
2
  /** Validate scalar values and normalize XML line endings in fixed windows. */
3
- export declare function normalizeXmlChunks(source: AsyncIterable<string> | Iterable<string>): AsyncGenerator<string>;
3
+ export declare function normalizeXmlChunks(source: AsyncIterable<string> | Iterable<string>, flushChunks?: boolean): AsyncGenerator<string>;
@@ -5,7 +5,7 @@ export function validCharacter(point) {
5
5
  || (point >= 0x10000 && point <= 0x10ffff);
6
6
  }
7
7
  /** Validate scalar values and normalize XML line endings in fixed windows. */
8
- export async function* normalizeXmlChunks(source) {
8
+ export async function* normalizeXmlChunks(source, flushChunks = false) {
9
9
  let first = true, afterCR = false, high = 0, output = '';
10
10
  for await (const chunk of source) {
11
11
  for (let index = 0; index < chunk.length; index++) {
@@ -41,6 +41,10 @@ export async function* normalizeXmlChunks(source) {
41
41
  output = '';
42
42
  }
43
43
  }
44
+ if (flushChunks && output) {
45
+ yield output;
46
+ output = '';
47
+ }
44
48
  }
45
49
  if (high)
46
50
  throw new SyntaxError('Invalid XML: invalid character');
@@ -0,0 +1,28 @@
1
+ import type { XmlContent, XmlElement } from './index.js';
2
+ export interface XmlParserFrame {
3
+ element: XmlElement;
4
+ content: XmlContent[] | undefined;
5
+ name: string;
6
+ namespaces: Map<string, string>;
7
+ }
8
+ export type XmlFrameRequest = {
9
+ readonly frameOperation: 'push';
10
+ readonly frame: XmlParserFrame;
11
+ } | {
12
+ readonly frameOperation: 'pop';
13
+ } | {
14
+ readonly frameOperation: 'peek';
15
+ frame?: XmlParserFrame;
16
+ };
17
+ /** External mode caches only the current frame. The host owns the linked stack. */
18
+ export declare class XmlFrames {
19
+ private readonly external;
20
+ private readonly frames;
21
+ private depth;
22
+ private current;
23
+ constructor(external: boolean);
24
+ get length(): number;
25
+ peek(): Generator<XmlFrameRequest, XmlParserFrame | undefined, void>;
26
+ push(frame: XmlParserFrame): Generator<XmlFrameRequest, void, void>;
27
+ pop(): Generator<XmlFrameRequest, XmlParserFrame | undefined, void>;
28
+ }
@@ -0,0 +1,45 @@
1
+ /** External mode caches only the current frame. The host owns the linked stack. */
2
+ export class XmlFrames {
3
+ external;
4
+ frames = [];
5
+ depth = 0;
6
+ current;
7
+ constructor(external) {
8
+ this.external = external;
9
+ }
10
+ get length() { return this.external ? this.depth : this.frames.length; }
11
+ *peek() {
12
+ if (!this.external)
13
+ return this.frames.at(-1);
14
+ if (!this.depth)
15
+ return undefined;
16
+ if (!this.current) {
17
+ const request = { frameOperation: 'peek' };
18
+ yield request;
19
+ if (!request.frame)
20
+ throw new TypeError('Incomplete XML frame read');
21
+ this.current = request.frame;
22
+ }
23
+ return this.current;
24
+ }
25
+ *push(frame) {
26
+ if (!this.external) {
27
+ this.frames.push(frame);
28
+ return;
29
+ }
30
+ yield { frameOperation: 'push', frame };
31
+ this.current = frame;
32
+ this.depth++;
33
+ }
34
+ *pop() {
35
+ if (!this.external)
36
+ return this.frames.pop();
37
+ if (!this.depth)
38
+ return undefined;
39
+ const frame = yield* this.peek();
40
+ yield { frameOperation: 'pop' };
41
+ this.depth--;
42
+ this.current = undefined;
43
+ return frame;
44
+ }
45
+ }
@@ -1,3 +1,5 @@
1
+ import { type XmlFrameRequest } from "./frames.js";
2
+ export type { XmlParserFrame, XmlFrameRequest } from "./frames.js";
1
3
  export { normalizeXmlChunks } from "./characters.js";
2
4
  import { type XmlSourceStep } from "./source.js";
3
5
  export type { XmlSourceRead, XmlSourceStep } from "./source.js";
@@ -51,8 +53,16 @@ export interface XmlStepLimits extends XmlLimits {
51
53
  * false. Drive the step iterator to await external consumers between windows. */
52
54
  readonly events?: (event: XmlStreamEvent) => void;
53
55
  }
56
+ export interface XmlSourceLimits extends XmlStepLimits {
57
+ /** Store active parser frames through host-serviced requests. Requires retainTree: false. */
58
+ readonly storeFrames?: boolean;
59
+ }
60
+ export type XmlParseStep = XmlSourceStep | XmlFrameRequest;
54
61
  /** Parse a validated, BOM-free, line-normalized UTF-16 source via bounded read requests. */
55
- export declare function parseXmlSourceSteps(length: number, limits?: XmlStepLimits): Generator<XmlSourceStep, XmlElement, void>;
62
+ export declare function parseXmlSourceSteps(length: number | undefined, limits?: XmlStepLimits & {
63
+ readonly storeFrames?: false;
64
+ }): Generator<XmlSourceStep, XmlElement, void>;
65
+ export declare function parseXmlSourceSteps(length: number | undefined, limits: XmlSourceLimits): Generator<XmlParseStep, XmlElement, void>;
56
66
  export declare function parseXmlSteps(input: string, limits?: XmlStepLimits): Generator<number, XmlElement, void>;
57
67
  export declare function parseXml(input: string, limits?: XmlLimits): XmlElement;
58
68
  export { parseXmlStream, type XmlStreamLimits, type XmlStreamEvent } from "./stream.js";
@@ -1,3 +1,4 @@
1
+ import { XmlFrames } from "./frames.js";
1
2
  import { validCharacter } from "./characters.js";
2
3
  export { normalizeXmlChunks } from "./characters.js";
3
4
  import { XmlSource } from "./source.js";
@@ -189,9 +190,10 @@ function validateLimits(limits) {
189
190
  if (limits.events && limits.retainTree !== false)
190
191
  throw new TypeError("XML events require retainTree: false");
191
192
  }
192
- /** Parse a validated, BOM-free, line-normalized UTF-16 source via bounded read requests. */
193
193
  export function* parseXmlSourceSteps(length, limits = {}) {
194
194
  validateLimits(limits);
195
+ if (limits.storeFrames && limits.retainTree !== false)
196
+ throw new TypeError("Stored XML frames require retainTree: false");
195
197
  const source = new XmlSource(length);
196
198
  const maxDepth = limits.maxDepth ?? Infinity;
197
199
  const maxNodes = limits.maxNodes ?? Infinity;
@@ -212,7 +214,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
212
214
  throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
213
215
  textLength += text.length;
214
216
  };
215
- const stack = [];
217
+ const stack = new XmlFrames(limits.storeFrames === true);
216
218
  let root;
217
219
  const prolog = [];
218
220
  const epilog = [];
@@ -230,7 +232,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
230
232
  let pendingWork = 0;
231
233
  const skipWhitespace = function* () {
232
234
  const start = offset;
233
- while (offset < source.length) {
235
+ while (yield* source.has(offset)) {
234
236
  const c = (yield* source.charCodeAt(offset));
235
237
  if (c !== 32 && c !== 9 && c !== 10 && c !== 13)
236
238
  break;
@@ -245,7 +247,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
245
247
  };
246
248
  const scanName = function* () {
247
249
  const start = offset;
248
- while (offset < source.length) {
250
+ while (yield* source.has(offset)) {
249
251
  const c = (yield* source.charCodeAt(offset));
250
252
  if (c === 32 || c === 9 || c === 13 || c === 10 || c === 47 || c === 61 || c === 62 || c === 63)
251
253
  break;
@@ -265,7 +267,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
265
267
  const [prefix, localName] = qualifiedNameSync(name, qualifiedNames);
266
268
  return [name, prefix, localName];
267
269
  };
268
- while (offset < source.length) {
270
+ while (yield* source.has(offset)) {
269
271
  pendingWork += 1;
270
272
  if (pendingWork >= 512) {
271
273
  while (pendingWork >= 512) {
@@ -290,7 +292,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
290
292
  pendingWork -= 512;
291
293
  }
292
294
  admitText(resolved);
293
- const parent = stack.at(-1);
295
+ const parent = (yield* stack.peek());
294
296
  if (parent) {
295
297
  if (retainTree)
296
298
  parent.element.text += resolved;
@@ -331,7 +333,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
331
333
  if (end < 0 || (yield* find(text, "--", 0)) >= 0 || text.endsWith("-")) {
332
334
  invalid("malformed comment");
333
335
  }
334
- const parent = stack.at(-1);
336
+ const parent = (yield* stack.peek());
335
337
  admitText(text);
336
338
  admitContent();
337
339
  if (retain) {
@@ -348,7 +350,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
348
350
  invalid("unterminated CDATA");
349
351
  const cdataText = (yield* source.slice(offset + 9, end));
350
352
  admitText(cdataText);
351
- const parent = stack.at(-1);
353
+ const parent = (yield* stack.peek());
352
354
  if (retainTree)
353
355
  parent.element.text += cdataText;
354
356
  admitContent();
@@ -380,7 +382,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
380
382
  else if (content && !" \t\n\r".includes(content[0]))
381
383
  invalid("invalid processing instruction");
382
384
  if (!(target.length === 3 && target.toLowerCase() === "xml")) {
383
- const parent = stack.at(-1);
385
+ const parent = (yield* stack.peek());
384
386
  let wsStart = 0;
385
387
  while (wsStart < content.length && " \t\n\r".includes(content[wsStart]))
386
388
  wsStart++;
@@ -410,14 +412,14 @@ export function* parseXmlSourceSteps(length, limits = {}) {
410
412
  yield 512;
411
413
  pendingWork -= 512;
412
414
  }
413
- const closed = (yield* source.slice(offset++, offset)) === ">" ? stack.pop() : undefined;
415
+ const closed = (yield* source.slice(offset++, offset)) === ">" ? (yield* stack.pop()) : undefined;
414
416
  if (closed?.name !== name) {
415
417
  if (!limits.recover)
416
418
  invalid("mismatched closing tag");
417
419
  limits.recover("mismatched closing tag");
418
420
  }
419
421
  if (closed)
420
- limits.events?.({ type: "close", element: closed.element, parent: stack.at(-1)?.element });
422
+ limits.events?.({ type: "close", element: closed.element, parent: (yield* stack.peek())?.element });
421
423
  }
422
424
  else {
423
425
  offset++;
@@ -436,7 +438,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
436
438
  }
437
439
  let attributes;
438
440
  const attrMeta = [];
439
- let namespaces = stack.at(-1)?.namespaces ?? new Map([["xml", xmlNamespace]]);
441
+ let namespaces = (yield* stack.peek())?.namespaces ?? new Map([["xml", xmlNamespace]]);
440
442
  let ownsNamespaces = stack.length === 0;
441
443
  while (!repeated) {
442
444
  const ws = (yield* skipWhitespace());
@@ -543,7 +545,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
543
545
  if (stack.length + 1 > maxDepth)
544
546
  throw new XmlLimitError("maxDepth", "XML resource limit exceeded");
545
547
  const namespace = namespaces.get(prefix) ?? "";
546
- limits.onElement?.({ name, namespace, localName }, stack.at(-1)?.element, stack.length + 1);
548
+ limits.onElement?.({ name, namespace, localName }, (yield* stack.peek())?.element, stack.length + 1);
547
549
  admitContent();
548
550
  const retainedAttributes = [];
549
551
  if (retainContent && attributes) {
@@ -562,7 +564,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
562
564
  }
563
565
  const content = retain ? [] : undefined;
564
566
  const element = { kind: "element", name, namespace, localName, children: [], text: "", content: content ?? emptyContent, attributes: retainContent ? retainedAttributes : emptyAttributes, namespaces: retainContent ? namespaces : emptyNamespaces, ...(root === undefined && retainContent ? { prolog, epilog, ...(declaration === undefined ? {} : { declaration }) } : {}) };
565
- const parent = stack.at(-1);
567
+ const parent = (yield* stack.peek());
566
568
  if (parent) {
567
569
  if (retainTree) {
568
570
  parent.element.children.push(element);
@@ -587,7 +589,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
587
589
  previousEmpty = { suffix: name + "/>", name, prefix, localName };
588
590
  limits.events?.({ type: "open", element, parent: parent?.element });
589
591
  if (!empty)
590
- stack.push({ element, content, name, namespaces });
592
+ yield* stack.push({ element, content, name, namespaces });
591
593
  else
592
594
  limits.events?.({ type: "close", element, parent: parent?.element });
593
595
  }
@@ -603,8 +605,8 @@ export function* parseXmlSourceSteps(length, limits = {}) {
603
605
  invalid("incomplete document");
604
606
  limits.recover("incomplete document");
605
607
  while (stack.length) {
606
- const closed = stack.pop();
607
- limits.events?.({ type: "close", element: closed.element, parent: stack.at(-1)?.element });
608
+ const closed = (yield* stack.pop());
609
+ limits.events?.({ type: "close", element: closed.element, parent: (yield* stack.peek())?.element });
608
610
  yield 1;
609
611
  }
610
612
  }
@@ -1,16 +1,22 @@
1
- /** Fill value with exactly the requested UTF-16 span before advancing the parser. */
1
+ /** Fill value before advancing. Streaming reads may be short; mark complete at EOF. */
2
2
  export interface XmlSourceRead {
3
3
  readonly offset: number;
4
4
  readonly length: number;
5
+ readonly streaming?: boolean;
5
6
  value?: string;
7
+ complete?: boolean;
6
8
  }
7
9
  export type XmlSourceStep = number | XmlSourceRead;
8
10
  /** One fixed window; individual returned tokens still have their own size cost. */
9
11
  export declare class XmlSource {
10
- readonly length: number;
11
12
  private window;
12
13
  private start;
13
- constructor(length: number);
14
+ private end;
15
+ private readonly streaming;
16
+ constructor(length: number | undefined);
17
+ get length(): number;
18
+ private load;
19
+ has(offset: number): Generator<XmlSourceStep, boolean, void>;
14
20
  slice(start: number, end?: number): Generator<XmlSourceStep, string, void>;
15
21
  charCodeAt(offset: number): Generator<XmlSourceStep, number, void>;
16
22
  startsWith(value: string, offset: number): Generator<XmlSourceStep, boolean, void>;
@@ -1,27 +1,44 @@
1
1
  /** One fixed window; individual returned tokens still have their own size cost. */
2
2
  export class XmlSource {
3
- length;
4
3
  window = '';
5
4
  start = -1;
5
+ end;
6
+ streaming;
6
7
  constructor(length) {
7
- this.length = length;
8
- if (!Number.isSafeInteger(length) || length < 0)
9
- throw new RangeError("Invalid XML source length");
8
+ if (length !== undefined && (!Number.isSafeInteger(length) || length < 0))
9
+ throw new RangeError('Invalid XML source length');
10
+ this.streaming = length === undefined;
11
+ this.end = length ?? Infinity;
12
+ }
13
+ get length() { return this.end; }
14
+ *load(offset) {
15
+ if (offset >= this.end || offset >= this.start && offset < this.start + this.window.length)
16
+ return;
17
+ const start = this.streaming ? offset : Math.floor(offset / 4096) * 4096;
18
+ const request = { offset: start, length: Math.min(this.streaming ? 512 : 4096, this.end - start),
19
+ ...(this.streaming ? { streaming: true } : {}) };
20
+ yield request;
21
+ if (typeof request.value !== 'string' || request.value.length > request.length ||
22
+ (!this.streaming && request.value.length !== request.length) ||
23
+ (!request.value.length && !request.complete && this.streaming))
24
+ throw new TypeError('Incomplete XML source read');
25
+ if (this.streaming && request.complete)
26
+ this.end = start + request.value.length;
27
+ this.window = request.value;
28
+ this.start = start;
29
+ }
30
+ *has(offset) {
31
+ yield* this.load(offset);
32
+ return offset < this.end;
10
33
  }
11
34
  *slice(start, end = this.length) {
12
35
  start = start < 0 ? Math.max(0, this.length + start) : Math.min(start, this.length);
13
36
  end = end < 0 ? Math.max(0, this.length + end) : Math.min(end, this.length);
14
37
  let result = '';
15
- while (start < end) {
16
- if (start < this.start || start >= this.start + this.window.length) {
17
- const offset = Math.floor(start / 4096) * 4096;
18
- const read = { offset, length: Math.min(4096, this.length - offset) };
19
- yield read;
20
- if (typeof read.value !== 'string' || read.value.length !== read.length)
21
- throw new TypeError('Incomplete XML source read');
22
- this.window = read.value;
23
- this.start = offset;
24
- }
38
+ while (start < Math.min(end, this.length)) {
39
+ yield* this.load(start);
40
+ if (start >= this.end)
41
+ break;
25
42
  const stop = Math.min(end, this.start + this.window.length);
26
43
  result += this.window.slice(start - this.start, stop - this.start);
27
44
  start = stop;
@@ -32,15 +49,21 @@ export class XmlSource {
32
49
  return (yield* this.slice(offset, offset + 1)).charCodeAt(0);
33
50
  }
34
51
  *startsWith(value, offset) {
35
- return (yield* this.slice(offset, offset + value.length)) === value;
52
+ for (let index = 0; index < value.length; index++) {
53
+ if ((yield* this.charCodeAt(offset + index)) !== value.charCodeAt(index))
54
+ return false;
55
+ }
56
+ return true;
36
57
  }
37
58
  *indexOf(value, offset) {
38
- while (offset < this.length) {
39
- const end = Math.min(this.length, offset + 4096 + value.length - 1);
40
- const found = (yield* this.slice(offset, end)).indexOf(value);
59
+ let tail = '';
60
+ while (yield* this.has(offset)) {
61
+ const part = tail + this.window.slice(offset - this.start);
62
+ const found = part.indexOf(value);
41
63
  if (found >= 0)
42
- return offset + found;
43
- offset += 4096;
64
+ return offset - tail.length + found;
65
+ tail = value.length > 1 ? part.slice(-(value.length - 1)) : '';
66
+ offset = this.start + this.window.length;
44
67
  }
45
68
  return -1;
46
69
  }
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.789",
11
+ "version": "0.0.790",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.789",
3
+ "version": "0.0.790",
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.789",
3
+ "version": "0.0.790",
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.789",
180
+ "toolcraft-schema": "0.0.790",
181
181
  "toolcraft-design": "*",
182
182
  "@poe-code/frontmatter": "*",
183
183
  "@poe-code/agent-mcp-config": "*",