toolcraft 0.0.788 → 0.0.789

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.788",
141
+ "version": "0.0.789",
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.788",
151
+ "version": "0.0.789",
152
152
  "license": "MIT"
153
153
  },
154
154
  {
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "name": "toolcraft",
141
- "version": "0.0.788",
141
+ "version": "0.0.789",
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.788",
151
+ "version": "0.0.789",
152
152
  "license": "MIT"
153
153
  },
154
154
  {
@@ -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, parseXmlStream, XmlLimitError } from "./xml.js";
11
+ export { parseXml, parseXmlSteps, parseXmlSourceSteps, normalizeXmlChunks, 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, parseXmlStream, XmlLimitError } from "./xml.js";
11
+ export { parseXml, parseXmlSteps, parseXmlSourceSteps, normalizeXmlChunks, 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";
@@ -31,7 +31,7 @@ Resource limits and the prohibition on DTDs remain in force. With `retainTree: f
31
31
  repaired closing events. Drain events between generator checkpoints to await
32
32
  external storage; the source string and open parser frames remain resident.
33
33
 
34
- `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.
34
+ `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 is available through `parseXmlSteps` or the externally backed source API.
35
35
 
36
36
  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.
37
37
 
@@ -45,3 +45,12 @@ window awaits the consumer before advancing. Event elements contain names,
45
45
  attributes and namespace metadata; individual XML tokens and open ancestry still
46
46
  remain resident. This mode lets a caller own document storage independently of
47
47
  the codec. Consumer failures propagate unchanged and close the input iterator.
48
+
49
+ `normalizeXmlChunks(chunks)` validates XML characters and normalizes BOM/line endings
50
+ in windows of at most 513 UTF-16 units, preserving surrogate pairs across chunks.
51
+ Store those windows in caller-owned backing and drive `parseXmlSourceSteps(length,
52
+ limits)`: numbers are work checkpoints; read requests specify UTF-16 `offset` and
53
+ `length` (at most 4096 units). Fill each request's `value` before advancing the
54
+ iterator. The source must already be validated and normalized. This supports the
55
+ same repairs and events without a complete source string; tokens and open ancestry
56
+ still remain resident. Close the iterator and backing on errors or cancellation.
@@ -0,0 +1,3 @@
1
+ export declare function validCharacter(point: number): boolean;
2
+ /** Validate scalar values and normalize XML line endings in fixed windows. */
3
+ export declare function normalizeXmlChunks(source: AsyncIterable<string> | Iterable<string>): AsyncGenerator<string>;
@@ -0,0 +1,49 @@
1
+ export function validCharacter(point) {
2
+ return point === 9 || point === 10 || point === 13
3
+ || (point >= 0x20 && point <= 0xd7ff)
4
+ || (point >= 0xe000 && point <= 0xfffd)
5
+ || (point >= 0x10000 && point <= 0x10ffff);
6
+ }
7
+ /** Validate scalar values and normalize XML line endings in fixed windows. */
8
+ export async function* normalizeXmlChunks(source) {
9
+ let first = true, afterCR = false, high = 0, output = '';
10
+ for await (const chunk of source) {
11
+ for (let index = 0; index < chunk.length; index++) {
12
+ const code = chunk.charCodeAt(index);
13
+ if (first) {
14
+ first = false;
15
+ if (code === 0xfeff)
16
+ continue;
17
+ }
18
+ if (high) {
19
+ if (code < 0xdc00 || code > 0xdfff)
20
+ throw new SyntaxError('Invalid XML: invalid character');
21
+ output += String.fromCharCode(high, code);
22
+ high = 0;
23
+ }
24
+ else {
25
+ if (afterCR) {
26
+ afterCR = false;
27
+ if (code === 10)
28
+ continue;
29
+ }
30
+ if (code >= 0xd800 && code <= 0xdbff) {
31
+ high = code;
32
+ continue;
33
+ }
34
+ if (!validCharacter(code))
35
+ throw new SyntaxError('Invalid XML: invalid character');
36
+ afterCR = code === 13;
37
+ output += code === 13 ? '\n' : chunk[index];
38
+ }
39
+ if (output.length >= 512) {
40
+ yield output;
41
+ output = '';
42
+ }
43
+ }
44
+ }
45
+ if (high)
46
+ throw new SyntaxError('Invalid XML: invalid character');
47
+ if (output)
48
+ yield output;
49
+ }
@@ -1,3 +1,6 @@
1
+ export { normalizeXmlChunks } from "./characters.js";
2
+ import { type XmlSourceStep } from "./source.js";
3
+ export type { XmlSourceRead, XmlSourceStep } from "./source.js";
1
4
  import type { XmlStreamEvent } from "./stream.js";
2
5
  export { XmlLimitError } from "./errors.js";
3
6
  export interface XmlName {
@@ -48,6 +51,8 @@ export interface XmlStepLimits extends XmlLimits {
48
51
  * false. Drive the step iterator to await external consumers between windows. */
49
52
  readonly events?: (event: XmlStreamEvent) => void;
50
53
  }
54
+ /** 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>;
51
56
  export declare function parseXmlSteps(input: string, limits?: XmlStepLimits): Generator<number, XmlElement, void>;
52
57
  export declare function parseXml(input: string, limits?: XmlLimits): XmlElement;
53
58
  export { parseXmlStream, type XmlStreamLimits, type XmlStreamEvent } from "./stream.js";
@@ -1,7 +1,10 @@
1
+ import { validCharacter } from "./characters.js";
2
+ export { normalizeXmlChunks } from "./characters.js";
3
+ import { XmlSource } from "./source.js";
1
4
  import { XmlLimitError } from "./errors.js";
2
5
  export { XmlLimitError } from "./errors.js";
3
6
  function* find(source, needle, start) {
4
- const found = source.indexOf(needle, start);
7
+ const found = typeof source === "string" ? source.indexOf(needle, start) : yield* source.indexOf(needle, start);
5
8
  const end = found < 0 ? source.length : found;
6
9
  let remaining = end - start;
7
10
  while (remaining >= 512) {
@@ -17,12 +20,6 @@ const xmlnsNamespace = "http://www.w3.org/2000/xmlns/";
17
20
  function invalid(message) {
18
21
  throw new SyntaxError(`Invalid XML: ${message}`);
19
22
  }
20
- function validCharacter(point) {
21
- return point === 9 || point === 10 || point === 13
22
- || (point >= 0x20 && point <= 0xd7ff)
23
- || (point >= 0xe000 && point <= 0xfffd)
24
- || (point >= 0x10000 && point <= 0x10ffff);
25
- }
26
23
  function nameStart(point) {
27
24
  return point === 95 || (point >= 65 && point <= 90) || (point >= 97 && point <= 122)
28
25
  || (point >= 0xc0 && point <= 0xd6) || (point >= 0xd8 && point <= 0xf6)
@@ -182,71 +179,39 @@ function* validDeclaration(content, expectedEncoding) {
182
179
  }
183
180
  return offset === content.length;
184
181
  }
185
- export function* parseXmlSteps(input, limits = {}) {
182
+ function validateLimits(limits) {
183
+ for (const limit of [limits.maxDepth, limits.maxNodes, limits.maxAttributes, limits.maxContentNodes, limits.maxAttributesPerElement, limits.maxNamespaces]) {
184
+ if (limit !== undefined && ((limit !== Infinity && !Number.isSafeInteger(limit)) || limit < 1))
185
+ throw new RangeError("XML limits must be positive integers");
186
+ }
187
+ if (limits.maxTextLength !== undefined && ((limits.maxTextLength !== Infinity && !Number.isSafeInteger(limits.maxTextLength)) || limits.maxTextLength < 1))
188
+ throw new RangeError("XML limits must be positive integers");
189
+ if (limits.events && limits.retainTree !== false)
190
+ throw new TypeError("XML events require retainTree: false");
191
+ }
192
+ /** Parse a validated, BOM-free, line-normalized UTF-16 source via bounded read requests. */
193
+ export function* parseXmlSourceSteps(length, limits = {}) {
194
+ validateLimits(limits);
195
+ const source = new XmlSource(length);
186
196
  const maxDepth = limits.maxDepth ?? Infinity;
187
197
  const maxNodes = limits.maxNodes ?? Infinity;
188
198
  const maxAttributes = limits.maxAttributes ?? Infinity;
189
199
  const retainContent = limits.retainContent !== false;
190
200
  const retainTree = limits.retainTree !== false;
191
201
  const retain = retainTree && retainContent;
192
- if (limits.events && retainTree)
193
- throw new TypeError("XML events require retainTree: false");
194
202
  const maxContentNodes = limits.maxContentNodes ?? Infinity;
195
203
  const emptyContent = Object.freeze([]);
196
204
  const emptyAttributes = Object.freeze([]);
197
205
  const emptyNamespaces = new Map();
198
206
  const maxAttributesPerElement = limits.maxAttributesPerElement ?? Infinity;
199
207
  const maxNamespaces = limits.maxNamespaces ?? Infinity;
200
- for (const limit of [limits.maxDepth, limits.maxNodes, limits.maxAttributes, limits.maxContentNodes, limits.maxAttributesPerElement, limits.maxNamespaces]) {
201
- if (limit !== undefined && ((limit !== Infinity && !Number.isSafeInteger(limit)) || limit < 1))
202
- throw new RangeError("XML limits must be positive integers");
203
- }
204
208
  const maxTextLength = limits.maxTextLength ?? Infinity;
205
- if (limits.maxTextLength !== undefined && ((maxTextLength !== Infinity && !Number.isSafeInteger(maxTextLength)) || maxTextLength < 1))
206
- throw new RangeError("XML limits must be positive integers");
207
209
  let textLength = 0;
208
210
  const admitText = (text) => {
209
211
  if (text.length > maxTextLength - textLength)
210
212
  throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
211
213
  textLength += text.length;
212
214
  };
213
- const chunks = [];
214
- const start = input.charCodeAt(0) === 0xfeff ? 1 : 0;
215
- let chunkStart = start;
216
- let chunkLength = 0;
217
- let normalizedChunk = "";
218
- // Keep validated spans intact; only changed line endings need new strings.
219
- for (let index = start; index < input.length; index++) {
220
- const point = input.codePointAt(index);
221
- if (!validCharacter(point))
222
- invalid("invalid character");
223
- if (point === 13) {
224
- normalizedChunk += input.slice(chunkStart, index) + "\n";
225
- if (input.charCodeAt(index + 1) === 10)
226
- index++;
227
- chunkStart = index + 1;
228
- chunkLength++;
229
- }
230
- else {
231
- chunkLength += point > 0xffff ? 2 : 1;
232
- if (point > 0xffff)
233
- index++;
234
- }
235
- if (chunkLength >= 512) {
236
- if (normalizedChunk) {
237
- chunks.push(normalizedChunk + input.slice(chunkStart, index + 1));
238
- normalizedChunk = "";
239
- chunkStart = index + 1;
240
- }
241
- chunkLength = 0;
242
- yield 512;
243
- }
244
- }
245
- if (chunkLength)
246
- yield chunkLength;
247
- if (chunks.length || normalizedChunk)
248
- chunks.push(normalizedChunk + input.slice(chunkStart));
249
- const source = chunks.length ? chunks.join("") : input.slice(start);
250
215
  const stack = [];
251
216
  let root;
252
217
  const prolog = [];
@@ -266,7 +231,7 @@ export function* parseXmlSteps(input, limits = {}) {
266
231
  const skipWhitespace = function* () {
267
232
  const start = offset;
268
233
  while (offset < source.length) {
269
- const c = source.charCodeAt(offset);
234
+ const c = (yield* source.charCodeAt(offset));
270
235
  if (c !== 32 && c !== 9 && c !== 10 && c !== 13)
271
236
  break;
272
237
  offset++;
@@ -281,7 +246,7 @@ export function* parseXmlSteps(input, limits = {}) {
281
246
  const scanName = function* () {
282
247
  const start = offset;
283
248
  while (offset < source.length) {
284
- const c = source.charCodeAt(offset);
249
+ const c = (yield* source.charCodeAt(offset));
285
250
  if (c === 32 || c === 9 || c === 13 || c === 10 || c === 47 || c === 61 || c === 62 || c === 63)
286
251
  break;
287
252
  offset++;
@@ -291,7 +256,7 @@ export function* parseXmlSteps(input, limits = {}) {
291
256
  pendingWork -= 512;
292
257
  }
293
258
  }
294
- const name = source.slice(start, offset);
259
+ const name = (yield* source.slice(start, offset));
295
260
  pendingWork += offset - start;
296
261
  while (pendingWork >= 512) {
297
262
  yield 512;
@@ -308,10 +273,10 @@ export function* parseXmlSteps(input, limits = {}) {
308
273
  pendingWork -= 512;
309
274
  }
310
275
  }
311
- if (source.charCodeAt(offset) !== 60) {
312
- const next = source.indexOf("<", offset);
276
+ if ((yield* source.charCodeAt(offset)) !== 60) {
277
+ const next = (yield* source.indexOf("<", offset));
313
278
  const endPos = next < 0 ? source.length : next;
314
- const text = source.slice(offset, endPos);
279
+ const text = (yield* source.slice(offset, endPos));
315
280
  pendingWork += (endPos - offset) + text.length;
316
281
  while (pendingWork >= 512) {
317
282
  yield 512;
@@ -360,13 +325,13 @@ export function* parseXmlSteps(input, limits = {}) {
360
325
  }
361
326
  offset = endPos;
362
327
  }
363
- else if (source.startsWith("<!--", offset)) {
328
+ else if ((yield* source.startsWith("<!--", offset))) {
364
329
  const end = yield* find(source, "-->", offset + 4);
365
- if (end < 0 || (yield* find(source.slice(offset + 4, end), "--", 0)) >= 0 || source.slice(offset + 4, end).endsWith("-")) {
330
+ const text = end < 0 ? "" : yield* source.slice(offset + 4, end);
331
+ if (end < 0 || (yield* find(text, "--", 0)) >= 0 || text.endsWith("-")) {
366
332
  invalid("malformed comment");
367
333
  }
368
334
  const parent = stack.at(-1);
369
- const text = source.slice(offset + 4, end);
370
335
  admitText(text);
371
336
  admitContent();
372
337
  if (retain) {
@@ -375,13 +340,13 @@ export function* parseXmlSteps(input, limits = {}) {
375
340
  limits.events?.({ type: "content", content: { kind: "comment", text }, parent: parent?.element });
376
341
  offset = end + 3;
377
342
  }
378
- else if (source.startsWith("<![CDATA[", offset)) {
343
+ else if ((yield* source.startsWith("<![CDATA[", offset))) {
379
344
  if (!stack.length)
380
345
  invalid("CDATA outside root");
381
346
  const end = yield* find(source, "]]>", offset + 9);
382
347
  if (end < 0)
383
348
  invalid("unterminated CDATA");
384
- const cdataText = source.slice(offset + 9, end);
349
+ const cdataText = (yield* source.slice(offset + 9, end));
385
350
  admitText(cdataText);
386
351
  const parent = stack.at(-1);
387
352
  if (retainTree)
@@ -392,7 +357,7 @@ export function* parseXmlSteps(input, limits = {}) {
392
357
  limits.events?.({ type: "content", content: { kind: "cdata", text: cdataText }, parent: parent.element });
393
358
  offset = end + 3;
394
359
  }
395
- else if (source.startsWith("<?", offset)) {
360
+ else if ((yield* source.startsWith("<?", offset))) {
396
361
  const start = offset;
397
362
  offset += 2;
398
363
  const [target] = yield* scanName();
@@ -403,14 +368,14 @@ export function* parseXmlSteps(input, limits = {}) {
403
368
  const end = yield* find(source, "?>", offset);
404
369
  if (end < 0)
405
370
  invalid("unterminated processing instruction");
406
- const content = source.slice(offset, end);
371
+ const content = (yield* source.slice(offset, end));
407
372
  if (target.length === 3 && target.toLowerCase() === "xml") {
408
373
  if (start !== 0 || target !== "xml"
409
374
  || !(yield* validDeclaration(content, limits.expectedEncoding))) {
410
375
  invalid("unsupported XML declaration");
411
376
  }
412
377
  if (retainContent)
413
- declaration = source.slice(start, end + 2);
378
+ declaration = (yield* source.slice(start, end + 2));
414
379
  }
415
380
  else if (content && !" \t\n\r".includes(content[0]))
416
381
  invalid("invalid processing instruction");
@@ -434,10 +399,10 @@ export function* parseXmlSteps(input, limits = {}) {
434
399
  }
435
400
  offset = end + 2;
436
401
  }
437
- else if (source.startsWith("<!", offset)) {
402
+ else if ((yield* source.startsWith("<!", offset))) {
438
403
  invalid("DTD and entity declarations are forbidden");
439
404
  }
440
- else if (source.charCodeAt(offset + 1) === 47) {
405
+ else if ((yield* source.charCodeAt(offset + 1)) === 47) {
441
406
  offset += 2;
442
407
  const [name] = yield* scanName();
443
408
  yield* skipWhitespace();
@@ -445,7 +410,7 @@ export function* parseXmlSteps(input, limits = {}) {
445
410
  yield 512;
446
411
  pendingWork -= 512;
447
412
  }
448
- const closed = source[offset++] === ">" ? stack.pop() : undefined;
413
+ const closed = (yield* source.slice(offset++, offset)) === ">" ? stack.pop() : undefined;
449
414
  if (closed?.name !== name) {
450
415
  if (!limits.recover)
451
416
  invalid("mismatched closing tag");
@@ -456,7 +421,7 @@ export function* parseXmlSteps(input, limits = {}) {
456
421
  }
457
422
  else {
458
423
  offset++;
459
- const repeated = previousEmpty && source.startsWith(previousEmpty.suffix, offset) ? previousEmpty : undefined;
424
+ const repeated = previousEmpty && (yield* source.startsWith(previousEmpty.suffix, offset)) ? previousEmpty : undefined;
460
425
  const [name, prefix, localName] = repeated
461
426
  ? [repeated.name, repeated.prefix, repeated.localName]
462
427
  : (yield* scanName());
@@ -479,7 +444,7 @@ export function* parseXmlSteps(input, limits = {}) {
479
444
  yield 512;
480
445
  pendingWork -= 512;
481
446
  }
482
- const ch = source.charCodeAt(offset);
447
+ const ch = (yield* source.charCodeAt(offset));
483
448
  if (ch === 47 || ch === 62 || limits.recover && offset === source.length)
484
449
  break;
485
450
  if (ws === 0)
@@ -496,13 +461,13 @@ export function* parseXmlSteps(input, limits = {}) {
496
461
  if ((attributes?.size ?? 0) >= maxAttributesPerElement)
497
462
  throw new XmlLimitError("maxAttributesPerElement", "XML attribute limit exceeded");
498
463
  yield* skipWhitespace();
499
- if (source[offset++] !== "=")
464
+ if ((yield* source.slice(offset++, offset)) !== "=")
500
465
  invalid("missing attribute equals");
501
466
  yield* skipWhitespace();
502
- const quote = source[offset++];
467
+ const quote = (yield* source.slice(offset++, offset));
503
468
  if (quote !== '"' && quote !== "'")
504
469
  invalid("unquoted attribute");
505
- const end = source.indexOf(quote, offset);
470
+ const end = (yield* source.indexOf(quote, offset));
506
471
  const scanLen = (end < 0 ? source.length : end) - offset;
507
472
  pendingWork += scanLen;
508
473
  while (pendingWork >= 512) {
@@ -511,7 +476,7 @@ export function* parseXmlSteps(input, limits = {}) {
511
476
  }
512
477
  if (end < 0)
513
478
  invalid("unterminated attribute");
514
- const raw = source.slice(offset, end);
479
+ const raw = (yield* source.slice(offset, end));
515
480
  pendingWork += raw.length * 2;
516
481
  while (pendingWork >= 512) {
517
482
  yield 512;
@@ -608,17 +573,17 @@ export function* parseXmlSteps(input, limits = {}) {
608
573
  invalid("multiple root elements");
609
574
  else
610
575
  root = element;
611
- const empty = source[offset] === "/";
576
+ const empty = (yield* source.slice(offset, offset + 1)) === "/";
612
577
  if (empty)
613
578
  offset++;
614
- if (source[offset++] !== ">") {
579
+ if ((yield* source.slice(offset++, offset)) !== ">") {
615
580
  if (!limits.recover)
616
581
  invalid("unterminated start tag");
617
582
  limits.recover("unterminated start tag");
618
583
  }
619
584
  // Only cache compact, attribute-free spellings. Namespace resolution and
620
585
  // every admission counter still run for each distinct physical element.
621
- if (empty && !attributes && name.length <= 512 && source.slice(offset - name.length - 3, offset) === `<${name}/>`)
586
+ if (empty && !attributes && name.length <= 512 && (yield* source.slice(offset - name.length - 3, offset)) === `<${name}/>`)
622
587
  previousEmpty = { suffix: name + "/>", name, prefix, localName };
623
588
  limits.events?.({ type: "open", element, parent: parent?.element });
624
589
  if (!empty)
@@ -645,6 +610,65 @@ export function* parseXmlSteps(input, limits = {}) {
645
610
  }
646
611
  return root;
647
612
  }
613
+ function* normalizeXmlSteps(input) {
614
+ const chunks = [];
615
+ const start = input.charCodeAt(0) === 0xfeff ? 1 : 0;
616
+ let chunkStart = start;
617
+ let chunkLength = 0;
618
+ let normalizedChunk = "";
619
+ // Keep validated spans intact; only changed line endings need new strings.
620
+ for (let index = start; index < input.length; index++) {
621
+ const point = input.codePointAt(index);
622
+ if (!validCharacter(point))
623
+ invalid("invalid character");
624
+ if (point === 13) {
625
+ normalizedChunk += input.slice(chunkStart, index) + "\n";
626
+ if (input.charCodeAt(index + 1) === 10)
627
+ index++;
628
+ chunkStart = index + 1;
629
+ chunkLength++;
630
+ }
631
+ else {
632
+ chunkLength += point > 0xffff ? 2 : 1;
633
+ if (point > 0xffff)
634
+ index++;
635
+ }
636
+ if (chunkLength >= 512) {
637
+ if (normalizedChunk) {
638
+ chunks.push(normalizedChunk + input.slice(chunkStart, index + 1));
639
+ normalizedChunk = "";
640
+ chunkStart = index + 1;
641
+ }
642
+ chunkLength = 0;
643
+ yield 512;
644
+ }
645
+ }
646
+ if (chunkLength)
647
+ yield chunkLength;
648
+ if (chunks.length || normalizedChunk)
649
+ chunks.push(normalizedChunk + input.slice(chunkStart));
650
+ return chunks.length ? chunks.join("") : input.slice(start);
651
+ }
652
+ export function* parseXmlSteps(input, limits = {}) {
653
+ validateLimits(limits);
654
+ const source = yield* normalizeXmlSteps(input);
655
+ const parser = parseXmlSourceSteps(source.length, limits);
656
+ let step = parser.next();
657
+ try {
658
+ while (!step.done) {
659
+ if (typeof step.value === "number")
660
+ yield step.value;
661
+ else
662
+ step.value.value = source.slice(step.value.offset, step.value.offset + step.value.length);
663
+ step = parser.next();
664
+ }
665
+ return step.value;
666
+ }
667
+ finally {
668
+ if (!step.done)
669
+ parser.return(undefined);
670
+ }
671
+ }
648
672
  export function parseXml(input, limits = {}) {
649
673
  const parser = parseXmlSteps(input, limits);
650
674
  let result = parser.next();
@@ -0,0 +1,18 @@
1
+ /** Fill value with exactly the requested UTF-16 span before advancing the parser. */
2
+ export interface XmlSourceRead {
3
+ readonly offset: number;
4
+ readonly length: number;
5
+ value?: string;
6
+ }
7
+ export type XmlSourceStep = number | XmlSourceRead;
8
+ /** One fixed window; individual returned tokens still have their own size cost. */
9
+ export declare class XmlSource {
10
+ readonly length: number;
11
+ private window;
12
+ private start;
13
+ constructor(length: number);
14
+ slice(start: number, end?: number): Generator<XmlSourceStep, string, void>;
15
+ charCodeAt(offset: number): Generator<XmlSourceStep, number, void>;
16
+ startsWith(value: string, offset: number): Generator<XmlSourceStep, boolean, void>;
17
+ indexOf(value: string, offset: number): Generator<XmlSourceStep, number, void>;
18
+ }
@@ -0,0 +1,47 @@
1
+ /** One fixed window; individual returned tokens still have their own size cost. */
2
+ export class XmlSource {
3
+ length;
4
+ window = '';
5
+ start = -1;
6
+ constructor(length) {
7
+ this.length = length;
8
+ if (!Number.isSafeInteger(length) || length < 0)
9
+ throw new RangeError("Invalid XML source length");
10
+ }
11
+ *slice(start, end = this.length) {
12
+ start = start < 0 ? Math.max(0, this.length + start) : Math.min(start, this.length);
13
+ end = end < 0 ? Math.max(0, this.length + end) : Math.min(end, this.length);
14
+ 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
+ }
25
+ const stop = Math.min(end, this.start + this.window.length);
26
+ result += this.window.slice(start - this.start, stop - this.start);
27
+ start = stop;
28
+ }
29
+ return result;
30
+ }
31
+ *charCodeAt(offset) {
32
+ return (yield* this.slice(offset, offset + 1)).charCodeAt(0);
33
+ }
34
+ *startsWith(value, offset) {
35
+ return (yield* this.slice(offset, offset + value.length)) === value;
36
+ }
37
+ *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);
41
+ if (found >= 0)
42
+ return offset + found;
43
+ offset += 4096;
44
+ }
45
+ return -1;
46
+ }
47
+ }
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.788",
11
+ "version": "0.0.789",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.788",
3
+ "version": "0.0.789",
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.788",
3
+ "version": "0.0.789",
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.788",
180
+ "toolcraft-schema": "0.0.789",
181
181
  "toolcraft-design": "*",
182
182
  "@poe-code/frontmatter": "*",
183
183
  "@poe-code/agent-mcp-config": "*",