@refrakt-md/runes 0.26.0 → 0.28.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/dist/config.d.ts +4 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -2
- package/dist/config.js.map +1 -1
- package/dist/data-adapters.d.ts +64 -0
- package/dist/data-adapters.d.ts.map +1 -0
- package/dist/data-adapters.js +281 -0
- package/dist/data-adapters.js.map +1 -0
- package/dist/data-emit.d.ts +24 -0
- package/dist/data-emit.d.ts.map +1 -0
- package/dist/data-emit.js +48 -0
- package/dist/data-emit.js.map +1 -0
- package/dist/data-pipeline.d.ts +24 -0
- package/dist/data-pipeline.d.ts.map +1 -0
- package/dist/data-pipeline.js +154 -0
- package/dist/data-pipeline.js.map +1 -0
- package/dist/data-projection.d.ts +79 -0
- package/dist/data-projection.d.ts.map +1 -0
- package/dist/data-projection.js +212 -0
- package/dist/data-projection.js.map +1 -0
- package/dist/drawer-pipeline.d.ts +8 -5
- package/dist/drawer-pipeline.d.ts.map +1 -1
- package/dist/drawer-pipeline.js +2 -2
- package/dist/drawer-pipeline.js.map +1 -1
- package/dist/expand-pipeline.d.ts +4 -1
- package/dist/expand-pipeline.d.ts.map +1 -1
- package/dist/expand-pipeline.js +11 -11
- package/dist/expand-pipeline.js.map +1 -1
- package/dist/field-match.d.ts +4 -0
- package/dist/field-match.d.ts.map +1 -1
- package/dist/field-match.js +4 -2
- package/dist/field-match.js.map +1 -1
- package/dist/file-ref-resolve.js +3 -3
- package/dist/file-ref-resolve.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/read-file.d.ts +21 -26
- package/dist/lib/read-file.d.ts.map +1 -1
- package/dist/lib/read-file.js +25 -77
- package/dist/lib/read-file.js.map +1 -1
- package/dist/nodes.d.ts +9 -0
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +15 -0
- package/dist/nodes.js.map +1 -1
- package/dist/snippet-pipeline.d.ts.map +1 -1
- package/dist/snippet-pipeline.js +10 -9
- package/dist/snippet-pipeline.js.map +1 -1
- package/dist/tags/data.d.ts +19 -0
- package/dist/tags/data.d.ts.map +1 -0
- package/dist/tags/data.js +50 -0
- package/dist/tags/data.js.map +1 -0
- package/dist/tags/sandbox.d.ts.map +1 -1
- package/dist/tags/sandbox.js +11 -6
- package/dist/tags/sandbox.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format adapters for the `data` rune (SPEC-103).
|
|
3
|
+
*
|
|
4
|
+
* A format adapter has exactly one job: get raw bytes to the shared
|
|
5
|
+
* intermediate shape `{ headers, rows }`. That is the *only* place
|
|
6
|
+
* format-specific knobs live — everything downstream (projection, typing,
|
|
7
|
+
* table emission) is format-agnostic. This module owns the CSV/TSV adapter;
|
|
8
|
+
* JSON/NDJSON adapters (WORK-486) land here against the same contract.
|
|
9
|
+
*/
|
|
10
|
+
/** Thrown by adapters on a malformed source. The preprocess hook catches it
|
|
11
|
+
* and renders an in-page error callout (never a malformed table). */
|
|
12
|
+
export class DataSourceError extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'DataSourceError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Infer the format from a file extension, lower-cased. Returns null for an
|
|
19
|
+
* unknown extension so the caller can fall back to an explicit `format`. */
|
|
20
|
+
export function inferFormat(path) {
|
|
21
|
+
const dot = path.lastIndexOf('.');
|
|
22
|
+
if (dot === -1)
|
|
23
|
+
return null;
|
|
24
|
+
const ext = path.slice(dot + 1).toLowerCase();
|
|
25
|
+
switch (ext) {
|
|
26
|
+
case 'csv': return 'csv';
|
|
27
|
+
case 'tsv': return 'tsv';
|
|
28
|
+
case 'json': return 'json';
|
|
29
|
+
case 'ndjson':
|
|
30
|
+
case 'jsonl': return 'ndjson';
|
|
31
|
+
default: return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Parse delimited text (CSV/TSV) into a raw grid of string cells, following
|
|
36
|
+
* RFC 4180: double-quoted fields may contain the delimiter, CR/LF, and escaped
|
|
37
|
+
* quotes (`""`). Handles both `\n` and `\r\n` line endings. A trailing newline
|
|
38
|
+
* does not produce a spurious empty final row.
|
|
39
|
+
*/
|
|
40
|
+
export function parseDelimited(text, delimiter) {
|
|
41
|
+
const rows = [];
|
|
42
|
+
let row = [];
|
|
43
|
+
let field = '';
|
|
44
|
+
let inQuotes = false;
|
|
45
|
+
let started = false; // any char seen on the current row (so blank lines are kept faithfully)
|
|
46
|
+
const pushField = () => {
|
|
47
|
+
row.push(field);
|
|
48
|
+
field = '';
|
|
49
|
+
};
|
|
50
|
+
const pushRow = () => {
|
|
51
|
+
pushField();
|
|
52
|
+
rows.push(row);
|
|
53
|
+
row = [];
|
|
54
|
+
started = false;
|
|
55
|
+
};
|
|
56
|
+
for (let i = 0; i < text.length; i++) {
|
|
57
|
+
const ch = text[i];
|
|
58
|
+
if (inQuotes) {
|
|
59
|
+
if (ch === '"') {
|
|
60
|
+
if (text[i + 1] === '"') {
|
|
61
|
+
field += '"';
|
|
62
|
+
i++;
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
inQuotes = false;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
field += ch;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (ch === '"') {
|
|
73
|
+
inQuotes = true;
|
|
74
|
+
started = true;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (ch === delimiter) {
|
|
78
|
+
pushField();
|
|
79
|
+
started = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (ch === '\r') {
|
|
83
|
+
started = true;
|
|
84
|
+
continue;
|
|
85
|
+
} // CR handled with the following LF
|
|
86
|
+
if (ch === '\n') {
|
|
87
|
+
pushRow();
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
field += ch;
|
|
91
|
+
started = true;
|
|
92
|
+
}
|
|
93
|
+
// Flush the final field/row unless the input ended exactly on a newline.
|
|
94
|
+
if (started || field.length > 0 || row.length > 0)
|
|
95
|
+
pushRow();
|
|
96
|
+
return rows;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* CSV/TSV adapter — reduce delimited text to `{ headers, rows }`. Honors
|
|
100
|
+
* `delimiter` (explicit override or the format default) and `header`
|
|
101
|
+
* (false → synthesized `col1…`). Ragged rows are padded/truncated to the
|
|
102
|
+
* header width so the grid stays rectangular.
|
|
103
|
+
*/
|
|
104
|
+
export function delimitedAdapter(raw, opts) {
|
|
105
|
+
const delimiter = opts.delimiter && opts.delimiter.length > 0
|
|
106
|
+
? opts.delimiter
|
|
107
|
+
: (opts.format === 'tsv' ? '\t' : ',');
|
|
108
|
+
const grid = parseDelimited(raw, delimiter).filter(
|
|
109
|
+
// Drop fully-empty trailing rows (a single empty field from a blank line).
|
|
110
|
+
(r) => !(r.length === 1 && r[0] === ''));
|
|
111
|
+
if (grid.length === 0) {
|
|
112
|
+
throw new DataSourceError('source is empty (no rows)');
|
|
113
|
+
}
|
|
114
|
+
const useHeader = opts.header !== false;
|
|
115
|
+
let headers;
|
|
116
|
+
let bodyStart;
|
|
117
|
+
if (useHeader) {
|
|
118
|
+
headers = grid[0].map((h) => h.trim());
|
|
119
|
+
bodyStart = 1;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const width = grid.reduce((max, r) => Math.max(max, r.length), 0);
|
|
123
|
+
headers = Array.from({ length: width }, (_, i) => `col${i + 1}`);
|
|
124
|
+
bodyStart = 0;
|
|
125
|
+
}
|
|
126
|
+
const width = headers.length;
|
|
127
|
+
const rows = [];
|
|
128
|
+
for (let i = bodyStart; i < grid.length; i++) {
|
|
129
|
+
const r = grid[i];
|
|
130
|
+
const cells = [];
|
|
131
|
+
for (let c = 0; c < width; c++)
|
|
132
|
+
cells.push(r[c] ?? '');
|
|
133
|
+
rows.push(cells);
|
|
134
|
+
}
|
|
135
|
+
return { headers, rows };
|
|
136
|
+
}
|
|
137
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
138
|
+
// JSON / NDJSON adapters (WORK-486)
|
|
139
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
140
|
+
/** Stringify a leaf value for a cell. Primitives become their string form;
|
|
141
|
+
* arrays of primitives comma-join; anything else JSON-serializes. */
|
|
142
|
+
function cellString(v) {
|
|
143
|
+
if (v === null || v === undefined)
|
|
144
|
+
return '';
|
|
145
|
+
if (Array.isArray(v))
|
|
146
|
+
return v.map((x) => (x === null || x === undefined ? '' : String(x))).join(', ');
|
|
147
|
+
if (typeof v === 'object')
|
|
148
|
+
return JSON.stringify(v);
|
|
149
|
+
return String(v);
|
|
150
|
+
}
|
|
151
|
+
/** Recursively flatten a record's nested plain objects into dotted keys
|
|
152
|
+
* (`{ geo: { country } }` → `geo.country`), so `columns` can pluck nested
|
|
153
|
+
* fields by exact (dotted) header and the intermediate shape stays flat text.
|
|
154
|
+
* Arrays and primitives are leaves. */
|
|
155
|
+
function flattenRecord(obj, prefix = '', out = {}) {
|
|
156
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
157
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
158
|
+
if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
|
159
|
+
flattenRecord(v, key, out);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
out[key] = cellString(v);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
/** Reduce an array of record objects to `{ headers, rows }` — headers are the
|
|
168
|
+
* union of flattened keys in first-seen order. Shared by JSON `records`/`index`
|
|
169
|
+
* and NDJSON. */
|
|
170
|
+
function recordsToTable(records, leadKey) {
|
|
171
|
+
const flat = records.map((r) => flattenRecord(r));
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
const headers = [];
|
|
174
|
+
if (leadKey) {
|
|
175
|
+
headers.push(leadKey.header);
|
|
176
|
+
seen.add(leadKey.header);
|
|
177
|
+
}
|
|
178
|
+
for (const f of flat) {
|
|
179
|
+
for (const k of Object.keys(f)) {
|
|
180
|
+
if (!seen.has(k)) {
|
|
181
|
+
seen.add(k);
|
|
182
|
+
headers.push(k);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const rows = flat.map((f, i) => headers.map((h, c) => {
|
|
187
|
+
if (leadKey && c === 0)
|
|
188
|
+
return leadKey.values[i];
|
|
189
|
+
return f[h] ?? '';
|
|
190
|
+
}));
|
|
191
|
+
return { headers, rows };
|
|
192
|
+
}
|
|
193
|
+
/** Resolve a `root` locator (dotted path or JSON Pointer) within a document. */
|
|
194
|
+
function resolveRoot(doc, root) {
|
|
195
|
+
if (!root)
|
|
196
|
+
return doc;
|
|
197
|
+
const parts = root.startsWith('/')
|
|
198
|
+
? root.slice(1).split('/').map((p) => p.replace(/~1/g, '/').replace(/~0/g, '~'))
|
|
199
|
+
: root.split('.');
|
|
200
|
+
let cur = doc;
|
|
201
|
+
for (const p of parts) {
|
|
202
|
+
if (cur === null || typeof cur !== 'object') {
|
|
203
|
+
throw new DataSourceError(`root path "${root}" does not resolve to a value`);
|
|
204
|
+
}
|
|
205
|
+
cur = cur[p];
|
|
206
|
+
}
|
|
207
|
+
if (cur === undefined)
|
|
208
|
+
throw new DataSourceError(`root path "${root}" not found in the document`);
|
|
209
|
+
return cur;
|
|
210
|
+
}
|
|
211
|
+
/** JSON adapter — reduce a JSON document to `{ headers, rows }` per `root`/`orient`. */
|
|
212
|
+
export function jsonAdapter(raw, opts = {}) {
|
|
213
|
+
let doc;
|
|
214
|
+
try {
|
|
215
|
+
doc = JSON.parse(raw);
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
throw new DataSourceError(`invalid JSON — ${err.message}`);
|
|
219
|
+
}
|
|
220
|
+
const target = resolveRoot(doc, opts.root ?? '');
|
|
221
|
+
// Determine orientation.
|
|
222
|
+
let orient = opts.orient;
|
|
223
|
+
if (!orient) {
|
|
224
|
+
if (Array.isArray(target)) {
|
|
225
|
+
orient = Array.isArray(target[0]) ? 'values' : 'records';
|
|
226
|
+
}
|
|
227
|
+
else if (target && typeof target === 'object') {
|
|
228
|
+
throw new DataSourceError('source is an object map — set orient="index" (with key-column) to tabulate it');
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
throw new DataSourceError('source is not an array or object of records');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (orient === 'values') {
|
|
235
|
+
if (!Array.isArray(target) || target.length === 0) {
|
|
236
|
+
throw new DataSourceError('orient="values" expects a non-empty array of arrays');
|
|
237
|
+
}
|
|
238
|
+
const [headerRow, ...body] = target;
|
|
239
|
+
const headers = headerRow.map(cellString);
|
|
240
|
+
const rows = body.map((inner) => headers.map((_, i) => cellString(inner[i])));
|
|
241
|
+
return { headers, rows };
|
|
242
|
+
}
|
|
243
|
+
if (orient === 'index') {
|
|
244
|
+
if (!target || typeof target !== 'object' || Array.isArray(target)) {
|
|
245
|
+
throw new DataSourceError('orient="index" expects an object map ({ key: record, … })');
|
|
246
|
+
}
|
|
247
|
+
const entries = Object.entries(target);
|
|
248
|
+
const records = entries.map(([, v]) => (v && typeof v === 'object' && !Array.isArray(v) ? v : { value: v }));
|
|
249
|
+
const keys = entries.map(([k]) => k);
|
|
250
|
+
return recordsToTable(records, { header: opts.keyColumn && opts.keyColumn.length > 0 ? opts.keyColumn : 'key', values: keys });
|
|
251
|
+
}
|
|
252
|
+
// records
|
|
253
|
+
if (!Array.isArray(target)) {
|
|
254
|
+
throw new DataSourceError('orient="records" expects an array of objects');
|
|
255
|
+
}
|
|
256
|
+
const records = target.map((r) => (r && typeof r === 'object' && !Array.isArray(r) ? r : { value: r }));
|
|
257
|
+
return recordsToTable(records);
|
|
258
|
+
}
|
|
259
|
+
/** NDJSON adapter — parse line-delimited JSON records to `{ headers, rows }`
|
|
260
|
+
* (the union of record keys becomes the headers). */
|
|
261
|
+
export function ndjsonAdapter(raw) {
|
|
262
|
+
const records = [];
|
|
263
|
+
const lines = raw.split('\n');
|
|
264
|
+
for (let i = 0; i < lines.length; i++) {
|
|
265
|
+
const line = lines[i].trim();
|
|
266
|
+
if (line === '')
|
|
267
|
+
continue;
|
|
268
|
+
let parsed;
|
|
269
|
+
try {
|
|
270
|
+
parsed = JSON.parse(line);
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
throw new DataSourceError(`invalid NDJSON on line ${i + 1} — ${err.message}`);
|
|
274
|
+
}
|
|
275
|
+
records.push(parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : { value: parsed });
|
|
276
|
+
}
|
|
277
|
+
if (records.length === 0)
|
|
278
|
+
throw new DataSourceError('source is empty (no NDJSON records)');
|
|
279
|
+
return recordsToTable(records);
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=data-adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-adapters.js","sourceRoot":"","sources":["../src/data-adapters.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST emission for the `data` rune (SPEC-103).
|
|
3
|
+
*
|
|
4
|
+
* Turns the projected + typed table into a Markdoc `table` AST node — the same
|
|
5
|
+
* shape `Markdoc.parse` produces for a pipe table — so `chart` (`findTable`) and
|
|
6
|
+
* `datatable` (its table/`rf-table-wrapper` lookup) consume it with no
|
|
7
|
+
* structural edits, and it renders as a normal table standalone. Typed-numeric
|
|
8
|
+
* value cells carry a normalized `data-value` (forwarded by the `td` node).
|
|
9
|
+
*
|
|
10
|
+
* The error path emits a visible `hint` callout node instead of a malformed
|
|
11
|
+
* table, so a bad source surfaces on the page without crashing the build.
|
|
12
|
+
*/
|
|
13
|
+
import type { Node } from '@markdoc/markdoc';
|
|
14
|
+
import type { TypedTable } from './data-projection.js';
|
|
15
|
+
/** Build a Markdoc `table` AST node from a projected + typed table. */
|
|
16
|
+
export declare function emitTableNode(table: TypedTable): Node;
|
|
17
|
+
/**
|
|
18
|
+
* Build a visible in-page error callout (a `hint` rune node, `type="caution"`)
|
|
19
|
+
* carrying the message — used for sandbox-escape / missing-file / parse-error /
|
|
20
|
+
* empty-result, so the failure is visible and the build continues (the `data`
|
|
21
|
+
* tag never reaches its throwing transform).
|
|
22
|
+
*/
|
|
23
|
+
export declare function emitErrorNode(message: string): Node;
|
|
24
|
+
//# sourceMappingURL=data-emit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-emit.d.ts","sourceRoot":"","sources":["../src/data-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AASvD,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAmBrD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGnD"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST emission for the `data` rune (SPEC-103).
|
|
3
|
+
*
|
|
4
|
+
* Turns the projected + typed table into a Markdoc `table` AST node — the same
|
|
5
|
+
* shape `Markdoc.parse` produces for a pipe table — so `chart` (`findTable`) and
|
|
6
|
+
* `datatable` (its table/`rf-table-wrapper` lookup) consume it with no
|
|
7
|
+
* structural edits, and it renders as a normal table standalone. Typed-numeric
|
|
8
|
+
* value cells carry a normalized `data-value` (forwarded by the `td` node).
|
|
9
|
+
*
|
|
10
|
+
* The error path emits a visible `hint` callout node instead of a malformed
|
|
11
|
+
* table, so a bad source surfaces on the page without crashing the build.
|
|
12
|
+
*/
|
|
13
|
+
import Markdoc from '@markdoc/markdoc';
|
|
14
|
+
const { Ast } = Markdoc;
|
|
15
|
+
/** Wrap plain text as a Markdoc inline node (the content shape a th/td holds). */
|
|
16
|
+
function inlineText(text) {
|
|
17
|
+
return new Ast.Node('inline', {}, [new Ast.Node('text', { content: text })]);
|
|
18
|
+
}
|
|
19
|
+
/** Build a Markdoc `table` AST node from a projected + typed table. */
|
|
20
|
+
export function emitTableNode(table) {
|
|
21
|
+
const headerCells = table.headers.map((h) => new Ast.Node('th', {}, [inlineText(h)]));
|
|
22
|
+
const thead = new Ast.Node('thead', {}, [new Ast.Node('tr', {}, headerCells)]);
|
|
23
|
+
const bodyRows = table.rows.map((row) => {
|
|
24
|
+
const cells = row.map((cell, c) => {
|
|
25
|
+
// A numeric column with a parseable value carries the normalized number
|
|
26
|
+
// on `data-value`; the cell text stays the original human-formatted
|
|
27
|
+
// string (and the no-JS fallback).
|
|
28
|
+
const attrs = table.columnTypes[c] === 'numeric' && cell.value !== null
|
|
29
|
+
? { 'data-value': String(cell.value) }
|
|
30
|
+
: {};
|
|
31
|
+
return new Ast.Node('td', attrs, [inlineText(cell.text)]);
|
|
32
|
+
});
|
|
33
|
+
return new Ast.Node('tr', {}, cells);
|
|
34
|
+
});
|
|
35
|
+
const tbody = new Ast.Node('tbody', {}, bodyRows);
|
|
36
|
+
return new Ast.Node('table', {}, [thead, tbody]);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build a visible in-page error callout (a `hint` rune node, `type="caution"`)
|
|
40
|
+
* carrying the message — used for sandbox-escape / missing-file / parse-error /
|
|
41
|
+
* empty-result, so the failure is visible and the build continues (the `data`
|
|
42
|
+
* tag never reaches its throwing transform).
|
|
43
|
+
*/
|
|
44
|
+
export function emitErrorNode(message) {
|
|
45
|
+
const para = new Ast.Node('paragraph', {}, [inlineText(message)]);
|
|
46
|
+
return new Ast.Node('tag', { type: 'caution' }, [para], 'hint');
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=data-emit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-emit.js","sourceRoot":"","sources":["../src/data-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,OAAO,MAAM,kBAAkB,CAAC;AAIvC,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;AAExB,kFAAkF;AAClF,SAAS,UAAU,CAAC,IAAY;IAC/B,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC9C,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAE/E,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACvC,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;YACjC,wEAAwE;YACxE,oEAAoE;YACpE,mCAAmC;YACnC,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI;gBACtE,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;gBACtC,CAAC,CAAC,EAAE,CAAC;YACN,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;IAElD,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe;IAC5C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAClE,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data pipeline hook (SPEC-103).
|
|
3
|
+
*
|
|
4
|
+
* A preprocess sibling to `preprocessSnippets`: walk the parsed AST, and for
|
|
5
|
+
* every `{% data %}` tag resolve its `src` through the SPEC-113 `ProjectFiles`
|
|
6
|
+
* seam (whole-file, project-root bounded), run the format adapter + shared
|
|
7
|
+
* projection + typing, and replace the tag with a Markdoc `table` AST node. The
|
|
8
|
+
* emitted table is consumed by `chart`/`datatable` with no structural edits and
|
|
9
|
+
* is the honest no-JS fallback on a bare page.
|
|
10
|
+
*
|
|
11
|
+
* On any failure (sandbox escape / missing file / parse error / empty result)
|
|
12
|
+
* the tag is replaced with a visible error callout and a build warning is
|
|
13
|
+
* emitted — the build continues and the `data` tag never reaches its throwing
|
|
14
|
+
* transform.
|
|
15
|
+
*/
|
|
16
|
+
import type { Node } from '@markdoc/markdoc';
|
|
17
|
+
import type { PreprocessContext, PreprocessPage } from '@refrakt-md/types';
|
|
18
|
+
/**
|
|
19
|
+
* Preprocess: replace every `{% data %}` tag with a resolved `table` node (or an
|
|
20
|
+
* error callout). No-op when no provider is available (tree mode without a wired
|
|
21
|
+
* `ProjectFiles`), matching snippet.
|
|
22
|
+
*/
|
|
23
|
+
export declare function preprocessData(ast: Node, page: PreprocessPage, ctx: PreprocessContext): Node | void;
|
|
24
|
+
//# sourceMappingURL=data-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-pipeline.d.ts","sourceRoot":"","sources":["../src/data-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAgB,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA4CzF;;;;GAIG;AACH,wBAAgB,cAAc,CAC7B,GAAG,EAAE,IAAI,EACT,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,iBAAiB,GACpB,IAAI,GAAG,IAAI,CAKb"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data pipeline hook (SPEC-103).
|
|
3
|
+
*
|
|
4
|
+
* A preprocess sibling to `preprocessSnippets`: walk the parsed AST, and for
|
|
5
|
+
* every `{% data %}` tag resolve its `src` through the SPEC-113 `ProjectFiles`
|
|
6
|
+
* seam (whole-file, project-root bounded), run the format adapter + shared
|
|
7
|
+
* projection + typing, and replace the tag with a Markdoc `table` AST node. The
|
|
8
|
+
* emitted table is consumed by `chart`/`datatable` with no structural edits and
|
|
9
|
+
* is the honest no-JS fallback on a bare page.
|
|
10
|
+
*
|
|
11
|
+
* On any failure (sandbox escape / missing file / parse error / empty result)
|
|
12
|
+
* the tag is replaced with a visible error callout and a build warning is
|
|
13
|
+
* emitted — the build continues and the `data` tag never reaches its throwing
|
|
14
|
+
* transform.
|
|
15
|
+
*/
|
|
16
|
+
import { delimitedAdapter, jsonAdapter, ndjsonAdapter, inferFormat, DataSourceError, } from './data-adapters.js';
|
|
17
|
+
import { applyWhere, applySort, applyColumns, applyLimitOffset, applyTyping, } from './data-projection.js';
|
|
18
|
+
import { emitTableNode, emitErrorNode } from './data-emit.js';
|
|
19
|
+
/** Resolve a Markdoc attribute value to a string — literal strings and
|
|
20
|
+
* `Variable` AST nodes (e.g. `src=$file.dir`). Mirrors snippet's resolver. */
|
|
21
|
+
function resolveString(value, variables) {
|
|
22
|
+
if (value === undefined || value === null)
|
|
23
|
+
return '';
|
|
24
|
+
if (typeof value === 'string')
|
|
25
|
+
return value;
|
|
26
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
27
|
+
return String(value);
|
|
28
|
+
if (typeof value === 'object' && '$$mdtype' in value) {
|
|
29
|
+
const node = value;
|
|
30
|
+
if (node.$$mdtype === 'Variable' && Array.isArray(node.path)) {
|
|
31
|
+
let current = variables;
|
|
32
|
+
for (const segment of node.path) {
|
|
33
|
+
if (current === null || current === undefined)
|
|
34
|
+
return '';
|
|
35
|
+
current = current[segment];
|
|
36
|
+
}
|
|
37
|
+
return current === null || current === undefined ? '' : String(current);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
/** Parse a comma-separated column list into trimmed, non-empty names. */
|
|
43
|
+
function splitList(raw) {
|
|
44
|
+
return raw.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Preprocess: replace every `{% data %}` tag with a resolved `table` node (or an
|
|
48
|
+
* error callout). No-op when no provider is available (tree mode without a wired
|
|
49
|
+
* `ProjectFiles`), matching snippet.
|
|
50
|
+
*/
|
|
51
|
+
export function preprocessData(ast, page, ctx) {
|
|
52
|
+
if (!ctx.sandbox)
|
|
53
|
+
return;
|
|
54
|
+
let mutated = false;
|
|
55
|
+
walkAndReplaceData(ast, page, ctx, ctx.sandbox, () => { mutated = true; });
|
|
56
|
+
return mutated ? ast : undefined;
|
|
57
|
+
}
|
|
58
|
+
function walkAndReplaceData(node, page, ctx, files, onReplaced) {
|
|
59
|
+
if (!node.children)
|
|
60
|
+
return;
|
|
61
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
62
|
+
const child = node.children[i];
|
|
63
|
+
if (child.type === 'tag' && child.tag === 'data') {
|
|
64
|
+
node.children[i] = resolveDataToNode(child, page, ctx, files);
|
|
65
|
+
onReplaced();
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
walkAndReplaceData(child, page, ctx, files, onReplaced);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function resolveDataToNode(tag, page, ctx, files) {
|
|
72
|
+
const a = tag.attributes;
|
|
73
|
+
const src = resolveString(a.src, ctx.variables);
|
|
74
|
+
if (!src) {
|
|
75
|
+
const msg = 'data `src` attribute is required (and an unresolvable variable reference resolves to empty)';
|
|
76
|
+
ctx.error(msg, page.url);
|
|
77
|
+
return emitErrorNode(`data error: ${msg}`);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const raw = files.read(src);
|
|
81
|
+
if (raw === null) {
|
|
82
|
+
throw new DataSourceError(`source "${src}" cannot be resolved — the file is missing or outside the project root`);
|
|
83
|
+
}
|
|
84
|
+
const format = resolveFormat(src, resolveString(a.format, ctx.variables));
|
|
85
|
+
const table = runAdapter(raw, format, a, ctx);
|
|
86
|
+
// Shared projection: where → sort → columns → limit/offset.
|
|
87
|
+
const { table: filtered, warnings } = applyWhere(table, resolveString(a.where, ctx.variables) || undefined);
|
|
88
|
+
for (const w of warnings)
|
|
89
|
+
ctx.warn(`data "${src}": ${w}`, page.url);
|
|
90
|
+
const sorted = applySort(filtered, resolveString(a.sort, ctx.variables) || undefined);
|
|
91
|
+
const { table: selected, sources } = applyColumns(sorted, resolveString(a.columns, ctx.variables) || undefined);
|
|
92
|
+
const projected = applyLimitOffset(selected, numberAttr(a.limit), numberAttr(a.offset));
|
|
93
|
+
if (projected.rows.length === 0) {
|
|
94
|
+
throw new DataSourceError('result is empty after projection (no rows to render)');
|
|
95
|
+
}
|
|
96
|
+
// Shared typing → data-value channel (`numeric`/`text` may name the
|
|
97
|
+
// source or the renamed column).
|
|
98
|
+
const numericCols = splitList(resolveString(a.numeric, ctx.variables));
|
|
99
|
+
const textCols = splitList(resolveString(a.text, ctx.variables));
|
|
100
|
+
const typed = applyTyping(projected, { numeric: numericCols, text: textCols, sources });
|
|
101
|
+
return emitTableNode(typed);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
const msg = err instanceof DataSourceError
|
|
105
|
+
? err.message
|
|
106
|
+
: `unexpected failure — ${err instanceof Error ? err.message : String(err)}`;
|
|
107
|
+
ctx.error(`data "${src}": ${msg}`, page.url);
|
|
108
|
+
return emitErrorNode(`data error: ${msg}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Resolve the effective format: explicit override, else extension-inferred. */
|
|
112
|
+
function resolveFormat(src, explicit) {
|
|
113
|
+
if (explicit)
|
|
114
|
+
return explicit;
|
|
115
|
+
const inferred = inferFormat(src);
|
|
116
|
+
if (inferred)
|
|
117
|
+
return inferred;
|
|
118
|
+
throw new DataSourceError(`could not infer format from "${src}" — add format="csv|tsv|json|ndjson"`);
|
|
119
|
+
}
|
|
120
|
+
/** Dispatch to the format adapter. CSV/TSV land here (WORK-417); JSON/NDJSON
|
|
121
|
+
* adapters arrive in WORK-486 against this same contract. */
|
|
122
|
+
function runAdapter(raw, format, a, ctx) {
|
|
123
|
+
switch (format) {
|
|
124
|
+
case 'csv':
|
|
125
|
+
case 'tsv':
|
|
126
|
+
return delimitedAdapter(raw, {
|
|
127
|
+
format,
|
|
128
|
+
delimiter: resolveString(a.delimiter, ctx.variables) || undefined,
|
|
129
|
+
header: a.header === undefined ? undefined : a.header !== false,
|
|
130
|
+
});
|
|
131
|
+
case 'json': {
|
|
132
|
+
const orient = resolveString(a.orient, ctx.variables);
|
|
133
|
+
return jsonAdapter(raw, {
|
|
134
|
+
root: resolveString(a.root, ctx.variables) || undefined,
|
|
135
|
+
orient: orient ? orient : undefined,
|
|
136
|
+
keyColumn: resolveString(a['key-column'], ctx.variables) || undefined,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
case 'ndjson':
|
|
140
|
+
return ndjsonAdapter(raw);
|
|
141
|
+
default:
|
|
142
|
+
throw new DataSourceError(`unsupported format "${format}"`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function numberAttr(value) {
|
|
146
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
147
|
+
return value;
|
|
148
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
149
|
+
const n = Number(value);
|
|
150
|
+
return Number.isFinite(n) ? n : undefined;
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=data-pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-pipeline.js","sourceRoot":"","sources":["../src/data-pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared projection + typing for the `data` rune (SPEC-103).
|
|
3
|
+
*
|
|
4
|
+
* Everything here is format-agnostic: it runs on the intermediate
|
|
5
|
+
* `{ headers, rows }` shape every adapter produces, identically regardless of
|
|
6
|
+
* source. The pipeline is `where → sort → columns → limit/offset` (projection)
|
|
7
|
+
* then `numeric/text` typing, which emits the normalized `data-value` channel
|
|
8
|
+
* that carries a clean number to `chart`'s renderer and `datatable`'s sort.
|
|
9
|
+
*/
|
|
10
|
+
import type { DataTable } from './data-adapters.js';
|
|
11
|
+
export type ColumnType = 'numeric' | 'text';
|
|
12
|
+
/** A typed cell: the original (human-formatted) text plus a normalized numeric
|
|
13
|
+
* value when the column is numeric and the text parses. The `value` becomes
|
|
14
|
+
* the cell's `data-value`; the text stays the visible content + no-JS fallback. */
|
|
15
|
+
export interface TypedCell {
|
|
16
|
+
text: string;
|
|
17
|
+
value: number | null;
|
|
18
|
+
}
|
|
19
|
+
/** The fully projected + typed table the emitter turns into a Markdoc node. */
|
|
20
|
+
export interface TypedTable {
|
|
21
|
+
headers: string[];
|
|
22
|
+
columnTypes: ColumnType[];
|
|
23
|
+
rows: TypedCell[][];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Normalize a human-formatted number to a plain JS number, or null when the
|
|
27
|
+
* text isn't numeric. Strips common currency symbols and thousands separators
|
|
28
|
+
* (`"$1,200"` → `1200`); leaves percentages, units, and prose as non-numeric.
|
|
29
|
+
*/
|
|
30
|
+
export declare function normalizeNumber(text: string): number | null;
|
|
31
|
+
/** A single `columns` selection: source header + optional rename. */
|
|
32
|
+
interface ColumnSelection {
|
|
33
|
+
source: string;
|
|
34
|
+
as?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Parse a `columns` spec — `"name as Product, revenue as 'Revenue ($)'"` —
|
|
37
|
+
* into ordered selections. Commas inside quotes don't split; aliases may be
|
|
38
|
+
* bare, single-, or double-quoted. */
|
|
39
|
+
export declare function parseColumnsSpec(spec: string): ColumnSelection[];
|
|
40
|
+
/**
|
|
41
|
+
* Apply `where` — filter rows with the SPEC-070 `field:value` grammar, reusing
|
|
42
|
+
* `parseFieldMatch` + `matchValue` with a row-shaped resolver (the column header
|
|
43
|
+
* is the field; the cell text is the single candidate). AND across distinct
|
|
44
|
+
* fields, OR within a repeated field. Returns the (possibly filtered) table plus
|
|
45
|
+
* any parse warnings for the caller to surface.
|
|
46
|
+
*/
|
|
47
|
+
export declare function applyWhere(table: DataTable, expr: string | undefined): {
|
|
48
|
+
table: DataTable;
|
|
49
|
+
warnings: string[];
|
|
50
|
+
};
|
|
51
|
+
/** Apply `columns` — select, reorder, and rename. Unknown sources are skipped
|
|
52
|
+
* (a `where`/`sort` may legitimately reference a column the author then drops).
|
|
53
|
+
* Returns the (possibly renamed) table plus `sources` — the original source
|
|
54
|
+
* header for each output column, so typing can match `numeric`/`text` against
|
|
55
|
+
* either the source or the alias. With no spec, the table is unchanged and
|
|
56
|
+
* `sources` mirrors the headers. */
|
|
57
|
+
export declare function applyColumns(table: DataTable, spec: string | undefined): {
|
|
58
|
+
table: DataTable;
|
|
59
|
+
sources: string[];
|
|
60
|
+
};
|
|
61
|
+
/** Apply `sort` — `"revenue"` (asc) or `"-revenue"` (desc). Sorts numerically
|
|
62
|
+
* when every non-empty cell in the column parses as a number, else by natural
|
|
63
|
+
* string collation. A no-op when `spec` is empty or names an unknown column. */
|
|
64
|
+
export declare function applySort(table: DataTable, spec: string | undefined): DataTable;
|
|
65
|
+
/** Apply `limit`/`offset` — a row slice (the `data` analogue of snippet `lines=`). */
|
|
66
|
+
export declare function applyLimitOffset(table: DataTable, limit?: number, offset?: number): DataTable;
|
|
67
|
+
/**
|
|
68
|
+
* Type each column and emit the `data-value` channel. A column is numeric when
|
|
69
|
+
* explicitly listed in `numeric`, or (by default) when every non-empty cell
|
|
70
|
+
* parses as a number; `text` forces a column back to text. Numeric value cells
|
|
71
|
+
* carry the normalized number; text cells carry `null`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function applyTyping(table: DataTable, opts?: {
|
|
74
|
+
numeric?: string[];
|
|
75
|
+
text?: string[];
|
|
76
|
+
sources?: string[];
|
|
77
|
+
}): TypedTable;
|
|
78
|
+
export {};
|
|
79
|
+
//# sourceMappingURL=data-projection.d.ts.map
|