@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 +193 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +988 -0
- package/dist/lib/csv.d.ts +80 -0
- package/dist/lib/csv.js +240 -0
- package/dist/lib/diff.d.ts +44 -0
- package/dist/lib/diff.js +166 -0
- package/dist/lib/json.d.ts +65 -0
- package/dist/lib/json.js +133 -0
- package/dist/lib/jsonl.d.ts +39 -0
- package/dist/lib/jsonl.js +63 -0
- package/dist/lib/jsonpath.d.ts +91 -0
- package/dist/lib/jsonpath.js +430 -0
- package/dist/lib/patch.d.ts +72 -0
- package/dist/lib/patch.js +272 -0
- package/dist/lib/table.d.ts +156 -0
- package/dist/lib/table.js +571 -0
- package/dist/lib/toml.d.ts +59 -0
- package/dist/lib/toml.js +530 -0
- package/dist/lib/xml.d.ts +72 -0
- package/dist/lib/xml.js +344 -0
- package/dist/lib/yaml.d.ts +61 -0
- package/dist/lib/yaml.js +598 -0
- package/package.json +45 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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";
|