@crewhaus/tool-data 0.7.0

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/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # @crewhaus/tool-data
2
+
3
+ Deterministic structured-data tools. Every one is pure: no filesystem, no
4
+ network, no clock, no randomness. The same input always produces the same
5
+ bytes.
6
+
7
+ That property is the point. A harness spends a model call when it needs
8
+ judgement; it should not spend one to pull two fields out of an API response,
9
+ join two result sets, or turn a CSV into records. These twenty-three tools
10
+ cover the mechanical half so the model is left with the part that actually
11
+ needs it.
12
+
13
+ ```yaml
14
+ tools:
15
+ - all-data # every tool below
16
+ - -DataConvert # ...except this one
17
+ ```
18
+
19
+ | Tool | What it does |
20
+ |---|---|
21
+ | `ColumnsToRecords` | Column arrays back to records, short columns padded and named |
22
+ | `CsvParse` | RFC 4180 CSV to records or rows, with type inference |
23
+ | `CsvWrite` | Records or rows to RFC 4180 CSV, quoting whatever needs it |
24
+ | `DataConvert` | JSON ⇄ YAML ⇄ TOML ⇄ CSV ⇄ JSONL |
25
+ | `DataDiff` | Structural diff of two documents: added, removed, changed, by path |
26
+ | `DataShape` | What is actually in this document — fields, types, fill rates, examples |
27
+ | `DedupeRecords` | Drop duplicates by key fields or by the whole value, key order ignored |
28
+ | `FlattenObject` | Nested value to dotted keys |
29
+ | `JsonFormat` | Re-indent, minify, key-sort, address a subtree, hash the result |
30
+ | `JsonMergePatch` | Apply an RFC 7386 merge patch, or derive one between two documents |
31
+ | `JsonPatch` | Apply RFC 6902 operations, all-or-nothing |
32
+ | `JsonQuery` | Select values with a JSONPath-like expression |
33
+ | `JsonSortKeys` | Key-sorted JSON, for diff-stable output |
34
+ | `JsonlParse` | Line-delimited JSON, with the line number of any bad record |
35
+ | `JsonlWrite` | Values to line-delimited JSON |
36
+ | `RecordsToColumns` | Records to column arrays |
37
+ | `SampleRecords` | Head, tail, every nth, or evenly spread — never random |
38
+ | `SortRecords` | Stable, total ordering by one or more dotted paths |
39
+ | `TableAggregate` | Group by, with count/sum/min/max/avg/first/last/distinct |
40
+ | `TableJoin` | Inner, left, right or full join of two record arrays |
41
+ | `TableQuery` | Filter, project, sort and page records with a small predicate |
42
+ | `UnflattenObject` | Dotted keys back to a nested value |
43
+ | `XmlParse` | XML or an HTML fragment to a JSON tree |
44
+
45
+ ## Two input conventions
46
+
47
+ A whole **document** — JSON, YAML, TOML, CSV, XML, JSONL — arrives as text,
48
+ because that is how it comes off disk or off the wire. A **record array**
49
+ arrives as a real array of objects, because that is how it comes out of the
50
+ previous tool in the chain. Field references inside the record tools are
51
+ dotted paths (`user.team.id`), so nested records work without flattening
52
+ them first.
53
+
54
+ ## The parsers are hand-written, so here is exactly what they support
55
+
56
+ There is no YAML, TOML, XML or CSV dependency behind these tools. That keeps
57
+ the package free of a supply chain, and it means the supported subset is a
58
+ contract rather than a hopeful summary. Each module's header comment carries
59
+ the authoritative version; this is the short form.
60
+
61
+ ### JsonQuery's grammar
62
+
63
+ ```
64
+ $.a.b child
65
+ $["odd key"] quoted child, for keys with dots or spaces
66
+ $.a[0] $.a[-1] index, negative counts from the end
67
+ $.a[1:5] [::2] slice, Python semantics, negative step included
68
+ $.a[*] $.* every element / every value
69
+ $..name $..* recursive descent
70
+ $.u[?(@.age >= 30)] filter: == != < <= > >= =~ , or a bare existence test
71
+ ```
72
+
73
+ Not supported, each raising a parse error naming the position rather than
74
+ returning an empty result: unions (`['a','b']`), functions, parent axes, and
75
+ `&&` / `||` inside a filter — chain two filter steps instead. Comparing a
76
+ number against a string is simply false; there is no coercion, because a
77
+ quiet `"10" < 9` is how a filter silently drops rows.
78
+
79
+ ### YAML
80
+
81
+ **Read:** block mappings and sequences nested by space indentation, a compact
82
+ map on the dash line (`- name: a`), plain/single-quoted/double-quoted scalars
83
+ with the usual escapes, core-schema typing (`null` `~` `true` `false`,
84
+ integers, decimals, exponents, `0x`, `0o`), flow collections
85
+ (`[1, {a: b}]`), block scalars `|` and `>` with the `-` and `+` chomping
86
+ indicators, `#` comments, and `---` / `...` markers.
87
+
88
+ `.inf` and `.nan` read and write correctly, but JSON has no spelling for
89
+ either, so converting a document that uses them **to JSON** renders them
90
+ `null`. The same is true of TOML's `inf` and `nan`.
91
+
92
+ **Refused explicitly, never guessed at:** anchors and aliases (`&a`, `*a`),
93
+ merge keys (`<<:`), tags (`!!str`), multiple documents in one stream, complex
94
+ keys (`? `), and tabs in indentation. Dates stay strings.
95
+
96
+ **Write:** block style, two-space indent, keys in their existing order, with
97
+ a string quoted whenever leaving it bare would change how it reads back.
98
+
99
+ ### TOML
100
+
101
+ **Read:** bare, quoted and dotted keys; tables and arrays of tables at any
102
+ depth; basic, literal and both multi-line string forms, including the
103
+ backslash line continuation; integers with `_` separators and `0x`/`0o`/`0b`
104
+ literals; floats, `inf`, `nan`; arrays, nested and multi-line; inline tables;
105
+ `#` comments. Redefining a table or key is an error, as the spec requires.
106
+
107
+ **The one deliberate departure:** dates and times stay strings. TOML has four
108
+ first-class date-time types and JSON has none, so `2026-01-01` reads back as
109
+ `"2026-01-01"`. Converting to a `Date` would make the round trip lossy.
110
+ Integers outside the JavaScript safe range likewise stay strings rather than
111
+ being silently rounded.
112
+
113
+ Redefinition is enforced in both directions the spec names: a `[header]`
114
+ cannot reopen a table a dotted key already created, and a dotted key cannot
115
+ reach into a table a `[header]` already defined.
116
+
117
+ **Write:** scalars first, then sub-tables, then arrays of tables — the order
118
+ that reads back identically. `null` has no TOML representation, so it is
119
+ dropped and the dropped keys are named in the output.
120
+
121
+ ### CSV
122
+
123
+ RFC 4180 in full: quoted fields, delimiters and newlines inside quotes,
124
+ doubled quotes, a final row with no trailing newline, and a row that is a
125
+ single quoted empty field (`""` is a record, not a blank line). Plus the two
126
+ departures real files need — a configurable delimiter, and tolerance of bare
127
+ LF as well as CRLF. A byte-order mark is stripped.
128
+
129
+ Duplicate and blank column names are made unique before records are built,
130
+ and the suffix is bumped until the name is genuinely unused, so `a,a,a_2`
131
+ becomes `a, a_2, a_2_2` rather than losing a column to a collision.
132
+
133
+ Type inference is opt-in and conservative: `007`, `+5` and anything outside
134
+ the safe integer range stay strings, because turning a zip code into a number
135
+ loses data. Ragged rows are reported, not silently padded.
136
+
137
+ ### XML
138
+
139
+ A well-formedness-checking parser for the XML that shows up in feeds, SVG and
140
+ config files. It is **not** a conformant XML processor and does not claim to
141
+ be. Elements, attributes, text, CDATA, self-closing tags, the five predefined
142
+ entities and numeric character references; comments, processing instructions
143
+ and doctypes skipped; namespace prefixes kept verbatim, not resolved.
144
+
145
+ DTD-declared and external entities are refused outright — there is no
146
+ resolver, so this parser cannot be made to fetch anything. A numeric
147
+ character reference outside the Unicode range, or naming a surrogate, is a
148
+ reported error rather than a crash. Validation, `xml:space` and raw-text
149
+ elements (`<script>`, `<style>`) are not supported.
150
+ `html` mode adds void elements, unquoted and bare attributes, case-insensitive
151
+ tag names, and browser-style recovery from a mismatched close tag.
152
+
153
+ ## What is deliberately not here
154
+
155
+ Anything whose core is judgement rather than mechanism: inferring what a
156
+ column *means*, reconciling two schemas by intent, deciding which of two
157
+ conflicting records is right, summarizing a dataset in prose. `DataShape`
158
+ reports types and fill rates, not semantics.
159
+
160
+ Nor is there a random sampler. `SampleRecords` offers head, tail, every-nth
161
+ and evenly-spread, because a tool that returned a different subset on each
162
+ call could not be cached, retried, or compared between runs.
163
+
164
+ Where a deterministic first pass exists, it belongs first and the model is the
165
+ escalation path.
166
+
167
+ ## A caller's mistake is a result, not an exception
168
+
169
+ Malformed JSON, an unparseable path, an unterminated CSV quote, a mismatched
170
+ XML tag: each comes back as a readable string naming the format and, where
171
+ the format has lines, the line number. Tools throw only on a bug in the tool.
172
+ `JsonPatch` goes further and is transactional — if operation 4 of 6 fails, it
173
+ says so and the document is unchanged. That includes a pointer that is not a
174
+ pointer: `"a"` instead of `"/a"` names the operation it broke on, rather than
175
+ raising out of the tool.
176
+
177
+ `src/index.test.ts` drives every tool with malformed input and asserts a
178
+ readable result, and `src/integration.test.ts` does the same through
179
+ `executeTool`.
180
+
181
+ ## Layout
182
+
183
+ `src/lib/` holds the pure functions and is where the behaviour is tested;
184
+ `src/index.ts` wraps them as tools. A bug in the CSV quote handling reads
185
+ better as a failing unit than as a failing tool call.
186
+
187
+ ## Safety flags
188
+
189
+ All twenty-three are `readOnly`, non-destructive, `scope: "internal"`, and
190
+ declare no io capability, because none of them crosses a process or network
191
+ boundary. `packages/tool-data/src/index.test.ts` asserts that for every tool,
192
+ so a future addition that reaches outside has to change the assertion
193
+ deliberately.
@@ -0,0 +1,35 @@
1
+ import type { RegisteredTool } from "@crewhaus/tool-catalog";
2
+ export declare const jsonQuery: RegisteredTool;
3
+ export declare const jsonPatch: RegisteredTool;
4
+ export declare const jsonMergePatch: RegisteredTool;
5
+ export declare const jsonFormat: RegisteredTool;
6
+ export declare const dataDiff: RegisteredTool;
7
+ export declare const dataConvert: RegisteredTool;
8
+ export declare const csvParse: RegisteredTool;
9
+ export declare const csvWrite: RegisteredTool;
10
+ export declare const tableQuery: RegisteredTool;
11
+ export declare const tableAggregate: RegisteredTool;
12
+ export declare const tableJoin: RegisteredTool;
13
+ export declare const recordsToColumns: RegisteredTool;
14
+ export declare const columnsToRecords: RegisteredTool;
15
+ export declare const flattenObject: RegisteredTool;
16
+ export declare const unflattenObject: RegisteredTool;
17
+ export declare const jsonlParse: RegisteredTool;
18
+ export declare const jsonlWrite: RegisteredTool;
19
+ export declare const xmlParse: RegisteredTool;
20
+ export declare const sortRecords: RegisteredTool;
21
+ export declare const dedupeRecords: RegisteredTool;
22
+ export declare const sampleRecords: RegisteredTool;
23
+ export declare const dataShape: RegisteredTool;
24
+ export declare const jsonSortKeys: RegisteredTool;
25
+ /** Every tool this package registers, in the order a catalog should list them. */
26
+ export declare const DATA_TOOLS: ReadonlyArray<RegisteredTool>;
27
+ /**
28
+ * The CSV and record helpers, re-exported for other tool packages.
29
+ *
30
+ * `@crewhaus/tool-table` profiles and reconciles the same files this package
31
+ * parses, and two CSV readers in one repository would disagree about
32
+ * quoting, embedded newlines and what counts as a null token — on the same
33
+ * file, in different tools, which is the worst way to disagree.
34
+ */
35
+ export { type CsvParseOptions, type CsvParseResult, inferScalar, normalizeHeader, parseCsv, rowsToRecords, unionKeys, } from "./lib/csv";