@mostlyrightmd/core 0.1.0-rc.7
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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/discovery/index.cjs +1646 -0
- package/dist/discovery/index.cjs.map +1 -0
- package/dist/discovery/index.d.cts +313 -0
- package/dist/discovery/index.d.ts +313 -0
- package/dist/discovery/index.mjs +1609 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/formats/index.cjs +498 -0
- package/dist/formats/index.cjs.map +1 -0
- package/dist/formats/index.d.cts +97 -0
- package/dist/formats/index.d.ts +97 -0
- package/dist/formats/index.mjs +465 -0
- package/dist/formats/index.mjs.map +1 -0
- package/dist/index.cjs +1624 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +559 -0
- package/dist/index.d.ts +559 -0
- package/dist/index.global.js +1582 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.mjs +1557 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal/bounds.cjs +125 -0
- package/dist/internal/bounds.cjs.map +1 -0
- package/dist/internal/bounds.d.cts +36 -0
- package/dist/internal/bounds.d.ts +36 -0
- package/dist/internal/bounds.mjs +81 -0
- package/dist/internal/bounds.mjs.map +1 -0
- package/dist/internal/cache/fs.cjs +217 -0
- package/dist/internal/cache/fs.cjs.map +1 -0
- package/dist/internal/cache/fs.d.cts +57 -0
- package/dist/internal/cache/fs.d.ts +57 -0
- package/dist/internal/cache/fs.mjs +179 -0
- package/dist/internal/cache/fs.mjs.map +1 -0
- package/dist/internal/cache/index.browser.cjs +1184 -0
- package/dist/internal/cache/index.browser.cjs.map +1 -0
- package/dist/internal/cache/index.browser.d.cts +20 -0
- package/dist/internal/cache/index.browser.d.ts +20 -0
- package/dist/internal/cache/index.browser.mjs +36 -0
- package/dist/internal/cache/index.browser.mjs.map +1 -0
- package/dist/internal/cache/index.cjs +1389 -0
- package/dist/internal/cache/index.cjs.map +1 -0
- package/dist/internal/cache/index.d.cts +16 -0
- package/dist/internal/cache/index.d.ts +16 -0
- package/dist/internal/cache/index.mjs +40 -0
- package/dist/internal/cache/index.mjs.map +1 -0
- package/dist/internal/chunk-PKJXHY27.mjs +1137 -0
- package/dist/internal/chunk-PKJXHY27.mjs.map +1 -0
- package/dist/internal/convert.cjs +161 -0
- package/dist/internal/convert.cjs.map +1 -0
- package/dist/internal/convert.d.cts +44 -0
- package/dist/internal/convert.d.ts +44 -0
- package/dist/internal/convert.mjs +117 -0
- package/dist/internal/convert.mjs.map +1 -0
- package/dist/internal/fs-O6XR4WWW.mjs +183 -0
- package/dist/internal/fs-O6XR4WWW.mjs.map +1 -0
- package/dist/internal/keys-B7C8C88N.d.cts +191 -0
- package/dist/internal/keys-B7C8C88N.d.ts +191 -0
- package/dist/internal/merge/index.cjs +75 -0
- package/dist/internal/merge/index.cjs.map +1 -0
- package/dist/internal/merge/index.d.cts +74 -0
- package/dist/internal/merge/index.d.ts +74 -0
- package/dist/internal/merge/index.mjs +46 -0
- package/dist/internal/merge/index.mjs.map +1 -0
- package/dist/internal/pairs.cjs +328 -0
- package/dist/internal/pairs.cjs.map +1 -0
- package/dist/internal/pairs.d.cts +105 -0
- package/dist/internal/pairs.d.ts +105 -0
- package/dist/internal/pairs.mjs +298 -0
- package/dist/internal/pairs.mjs.map +1 -0
- package/dist/qc/index.cjs +247 -0
- package/dist/qc/index.cjs.map +1 -0
- package/dist/qc/index.d.cts +140 -0
- package/dist/qc/index.d.ts +140 -0
- package/dist/qc/index.mjs +212 -0
- package/dist/qc/index.mjs.map +1 -0
- package/dist/temporal/index.cjs +504 -0
- package/dist/temporal/index.cjs.map +1 -0
- package/dist/temporal/index.d.cts +121 -0
- package/dist/temporal/index.d.ts +121 -0
- package/dist/temporal/index.mjs +474 -0
- package/dist/temporal/index.mjs.map +1 -0
- package/dist/transforms/index.cjs +399 -0
- package/dist/transforms/index.cjs.map +1 -0
- package/dist/transforms/index.d.cts +193 -0
- package/dist/transforms/index.d.ts +193 -0
- package/dist/transforms/index.mjs +362 -0
- package/dist/transforms/index.mjs.map +1 -0
- package/dist/validator.cjs +1870 -0
- package/dist/validator.cjs.map +1 -0
- package/dist/validator.d.cts +30 -0
- package/dist/validator.d.ts +30 -0
- package/dist/validator.mjs +1843 -0
- package/dist/validator.mjs.map +1 -0
- package/package.json +115 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/formats/index.ts
|
|
21
|
+
var formats_exports = {};
|
|
22
|
+
__export(formats_exports, {
|
|
23
|
+
ToonTabularError: () => ToonTabularError,
|
|
24
|
+
csvDumps: () => csvDumps,
|
|
25
|
+
csvLoads: () => csvLoads,
|
|
26
|
+
jsonDumps: () => jsonDumps,
|
|
27
|
+
jsonLoads: () => jsonLoads,
|
|
28
|
+
toonDumps: () => toonDumps,
|
|
29
|
+
toonLoads: () => toonLoads
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(formats_exports);
|
|
32
|
+
|
|
33
|
+
// src/formats/json.ts
|
|
34
|
+
function jsonDumps(rows, columns) {
|
|
35
|
+
if (rows.length === 0) {
|
|
36
|
+
if (columns === void 0) {
|
|
37
|
+
throw new RangeError(
|
|
38
|
+
"jsonDumps: columns parameter is required when rows is empty (envelope form preserves column order)"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return JSON.stringify({ columns: [...columns], data: [] });
|
|
42
|
+
}
|
|
43
|
+
return JSON.stringify(rows);
|
|
44
|
+
}
|
|
45
|
+
function jsonLoads(data) {
|
|
46
|
+
const trimmed = data.trimStart();
|
|
47
|
+
if (trimmed.startsWith("{")) {
|
|
48
|
+
const parsed2 = JSON.parse(data);
|
|
49
|
+
if (Array.isArray(parsed2.columns) && Array.isArray(parsed2.data)) {
|
|
50
|
+
const columns2 = parsed2.columns.map(String);
|
|
51
|
+
const rows2 = parsed2.data;
|
|
52
|
+
return { rows: rows2, columns: columns2 };
|
|
53
|
+
}
|
|
54
|
+
throw new RangeError(
|
|
55
|
+
`jsonLoads: envelope form must have {columns: string[], data: object[]}; got ${data.slice(
|
|
56
|
+
0,
|
|
57
|
+
80
|
|
58
|
+
)}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const parsed = JSON.parse(data);
|
|
62
|
+
if (!Array.isArray(parsed)) {
|
|
63
|
+
throw new RangeError(`jsonLoads: expected array or envelope; got ${typeof parsed}`);
|
|
64
|
+
}
|
|
65
|
+
const rows = parsed;
|
|
66
|
+
const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
67
|
+
return { rows, columns };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/formats/csv.ts
|
|
71
|
+
var QUOTE_NEEDED = /[,"\n\r]/;
|
|
72
|
+
function quoteCell(value) {
|
|
73
|
+
if (value == null) return "";
|
|
74
|
+
const s = String(value);
|
|
75
|
+
if (QUOTE_NEEDED.test(s)) {
|
|
76
|
+
return `"${s.replace(/"/g, '""')}"`;
|
|
77
|
+
}
|
|
78
|
+
return s;
|
|
79
|
+
}
|
|
80
|
+
function csvDumps(rows) {
|
|
81
|
+
if (rows.length === 0) return "";
|
|
82
|
+
const columns = Object.keys(rows[0]);
|
|
83
|
+
const header = columns.map(quoteCell).join(",");
|
|
84
|
+
const dataLines = rows.map(
|
|
85
|
+
(r) => columns.map((c) => quoteCell(r[c])).join(",")
|
|
86
|
+
);
|
|
87
|
+
return `${[header, ...dataLines].join("\n")}
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
function parseCsvBuffer(data) {
|
|
91
|
+
const rows = [];
|
|
92
|
+
let cur = [];
|
|
93
|
+
let cell = "";
|
|
94
|
+
let inQuotes = false;
|
|
95
|
+
let i = 0;
|
|
96
|
+
const n = data.length;
|
|
97
|
+
while (i < n) {
|
|
98
|
+
const ch = data[i];
|
|
99
|
+
if (inQuotes) {
|
|
100
|
+
if (ch === '"') {
|
|
101
|
+
if (data[i + 1] === '"') {
|
|
102
|
+
cell += '"';
|
|
103
|
+
i += 2;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
inQuotes = false;
|
|
107
|
+
i++;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
cell += ch;
|
|
111
|
+
i++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (ch === '"') {
|
|
115
|
+
inQuotes = true;
|
|
116
|
+
i++;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (ch === ",") {
|
|
120
|
+
cur.push(cell);
|
|
121
|
+
cell = "";
|
|
122
|
+
i++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (ch === "\r") {
|
|
126
|
+
cur.push(cell);
|
|
127
|
+
rows.push(cur);
|
|
128
|
+
cur = [];
|
|
129
|
+
cell = "";
|
|
130
|
+
i++;
|
|
131
|
+
if (i < n && data[i] === "\n") i++;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (ch === "\n") {
|
|
135
|
+
cur.push(cell);
|
|
136
|
+
rows.push(cur);
|
|
137
|
+
cur = [];
|
|
138
|
+
cell = "";
|
|
139
|
+
i++;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
cell += ch;
|
|
143
|
+
i++;
|
|
144
|
+
}
|
|
145
|
+
if (cell.length > 0 || cur.length > 0) {
|
|
146
|
+
cur.push(cell);
|
|
147
|
+
rows.push(cur);
|
|
148
|
+
}
|
|
149
|
+
return rows;
|
|
150
|
+
}
|
|
151
|
+
function csvLoads(data) {
|
|
152
|
+
if (data.length === 0) return { rows: [], columns: [] };
|
|
153
|
+
const parsed = parseCsvBuffer(data);
|
|
154
|
+
if (parsed.length === 0) return { rows: [], columns: [] };
|
|
155
|
+
const columns = parsed[0] ?? [];
|
|
156
|
+
const dataRows = parsed.slice(1);
|
|
157
|
+
const rows = dataRows.map((cells) => {
|
|
158
|
+
const r = {};
|
|
159
|
+
for (let i = 0; i < columns.length; i++) {
|
|
160
|
+
r[columns[i]] = cells[i] ?? "";
|
|
161
|
+
}
|
|
162
|
+
return r;
|
|
163
|
+
});
|
|
164
|
+
return { rows, columns };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/formats/toon.ts
|
|
168
|
+
var SAFE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_.]*$/;
|
|
169
|
+
var NUMERIC_LIKE_RE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
170
|
+
var NEEDS_QUOTE_CHARS_RE = /[:\\\"'\[\]{}\x00-\x1f\x7f\x85\u2028\u2029]/;
|
|
171
|
+
var UNSUPPORTED_CTRL_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\x85\u2028\u2029]/g;
|
|
172
|
+
function formatNumber(n) {
|
|
173
|
+
if (!Number.isFinite(n)) return "null";
|
|
174
|
+
if (n === 0) return "0";
|
|
175
|
+
if (Number.isInteger(n) && Math.abs(n) <= 2 ** 53) return String(n);
|
|
176
|
+
let s = String(n);
|
|
177
|
+
if (/[eE]/.test(s)) {
|
|
178
|
+
s = expandExponent(s);
|
|
179
|
+
}
|
|
180
|
+
return s;
|
|
181
|
+
}
|
|
182
|
+
function expandExponent(s) {
|
|
183
|
+
const m = /^(-?)(\d+(?:\.\d+)?)[eE]([+-]?\d+)$/.exec(s);
|
|
184
|
+
if (!m) return s;
|
|
185
|
+
const sign = m[1] ?? "";
|
|
186
|
+
const mantissa = m[2] ?? "";
|
|
187
|
+
const exp = Number(m[3]);
|
|
188
|
+
const [intPart, fracPart = ""] = mantissa.split(".");
|
|
189
|
+
const digits = (intPart ?? "") + fracPart;
|
|
190
|
+
const pointPos = (intPart ?? "").length + exp;
|
|
191
|
+
let out;
|
|
192
|
+
if (pointPos <= 0) {
|
|
193
|
+
out = `0.${"0".repeat(-pointPos)}${digits}`.replace(/0+$/, "");
|
|
194
|
+
if (out.endsWith(".")) out = out.slice(0, -1);
|
|
195
|
+
} else if (pointPos >= digits.length) {
|
|
196
|
+
out = digits + "0".repeat(pointPos - digits.length);
|
|
197
|
+
} else {
|
|
198
|
+
out = `${digits.slice(0, pointPos)}.${digits.slice(pointPos)}`.replace(/0+$/, "");
|
|
199
|
+
if (out.endsWith(".")) out = out.slice(0, -1);
|
|
200
|
+
}
|
|
201
|
+
return sign + out;
|
|
202
|
+
}
|
|
203
|
+
function needsQuoting(s, delimiter) {
|
|
204
|
+
if (s.length === 0) return true;
|
|
205
|
+
const first = s.charAt(0);
|
|
206
|
+
const last = s.charAt(s.length - 1);
|
|
207
|
+
if (first === " " || first === " ") return true;
|
|
208
|
+
if (last === " " || last === " ") return true;
|
|
209
|
+
if (s === "true" || s === "false" || s === "null") return true;
|
|
210
|
+
if (first === "-" || first === "+") return true;
|
|
211
|
+
if (first >= "0" && first <= "9") return true;
|
|
212
|
+
if (NUMERIC_LIKE_RE.test(s)) return true;
|
|
213
|
+
if (s.includes(delimiter)) return true;
|
|
214
|
+
if (NEEDS_QUOTE_CHARS_RE.test(s)) return true;
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
function quoteString(s) {
|
|
218
|
+
let out = s.replace(UNSUPPORTED_CTRL_RE, "");
|
|
219
|
+
out = out.replace(/\\/g, "\\\\");
|
|
220
|
+
out = out.replace(/"/g, '\\"');
|
|
221
|
+
out = out.replace(/\n/g, "\\n");
|
|
222
|
+
out = out.replace(/\r/g, "\\r");
|
|
223
|
+
out = out.replace(/\t/g, "\\t");
|
|
224
|
+
return `"${out}"`;
|
|
225
|
+
}
|
|
226
|
+
function formatKey(key) {
|
|
227
|
+
if (typeof key !== "string") {
|
|
228
|
+
throw new TypeError(`TOON keys must be strings; got ${typeof key}`);
|
|
229
|
+
}
|
|
230
|
+
if (SAFE_KEY_RE.test(key)) return key;
|
|
231
|
+
return quoteString(key);
|
|
232
|
+
}
|
|
233
|
+
function encodeScalar(value, delimiter) {
|
|
234
|
+
if (value === null || value === void 0) return "null";
|
|
235
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
236
|
+
if (typeof value === "number") return formatNumber(value);
|
|
237
|
+
if (typeof value === "string") {
|
|
238
|
+
if (needsQuoting(value, delimiter)) return quoteString(value);
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
if (typeof value === "object") {
|
|
242
|
+
if (Array.isArray(value)) {
|
|
243
|
+
return quoteString(JSON.stringify(value));
|
|
244
|
+
}
|
|
245
|
+
const sorted = sortedJson(value);
|
|
246
|
+
return quoteString(sorted);
|
|
247
|
+
}
|
|
248
|
+
return quoteString(String(value));
|
|
249
|
+
}
|
|
250
|
+
function sortedJson(obj) {
|
|
251
|
+
const keys = Object.keys(obj).sort();
|
|
252
|
+
const parts = keys.map((k) => `${JSON.stringify(k)}:${JSON.stringify(obj[k])}`);
|
|
253
|
+
return `{${parts.join(",")}}`;
|
|
254
|
+
}
|
|
255
|
+
var ToonTabularError = class extends RangeError {
|
|
256
|
+
name = "ToonTabularError";
|
|
257
|
+
};
|
|
258
|
+
function isToonPrimitive(v) {
|
|
259
|
+
if (v === null || v === void 0) return true;
|
|
260
|
+
const t = typeof v;
|
|
261
|
+
return t === "string" || t === "number" || t === "boolean";
|
|
262
|
+
}
|
|
263
|
+
function assertTabular(rows) {
|
|
264
|
+
if (rows.length === 0) return;
|
|
265
|
+
const first = rows[0];
|
|
266
|
+
const expectedKeys = Object.keys(first);
|
|
267
|
+
if (expectedKeys.length === 0) {
|
|
268
|
+
throw new ToonTabularError(
|
|
269
|
+
"toonDumps requires non-empty rows; first row has no keys (Python parity: encode_tabular rejects empty key set)"
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
const expectedKeySet = new Set(expectedKeys);
|
|
273
|
+
for (let i = 0; i < rows.length; i++) {
|
|
274
|
+
const row = rows[i];
|
|
275
|
+
const rowKeys = Object.keys(row);
|
|
276
|
+
if (rowKeys.length !== expectedKeySet.size) {
|
|
277
|
+
throw new ToonTabularError(
|
|
278
|
+
`toonDumps requires uniform rows; row ${i} has ${rowKeys.length} key(s) vs row 0's ${expectedKeySet.size}. Python encode_tabular rejects rows whose key sets differ.`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
for (const k of rowKeys) {
|
|
282
|
+
if (!expectedKeySet.has(k)) {
|
|
283
|
+
throw new ToonTabularError(
|
|
284
|
+
`toonDumps requires uniform rows; row ${i} has key ${JSON.stringify(k)} not present in row 0. Python encode_tabular rejects rows whose key sets differ.`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
for (const k of expectedKeys) {
|
|
289
|
+
const v = row[k];
|
|
290
|
+
if (!isToonPrimitive(v)) {
|
|
291
|
+
throw new ToonTabularError(
|
|
292
|
+
`toonDumps requires primitive cell values; row ${i} column ${JSON.stringify(k)} has non-primitive value of type ${typeof v}. Python encode_tabular rejects nested objects/arrays at the cell level.`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function toonDumps(rows, columns) {
|
|
299
|
+
if (rows.length === 0) {
|
|
300
|
+
if (columns !== void 0) {
|
|
301
|
+
const cols2 = columns.map((c) => formatKey(String(c))).join(",");
|
|
302
|
+
return `rows[0]{${cols2}}:`;
|
|
303
|
+
}
|
|
304
|
+
return "rows[0]:";
|
|
305
|
+
}
|
|
306
|
+
assertTabular(rows);
|
|
307
|
+
const cols = Object.keys(rows[0]);
|
|
308
|
+
const colHeader = cols.map((c) => formatKey(c)).join(",");
|
|
309
|
+
const header = `rows[${rows.length}]{${colHeader}}:`;
|
|
310
|
+
const dataLines = rows.map((r) => {
|
|
311
|
+
const vals = cols.map((c) => encodeScalar(r[c], ","));
|
|
312
|
+
return ` ${vals.join(",")}`;
|
|
313
|
+
});
|
|
314
|
+
return `${header}
|
|
315
|
+
${dataLines.join("\n")}`;
|
|
316
|
+
}
|
|
317
|
+
var HEADER_PREFIX_RE = /^(?<key>[A-Za-z_][A-Za-z0-9_.]*)\[(?<count>\d+)\]/;
|
|
318
|
+
function parseHeaderLine(line) {
|
|
319
|
+
const prefix = HEADER_PREFIX_RE.exec(line);
|
|
320
|
+
if (prefix == null) {
|
|
321
|
+
throw new RangeError(`TOON payload missing tabular header; got: ${JSON.stringify(line)}`);
|
|
322
|
+
}
|
|
323
|
+
const declared = Number(prefix.groups?.count ?? "");
|
|
324
|
+
let i = prefix[0].length;
|
|
325
|
+
const n = line.length;
|
|
326
|
+
if (i < n && line[i] === ":" && declared === 0) {
|
|
327
|
+
const rest2 = line.slice(i + 1).trim();
|
|
328
|
+
if (rest2 === "") return { count: 0, cols: "" };
|
|
329
|
+
throw new RangeError(`TOON header has trailing junk: ${JSON.stringify(line)}`);
|
|
330
|
+
}
|
|
331
|
+
if (i >= n || line[i] !== "{") {
|
|
332
|
+
throw new RangeError(`TOON header missing column region: ${JSON.stringify(line)}`);
|
|
333
|
+
}
|
|
334
|
+
i++;
|
|
335
|
+
while (i < n) {
|
|
336
|
+
const ch = line[i];
|
|
337
|
+
if (ch === '"') {
|
|
338
|
+
let j = i + 1;
|
|
339
|
+
while (j < n) {
|
|
340
|
+
if (line[j] === "\\" && j + 1 < n) {
|
|
341
|
+
j += 2;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (line[j] === '"') break;
|
|
345
|
+
j++;
|
|
346
|
+
}
|
|
347
|
+
if (j >= n) {
|
|
348
|
+
throw new RangeError(
|
|
349
|
+
`TOON header has unterminated quoted column name: ${JSON.stringify(line)}`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
i = j + 1;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (ch === "}") break;
|
|
356
|
+
i++;
|
|
357
|
+
}
|
|
358
|
+
if (i >= n || line[i] !== "}") {
|
|
359
|
+
throw new RangeError(`TOON header missing closing brace: ${JSON.stringify(line)}`);
|
|
360
|
+
}
|
|
361
|
+
const cols = line.slice(prefix[0].length + 1, i);
|
|
362
|
+
const rest = line.slice(i + 1).trim();
|
|
363
|
+
if (rest !== ":") {
|
|
364
|
+
throw new RangeError(`TOON header missing colon terminator: ${JSON.stringify(line)}`);
|
|
365
|
+
}
|
|
366
|
+
return { count: declared, cols };
|
|
367
|
+
}
|
|
368
|
+
function splitCsvRow(line) {
|
|
369
|
+
const tokens = [];
|
|
370
|
+
const n = line.length;
|
|
371
|
+
if (n === 0) return tokens;
|
|
372
|
+
let i = 0;
|
|
373
|
+
while (true) {
|
|
374
|
+
while (i < n && line[i] === " ") i++;
|
|
375
|
+
if (i >= n) {
|
|
376
|
+
tokens.push("");
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
if (line[i] === '"') {
|
|
380
|
+
let j = i + 1;
|
|
381
|
+
while (j < n) {
|
|
382
|
+
if (line[j] === "\\" && j + 1 < n) {
|
|
383
|
+
j += 2;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (line[j] === '"') break;
|
|
387
|
+
j++;
|
|
388
|
+
}
|
|
389
|
+
tokens.push(line.slice(i, j + 1));
|
|
390
|
+
i = j + 1;
|
|
391
|
+
if (i >= n) break;
|
|
392
|
+
if (line[i] === ",") {
|
|
393
|
+
i++;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
while (i < n && line[i] !== ",") i++;
|
|
397
|
+
if (i >= n) break;
|
|
398
|
+
i++;
|
|
399
|
+
} else {
|
|
400
|
+
let j = i;
|
|
401
|
+
while (j < n && line[j] !== ",") j++;
|
|
402
|
+
tokens.push(line.slice(i, j));
|
|
403
|
+
i = j;
|
|
404
|
+
if (i >= n) break;
|
|
405
|
+
i++;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return tokens;
|
|
409
|
+
}
|
|
410
|
+
function decodeQuoted(token) {
|
|
411
|
+
const inner = token.slice(1, -1);
|
|
412
|
+
let out = "";
|
|
413
|
+
let i = 0;
|
|
414
|
+
while (i < inner.length) {
|
|
415
|
+
const ch = inner[i];
|
|
416
|
+
if (ch === "\\" && i + 1 < inner.length) {
|
|
417
|
+
const nxt = inner.charAt(i + 1);
|
|
418
|
+
if (nxt === "\\") out += "\\";
|
|
419
|
+
else if (nxt === '"') out += '"';
|
|
420
|
+
else if (nxt === "n") out += "\n";
|
|
421
|
+
else if (nxt === "r") out += "\r";
|
|
422
|
+
else if (nxt === "t") out += " ";
|
|
423
|
+
else out += nxt;
|
|
424
|
+
i += 2;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
out += ch;
|
|
428
|
+
i++;
|
|
429
|
+
}
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
function unquoteIfQuoted(token) {
|
|
433
|
+
if (token.length >= 2 && token[0] === '"' && token[token.length - 1] === '"') {
|
|
434
|
+
return decodeQuoted(token);
|
|
435
|
+
}
|
|
436
|
+
return token;
|
|
437
|
+
}
|
|
438
|
+
function decodeValue(token) {
|
|
439
|
+
if (token.length === 0) return null;
|
|
440
|
+
if (token[0] === '"' && token[token.length - 1] === '"' && token.length >= 2) {
|
|
441
|
+
return decodeQuoted(token);
|
|
442
|
+
}
|
|
443
|
+
if (token === "null") return null;
|
|
444
|
+
if (token === "true") return true;
|
|
445
|
+
if (token === "false") return false;
|
|
446
|
+
if (NUMERIC_LIKE_RE.test(token)) {
|
|
447
|
+
if (!token.includes(".") && !/[eE]/.test(token)) {
|
|
448
|
+
const n = Number.parseInt(token, 10);
|
|
449
|
+
if (!Number.isNaN(n)) return n;
|
|
450
|
+
}
|
|
451
|
+
const f = Number.parseFloat(token);
|
|
452
|
+
if (!Number.isNaN(f)) return f;
|
|
453
|
+
}
|
|
454
|
+
return token;
|
|
455
|
+
}
|
|
456
|
+
function toonLoads(data) {
|
|
457
|
+
const lines = data.split(/\r?\n/);
|
|
458
|
+
let idx = 0;
|
|
459
|
+
while (idx < lines.length && lines[idx]?.trim() === "") idx++;
|
|
460
|
+
if (idx >= lines.length) throw new RangeError("empty TOON payload");
|
|
461
|
+
const { count: declared, cols: colsRegion } = parseHeaderLine(lines[idx] ?? "");
|
|
462
|
+
const columns = colsRegion === "" ? [] : splitCsvRow(colsRegion).map((t) => unquoteIfQuoted(t));
|
|
463
|
+
const rawRows = [];
|
|
464
|
+
for (const raw of lines.slice(idx + 1)) {
|
|
465
|
+
const line = raw.replace(/\s+$/u, "");
|
|
466
|
+
if (line.trim() === "") continue;
|
|
467
|
+
const stripped = line.replace(/^ +/u, "");
|
|
468
|
+
const tokens = splitCsvRow(stripped);
|
|
469
|
+
if (columns.length > 0 && tokens.length !== columns.length) {
|
|
470
|
+
throw new RangeError(
|
|
471
|
+
`TOON row column count mismatch: expected ${columns.length}, got ${tokens.length}: ${JSON.stringify(stripped)}`
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
rawRows.push(tokens.map((t) => decodeValue(t)));
|
|
475
|
+
}
|
|
476
|
+
if (declared !== rawRows.length) {
|
|
477
|
+
throw new RangeError(`TOON declared row count ${declared} != actual ${rawRows.length}`);
|
|
478
|
+
}
|
|
479
|
+
const rows = rawRows.map((row) => {
|
|
480
|
+
const r = {};
|
|
481
|
+
for (let i = 0; i < columns.length; i++) {
|
|
482
|
+
r[columns[i]] = row[i];
|
|
483
|
+
}
|
|
484
|
+
return r;
|
|
485
|
+
});
|
|
486
|
+
return { rows, columns };
|
|
487
|
+
}
|
|
488
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
489
|
+
0 && (module.exports = {
|
|
490
|
+
ToonTabularError,
|
|
491
|
+
csvDumps,
|
|
492
|
+
csvLoads,
|
|
493
|
+
jsonDumps,
|
|
494
|
+
jsonLoads,
|
|
495
|
+
toonDumps,
|
|
496
|
+
toonLoads
|
|
497
|
+
});
|
|
498
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/formats/index.ts","../../src/formats/json.ts","../../src/formats/csv.ts","../../src/formats/toon.ts"],"sourcesContent":["// Barrel for @mostlyrightmd/core/formats — TS-W3 Plan 07.\n//\n// Three serializer pairs ported from Python:\n// - jsonDumps / jsonLoads (records form + empty-frame envelope)\n// - csvDumps / csvLoads (hand-rolled minimal RFC-4180, no papaparse)\n// - toonDumps / toonLoads (TOON v3.0 tabular block, byte-equivalent\n// to Python `encode_tabular` on shared fixture).\n//\n// Out of scope per TS-FORMAT-01:\n// - parquet → deferred to v0.2 via parquet-wasm (no stub).\n// - dataframe → TS has no DataFrames (no stub).\n\nexport { jsonDumps, jsonLoads } from \"./json.js\";\nexport { csvDumps, csvLoads } from \"./csv.js\";\nexport { toonDumps, toonLoads, ToonTabularError } from \"./toon.js\";\n","// JSON format — records-form / empty-frame-envelope serialization.\n//\n// Mirrors `packages/core/src/mostlyright/core/formats/json.py`. Non-empty rows\n// emit the records form (`[{col1:v1,col2:v2},...]`); empty rows emit the\n// envelope `{columns: [...], data: []}` so column names survive a roundtrip.\n//\n// The signature `jsonDumps(rows, columns?)` requires `columns` ONLY when\n// `rows.length === 0` — otherwise columns are inferred from `Object.keys\n// (rows[0])`.\n\n/**\n * Serialize rows to a JSON string.\n *\n * - Non-empty: emits records form `JSON.stringify(rows)`.\n * - Empty: emits envelope `{\"columns\": [...], \"data\": []}` — column names\n * survive the empty-frame roundtrip. Throws RangeError if `columns` is\n * not provided in the empty case.\n */\nexport function jsonDumps(\n rows: ReadonlyArray<Record<string, unknown>>,\n columns?: ReadonlyArray<string>,\n): string {\n if (rows.length === 0) {\n if (columns === undefined) {\n throw new RangeError(\n \"jsonDumps: columns parameter is required when rows is empty (envelope form preserves column order)\",\n );\n }\n return JSON.stringify({ columns: [...columns], data: [] });\n }\n return JSON.stringify(rows);\n}\n\n/**\n * Parse a JSON string into rows + column-order array.\n *\n * Accepts both the records form (`[{...}, ...]`) and the empty-frame\n * envelope (`{columns, data}`). Returns BOTH `rows` AND `columns` so\n * callers can preserve column order on empty cases.\n */\nexport function jsonLoads(data: string): {\n rows: Array<Record<string, unknown>>;\n columns: string[];\n} {\n const trimmed = data.trimStart();\n if (trimmed.startsWith(\"{\")) {\n const parsed = JSON.parse(data) as { columns?: unknown; data?: unknown };\n if (Array.isArray(parsed.columns) && Array.isArray(parsed.data)) {\n const columns = parsed.columns.map(String);\n const rows = parsed.data as Array<Record<string, unknown>>;\n return { rows, columns };\n }\n throw new RangeError(\n `jsonLoads: envelope form must have {columns: string[], data: object[]}; got ${data.slice(\n 0,\n 80,\n )}`,\n );\n }\n const parsed = JSON.parse(data);\n if (!Array.isArray(parsed)) {\n throw new RangeError(`jsonLoads: expected array or envelope; got ${typeof parsed}`);\n }\n const rows = parsed as Array<Record<string, unknown>>;\n const columns = rows.length > 0 ? Object.keys(rows[0] as Record<string, unknown>) : [];\n return { rows, columns };\n}\n","// CSV format — pandas-style serialization without an index column.\n//\n// Mirrors `packages/core/src/mostlyright/core/formats/csv.py`:\n// - dumps emits header row + value rows, no index column\n// - loads parses header + rows back into Array<Record<string,string>>\n// - all values stringify to \"\" if null/undefined (matches pandas NaN\n// emit behavior + the load-side empty-cell → empty-string convention)\n//\n// Hand-rolled RFC-4180 parser to avoid the `papaparse` bundle hit\n// (TS-SDK-DESIGN §5.4 explicit guidance). Iter-1 C4: the parser is now\n// stateful (character-level state machine) so it correctly preserves\n// newlines inside quoted cells — which `csvDumps` itself emits when a\n// cell contains `\\n`. Previously `csvLoads` line-split first, breaking\n// every multi-line quoted cell into spurious extra rows.\n\nconst QUOTE_NEEDED = /[,\"\\n\\r]/;\n\nfunction quoteCell(value: unknown): string {\n if (value == null) return \"\";\n const s = String(value);\n if (QUOTE_NEEDED.test(s)) {\n return `\"${s.replace(/\"/g, '\"\"')}\"`;\n }\n return s;\n}\n\n/**\n * Serialize rows to a CSV string. Column names come from\n * `Object.keys(rows[0])`. Empty rows emits an empty string (matches\n * `pd.DataFrame({}).to_csv(index=False)`).\n *\n * Iter-2 C7: header cells are quoted the same way as data cells.\n * Python's `DataFrame.to_csv` quotes header strings on the same\n * triggers as values; without this guard a column name like `\"a,b\"`\n * would dump as two headers and roundtrip into the wrong schema.\n */\nexport function csvDumps(rows: ReadonlyArray<Record<string, unknown>>): string {\n if (rows.length === 0) return \"\";\n const columns = Object.keys(rows[0] as Record<string, unknown>);\n const header = columns.map(quoteCell).join(\",\");\n const dataLines = rows.map((r) =>\n columns.map((c) => quoteCell((r as Record<string, unknown>)[c])).join(\",\"),\n );\n return `${[header, ...dataLines].join(\"\\n\")}\\n`;\n}\n\n/**\n * Stateful RFC-4180 parser. Reads the whole `data` buffer character by\n * character, tracking whether the cursor is inside a quoted cell. Inside\n * a quoted cell every byte except `\"\"` (escaped quote → literal `\"`) and\n * the closing `\"` is preserved verbatim — including newlines and commas,\n * which is the whole point of the quoting.\n *\n * Returns the parsed rows as `string[][]` (the header is row 0). Empty\n * `data` returns `[]`. A trailing newline is treated as a row terminator\n * (no spurious empty row), matching `pd.read_csv` and `csvDumps`'s emit.\n *\n * CR / CRLF normalization: standalone `\\r` or `\\r\\n` outside a quoted\n * cell collapse to `\\n`. INSIDE a quoted cell every newline byte is\n * preserved as-is to keep the roundtrip lossless.\n */\nfunction parseCsvBuffer(data: string): string[][] {\n const rows: string[][] = [];\n let cur: string[] = [];\n let cell = \"\";\n let inQuotes = false;\n let i = 0;\n const n = data.length;\n\n while (i < n) {\n const ch = data[i];\n\n if (inQuotes) {\n if (ch === '\"') {\n // Lookahead: doubled quote inside a quoted cell → literal `\"`.\n if (data[i + 1] === '\"') {\n cell += '\"';\n i += 2;\n continue;\n }\n // Otherwise this closes the quoted cell.\n inQuotes = false;\n i++;\n continue;\n }\n // Any other char (including raw `\\n`/`\\r`/`,`) is part of the cell.\n cell += ch;\n i++;\n continue;\n }\n\n // OUTSIDE a quoted cell:\n if (ch === '\"') {\n // Opening quote. Per RFC 4180, a quoted cell starts at the\n // beginning of a field — but we accept a stray `\"` mid-field\n // gracefully (matching `csv.reader` permissive mode): treat it as\n // entering quoted mode. The next `\"` (unless doubled) ends it.\n inQuotes = true;\n i++;\n continue;\n }\n if (ch === \",\") {\n cur.push(cell);\n cell = \"\";\n i++;\n continue;\n }\n if (ch === \"\\r\") {\n // CR or CRLF outside quotes → row terminator. Consume optional LF.\n cur.push(cell);\n rows.push(cur);\n cur = [];\n cell = \"\";\n i++;\n if (i < n && data[i] === \"\\n\") i++;\n continue;\n }\n if (ch === \"\\n\") {\n cur.push(cell);\n rows.push(cur);\n cur = [];\n cell = \"\";\n i++;\n continue;\n }\n cell += ch;\n i++;\n }\n\n // Flush the trailing record. Match csvDumps which always emits a final\n // `\\n`: if the buffer ends on a newline we already pushed the final\n // row and `cur`/`cell` are both empty — skip in that case.\n if (cell.length > 0 || cur.length > 0) {\n cur.push(cell);\n rows.push(cur);\n }\n return rows;\n}\n\n/**\n * Parse a CSV string into rows + columns.\n *\n * Returns string-valued cells; CSV is dtype-lossy (pandas\n * read_csv would re-infer dtypes — we leave that to the caller).\n *\n * Empty input → `{ rows: [], columns: [] }`. Header-only input →\n * `{ rows: [], columns: [...] }`.\n *\n * Iter-1 C4: stateful parser preserves newlines inside quoted cells, so\n * `csvLoads(csvDumps(rows))` is now a faithful roundtrip even when cells\n * contain `\\n` — previously the line-splitter exploded such cells into\n * extra rows.\n */\nexport function csvLoads(data: string): {\n rows: Array<Record<string, string>>;\n columns: string[];\n} {\n if (data.length === 0) return { rows: [], columns: [] };\n const parsed = parseCsvBuffer(data);\n if (parsed.length === 0) return { rows: [], columns: [] };\n const columns = parsed[0] ?? [];\n const dataRows = parsed.slice(1);\n const rows = dataRows.map((cells) => {\n const r: Record<string, string> = {};\n for (let i = 0; i < columns.length; i++) {\n r[columns[i] as string] = cells[i] ?? \"\";\n }\n return r;\n });\n return { rows, columns };\n}\n","// TOON v3.0 tabular format — byte-equivalent to Python `encode_tabular`.\n//\n// Ports the encoder portions of\n// `packages/core/src/mostlyright/core/formats/_toon.py` and the tabular\n// loader from `packages/core/src/mostlyright/core/formats/toon.py`.\n//\n// Wire shape (single tabular block):\n//\n// rows[N]{col1,col2,col3}:\n// v1a,v2a,v3a\n// v1b,v2b,v3b\n//\n// Where N is the row count, `{...}` is the column list, each subsequent line\n// is one row's values. Column order comes from the first row's keys.\n//\n// TOON loss matrix (matches Python `toon.py`):\n// - dict/object cells stringify deterministically via canonical JSON\n// (sorted keys + JSON.stringify of nested values).\n// - null + undefined both encode as the bare literal `null`.\n// - NaN / +Infinity / -Infinity encode as `null` (per `_format_number`).\n// - Integer-valued floats serialize without a fractional part (1.0 → \"1\").\n// - Strings that look numeric, start with - / + / digit, are\n// `true`/`false`/`null`, or contain commas/quotes/control chars are\n// quoted. Otherwise emitted bare.\n\n// ---------------------------------------------------------------------------\n// Encoder regex set (mirrors Python `_toon.py`)\n// ---------------------------------------------------------------------------\n\nconst SAFE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_.]*$/;\nconst NUMERIC_LIKE_RE = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/;\n// Quote triggers per TOON spec: colon, quote, backslash, brackets, braces,\n// ASCII control chars, NEL, LSEP, PSEP. Constructed via RegExp(...) to avoid\n// embedding literal LSEP/PSEP bytes (esbuild rejects those in regex literals).\n// biome-ignore lint/suspicious/noControlCharactersInRegex: TOON spec parity\nconst NEEDS_QUOTE_CHARS_RE = /[:\\\\\\\"'\\[\\]{}\\x00-\\x1f\\x7f\\x85\\u2028\\u2029]/;\n// Control chars without a defined TOON escape — stripped on quote.\n// biome-ignore lint/suspicious/noControlCharactersInRegex: TOON spec parity\nconst UNSUPPORTED_CTRL_RE = /[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f\\x85\\u2028\\u2029]/g;\n\n// ---------------------------------------------------------------------------\n// Scalar encoding\n// ---------------------------------------------------------------------------\n\nfunction formatNumber(n: number): string {\n if (!Number.isFinite(n)) return \"null\";\n if (n === 0) return \"0\"; // collapses -0 to \"0\"\n if (Number.isInteger(n) && Math.abs(n) <= 2 ** 53) return String(n);\n let s = String(n);\n // Expand scientific notation to plain decimal. Python uses Decimal —\n // we use toFixed where possible, falling back to toPrecision-based logic.\n if (/[eE]/.test(s)) {\n // toFixed/toPrecision don't fully match Python's Decimal expansion at\n // extreme values, but for the values realistically in weather data\n // (temperatures, lat/lng) String(n) returns plain decimal already.\n // For extreme cases, fall back to a manual expansion.\n s = expandExponent(s);\n }\n return s;\n}\n\nfunction expandExponent(s: string): string {\n const m = /^(-?)(\\d+(?:\\.\\d+)?)[eE]([+-]?\\d+)$/.exec(s);\n if (!m) return s;\n const sign = m[1] ?? \"\";\n const mantissa = m[2] ?? \"\";\n const exp = Number(m[3]);\n const [intPart, fracPart = \"\"] = mantissa.split(\".\");\n const digits = (intPart ?? \"\") + fracPart;\n const pointPos = (intPart ?? \"\").length + exp;\n let out: string;\n if (pointPos <= 0) {\n out = `0.${\"0\".repeat(-pointPos)}${digits}`.replace(/0+$/, \"\");\n if (out.endsWith(\".\")) out = out.slice(0, -1);\n } else if (pointPos >= digits.length) {\n out = digits + \"0\".repeat(pointPos - digits.length);\n } else {\n out = `${digits.slice(0, pointPos)}.${digits.slice(pointPos)}`.replace(/0+$/, \"\");\n if (out.endsWith(\".\")) out = out.slice(0, -1);\n }\n return sign + out;\n}\n\nfunction needsQuoting(s: string, delimiter: string): boolean {\n if (s.length === 0) return true;\n const first = s.charAt(0);\n const last = s.charAt(s.length - 1);\n if (first === \" \" || first === \"\\t\") return true;\n if (last === \" \" || last === \"\\t\") return true;\n if (s === \"true\" || s === \"false\" || s === \"null\") return true;\n if (first === \"-\" || first === \"+\") return true;\n if (first >= \"0\" && first <= \"9\") return true;\n if (NUMERIC_LIKE_RE.test(s)) return true;\n if (s.includes(delimiter)) return true;\n if (NEEDS_QUOTE_CHARS_RE.test(s)) return true;\n return false;\n}\n\nfunction quoteString(s: string): string {\n // Strip unsupported control chars first (matches Python — no escape\n // sequence exists for them).\n let out = s.replace(UNSUPPORTED_CTRL_RE, \"\");\n out = out.replace(/\\\\/g, \"\\\\\\\\\");\n out = out.replace(/\"/g, '\\\\\"');\n out = out.replace(/\\n/g, \"\\\\n\");\n out = out.replace(/\\r/g, \"\\\\r\");\n out = out.replace(/\\t/g, \"\\\\t\");\n return `\"${out}\"`;\n}\n\nfunction formatKey(key: string): string {\n if (typeof key !== \"string\") {\n throw new TypeError(`TOON keys must be strings; got ${typeof key}`);\n }\n if (SAFE_KEY_RE.test(key)) return key;\n return quoteString(key);\n}\n\nfunction encodeScalar(value: unknown, delimiter: string): string {\n if (value === null || value === undefined) return \"null\";\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (typeof value === \"number\") return formatNumber(value);\n if (typeof value === \"string\") {\n if (needsQuoting(value, delimiter)) return quoteString(value);\n return value;\n }\n // Objects / arrays / etc. — canonical JSON (sorted keys) for parity with\n // Python `_coerce_cell` dict-handling, otherwise String(value).\n if (typeof value === \"object\") {\n if (Array.isArray(value)) {\n return quoteString(JSON.stringify(value));\n }\n // Sorted-key JSON for determinism (matches Python json.dumps sort_keys).\n const sorted = sortedJson(value as Record<string, unknown>);\n return quoteString(sorted);\n }\n return quoteString(String(value));\n}\n\nfunction sortedJson(obj: Record<string, unknown>): string {\n const keys = Object.keys(obj).sort();\n const parts = keys.map((k) => `${JSON.stringify(k)}:${JSON.stringify(obj[k])}`);\n return `{${parts.join(\",\")}}`;\n}\n\n// ---------------------------------------------------------------------------\n// Tabular validation (mirrors Python `_is_tabular`)\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when {@link toonDumps} receives rows that aren't valid tabular\n * input. Mirrors the Python `encode_tabular` `ValueError` — the encoder\n * MUST refuse non-uniform rows or non-primitive values rather than\n * silently dropping columns or stringifying nested structures.\n *\n * Iter-1 C3: the previous TS encoder used `Object.keys(rows[0])` and\n * encoded every subsequent row through that column list — meaning rows\n * with extra keys had them dropped, rows missing a first-row key got a\n * silent `null`, and rows with object/array values stringified via\n * `JSON.stringify` (also silent). All three are data corruption when the\n * caller didn't realize their rows weren't uniform.\n */\nexport class ToonTabularError extends RangeError {\n override name = \"ToonTabularError\";\n}\n\nfunction isToonPrimitive(v: unknown): boolean {\n // Python `_is_tabular` accepts None / str / int / float / bool. The TS\n // analog: null/undefined (both encode as `null`), string, finite or\n // non-finite number (NaN/Inf encode as `null` via formatNumber),\n // boolean. Anything else (object, array, function, symbol, bigint) is\n // non-tabular.\n if (v === null || v === undefined) return true;\n const t = typeof v;\n return t === \"string\" || t === \"number\" || t === \"boolean\";\n}\n\nfunction assertTabular(rows: ReadonlyArray<Record<string, unknown>>): void {\n if (rows.length === 0) return;\n const first = rows[0] as Record<string, unknown>;\n const expectedKeys = Object.keys(first);\n if (expectedKeys.length === 0) {\n throw new ToonTabularError(\n \"toonDumps requires non-empty rows; first row has no keys (Python parity: encode_tabular rejects empty key set)\",\n );\n }\n const expectedKeySet = new Set(expectedKeys);\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i] as Record<string, unknown>;\n const rowKeys = Object.keys(row);\n // (a) Key-set equality. Python compares `set(item.keys()) != key_set`.\n if (rowKeys.length !== expectedKeySet.size) {\n throw new ToonTabularError(\n `toonDumps requires uniform rows; row ${i} has ${rowKeys.length} key(s) vs row 0's ${expectedKeySet.size}. Python encode_tabular rejects rows whose key sets differ.`,\n );\n }\n for (const k of rowKeys) {\n if (!expectedKeySet.has(k)) {\n throw new ToonTabularError(\n `toonDumps requires uniform rows; row ${i} has key ${JSON.stringify(k)} not present in row 0. Python encode_tabular rejects rows whose key sets differ.`,\n );\n }\n }\n // (b) Value primitivity. Python check: `v is None or isinstance(v, str|int|float|bool)`.\n for (const k of expectedKeys) {\n const v = row[k];\n if (!isToonPrimitive(v)) {\n throw new ToonTabularError(\n `toonDumps requires primitive cell values; row ${i} column ${JSON.stringify(k)} has non-primitive value of type ${typeof v}. Python encode_tabular rejects nested objects/arrays at the cell level.`,\n );\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public encoder\n// ---------------------------------------------------------------------------\n\n/**\n * Encode rows as a TOON v3.0 tabular block.\n *\n * Header is `rows[N]{c1,c2,...}:`; data lines are 2-space indented and\n * comma-separated. Empty rows emits `rows[0]:` (header-only, no columns\n * region; matches Python `_encode_array_field` empty-list path).\n *\n * Note: empty-row encoding differs from `dumps()` in `toon.py` (which\n * carries column names through `rows[0]{...}:`). The TS encoder accepts\n * a `columns` second arg in the empty case for parity with that\n * pandas-aware wrapper.\n *\n * @throws {ToonTabularError} when rows are non-uniform (differing key\n * sets across rows) or when any cell value is non-primitive\n * (object/array/bigint/etc.). Mirrors Python `encode_tabular`'s\n * `ValueError`. Iter-1 C3 fix.\n */\nexport function toonDumps(\n rows: ReadonlyArray<Record<string, unknown>>,\n columns?: ReadonlyArray<string>,\n): string {\n if (rows.length === 0) {\n // Empty-frame: carry column names if provided (matches the Python\n // DataFrame wrapper's `rows[0]{...}:` empty form). Otherwise emit the\n // bare encoder form.\n if (columns !== undefined) {\n const cols = columns.map((c) => formatKey(String(c))).join(\",\");\n return `rows[0]{${cols}}:`;\n }\n return \"rows[0]:\";\n }\n // C3 hard gate: refuse non-uniform rows BEFORE looking at row 0's keys\n // for the column header. Otherwise we'd silently drop extra columns or\n // null-fill missing ones.\n assertTabular(rows);\n const cols = Object.keys(rows[0] as Record<string, unknown>);\n const colHeader = cols.map((c) => formatKey(c)).join(\",\");\n const header = `rows[${rows.length}]{${colHeader}}:`;\n const dataLines = rows.map((r) => {\n const vals = cols.map((c) => encodeScalar((r as Record<string, unknown>)[c], \",\"));\n return ` ${vals.join(\",\")}`;\n });\n return `${header}\\n${dataLines.join(\"\\n\")}`;\n}\n\n// ---------------------------------------------------------------------------\n// Decoder\n// ---------------------------------------------------------------------------\n\nconst HEADER_PREFIX_RE = /^(?<key>[A-Za-z_][A-Za-z0-9_.]*)\\[(?<count>\\d+)\\]/;\n\nfunction parseHeaderLine(line: string): { count: number; cols: string } {\n const prefix = HEADER_PREFIX_RE.exec(line);\n if (prefix == null) {\n throw new RangeError(`TOON payload missing tabular header; got: ${JSON.stringify(line)}`);\n }\n const declared = Number(prefix.groups?.count ?? \"\");\n let i = prefix[0].length;\n const n = line.length;\n // Handle the header-only empty form: `rows[0]:` (no `{cols}` region).\n if (i < n && line[i] === \":\" && declared === 0) {\n const rest = line.slice(i + 1).trim();\n if (rest === \"\") return { count: 0, cols: \"\" };\n throw new RangeError(`TOON header has trailing junk: ${JSON.stringify(line)}`);\n }\n if (i >= n || line[i] !== \"{\") {\n throw new RangeError(`TOON header missing column region: ${JSON.stringify(line)}`);\n }\n i++; // consume `{`\n // Walk to matching `}` honoring quoted strings.\n while (i < n) {\n const ch = line[i];\n if (ch === '\"') {\n let j = i + 1;\n while (j < n) {\n if (line[j] === \"\\\\\" && j + 1 < n) {\n j += 2;\n continue;\n }\n if (line[j] === '\"') break;\n j++;\n }\n if (j >= n) {\n throw new RangeError(\n `TOON header has unterminated quoted column name: ${JSON.stringify(line)}`,\n );\n }\n i = j + 1;\n continue;\n }\n if (ch === \"}\") break;\n i++;\n }\n if (i >= n || line[i] !== \"}\") {\n throw new RangeError(`TOON header missing closing brace: ${JSON.stringify(line)}`);\n }\n const cols = line.slice(prefix[0].length + 1, i);\n const rest = line.slice(i + 1).trim();\n if (rest !== \":\") {\n throw new RangeError(`TOON header missing colon terminator: ${JSON.stringify(line)}`);\n }\n return { count: declared, cols };\n}\n\nfunction splitCsvRow(line: string): string[] {\n const tokens: string[] = [];\n const n = line.length;\n if (n === 0) return tokens;\n let i = 0;\n while (true) {\n // Skip leading whitespace (defensive — encoder never emits padding).\n while (i < n && line[i] === \" \") i++;\n if (i >= n) {\n tokens.push(\"\");\n break;\n }\n if (line[i] === '\"') {\n let j = i + 1;\n while (j < n) {\n if (line[j] === \"\\\\\" && j + 1 < n) {\n j += 2;\n continue;\n }\n if (line[j] === '\"') break;\n j++;\n }\n tokens.push(line.slice(i, j + 1));\n i = j + 1;\n if (i >= n) break;\n if (line[i] === \",\") {\n i++;\n continue;\n }\n while (i < n && line[i] !== \",\") i++;\n if (i >= n) break;\n i++;\n } else {\n let j = i;\n while (j < n && line[j] !== \",\") j++;\n tokens.push(line.slice(i, j));\n i = j;\n if (i >= n) break;\n i++; // consume comma\n }\n }\n return tokens;\n}\n\nfunction decodeQuoted(token: string): string {\n const inner = token.slice(1, -1);\n let out = \"\";\n let i = 0;\n while (i < inner.length) {\n const ch = inner[i];\n if (ch === \"\\\\\" && i + 1 < inner.length) {\n const nxt = inner.charAt(i + 1);\n if (nxt === \"\\\\\") out += \"\\\\\";\n else if (nxt === '\"') out += '\"';\n else if (nxt === \"n\") out += \"\\n\";\n else if (nxt === \"r\") out += \"\\r\";\n else if (nxt === \"t\") out += \"\\t\";\n else out += nxt;\n i += 2;\n continue;\n }\n out += ch;\n i++;\n }\n return out;\n}\n\nfunction unquoteIfQuoted(token: string): string {\n if (token.length >= 2 && token[0] === '\"' && token[token.length - 1] === '\"') {\n return decodeQuoted(token);\n }\n return token;\n}\n\nfunction decodeValue(token: string): unknown {\n if (token.length === 0) return null;\n if (token[0] === '\"' && token[token.length - 1] === '\"' && token.length >= 2) {\n return decodeQuoted(token);\n }\n if (token === \"null\") return null;\n if (token === \"true\") return true;\n if (token === \"false\") return false;\n // Numeric attempt.\n if (NUMERIC_LIKE_RE.test(token)) {\n if (!token.includes(\".\") && !/[eE]/.test(token)) {\n const n = Number.parseInt(token, 10);\n if (!Number.isNaN(n)) return n;\n }\n const f = Number.parseFloat(token);\n if (!Number.isNaN(f)) return f;\n }\n // Bare unquoted string — TOON allows it when no quote-triggers fire.\n return token;\n}\n\n/**\n * Parse a TOON v3.0 tabular block back into rows + columns.\n *\n * Accepts ONLY the tabular shape that `toonDumps` produces; nested objects\n * / expanded lists are out of scope for the formats module.\n */\nexport function toonLoads(data: string): {\n rows: Array<Record<string, unknown>>;\n columns: string[];\n} {\n const lines = data.split(/\\r?\\n/);\n let idx = 0;\n while (idx < lines.length && lines[idx]?.trim() === \"\") idx++;\n if (idx >= lines.length) throw new RangeError(\"empty TOON payload\");\n\n const { count: declared, cols: colsRegion } = parseHeaderLine(lines[idx] ?? \"\");\n const columns = colsRegion === \"\" ? [] : splitCsvRow(colsRegion).map((t) => unquoteIfQuoted(t));\n\n const rawRows: unknown[][] = [];\n for (const raw of lines.slice(idx + 1)) {\n const line = raw.replace(/\\s+$/u, \"\");\n if (line.trim() === \"\") continue;\n const stripped = line.replace(/^ +/u, \"\");\n const tokens = splitCsvRow(stripped);\n if (columns.length > 0 && tokens.length !== columns.length) {\n throw new RangeError(\n `TOON row column count mismatch: expected ${columns.length}, got ${tokens.length}: ${JSON.stringify(stripped)}`,\n );\n }\n rawRows.push(tokens.map((t) => decodeValue(t)));\n }\n if (declared !== rawRows.length) {\n throw new RangeError(`TOON declared row count ${declared} != actual ${rawRows.length}`);\n }\n const rows = rawRows.map((row) => {\n const r: Record<string, unknown> = {};\n for (let i = 0; i < columns.length; i++) {\n r[columns[i] as string] = row[i];\n }\n return r;\n });\n return { rows, columns };\n}\n"],"mappings":"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","names":["parsed","columns","rows","cols","rest"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize rows to a JSON string.
|
|
3
|
+
*
|
|
4
|
+
* - Non-empty: emits records form `JSON.stringify(rows)`.
|
|
5
|
+
* - Empty: emits envelope `{"columns": [...], "data": []}` — column names
|
|
6
|
+
* survive the empty-frame roundtrip. Throws RangeError if `columns` is
|
|
7
|
+
* not provided in the empty case.
|
|
8
|
+
*/
|
|
9
|
+
declare function jsonDumps(rows: ReadonlyArray<Record<string, unknown>>, columns?: ReadonlyArray<string>): string;
|
|
10
|
+
/**
|
|
11
|
+
* Parse a JSON string into rows + column-order array.
|
|
12
|
+
*
|
|
13
|
+
* Accepts both the records form (`[{...}, ...]`) and the empty-frame
|
|
14
|
+
* envelope (`{columns, data}`). Returns BOTH `rows` AND `columns` so
|
|
15
|
+
* callers can preserve column order on empty cases.
|
|
16
|
+
*/
|
|
17
|
+
declare function jsonLoads(data: string): {
|
|
18
|
+
rows: Array<Record<string, unknown>>;
|
|
19
|
+
columns: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Serialize rows to a CSV string. Column names come from
|
|
24
|
+
* `Object.keys(rows[0])`. Empty rows emits an empty string (matches
|
|
25
|
+
* `pd.DataFrame({}).to_csv(index=False)`).
|
|
26
|
+
*
|
|
27
|
+
* Iter-2 C7: header cells are quoted the same way as data cells.
|
|
28
|
+
* Python's `DataFrame.to_csv` quotes header strings on the same
|
|
29
|
+
* triggers as values; without this guard a column name like `"a,b"`
|
|
30
|
+
* would dump as two headers and roundtrip into the wrong schema.
|
|
31
|
+
*/
|
|
32
|
+
declare function csvDumps(rows: ReadonlyArray<Record<string, unknown>>): string;
|
|
33
|
+
/**
|
|
34
|
+
* Parse a CSV string into rows + columns.
|
|
35
|
+
*
|
|
36
|
+
* Returns string-valued cells; CSV is dtype-lossy (pandas
|
|
37
|
+
* read_csv would re-infer dtypes — we leave that to the caller).
|
|
38
|
+
*
|
|
39
|
+
* Empty input → `{ rows: [], columns: [] }`. Header-only input →
|
|
40
|
+
* `{ rows: [], columns: [...] }`.
|
|
41
|
+
*
|
|
42
|
+
* Iter-1 C4: stateful parser preserves newlines inside quoted cells, so
|
|
43
|
+
* `csvLoads(csvDumps(rows))` is now a faithful roundtrip even when cells
|
|
44
|
+
* contain `\n` — previously the line-splitter exploded such cells into
|
|
45
|
+
* extra rows.
|
|
46
|
+
*/
|
|
47
|
+
declare function csvLoads(data: string): {
|
|
48
|
+
rows: Array<Record<string, string>>;
|
|
49
|
+
columns: string[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Thrown when {@link toonDumps} receives rows that aren't valid tabular
|
|
54
|
+
* input. Mirrors the Python `encode_tabular` `ValueError` — the encoder
|
|
55
|
+
* MUST refuse non-uniform rows or non-primitive values rather than
|
|
56
|
+
* silently dropping columns or stringifying nested structures.
|
|
57
|
+
*
|
|
58
|
+
* Iter-1 C3: the previous TS encoder used `Object.keys(rows[0])` and
|
|
59
|
+
* encoded every subsequent row through that column list — meaning rows
|
|
60
|
+
* with extra keys had them dropped, rows missing a first-row key got a
|
|
61
|
+
* silent `null`, and rows with object/array values stringified via
|
|
62
|
+
* `JSON.stringify` (also silent). All three are data corruption when the
|
|
63
|
+
* caller didn't realize their rows weren't uniform.
|
|
64
|
+
*/
|
|
65
|
+
declare class ToonTabularError extends RangeError {
|
|
66
|
+
name: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Encode rows as a TOON v3.0 tabular block.
|
|
70
|
+
*
|
|
71
|
+
* Header is `rows[N]{c1,c2,...}:`; data lines are 2-space indented and
|
|
72
|
+
* comma-separated. Empty rows emits `rows[0]:` (header-only, no columns
|
|
73
|
+
* region; matches Python `_encode_array_field` empty-list path).
|
|
74
|
+
*
|
|
75
|
+
* Note: empty-row encoding differs from `dumps()` in `toon.py` (which
|
|
76
|
+
* carries column names through `rows[0]{...}:`). The TS encoder accepts
|
|
77
|
+
* a `columns` second arg in the empty case for parity with that
|
|
78
|
+
* pandas-aware wrapper.
|
|
79
|
+
*
|
|
80
|
+
* @throws {ToonTabularError} when rows are non-uniform (differing key
|
|
81
|
+
* sets across rows) or when any cell value is non-primitive
|
|
82
|
+
* (object/array/bigint/etc.). Mirrors Python `encode_tabular`'s
|
|
83
|
+
* `ValueError`. Iter-1 C3 fix.
|
|
84
|
+
*/
|
|
85
|
+
declare function toonDumps(rows: ReadonlyArray<Record<string, unknown>>, columns?: ReadonlyArray<string>): string;
|
|
86
|
+
/**
|
|
87
|
+
* Parse a TOON v3.0 tabular block back into rows + columns.
|
|
88
|
+
*
|
|
89
|
+
* Accepts ONLY the tabular shape that `toonDumps` produces; nested objects
|
|
90
|
+
* / expanded lists are out of scope for the formats module.
|
|
91
|
+
*/
|
|
92
|
+
declare function toonLoads(data: string): {
|
|
93
|
+
rows: Array<Record<string, unknown>>;
|
|
94
|
+
columns: string[];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export { ToonTabularError, csvDumps, csvLoads, jsonDumps, jsonLoads, toonDumps, toonLoads };
|