toon-parser 2.0.0 → 2.1.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.
Files changed (52) hide show
  1. package/README.md +36 -6
  2. package/dist/cjs.test.cjs +46 -0
  3. package/dist/complex.test.cjs +35 -0
  4. package/dist/coverage.test.cjs +49 -0
  5. package/dist/csv.cjs +150 -0
  6. package/dist/csv.d.ts +20 -0
  7. package/dist/csv.d.ts.map +1 -0
  8. package/dist/csv.edge.test.cjs +20 -0
  9. package/dist/csv.js +142 -0
  10. package/dist/csv.js.map +1 -0
  11. package/dist/csv.test.cjs +27 -0
  12. package/dist/edgecases.test.cjs +23 -0
  13. package/dist/extra.test.cjs +19 -0
  14. package/dist/html.cjs +176 -0
  15. package/dist/html.d.ts +17 -0
  16. package/dist/html.d.ts.map +1 -0
  17. package/dist/html.edge.test.cjs +16 -0
  18. package/dist/html.js +181 -0
  19. package/dist/html.js.map +1 -0
  20. package/dist/html.test.cjs +28 -0
  21. package/dist/index.cjs +9 -1
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +4 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.test.cjs +59 -0
  27. package/dist/limits.test.cjs +21 -0
  28. package/dist/log.cjs +57 -0
  29. package/dist/log.clf.redos.test.cjs +15 -0
  30. package/dist/log.d.ts +9 -0
  31. package/dist/log.d.ts.map +1 -0
  32. package/dist/log.js +36 -0
  33. package/dist/log.js.map +1 -0
  34. package/dist/log.test.cjs +20 -0
  35. package/dist/property.test.cjs +60 -0
  36. package/dist/smoke.test.cjs +132 -0
  37. package/dist/snapshots.test.cjs +21 -0
  38. package/dist/tabular.test.cjs +21 -0
  39. package/dist/url.cjs +72 -0
  40. package/dist/url.d.ts +3 -0
  41. package/dist/url.d.ts.map +1 -0
  42. package/dist/url.js +57 -0
  43. package/dist/url.js.map +1 -0
  44. package/dist/url.test.cjs +25 -0
  45. package/dist/xml.cjs +22 -0
  46. package/dist/xml.d.ts +1 -0
  47. package/dist/xml.d.ts.map +1 -1
  48. package/dist/xml.edge.test.cjs +16 -0
  49. package/dist/xml.js +22 -1
  50. package/dist/xml.js.map +1 -1
  51. package/dist/xml.test.cjs +86 -0
  52. package/package.json +14 -6
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_index = require("./index");
4
+ (0, import_vitest.describe)("json-toon parser", () => {
5
+ (0, import_vitest.it)("round-trips objects with tabular arrays", () => {
6
+ const payload = {
7
+ context: { task: "hike planning", year: 2025 },
8
+ friends: ["ana", "luis", "sam"],
9
+ hikes: [
10
+ { id: 1, name: "Blue Lake", distanceKm: 7.5, wasSunny: true },
11
+ { id: 2, name: "Ridge Overlook", distanceKm: 9.2, wasSunny: false }
12
+ ]
13
+ };
14
+ const toon = (0, import_index.jsonToToon)(payload);
15
+ (0, import_vitest.expect)(toon).toContain("hikes[2]{id,name,distanceKm,wasSunny}");
16
+ const decoded = (0, import_index.toonToJson)(toon);
17
+ (0, import_vitest.expect)(decoded).toEqual(payload);
18
+ });
19
+ (0, import_vitest.it)("quotes strings that could be misread as primitives", () => {
20
+ const payload = {
21
+ literalTrue: "true",
22
+ spaced: " leading",
23
+ dash: "-value",
24
+ colon: "a:b"
25
+ };
26
+ const toon = (0, import_index.jsonToToon)(payload);
27
+ (0, import_vitest.expect)(toon).toContain('"true"');
28
+ (0, import_vitest.expect)(toon).toContain('" leading"');
29
+ (0, import_vitest.expect)(toon).toContain('"-value"');
30
+ const decoded = (0, import_index.toonToJson)(toon);
31
+ (0, import_vitest.expect)(decoded).toEqual(payload);
32
+ });
33
+ (0, import_vitest.it)("rejects prototype-polluting keys when encoding and decoding", () => {
34
+ const polluted = /* @__PURE__ */ Object.create(null);
35
+ polluted["__proto__"] = 1;
36
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)(polluted)).toThrow(/Disallowed key/);
37
+ (0, import_vitest.expect)(() => (0, import_index.toonToJson)("__proto__: 1")).toThrow(/Disallowed key/);
38
+ });
39
+ (0, import_vitest.it)("enforces declared array lengths in strict mode", () => {
40
+ (0, import_vitest.expect)(() => (0, import_index.toonToJson)("nums[2]: 1")).toThrow(/length mismatch/);
41
+ });
42
+ (0, import_vitest.it)("rejects numbers with leading zeros", () => {
43
+ (0, import_vitest.expect)(() => (0, import_index.toonToJson)("value: 007")).toThrow(/leading zeros/);
44
+ });
45
+ (0, import_vitest.it)('treats a numeric-like but non-numeric token as a quoted string (e.g., "00;")', () => {
46
+ (0, import_vitest.expect)((0, import_index.toonToJson)('value: "00;"')).toEqual({ value: "00;" });
47
+ });
48
+ (0, import_vitest.it)("round-trips nested array items that contain objects", () => {
49
+ const payload = [
50
+ [
51
+ { id: 1, label: "alpha" },
52
+ { id: 2, label: "beta" }
53
+ ]
54
+ ];
55
+ const toon = (0, import_index.jsonToToon)(payload);
56
+ const decoded = (0, import_index.toonToJson)(toon);
57
+ (0, import_vitest.expect)(decoded).toEqual(payload);
58
+ });
59
+ });
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_index = require("./index");
4
+ (0, import_vitest.describe)("security enforcement and limits", () => {
5
+ (0, import_vitest.it)("throws when maxDepth exceeded", () => {
6
+ let nested = { a: 1 };
7
+ for (let i = 0; i < 70; i++) {
8
+ nested = [nested];
9
+ }
10
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)(nested)).toThrow(/Maximum depth/);
11
+ });
12
+ (0, import_vitest.it)("throws when maxArrayLength is exceeded", () => {
13
+ const arr = new Array(50001).fill(0);
14
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)({ arr })).toThrow(/Array length/);
15
+ });
16
+ (0, import_vitest.it)("throws when maxTotalNodes exceeded", () => {
17
+ const bigObj = {};
18
+ for (let i = 0; i < 1e3; i++) bigObj["k" + i] = i;
19
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)(bigObj, { maxTotalNodes: 100 })).toThrow(/Node count/);
20
+ });
21
+ });
package/dist/log.cjs ADDED
@@ -0,0 +1,57 @@
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
+ var log_exports = {};
20
+ __export(log_exports, {
21
+ logToToon: () => logToToon
22
+ });
23
+ module.exports = __toCommonJS(log_exports);
24
+ var import_index = require("./index.cjs");
25
+ const CLF_REGEX = /^(\S+) - - \[([^\]]*)\] "([^"]*)" (\d+) (\d+)$/;
26
+ function logToToon(logData, options = {}) {
27
+ const lines = logData.split(/\r?\n/).filter((line) => line.trim() !== "");
28
+ const format = options.format ?? "auto";
29
+ if (format === "json" || format === "auto" && lines[0]?.trim().startsWith("{")) {
30
+ const logs = lines.map((line) => {
31
+ try {
32
+ return JSON.parse(line);
33
+ } catch {
34
+ return { raw: line };
35
+ }
36
+ });
37
+ return (0, import_index.jsonToToon)(logs, options);
38
+ }
39
+ const parsed = lines.map((line) => {
40
+ const match = line.match(CLF_REGEX);
41
+ if (match) {
42
+ return {
43
+ host: match[1] || "",
44
+ date: match[2] || "",
45
+ request: match[3] || "",
46
+ status: parseInt(match[4] || "0", 10),
47
+ size: parseInt(match[5] || "0", 10)
48
+ };
49
+ }
50
+ return { raw: line };
51
+ });
52
+ return (0, import_index.jsonToToon)(parsed, options);
53
+ }
54
+ // Annotate the CommonJS export names for ESM import in node:
55
+ 0 && (module.exports = {
56
+ logToToon
57
+ });
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_log = require("./log");
4
+ (0, import_vitest.describe)("logToToon CLF ReDoS mitigation", () => {
5
+ (0, import_vitest.it)("parses malicious CLF line without hanging", () => {
6
+ const maliciousRequest = "GET " + "a".repeat(5e3) + " HTTP/1.1";
7
+ const line = `127.0.0.1 - - [01/Jan/2020:00:00:00 +0000] "${maliciousRequest}" 200 1234`;
8
+ const logData = line + "\n";
9
+ const start = Date.now();
10
+ const result = (0, import_log.logToToon)(logData);
11
+ const duration = Date.now() - start;
12
+ (0, import_vitest.expect)(result).toContain("GET");
13
+ (0, import_vitest.expect)(duration).toBeLessThan(100);
14
+ });
15
+ });
package/dist/log.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { JsonToToonOptions } from './index.js';
2
+ export interface LogToToonOptions extends JsonToToonOptions {
3
+ /**
4
+ * Log format. Defaults to 'auto' which tries to detect CLF or JSON.
5
+ */
6
+ format?: 'auto' | 'clf' | 'json';
7
+ }
8
+ export declare function logToToon(logData: string, options?: LogToToonOptions): string;
9
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;CAClC;AAKD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CA6BjF"}
package/dist/log.js ADDED
@@ -0,0 +1,36 @@
1
+ import { jsonToToon } from './index.js';
2
+ // Updated CLF regex to avoid catastrophic backtracking by using negated character classes
3
+ const CLF_REGEX = /^(\S+) - - \[([^\]]*)\] "([^"]*)" (\d+) (\d+)$/;
4
+ export function logToToon(logData, options = {}) {
5
+ const lines = logData.split(/\r?\n/).filter(line => line.trim() !== '');
6
+ const format = options.format ?? 'auto';
7
+ // Attempt JSON detection on first line
8
+ if (format === 'json' || (format === 'auto' && lines[0]?.trim().startsWith('{'))) {
9
+ const logs = lines.map(line => {
10
+ try {
11
+ return JSON.parse(line);
12
+ }
13
+ catch {
14
+ return { raw: line };
15
+ }
16
+ });
17
+ return jsonToToon(logs, options);
18
+ }
19
+ // Attempt CLF (Common Log Format)
20
+ // Format: host - - [date] "request" status size
21
+ const parsed = lines.map(line => {
22
+ const match = line.match(CLF_REGEX);
23
+ if (match) {
24
+ return {
25
+ host: match[1] || '',
26
+ date: match[2] || '',
27
+ request: match[3] || '',
28
+ status: parseInt(match[4] || '0', 10),
29
+ size: parseInt(match[5] || '0', 10)
30
+ };
31
+ }
32
+ return { raw: line };
33
+ });
34
+ return jsonToToon(parsed, options);
35
+ }
36
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;AAS3D,0FAA0F;AAC1F,MAAM,SAAS,GAAG,gDAAgD,CAAC;AAEnE,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,UAA4B,EAAE;IACvE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC;IAExC,uCAAuC;IACvC,IAAI,MAAM,KAAK,MAAM,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC/E,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YAC1B,IAAI,CAAC;gBAAC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;YAAC,CAAC;QACpE,CAAC,CAAC,CAAC;QACH,OAAO,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,kCAAkC;IAClC,gDAAgD;IAChD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,KAAK,EAAE,CAAC;YACR,OAAO;gBACH,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;gBACpB,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;gBACpB,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;gBACvB,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC;gBACrC,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC;aACtC,CAAC;QACN,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,OAAO,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_log = require("./log");
4
+ (0, import_vitest.describe)("logToToon", () => {
5
+ (0, import_vitest.it)("converts json logs", () => {
6
+ const logs = `{"id":1}
7
+ {"id":2}`;
8
+ const toon = (0, import_log.logToToon)(logs, { format: "json" });
9
+ (0, import_vitest.expect)(toon).toContain("[2]{id}:");
10
+ (0, import_vitest.expect)(toon).toContain("1");
11
+ (0, import_vitest.expect)(toon).toContain("2");
12
+ });
13
+ (0, import_vitest.it)("converts CLF logs", () => {
14
+ const log = `127.0.0.1 - - [10/Oct:13:55:36] "GET /index.html" 200 1024`;
15
+ const toon = (0, import_log.logToToon)(log, { format: "clf" });
16
+ (0, import_vitest.expect)(toon).toContain("[1]{host,date,request,status,size}:");
17
+ (0, import_vitest.expect)(toon).toContain("127.0.0.1");
18
+ (0, import_vitest.expect)(toon).toContain("GET /index.html");
19
+ });
20
+ });
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ var import_vitest = require("vitest");
25
+ var fc = __toESM(require("fast-check"), 1);
26
+ var import_index = require("./index");
27
+ const arbJson = fc.letrec((tie) => {
28
+ const primitiveArb = fc.oneof(
29
+ // allow strings with punctuation and whitespace - they will be quoted by serializer
30
+ fc.string({ minLength: 0, maxLength: 6 }).filter((s) => s.length <= 6),
31
+ fc.integer(),
32
+ fc.boolean(),
33
+ fc.constant(null)
34
+ );
35
+ const innerObjArb = fc.dictionary(fc.string({ minLength: 1, maxLength: 6 }), fc.oneof(fc.string(), fc.integer(), fc.boolean()), { maxKeys: 3 });
36
+ const arrArb = fc.array(fc.oneof(primitiveArb, fc.array(primitiveArb, { maxLength: 2 }), innerObjArb), { maxLength: 3 });
37
+ const dictArb = fc.dictionary(
38
+ fc.string({ minLength: 1, maxLength: 6 }).filter((k) => /^[A-Za-z_][A-Za-z0-9_.]*$/.test(k)),
39
+ tie("json"),
40
+ { maxKeys: 3 }
41
+ ).filter((o) => Object.keys(o).length > 0);
42
+ return {
43
+ json: fc.oneof(fc.string(), fc.integer(), fc.boolean(), fc.constant(null), arrArb, dictArb)
44
+ };
45
+ }).json;
46
+ const safeKey = fc.string({ minLength: 1, maxLength: 8 });
47
+ const arbTopObject = fc.dictionary(safeKey, arbJson, { maxKeys: 4 }).filter((o) => Object.keys(o).length > 0);
48
+ (0, import_vitest.describe)("property-based round-trip", () => {
49
+ (0, import_vitest.it)("round trips random JSON values", () => {
50
+ fc.assert(
51
+ fc.property(arbTopObject, (val) => {
52
+ const t = (0, import_index.jsonToToon)(val);
53
+ const back = (0, import_index.toonToJson)(t);
54
+ (0, import_vitest.expect)(back).toEqual(val);
55
+ return true;
56
+ }),
57
+ { numRuns: 100 }
58
+ );
59
+ });
60
+ });
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_index = require("./index");
4
+ (0, import_vitest.describe)("Smoke Tests: Unexpected Inputs & Edge Cases", () => {
5
+ (0, import_vitest.describe)("Unsupported Types", () => {
6
+ (0, import_vitest.it)("throws on RegExp", () => {
7
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)({ regex: /abc/ })).toThrow(/Unsupported value type/);
8
+ });
9
+ (0, import_vitest.it)("throws on Map", () => {
10
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)({ map: /* @__PURE__ */ new Map() })).toThrow(/Unsupported value type/);
11
+ });
12
+ (0, import_vitest.it)("throws on Set", () => {
13
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)({ set: /* @__PURE__ */ new Set() })).toThrow(/Unsupported value type/);
14
+ });
15
+ (0, import_vitest.it)("throws on Function", () => {
16
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)({ fn: () => {
17
+ } })).toThrow(/Unsupported value type/);
18
+ });
19
+ (0, import_vitest.it)("throws on Symbol", () => {
20
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)({ sym: Symbol("sym") })).toThrow(/Unsupported value type/);
21
+ });
22
+ (0, import_vitest.it)("throws on undefined values (unlike JSON.stringify which omits them)", () => {
23
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)({ val: void 0 })).toThrow(/Unsupported value type: undefined/);
24
+ });
25
+ });
26
+ (0, import_vitest.describe)("Numeric Edge Cases", () => {
27
+ (0, import_vitest.it)("handles NaN (converts to string or throws?) - current impl expects finite numbers", () => {
28
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)({ val: NaN })).toThrow(/Numeric values must be finite/);
29
+ });
30
+ (0, import_vitest.it)("handles Infinity", () => {
31
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)({ val: Infinity })).toThrow(/Numeric values must be finite/);
32
+ });
33
+ (0, import_vitest.it)("handles -Infinity", () => {
34
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)({ val: -Infinity })).toThrow(/Numeric values must be finite/);
35
+ });
36
+ (0, import_vitest.it)("handles negative zero", () => {
37
+ const result = (0, import_index.jsonToToon)({ val: -0 });
38
+ (0, import_vitest.expect)(result).toContain("-0");
39
+ const decoded = (0, import_index.toonToJson)(result);
40
+ (0, import_vitest.expect)(decoded).toEqual({ val: -0 });
41
+ });
42
+ });
43
+ (0, import_vitest.describe)("String Quoting & Special Characters", () => {
44
+ (0, import_vitest.it)("quotes strings that look like boolean/null", () => {
45
+ const data = { a: "true", b: "false", c: "null" };
46
+ const encoded = (0, import_index.jsonToToon)(data);
47
+ (0, import_vitest.expect)(encoded).toContain('"true"');
48
+ (0, import_vitest.expect)(encoded).toContain('"false"');
49
+ (0, import_vitest.expect)(encoded).toContain('"null"');
50
+ (0, import_vitest.expect)((0, import_index.toonToJson)(encoded)).toEqual(data);
51
+ });
52
+ (0, import_vitest.it)("quotes strings that look like numbers", () => {
53
+ const data = { a: "123", b: "-45.67" };
54
+ const encoded = (0, import_index.jsonToToon)(data);
55
+ (0, import_vitest.expect)(encoded).toContain('"123"');
56
+ (0, import_vitest.expect)(encoded).toContain('"-45.67"');
57
+ (0, import_vitest.expect)((0, import_index.toonToJson)(encoded)).toEqual(data);
58
+ });
59
+ (0, import_vitest.it)("quotes strings with leading zeros", () => {
60
+ const data = { code: "007" };
61
+ const encoded = (0, import_index.jsonToToon)(data);
62
+ (0, import_vitest.expect)(encoded).toContain('"007"');
63
+ (0, import_vitest.expect)((0, import_index.toonToJson)(encoded)).toEqual(data);
64
+ });
65
+ (0, import_vitest.it)("quotes strings containing delimiters", () => {
66
+ const data = { val: "a,b|c" };
67
+ const encoded = (0, import_index.jsonToToon)(data);
68
+ (0, import_vitest.expect)(encoded).toContain('"a,b|c"');
69
+ (0, import_vitest.expect)((0, import_index.toonToJson)(encoded)).toEqual(data);
70
+ });
71
+ (0, import_vitest.it)("handles emoji and unicode", () => {
72
+ const data = { "\u{1F680}": "To the moon \u{1F315}", "\xFCber": "m\xFCnchen" };
73
+ const encoded = (0, import_index.jsonToToon)(data);
74
+ (0, import_vitest.expect)(encoded).toContain("\u{1F680}");
75
+ (0, import_vitest.expect)(encoded).toContain("\u{1F315}");
76
+ (0, import_vitest.expect)((0, import_index.toonToJson)(encoded)).toEqual(data);
77
+ });
78
+ (0, import_vitest.it)("handles control characters by escaping", () => {
79
+ const data = { newline: "line1\nline2", tab: "col1 col2" };
80
+ const encoded = (0, import_index.jsonToToon)(data);
81
+ (0, import_vitest.expect)(encoded).toContain("\\n");
82
+ (0, import_vitest.expect)(encoded).toContain("\\t");
83
+ (0, import_vitest.expect)((0, import_index.toonToJson)(encoded)).toEqual(data);
84
+ });
85
+ });
86
+ (0, import_vitest.describe)("Deep Nesting", () => {
87
+ (0, import_vitest.it)("throws when max depth is exceeded", () => {
88
+ let deep = { val: 1 };
89
+ for (let i = 0; i < 70; i++) {
90
+ deep = { next: deep };
91
+ }
92
+ (0, import_vitest.expect)(() => (0, import_index.jsonToToon)(deep)).toThrow(/Maximum depth 64 exceeded/);
93
+ });
94
+ });
95
+ (0, import_vitest.describe)("Dates (Standard Smoke)", () => {
96
+ (0, import_vitest.it)("supports multiple Date objects in array", () => {
97
+ const dates = [/* @__PURE__ */ new Date("2023-01-01"), /* @__PURE__ */ new Date("2024-01-01")];
98
+ const encoded = (0, import_index.jsonToToon)(dates);
99
+ (0, import_vitest.expect)(encoded).toContain("2023");
100
+ (0, import_vitest.expect)(encoded).toContain("2024");
101
+ const decoded = (0, import_index.toonToJson)(encoded);
102
+ (0, import_vitest.expect)(decoded).toEqual(dates.map((d) => d.toISOString()));
103
+ });
104
+ });
105
+ (0, import_vitest.describe)("New Parsers Edge Cases", () => {
106
+ (0, import_vitest.it)("CSV: handles mixed quotes and delimiters", () => {
107
+ const csv = `a,"b,c",d
108
+ 1,"2|3",4`;
109
+ const toon = (0, import_index.csvToToon)(csv, { delimiter: "," });
110
+ (0, import_vitest.expect)(toon).toContain("1,2|3,4");
111
+ });
112
+ (0, import_vitest.it)("HTML: handles internal scripts and styles (content preservation)", () => {
113
+ const html = "<div><script>alert(1)</script></div>";
114
+ const toon = (0, import_index.htmlToToon)(html);
115
+ (0, import_vitest.expect)(toon).toContain("alert(1)");
116
+ });
117
+ (0, import_vitest.it)("URL: handles duplicate keys as arrays", () => {
118
+ const qs = "id=1&id=2";
119
+ const toon = (0, import_index.urlToToon)(qs);
120
+ (0, import_vitest.expect)(toon).toContain("id: 2");
121
+ });
122
+ (0, import_vitest.it)("Log: handles empty lines or malformed lines gracefully", () => {
123
+ const log = `127.0.0.1 - - [10/Oct:2023] "GET /" 200 123
124
+
125
+
126
+ [malformed line]`;
127
+ const toon = (0, import_index.logToToon)(log);
128
+ (0, import_vitest.expect)(toon).toContain("host: 127.0.0.1");
129
+ (0, import_vitest.expect)(toon).toContain('raw: "[malformed line]"');
130
+ });
131
+ });
132
+ });
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_index = require("./index");
4
+ (0, import_vitest.describe)("snapshot tests for JSON -> TOON", () => {
5
+ (0, import_vitest.it)("tabular snapshot", () => {
6
+ const data = {
7
+ readings: [
8
+ { id: 1, a: "x" },
9
+ { id: 2, a: "y" }
10
+ ],
11
+ list: [1, 2, 3]
12
+ };
13
+ const toon = (0, import_index.jsonToToon)(data);
14
+ (0, import_vitest.expect)(toon).toMatchSnapshot();
15
+ });
16
+ (0, import_vitest.it)("nested snapshot", () => {
17
+ const data = { project: { name: "Test", version: "0.1" }, items: [{ id: 1 }, {}] };
18
+ const toon = (0, import_index.jsonToToon)(data);
19
+ (0, import_vitest.expect)(toon).toMatchSnapshot();
20
+ });
21
+ });
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_index = require("./index");
4
+ (0, import_vitest.describe)("Tabular arrays detection and behavior", () => {
5
+ (0, import_vitest.it)("detects tabular arrays and emits tabular header", () => {
6
+ const arr = [{ a: 1, b: 2 }, { a: 2, b: 3 }];
7
+ const t = (0, import_index.jsonToToon)({ rows: arr });
8
+ (0, import_vitest.expect)(t).toContain("rows[2]{a,b}:");
9
+ const decoded = (0, import_index.toonToJson)(t);
10
+ (0, import_vitest.expect)(decoded).toEqual({ rows: arr });
11
+ });
12
+ (0, import_vitest.it)("rejects tabular row width mismatch in strict mode", () => {
13
+ const input = "rows[2]{a,b}:\n 1,2\n 1";
14
+ (0, import_vitest.expect)(() => (0, import_index.toonToJson)(input)).toThrow(/Tabular row width mismatch/);
15
+ });
16
+ (0, import_vitest.it)("skips tabular detection when object shapes differ", () => {
17
+ const arr = [{ a: 1, b: 2 }, { a: 2, c: 3 }];
18
+ const t = (0, import_index.jsonToToon)({ rows: arr });
19
+ (0, import_vitest.expect)(t).toContain("rows[2]:");
20
+ });
21
+ });
package/dist/url.cjs ADDED
@@ -0,0 +1,72 @@
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
+ var url_exports = {};
20
+ __export(url_exports, {
21
+ urlToToon: () => urlToToon
22
+ });
23
+ module.exports = __toCommonJS(url_exports);
24
+ var import_index = require("./index.cjs");
25
+ function urlToToon(urlString, options = {}) {
26
+ let search = urlString;
27
+ try {
28
+ const u = new URL(urlString);
29
+ search = u.search;
30
+ } catch {
31
+ const qIndex = urlString.indexOf("?");
32
+ if (qIndex !== -1) {
33
+ search = urlString.slice(qIndex);
34
+ }
35
+ }
36
+ const params = new URLSearchParams(search);
37
+ const obj = {};
38
+ for (const [key, value] of params.entries()) {
39
+ assignDeep(obj, key, value);
40
+ }
41
+ return (0, import_index.jsonToToon)(obj, options);
42
+ }
43
+ function assignDeep(obj, key, value) {
44
+ const parts = key.split(/\[|\]\[|\]|\./).filter(Boolean);
45
+ if (parts.length === 0) return;
46
+ let current = obj;
47
+ for (let i = 0; i < parts.length - 1; i++) {
48
+ const part = parts[i];
49
+ if (part === void 0) continue;
50
+ if (!(part in current) || typeof current[part] !== "object" || current[part] === null) {
51
+ current[part] = {};
52
+ }
53
+ current = current[part];
54
+ }
55
+ const last = parts[parts.length - 1];
56
+ if (last === "" || last === void 0) {
57
+ return;
58
+ }
59
+ const typed = inferType(value);
60
+ current[last] = typed;
61
+ }
62
+ function inferType(val) {
63
+ if (val === "true") return true;
64
+ if (val === "false") return false;
65
+ const num = Number(val);
66
+ if (!isNaN(num) && val.trim() !== "") return num;
67
+ return val;
68
+ }
69
+ // Annotate the CommonJS export names for ESM import in node:
70
+ 0 && (module.exports = {
71
+ urlToToon
72
+ });
package/dist/url.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { JsonToToonOptions } from './index.js';
2
+ export declare function urlToToon(urlString: string, options?: JsonToToonOptions): string;
3
+ //# sourceMappingURL=url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE3D,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAsBpF"}
package/dist/url.js ADDED
@@ -0,0 +1,57 @@
1
+ import { jsonToToon } from './index.js';
2
+ export function urlToToon(urlString, options = {}) {
3
+ // If full URL, extract search params. If just params, use as is.
4
+ let search = urlString;
5
+ try {
6
+ const u = new URL(urlString);
7
+ search = u.search;
8
+ }
9
+ catch {
10
+ // likely just a query string part or relative path
11
+ const qIndex = urlString.indexOf('?');
12
+ if (qIndex !== -1) {
13
+ search = urlString.slice(qIndex);
14
+ }
15
+ }
16
+ const params = new URLSearchParams(search);
17
+ const obj = {};
18
+ for (const [key, value] of params.entries()) {
19
+ assignDeep(obj, key, value);
20
+ }
21
+ return jsonToToon(obj, options);
22
+ }
23
+ function assignDeep(obj, key, value) {
24
+ // Handle "key[subkey]" or "key.subkey" notation roughly
25
+ // For safety and simplicity, we'll try to support basic bracket notation commonly used in QS
26
+ // e.g. "filters[date][start]" -> filters: { date: { start: value } }
27
+ const parts = key.split(/\[|\]\[|\]|\./).filter(Boolean);
28
+ if (parts.length === 0)
29
+ return;
30
+ let current = obj;
31
+ for (let i = 0; i < parts.length - 1; i++) {
32
+ const part = parts[i];
33
+ if (part === undefined)
34
+ continue;
35
+ if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
36
+ current[part] = {};
37
+ }
38
+ current = current[part];
39
+ }
40
+ const last = parts[parts.length - 1];
41
+ if (last === '' || last === undefined) {
42
+ return;
43
+ }
44
+ const typed = inferType(value);
45
+ current[last] = typed;
46
+ }
47
+ function inferType(val) {
48
+ if (val === 'true')
49
+ return true;
50
+ if (val === 'false')
51
+ return false;
52
+ const num = Number(val);
53
+ if (!isNaN(num) && val.trim() !== '')
54
+ return num;
55
+ return val;
56
+ }
57
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;AAE3D,MAAM,UAAU,SAAS,CAAC,SAAiB,EAAE,UAA6B,EAAE;IAC1E,iEAAiE;IACjE,IAAI,MAAM,GAAG,SAAS,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,mDAAmD;QACnD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;YAClB,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,GAAG,GAA4B,EAAE,CAAC;IAExC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QAC5C,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,UAAU,CAAC,GAA4B,EAAE,GAAW,EAAE,KAAa;IAC1E,wDAAwD;IACxD,6FAA6F;IAC7F,qEAAqE;IAErE,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAE/B,IAAI,OAAO,GAAG,GAAG,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,SAAS;YAAE,SAAS;QAEjC,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACpF,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,CAAC;QACD,OAAO,GAAG,OAAO,CAAC,IAAI,CAA4B,CAAC;IACrD,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrC,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACpC,OAAO;IACX,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;AACxB,CAAC;AAGD,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAClC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACxB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,GAAG,CAAC;IACjD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var import_vitest = require("vitest");
3
+ var import_url = require("./url");
4
+ (0, import_vitest.describe)("urlToToon", () => {
5
+ (0, import_vitest.it)("parses simple query string", () => {
6
+ const qs = "foo=bar&baz=123";
7
+ const toon = (0, import_url.urlToToon)(qs);
8
+ (0, import_vitest.expect)(toon).toContain("foo: bar");
9
+ (0, import_vitest.expect)(toon).toContain("baz: 123");
10
+ });
11
+ (0, import_vitest.it)("handles full url", () => {
12
+ const url = "https://example.com/api?a=1";
13
+ const toon = (0, import_url.urlToToon)(url);
14
+ (0, import_vitest.expect)(toon).toContain("a: 1");
15
+ });
16
+ (0, import_vitest.it)("expands nested keys", () => {
17
+ const qs = "user[name]=Alice&user[age]=30&filter.sort=asc";
18
+ const toon = (0, import_url.urlToToon)(qs);
19
+ (0, import_vitest.expect)(toon).toContain("user:");
20
+ (0, import_vitest.expect)(toon).toContain("name: Alice");
21
+ (0, import_vitest.expect)(toon).toContain("age: 30");
22
+ (0, import_vitest.expect)(toon).toContain("filter:");
23
+ (0, import_vitest.expect)(toon).toContain("sort: asc");
24
+ });
25
+ });