@creationix/jot 0.0.1 → 0.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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -23
  3. package/dist/jot.cjs +446 -0
  4. package/dist/jot.d.ts +6 -0
  5. package/dist/jot.js +442 -0
  6. package/package.json +34 -4
  7. package/SUMMARY.md +0 -151
  8. package/TOKEN_COUNTS.md +0 -97
  9. package/bun.lock +0 -19
  10. package/jot.test.ts +0 -133
  11. package/jot.ts +0 -650
  12. package/samples/chat.jot +0 -1
  13. package/samples/chat.json +0 -1
  14. package/samples/chat.pretty.jot +0 -6
  15. package/samples/chat.pretty.json +0 -16
  16. package/samples/firewall.jot +0 -1
  17. package/samples/firewall.json +0 -1
  18. package/samples/firewall.pretty.jot +0 -235
  19. package/samples/firewall.pretty.json +0 -344
  20. package/samples/github-issue.jot +0 -1
  21. package/samples/github-issue.json +0 -1
  22. package/samples/github-issue.pretty.jot +0 -15
  23. package/samples/github-issue.pretty.json +0 -20
  24. package/samples/hikes.jot +0 -1
  25. package/samples/hikes.json +0 -1
  26. package/samples/hikes.pretty.jot +0 -14
  27. package/samples/hikes.pretty.json +0 -38
  28. package/samples/irregular.jot +0 -1
  29. package/samples/irregular.json +0 -1
  30. package/samples/irregular.pretty.jot +0 -13
  31. package/samples/irregular.pretty.json +0 -23
  32. package/samples/json-counts-cache.jot +0 -1
  33. package/samples/json-counts-cache.json +0 -1
  34. package/samples/json-counts-cache.pretty.jot +0 -26
  35. package/samples/json-counts-cache.pretty.json +0 -26
  36. package/samples/key-folding-basic.jot +0 -1
  37. package/samples/key-folding-basic.json +0 -1
  38. package/samples/key-folding-basic.pretty.jot +0 -7
  39. package/samples/key-folding-basic.pretty.json +0 -25
  40. package/samples/key-folding-mixed.jot +0 -1
  41. package/samples/key-folding-mixed.json +0 -1
  42. package/samples/key-folding-mixed.pretty.jot +0 -16
  43. package/samples/key-folding-mixed.pretty.json +0 -24
  44. package/samples/key-folding-with-array.jot +0 -1
  45. package/samples/key-folding-with-array.json +0 -1
  46. package/samples/key-folding-with-array.pretty.jot +0 -6
  47. package/samples/key-folding-with-array.pretty.json +0 -29
  48. package/samples/large.jot +0 -1
  49. package/samples/large.json +0 -1
  50. package/samples/large.pretty.jot +0 -72
  51. package/samples/large.pretty.json +0 -93
  52. package/samples/logs.jot +0 -1
  53. package/samples/logs.json +0 -1
  54. package/samples/logs.pretty.jot +0 -96
  55. package/samples/logs.pretty.json +0 -350
  56. package/samples/medium.jot +0 -1
  57. package/samples/medium.json +0 -1
  58. package/samples/medium.pretty.jot +0 -13
  59. package/samples/medium.pretty.json +0 -30
  60. package/samples/metrics.jot +0 -1
  61. package/samples/metrics.json +0 -1
  62. package/samples/metrics.pretty.jot +0 -11
  63. package/samples/metrics.pretty.json +0 -25
  64. package/samples/package.jot +0 -1
  65. package/samples/package.json +0 -1
  66. package/samples/package.pretty.jot +0 -18
  67. package/samples/package.pretty.json +0 -18
  68. package/samples/products.jot +0 -1
  69. package/samples/products.json +0 -1
  70. package/samples/products.pretty.jot +0 -69
  71. package/samples/products.pretty.json +0 -235
  72. package/samples/routes.jot +0 -1
  73. package/samples/routes.json +0 -1
  74. package/samples/routes.pretty.jot +0 -142
  75. package/samples/routes.pretty.json +0 -354
  76. package/samples/small.jot +0 -1
  77. package/samples/small.json +0 -1
  78. package/samples/small.pretty.jot +0 -8
  79. package/samples/small.pretty.json +0 -12
  80. package/samples/users-50.jot +0 -1
  81. package/samples/users-50.json +0 -1
  82. package/samples/users-50.pretty.jot +0 -53
  83. package/samples/users-50.pretty.json +0 -354
package/TOKEN_COUNTS.md DELETED
@@ -1,97 +0,0 @@
1
- # Token Counts by Format
2
-
3
- ## Qwen Tokenizer (LM Studio)
4
-
5
- Tokens measured using **Qwen3-Coder-30b** via LM Studio API. This is the primary tokenizer used for testing local model efficiency.
6
-
7
- <!-- QWEN_CHART_START -->
8
- ```mermaid
9
- xychart-beta
10
- title "Token Savings vs JSON (negative = better)"
11
- x-axis ["Users50", "Hikes", "Medium", "KF-arr", "Firewall", "KF-basic", "Products", "Routes", "Metrics", "Package", "Chat", "KF-mix", "Issue", "Small", "Large", "Irregular", "Logs"]
12
- y-axis "% vs JSON" -60 --> 40
13
- line "JSON" [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
14
- line "Jot" [-48, -29, -27, -26, -19, -19, -19, -15, -14, -12, -12, -12, -10, -8, -8, -7, -3]
15
- line "YAML" [25, 18, 27, 19, 24, 10, 26, 16, 20, 7, 8, 14, 11, 17, 23, 28, 18]
16
- line "TOON" [-40, -23, -14, 9, 30, 9, 10, 8, -6, 7, -11, 13, 2, 4, 18, 29, 18]
17
- ```
18
- <!-- QWEN_CHART_END -->
19
-
20
- ### Per-File Breakdown (Qwen)
21
-
22
- <!-- QWEN_TABLE_START -->
23
- | Format | Chat | Firewall | Issue | Hikes | Irregular | KF-basic | KF-mix | KF-arr | Large | Logs | Medium | Metrics | Package | Products | Routes | Small | Users50 | Total |
24
- |--------|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|
25
- | Jot | [67](jot/chat.jot) | [669](jot/firewall.jot) | [79](jot/github-issue.jot) | [112](jot/hikes.jot) | [63](jot/irregular.jot) | [47](jot/key-folding-basic.jot) | [69](jot/key-folding-mixed.jot) | [43](jot/key-folding-with-array.jot) | [246](jot/large.jot) | [2044](jot/logs.jot) | [71](jot/medium.jot) | [101](jot/metrics.jot) | [85](jot/package.jot) | [702](jot/products.jot) | [1246](jot/routes.jot) | [44](jot/small.jot) | [663](jot/users-50.jot) | 6,351 |
26
- | JSONito | [86](jsonito/chat.jito) | [919](jsonito/firewall.jito) | [89](jsonito/github-issue.jito) | [158](jsonito/hikes.jito) | [59](jsonito/irregular.jito) | [62](jsonito/key-folding-basic.jito) | [82](jsonito/key-folding-mixed.jito) | [63](jsonito/key-folding-with-array.jito) | [312](jsonito/large.jito) | [1941](jsonito/logs.jito) | [103](jsonito/medium.jito) | [89](jsonito/metrics.jito) | [101](jsonito/package.jito) | [846](jsonito/products.jito) | [1426](jsonito/routes.jito) | [45](jsonito/small.jito) | [1234](jsonito/users-50.jito) | 7,615 |
27
- | JSON-m | [76](json/chat.json) | [827](json/firewall.json) | [88](json/github-issue.json) | [158](json/hikes.json) | [68](json/irregular.json) | [58](json/key-folding-basic.json) | [78](json/key-folding-mixed.json) | [58](json/key-folding-with-array.json) | [266](json/large.json) | [2108](json/logs.json) | [97](json/medium.json) | [117](json/metrics.json) | [97](json/package.json) | [866](json/products.json) | [1459](json/routes.json) | [48](json/small.json) | [1279](json/users-50.json) | 7,748 |
28
- | Jot-P | [73](jot/chat.pretty.jot) | [1119](jot/firewall.pretty.jot) | [105](jot/github-issue.pretty.jot) | [142](jot/hikes.pretty.jot) | [91](jot/irregular.pretty.jot) | [54](jot/key-folding-basic.pretty.jot) | [96](jot/key-folding-mixed.pretty.jot) | [52](jot/key-folding-with-array.pretty.jot) | [365](jot/large.pretty.jot) | [2304](jot/logs.pretty.jot) | [100](jot/medium.pretty.jot) | [123](jot/metrics.pretty.jot) | [120](jot/package.pretty.jot) | [828](jot/products.pretty.jot) | [1521](jot/routes.pretty.jot) | [59](jot/small.pretty.jot) | [863](jot/users-50.pretty.jot) | 8,015 |
29
- | D2 | [80](d2/chat.d2) | [894](d2/firewall.d2) | [97](d2/github-issue.d2) | [173](d2/hikes.d2) | [81](d2/irregular.d2) | [60](d2/key-folding-basic.d2) | [80](d2/key-folding-mixed.d2) | [67](d2/key-folding-with-array.d2) | [316](d2/large.d2) | [2092](d2/logs.d2) | [104](d2/medium.d2) | [138](d2/metrics.d2) | [90](d2/package.d2) | [994](d2/products.d2) | [1536](d2/routes.d2) | [55](d2/small.d2) | [1202](d2/users-50.d2) | 8,059 |
30
- | TOON | [68](toon/chat.toon) | [1073](toon/firewall.toon) | [90](toon/github-issue.toon) | [122](toon/hikes.toon) | [88](toon/irregular.toon) | [63](toon/key-folding-basic.toon) | [88](toon/key-folding-mixed.toon) | [63](toon/key-folding-with-array.toon) | [313](toon/large.toon) | [2492](toon/logs.toon) | [83](toon/medium.toon) | [110](toon/metrics.toon) | [104](toon/package.toon) | [954](toon/products.toon) | [1574](toon/routes.toon) | [50](toon/small.toon) | [763](toon/users-50.toon) | 8,098 |
31
- | YAML | [82](yaml/chat.yaml) | [1029](yaml/firewall.yaml) | [98](yaml/github-issue.yaml) | [187](yaml/hikes.yaml) | [87](yaml/irregular.yaml) | [64](yaml/key-folding-basic.yaml) | [89](yaml/key-folding-mixed.yaml) | [69](yaml/key-folding-with-array.yaml) | [327](yaml/large.yaml) | [2487](yaml/logs.yaml) | [123](yaml/medium.yaml) | [140](yaml/metrics.yaml) | [104](yaml/package.yaml) | [1095](yaml/products.yaml) | [1696](yaml/routes.yaml) | [56](yaml/small.yaml) | [1597](yaml/users-50.yaml) | 9,330 |
32
- | TOML | [84](toml/chat.toml) | [1495](toml/firewall.toml) | [99](toml/github-issue.toml) | [189](toml/hikes.toml) | [86](toml/irregular.toml) | [60](toml/key-folding-basic.toml) | [85](toml/key-folding-mixed.toml) | [61](toml/key-folding-with-array.toml) | [377](toml/large.toml) | [2498](toml/logs.toml) | [118](toml/medium.toml) | [139](toml/metrics.toml) | [104](toml/package.toml) | [1114](toml/products.toml) | [1790](toml/routes.toml) | [56](toml/small.toml) | [1625](toml/users-50.toml) | 9,980 |
33
- <!-- QWEN_TABLE_END -->
34
-
35
- ---
36
-
37
- ## Legacy Claude Tokenizer
38
-
39
- Tokens measured using **@anthropic-ai/tokenizer** (Claude's legacy tokenizer). This is the older tokenizer used by earlier Claude models.
40
-
41
- <!-- LEGACY_CHART_START -->
42
- ```mermaid
43
- xychart-beta
44
- title "Token Savings vs JSON (negative = better)"
45
- x-axis ["Users50", "Hikes", "Products", "Metrics", "Medium", "Routes", "Chat", "KF-arr", "KF-basic", "Large", "KF-mix", "Issue", "Firewall", "Package", "Logs", "Small", "Irregular"]
46
- y-axis "% vs JSON" -50 --> 30
47
- line "JSON" [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
48
- line "Jot" [-37, -26, -20, -20, -20, -11, -10, -10, -8, -8, -6, -6, -2, -2, -1, 0, 2]
49
- line "YAML" [3, 3, 4, 5, 4, 4, 4, 15, 8, 9, 4, 7, 8, 0, 2, 11, 8]
50
- line "TOON" [-37, -24, -7, -21, -20, 3, -11, 12, 6, 4, 6, 0, 13, 0, 8, 3, 10]
51
- ```
52
- <!-- LEGACY_CHART_END -->
53
-
54
- ### Per-File Breakdown (Legacy)
55
-
56
- <!-- LEGACY_TABLE_START -->
57
- | Format | Chat | Metrics | Large | KF-mix | Logs | Firewall | Small | Issue | Users50 | Medium | Hikes | Package | KF-basic | Irregular | KF-arr | Products | Routes | Total |
58
- |--------|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|
59
- | Jot | [63](jot/chat.jot) | [60](jot/metrics.jot) | [221](jot/large.jot) | [65](jot/key-folding-mixed.jot) | [1737](jot/logs.jot) | [825](jot/firewall.jot) | [36](jot/small.jot) | [68](jot/github-issue.jot) | [837](jot/users-50.jot) | [76](jot/medium.jot) | [103](jot/hikes.jot) | [91](jot/package.jot) | [45](jot/key-folding-basic.jot) | [49](jot/irregular.jot) | [47](jot/key-folding-with-array.jot) | [613](jot/products.jot) | [1352](jot/routes.jot) | 6,288 |
60
- | JSON-m | [70](json/chat.json) | [75](json/metrics.json) | [239](json/large.json) | [69](json/key-folding-mixed.json) | [1750](json/logs.json) | [845](json/firewall.json) | [36](json/small.json) | [72](json/github-issue.json) | [1326](json/users-50.json) | [95](json/medium.json) | [139](json/hikes.json) | [93](json/package.json) | [49](json/key-folding-basic.json) | [48](json/irregular.json) | [52](json/key-folding-with-array.json) | [771](json/products.json) | [1516](json/routes.json) | 7,245 |
61
- | YAML | [73](yaml/chat.yaml) | [79](yaml/metrics.yaml) | [260](yaml/large.yaml) | [72](yaml/key-folding-mixed.yaml) | [1780](yaml/logs.yaml) | [911](yaml/firewall.yaml) | [40](yaml/small.yaml) | [77](yaml/github-issue.yaml) | [1365](yaml/users-50.yaml) | [99](yaml/medium.yaml) | [143](yaml/hikes.yaml) | [93](yaml/package.yaml) | [53](yaml/key-folding-basic.yaml) | [52](yaml/irregular.yaml) | [60](yaml/key-folding-with-array.yaml) | [799](yaml/products.yaml) | [1572](yaml/routes.yaml) | 7,528 |
62
- | TOON | [62](toon/chat.toon) | [59](toon/metrics.toon) | [249](toon/large.toon) | [73](toon/key-folding-mixed.toon) | [1885](toon/logs.toon) | [955](toon/firewall.toon) | [37](toon/small.toon) | [72](toon/github-issue.toon) | [836](toon/users-50.toon) | [76](toon/medium.toon) | [105](toon/hikes.toon) | [93](toon/package.toon) | [52](toon/key-folding-basic.toon) | [53](toon/irregular.toon) | [58](toon/key-folding-with-array.toon) | [720](toon/products.toon) | [1557](toon/routes.toon) | 6,942 |
63
- <!-- LEGACY_TABLE_END -->
64
-
65
- Note: The legacy tokenizer may produce different results than modern Claude models, but is useful for comparison and runs locally without API calls.
66
-
67
- ---
68
-
69
- ## Modern Claude Tokenizer
70
-
71
- Tokens measured using **Claude API** token counting endpoint (claude-sonnet-4). This represents the actual token usage for modern Claude models. Token counting is free via the API.
72
-
73
- <!-- CLAUDE_CHART_START -->
74
- ```mermaid
75
- xychart-beta
76
- title "Token Savings vs JSON (negative = better)"
77
- x-axis ["Users50", "Hikes", "Products", "KF-basic", "KF-arr", "Metrics", "Medium", "Large", "Firewall", "KF-mix", "Routes", "Chat", "Issue", "Irregular", "Package", "Small", "Logs"]
78
- y-axis "% vs JSON" -50 --> 40
79
- line "JSON" [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
80
- line "Jot" [-39, -29, -24, -24, -24, -23, -22, -16, -15, -15, -14, -12, -9, -6, -6, -4, -2]
81
- line "YAML" [22, 19, 27, 1, 14, 19, 19, 16, 13, 6, 9, 6, 8, 21, 4, 13, 18]
82
- line "TOON" [-36, -25, 8, 1, 6, -18, -14, 12, 17, 8, 7, -10, 1, 24, 5, 2, 21]
83
- ```
84
- <!-- CLAUDE_CHART_END -->
85
-
86
- ### Per-File Breakdown (Claude)
87
-
88
- <!-- CLAUDE_TABLE_START -->
89
- | Format | Chat | Metrics | Large | KF-mix | Logs | Firewall | Small | Issue | Users50 | Medium | Hikes | Package | KF-basic | Irregular | KF-arr | Products | Routes | Total |
90
- |--------|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|------:|
91
- | Jot | [71](jot/chat.jot) | [68](jot/metrics.jot) | [241](jot/large.jot) | [76](jot/key-folding-mixed.jot) | [1762](jot/logs.jot) | [867](jot/firewall.jot) | [44](jot/small.jot) | [78](jot/github-issue.jot) | [830](jot/users-50.jot) | [83](jot/medium.jot) | [115](jot/hikes.jot) | [99](jot/package.jot) | [53](jot/key-folding-basic.jot) | [59](jot/irregular.jot) | [53](jot/key-folding-with-array.jot) | [634](jot/products.jot) | [1472](jot/routes.jot) | 6,605 |
92
- | JSON-m | [81](json/chat.json) | [88](json/metrics.json) | [286](json/large.json) | [89](json/key-folding-mixed.json) | [1798](json/logs.json) | [1022](json/firewall.json) | [46](json/small.json) | [86](json/github-issue.json) | [1369](json/users-50.json) | [107](json/medium.json) | [163](json/hikes.json) | [105](json/package.json) | [70](json/key-folding-basic.json) | [63](json/irregular.json) | [70](json/key-folding-with-array.json) | [839](json/products.json) | [1707](json/routes.json) | 7,989 |
93
- | YAML | [86](yaml/chat.yaml) | [105](yaml/metrics.yaml) | [333](yaml/large.yaml) | [94](yaml/key-folding-mixed.yaml) | [2118](yaml/logs.yaml) | [1152](yaml/firewall.yaml) | [52](yaml/small.yaml) | [93](yaml/github-issue.yaml) | [1666](yaml/users-50.yaml) | [127](yaml/medium.yaml) | [194](yaml/hikes.yaml) | [109](yaml/package.yaml) | [71](yaml/key-folding-basic.yaml) | [76](yaml/irregular.yaml) | [80](yaml/key-folding-with-array.yaml) | [1063](yaml/products.yaml) | [1857](yaml/routes.yaml) | 9,276 |
94
- | TOON | [73](toon/chat.toon) | [72](toon/metrics.toon) | [319](toon/large.toon) | [96](toon/key-folding-mixed.toon) | [2175](toon/logs.toon) | [1195](toon/firewall.toon) | [47](toon/small.toon) | [87](toon/github-issue.toon) | [879](toon/users-50.toon) | [92](toon/medium.toon) | [123](toon/hikes.toon) | [110](toon/package.toon) | [71](toon/key-folding-basic.toon) | [78](toon/irregular.toon) | [74](toon/key-folding-with-array.toon) | [907](toon/products.toon) | [1822](toon/routes.toon) | 8,220 |
95
- <!-- CLAUDE_TABLE_END -->
96
-
97
- Note: Run `ANTHROPIC_API_KEY=... bun scripts/count-claude-tokens.ts` to regenerate Claude counts.
package/bun.lock DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "workspaces": {
4
- "": {
5
- "devDependencies": {
6
- "@types/bun": "^1.3.5",
7
- },
8
- },
9
- },
10
- "packages": {
11
- "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
12
-
13
- "@types/node": ["@types/node@25.0.6", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q=="],
14
-
15
- "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
16
-
17
- "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
18
- }
19
- }
package/jot.test.ts DELETED
@@ -1,133 +0,0 @@
1
- import { describe, test, expect } from "bun:test"
2
- import { stringify, parse } from "./jot"
3
-
4
- describe("stringify", () => {
5
- test("null", () => expect(stringify(null)).toBe("null"))
6
- test("true", () => expect(stringify(true)).toBe("true"))
7
- test("false", () => expect(stringify(false)).toBe("false"))
8
- test("number", () => expect(stringify(42)).toBe("42"))
9
- test("float", () => expect(stringify(3.14)).toBe("3.14"))
10
-
11
- describe("strings", () => {
12
- test("simple string", () => expect(stringify("hello")).toBe("hello"))
13
- test("string with space", () => expect(stringify("hello world")).toBe("hello world"))
14
- test("empty string", () => expect(stringify("")).toBe('""'))
15
- test("numeric string", () => expect(stringify("123")).toBe('"123"'))
16
- test("reserved word", () => expect(stringify("true")).toBe('"true"'))
17
- test("contains colon", () => expect(stringify("a:b")).toBe('"a:b"'))
18
- test("contains semicolon", () => expect(stringify("a;b")).toBe('"a;b"'))
19
- })
20
-
21
- describe("arrays", () => {
22
- test("empty array", () => expect(stringify([])).toBe("[]"))
23
- test("simple array", () => expect(stringify([1, 2, 3])).toBe("[1,2,3]"))
24
- test("string array", () => expect(stringify(["a", "b"])).toBe("[a,b]"))
25
- })
26
-
27
- describe("objects", () => {
28
- test("empty object", () => expect(stringify({})).toBe("{}"))
29
- test("simple object", () => expect(stringify({ name: "Alice", age: 30 })).toBe("{name:Alice,age:30}"))
30
- })
31
-
32
- describe("key folding", () => {
33
- test("fold 1 level", () => expect(stringify({ a: { b: 1 } })).toBe("{a.b:1}"))
34
- test("fold 2 levels", () => expect(stringify({ a: { b: { c: 1 } } })).toBe("{a.b.c:1}"))
35
- test("no fold multi-key", () => expect(stringify({ a: { b: 1, c: 2 } })).toBe("{a:{b:1,c:2}}"))
36
- test("key with dot", () => expect(stringify({ "a.b": 1 })).toBe('{"a.b":1}'))
37
- test("key with dot nested", () => expect(stringify({ "a.b": { c: 1 } })).toBe('{"a.b":{c:1}}'))
38
- })
39
-
40
- describe("tables", () => {
41
- test("uniform table", () => expect(stringify([{ a: 1, b: 2 }, { a: 3, b: 4 }])).toBe("{{:a,b;1,2;3,4}}"))
42
- test("3-row table", () => expect(stringify([{ x: 1 }, { x: 2 }, { x: 3 }])).toBe("{{:x;1;2;3}}"))
43
- test("mixed schema with reuse", () => expect(stringify([{ a: 1 }, { a: 2 }, { b: 3 }])).toBe("{{:a;1;2;:b;3}}"))
44
- test("no reuse", () => expect(stringify([{ a: 1 }, { b: 2 }])).toBe("[{a:1},{b:2}]"))
45
- test("single obj", () => expect(stringify([{ a: 1 }])).toBe("[{a:1}]"))
46
- })
47
- })
48
-
49
- describe("parse", () => {
50
- test("null", () => expect(parse("null")).toBe(null))
51
- test("true", () => expect(parse("true")).toBe(true))
52
- test("number", () => expect(parse("42")).toBe(42))
53
- test("string", () => expect(parse("hello")).toBe("hello"))
54
- test("quoted", () => expect(parse('"hello world"')).toBe("hello world"))
55
-
56
- test("array", () => expect(parse("[1,2,3]")).toEqual([1, 2, 3]))
57
- test("object", () => expect(parse("{name:Alice,age:30}")).toEqual({ name: "Alice", age: 30 }))
58
-
59
- describe("key unfolding", () => {
60
- test("fold", () => expect(parse("{a.b:1}")).toEqual({ a: { b: 1 } }))
61
- test("fold deep", () => expect(parse("{a.b.c:1}")).toEqual({ a: { b: { c: 1 } } }))
62
- test("quoted dot key", () => expect(parse('{"a.b":1}')).toEqual({ "a.b": 1 }))
63
- })
64
-
65
- describe("tables", () => {
66
- test("table", () => expect(parse("{{:a,b;1,2;3,4}}")).toEqual([{ a: 1, b: 2 }, { a: 3, b: 4 }]))
67
- test("schema change", () => expect(parse("{{:a;1;:b;2}}")).toEqual([{ a: 1 }, { b: 2 }]))
68
- })
69
- })
70
-
71
- describe("round-trip", () => {
72
- const testCases: [string, unknown][] = [
73
- ["null", null],
74
- ["true", true],
75
- ["false", false],
76
- ["number", 42],
77
- ["string", "hello"],
78
- ["empty array", []],
79
- ["number array", [1, 2, 3]],
80
- ["empty object", {}],
81
- ["simple object", { a: 1, b: 2 }],
82
- ["nested 1 level", { a: { b: 1 } }],
83
- ["nested 2 levels", { a: { b: { c: 1 } } }],
84
- ["uniform table", [{ a: 1, b: 2 }, { a: 3, b: 4 }]],
85
- ["mixed schema", [{ a: 1 }, { b: 2 }]],
86
- ["nested table", { users: [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }] }],
87
- ["key with dot", { "a.b": 1 }],
88
- ["key with semicolon", { "x;y": "test" }],
89
- ["string with semicolon", "a;b"],
90
- ]
91
-
92
- for (const [name, original] of testCases) {
93
- test(name, () => {
94
- const encoded = stringify(original)
95
- const decoded = parse(encoded)
96
- expect(decoded).toEqual(original)
97
- })
98
- }
99
- })
100
-
101
- describe("samples", () => {
102
- const { readdirSync, readFileSync } = require("fs")
103
- const { join } = require("path")
104
-
105
- const samplesDir = join(__dirname, "samples")
106
- const jsonFiles = readdirSync(samplesDir)
107
- .filter((f: string) => f.endsWith(".json") && !f.includes(".pretty."))
108
-
109
- for (const jsonFile of jsonFiles) {
110
- const baseName = jsonFile.replace(".json", "")
111
- const jotFile = `${baseName}.jot`
112
-
113
- describe(baseName, () => {
114
- const jsonPath = join(samplesDir, jsonFile)
115
- const jotPath = join(samplesDir, jotFile)
116
-
117
- const jsonContent = readFileSync(jsonPath, "utf-8").trim()
118
- const expectedJot = readFileSync(jotPath, "utf-8").trim()
119
- const originalData = JSON.parse(jsonContent)
120
-
121
- test("encoding matches jot", () => {
122
- const encoded = stringify(originalData)
123
- expect(encoded).toBe(expectedJot)
124
- })
125
-
126
- test("round-trip matches json semantically", () => {
127
- const encoded = stringify(originalData)
128
- const decoded = parse(encoded)
129
- expect(decoded).toEqual(originalData)
130
- })
131
- })
132
- }
133
- })