toolcraft 0.0.797 → 0.0.798

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.797",
141
+ "version": "0.0.798",
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.797",
151
+ "version": "0.0.798",
152
152
  "license": "MIT"
153
153
  },
154
154
  {
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "name": "toolcraft",
141
- "version": "0.0.797",
141
+ "version": "0.0.798",
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.797",
151
+ "version": "0.0.798",
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, XmlParserFrame, XmlFrameRequest, XmlNamespaceScope, XmlNamespaceRequest } from "./xml.js";
12
+ export type { XmlName, XmlAttribute, XmlContent, XmlElement, XmlLimits, XmlParserFrame, XmlFrameRequest, XmlNamespaceScope, XmlNamespaceRequest, XmlAttributeRequest, XmlAttributeRecord } 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";
@@ -72,6 +72,17 @@ never changes ancestor scopes. In this mode event elements have an empty
72
72
  The codec remains filesystem-independent, and individual names, attribute values
73
73
  and namespace URIs still need a token storage strategy.
74
74
 
75
+ Set `storeAttributes: true` to move current-tag attribute values, source order,
76
+ and duplicate-name state behind `attributeOperation` requests (`has`, `append`,
77
+ `read`, and `expanded`). Service the request's `found` or `result` field as described
78
+ by `XmlAttributeRequest`, keeping records in caller-owned backing. With this flag,
79
+ element `attributes` arrays are empty. Supply `onAttribute(attribute, element)` to
80
+ receive resolved attributes in source order, after the open event and before any
81
+ content or close event. The parser yields between attributes so a host can await
82
+ storage or a consumer. Namespace declarations may follow prefixed attributes;
83
+ resolution and duplicate-expanded-name validation still happen after the complete
84
+ start tag. Individual attribute names and values retain their token cost.
85
+
75
86
  Pass `undefined` as the source length to parse incrementally. Such read requests
76
87
  have `streaming: true` and request at most 512 UTF-16 units. Return any nonempty
77
88
  prefix available without pulling more input; set `complete: true` only when that
@@ -0,0 +1,47 @@
1
+ export interface XmlAttributeRecord {
2
+ name: string;
3
+ prefix: string;
4
+ localName: string;
5
+ value: string;
6
+ next: number;
7
+ }
8
+ export interface XmlAttributeState {
9
+ reference: number;
10
+ first: number;
11
+ length: number;
12
+ size: number;
13
+ }
14
+ export type XmlAttributeRequest = {
15
+ attributeOperation: 'has' | 'expanded';
16
+ state: XmlAttributeState;
17
+ name: string;
18
+ found?: boolean;
19
+ } | {
20
+ attributeOperation: 'append';
21
+ state: XmlAttributeState;
22
+ attribute: Omit<XmlAttributeRecord, 'next'>;
23
+ result?: XmlAttributeState;
24
+ } | {
25
+ attributeOperation: 'read';
26
+ state: XmlAttributeState;
27
+ reference: number;
28
+ values: boolean;
29
+ result?: XmlAttributeRecord;
30
+ };
31
+ /** Buffered APIs use local collections; external parsing retains only a store
32
+ * handle and one current attribute. Hosts preserve order and last-value recovery. */
33
+ export declare class XmlAttributes {
34
+ private readonly external;
35
+ private state;
36
+ private readonly names;
37
+ private readonly records;
38
+ private readonly expandedNames;
39
+ constructor(external: boolean);
40
+ get length(): number;
41
+ get size(): number;
42
+ get first(): number;
43
+ has(name: string): Generator<XmlAttributeRequest, boolean, void>;
44
+ append(name: string, prefix: string, localName: string, value: string): Generator<XmlAttributeRequest, void, void>;
45
+ read(reference: number, values: boolean): Generator<XmlAttributeRequest, XmlAttributeRecord, void>;
46
+ expanded(name: string): Generator<XmlAttributeRequest, boolean, void>;
47
+ }
@@ -0,0 +1,62 @@
1
+ /** Buffered APIs use local collections; external parsing retains only a store
2
+ * handle and one current attribute. Hosts preserve order and last-value recovery. */
3
+ export class XmlAttributes {
4
+ external;
5
+ state = { reference: 0, first: 0, length: 0, size: 0 };
6
+ names = new Map();
7
+ records = [];
8
+ expandedNames = new Set();
9
+ constructor(external) {
10
+ this.external = external;
11
+ }
12
+ get length() { return this.state.length; }
13
+ get size() { return this.state.size; }
14
+ get first() { return this.state.first; }
15
+ *has(name) {
16
+ if (!this.length)
17
+ return false;
18
+ if (!this.external)
19
+ return this.names.has(name);
20
+ const request = { attributeOperation: 'has', state: this.state, name };
21
+ yield request;
22
+ if (request.found === undefined)
23
+ throw new TypeError('Incomplete XML attribute lookup');
24
+ return request.found;
25
+ }
26
+ *append(name, prefix, localName, value) {
27
+ if (!this.external) {
28
+ this.names.set(name, value);
29
+ this.records.push({ name, prefix, localName });
30
+ this.state = { reference: 0, first: 1, length: this.records.length, size: this.names.size };
31
+ return;
32
+ }
33
+ const request = { attributeOperation: 'append', state: this.state, attribute: { name, prefix, localName, value } };
34
+ yield request;
35
+ if (!request.result)
36
+ throw new TypeError('Incomplete XML attribute append');
37
+ this.state = request.result;
38
+ }
39
+ *read(reference, values) {
40
+ if (!this.external) {
41
+ const attribute = this.records[reference - 1];
42
+ return { ...attribute, value: values ? this.names.get(attribute.name) : '', next: reference < this.length ? reference + 1 : 0 };
43
+ }
44
+ const request = { attributeOperation: 'read', state: this.state, reference, values };
45
+ yield request;
46
+ if (!request.result)
47
+ throw new TypeError('Incomplete XML attribute read');
48
+ return request.result;
49
+ }
50
+ *expanded(name) {
51
+ if (!this.external) {
52
+ const found = this.expandedNames.has(name);
53
+ this.expandedNames.add(name);
54
+ return found;
55
+ }
56
+ const request = { attributeOperation: 'expanded', state: this.state, name };
57
+ yield request;
58
+ if (request.found === undefined)
59
+ throw new TypeError('Incomplete XML expanded attribute lookup');
60
+ return request.found;
61
+ }
62
+ }
@@ -1,3 +1,5 @@
1
+ import { type XmlAttributeRequest } from "./attributes.js";
2
+ export type { XmlAttributeRequest, XmlAttributeRecord, XmlAttributeState } from "./attributes.js";
1
3
  import { type XmlNamespaceRequest, type XmlFrameRequest } from "./frames.js";
2
4
  export type { XmlParserFrame, XmlFrameRequest, XmlNamespaceScope, XmlNamespaceRequest } from "./frames.js";
3
5
  export { normalizeXmlChunks } from "./characters.js";
@@ -63,12 +65,16 @@ export interface XmlSourceLimits extends XmlStepLimits {
63
65
  readonly storeFrames?: boolean;
64
66
  /** Resolve namespace scopes through host-serviced requests. Requires retainTree: false. */
65
67
  readonly storeNamespaces?: boolean;
68
+ /** Store attribute collections through host requests and emit resolved attributes individually. */
69
+ readonly storeAttributes?: boolean;
70
+ readonly onAttribute?: (attribute: XmlAttribute, element: XmlElement) => void;
66
71
  }
67
- export type XmlParseStep = XmlSourceStep | XmlFrameRequest | XmlNamespaceRequest;
72
+ export type XmlParseStep = XmlSourceStep | XmlFrameRequest | XmlNamespaceRequest | XmlAttributeRequest;
68
73
  /** Parse a validated, BOM-free, line-normalized UTF-16 source via bounded read requests. */
69
74
  export declare function parseXmlSourceSteps(length: number | undefined, limits?: XmlStepLimits & {
70
75
  readonly storeFrames?: false;
71
76
  readonly storeNamespaces?: false;
77
+ readonly storeAttributes?: false;
72
78
  }): Generator<XmlSourceStep, XmlElement, void>;
73
79
  export declare function parseXmlSourceSteps(length: number | undefined, limits: XmlSourceLimits): Generator<XmlParseStep, XmlElement, void>;
74
80
  export declare function parseXmlSteps(input: string, limits?: XmlStepLimits): Generator<number, XmlElement, void>;
@@ -1,3 +1,4 @@
1
+ import { XmlAttributes } from "./attributes.js";
1
2
  import { XmlFrames, bindNamespace, namespaceValue } from "./frames.js";
2
3
  import { validCharacter } from "./characters.js";
3
4
  export { normalizeXmlChunks } from "./characters.js";
@@ -255,6 +256,8 @@ export function* parseXmlSourceSteps(length, limits = {}) {
255
256
  validateLimits(limits);
256
257
  if (limits.storeFrames && limits.retainTree !== false)
257
258
  throw new TypeError("Stored XML frames require retainTree: false");
259
+ if (limits.storeAttributes && limits.retainTree !== false)
260
+ throw new TypeError("Stored XML attributes require retainTree: false");
258
261
  if (limits.storeNamespaces && limits.retainTree !== false)
259
262
  throw new TypeError("Stored XML namespaces require retainTree: false");
260
263
  if (limits.fragmentContent && limits.retainTree !== false)
@@ -555,8 +558,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
555
558
  yield 512;
556
559
  pendingWork -= 512;
557
560
  }
558
- let attributes;
559
- const attrMeta = [];
561
+ const attributes = new XmlAttributes(limits.storeAttributes === true);
560
562
  const namespaceParent = yield* stack.peek();
561
563
  let namespaces = namespaceParent?.namespaceScope ?? namespaceParent?.namespaces ?? (initialNamespaces instanceof Map ? new Map(initialNamespaces) : initialNamespaces);
562
564
  let ownsNamespaces = stack.length === 0;
@@ -576,11 +578,11 @@ export function* parseXmlSourceSteps(length, limits = {}) {
576
578
  yield 512;
577
579
  pendingWork -= 512;
578
580
  }
579
- if (attributes?.has(attribute))
581
+ if ((yield* attributes.has(attribute)))
580
582
  invalid("duplicate attribute");
581
583
  if (++attributeCount > maxAttributes)
582
584
  throw new XmlLimitError("maxAttributes", "XML attribute limit exceeded");
583
- if ((attributes?.size ?? 0) >= maxAttributesPerElement)
585
+ if (attributes.size >= maxAttributesPerElement)
584
586
  throw new XmlLimitError("maxAttributesPerElement", "XML attribute limit exceeded");
585
587
  yield* skipWhitespace();
586
588
  if ((yield* source.slice(offset++, offset)) !== "=")
@@ -613,8 +615,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
613
615
  pendingWork -= 512;
614
616
  }
615
617
  admitText(value);
616
- (attributes ??= new Map()).set(attribute, value);
617
- attrMeta.push([attribute, attrPrefix, attrLocal]);
618
+ yield* attributes.append(attribute, attrPrefix, attrLocal, value);
618
619
  offset = end + 1;
619
620
  if (attribute === "xmlns" || attribute.startsWith("xmlns:")) {
620
621
  const nsPrefix = attribute === "xmlns" ? "" : attribute.slice(6);
@@ -636,20 +637,20 @@ export function* parseXmlSourceSteps(length, limits = {}) {
636
637
  throw new XmlLimitError("maxNamespaces", "XML namespace scope limit exceeded");
637
638
  }
638
639
  }
639
- if (attrMeta.length > 0) {
640
- const expanded = attrMeta.length > 1 ? new Set() : undefined;
641
- for (let i = 0; i < attrMeta.length; i++) {
642
- const [attribute, attrPrefix, attrLocal] = attrMeta[i];
640
+ if (attributes.length > 0) {
641
+ for (let cursor = attributes.first; cursor;) {
642
+ const record = yield* attributes.read(cursor, false);
643
+ cursor = record.next;
644
+ const { name: attribute, prefix: attrPrefix, localName: attrLocal } = record;
643
645
  if (attribute === "xmlns" || attribute.startsWith("xmlns:"))
644
646
  continue;
645
647
  pendingWork += attribute.length;
646
648
  if (attrPrefix && (yield* namespaceValue(namespaces, attrPrefix)) === undefined)
647
649
  invalid("unbound attribute prefix");
648
- if (expanded) {
650
+ if (attributes.length > 1) {
649
651
  const key = `${attrPrefix ? (yield* namespaceValue(namespaces, attrPrefix)) : ""}\0${attrLocal}`;
650
- if (expanded.has(key))
652
+ if (yield* attributes.expanded(key))
651
653
  invalid("duplicate expanded attribute");
652
- expanded.add(key);
653
654
  }
654
655
  }
655
656
  }
@@ -668,13 +669,15 @@ export function* parseXmlSourceSteps(length, limits = {}) {
668
669
  limits.onElement?.({ name, namespace, localName }, (yield* stack.peek())?.element, stack.length + 1);
669
670
  admitContent();
670
671
  const retainedAttributes = [];
671
- if (retainContent && attributes) {
672
- for (let i = 0; i < attrMeta.length; i++) {
673
- const [attribute, attrPrefix, attrLocal] = attrMeta[i];
674
- const value = attributes.get(attribute);
672
+ if (retainContent) {
673
+ for (let cursor = attributes.first; cursor;) {
674
+ const record = yield* attributes.read(cursor, !limits.storeAttributes);
675
+ cursor = record.next;
676
+ const { name: attribute, prefix: attrPrefix, localName: attrLocal, value } = record;
675
677
  admitContent();
676
- retainedAttributes.push({ name: attribute, localName: attrLocal,
677
- namespace: attribute === "xmlns" || attrPrefix === "xmlns" ? xmlnsNamespace : attrPrefix ? (yield* namespaceValue(namespaces, attrPrefix)) : "", value });
678
+ if (!limits.storeAttributes)
679
+ retainedAttributes.push({ name: attribute, localName: attrLocal,
680
+ namespace: attribute === "xmlns" || attrPrefix === "xmlns" ? xmlnsNamespace : attrPrefix ? (yield* namespaceValue(namespaces, attrPrefix)) : "", value });
678
681
  pendingWork += attribute.length + 1;
679
682
  }
680
683
  }
@@ -705,9 +708,20 @@ export function* parseXmlSourceSteps(length, limits = {}) {
705
708
  }
706
709
  // Only cache compact, attribute-free spellings. Namespace resolution and
707
710
  // every admission counter still run for each distinct physical element.
708
- if (empty && !attributes && name.length <= 512 && (yield* source.slice(offset - name.length - 3, offset)) === `<${name}/>`)
711
+ if (empty && !attributes.length && name.length <= 512 && (yield* source.slice(offset - name.length - 3, offset)) === `<${name}/>`)
709
712
  previousEmpty = { suffix: name + "/>", name, prefix, localName };
710
713
  limits.events?.({ type: "open", element, parent: parent?.element });
714
+ if (limits.storeAttributes && limits.onAttribute && retainContent) {
715
+ yield 1;
716
+ for (let cursor = attributes.first; cursor;) {
717
+ const attribute = yield* attributes.read(cursor, true);
718
+ cursor = attribute.next;
719
+ const namespace = attribute.name === "xmlns" || attribute.prefix === "xmlns" ? xmlnsNamespace
720
+ : attribute.prefix ? (yield* namespaceValue(namespaces, attribute.prefix)) : "";
721
+ limits.onAttribute({ name: attribute.name, localName: attribute.localName, namespace, value: attribute.value }, element);
722
+ yield 1;
723
+ }
724
+ }
711
725
  if (!empty)
712
726
  yield* stack.push({ element, content, name, namespaces: namespaces instanceof Map ? namespaces : new Map(), ...(namespaces instanceof Map ? {} : { namespaceScope: namespaces }) });
713
727
  else
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.797",
11
+ "version": "0.0.798",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.797",
3
+ "version": "0.0.798",
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.797",
3
+ "version": "0.0.798",
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.797",
180
+ "toolcraft-schema": "0.0.798",
181
181
  "toolcraft-design": "*",
182
182
  "@poe-code/frontmatter": "*",
183
183
  "@poe-code/agent-mcp-config": "*",