@chrismo/superkit 1.0.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 (74) hide show
  1. package/LICENSE.txt +29 -0
  2. package/README.md +26 -0
  3. package/dist/cli/pager.d.ts +6 -0
  4. package/dist/cli/pager.d.ts.map +1 -0
  5. package/dist/cli/pager.js +21 -0
  6. package/dist/cli/pager.js.map +1 -0
  7. package/dist/cli/skdoc.d.ts +3 -0
  8. package/dist/cli/skdoc.d.ts.map +1 -0
  9. package/dist/cli/skdoc.js +42 -0
  10. package/dist/cli/skdoc.js.map +1 -0
  11. package/dist/cli/skgrok.d.ts +3 -0
  12. package/dist/cli/skgrok.d.ts.map +1 -0
  13. package/dist/cli/skgrok.js +21 -0
  14. package/dist/cli/skgrok.js.map +1 -0
  15. package/dist/cli/skops.d.ts +3 -0
  16. package/dist/cli/skops.d.ts.map +1 -0
  17. package/dist/cli/skops.js +32 -0
  18. package/dist/cli/skops.js.map +1 -0
  19. package/dist/index.d.ts +10 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +11 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/lib/docs.d.ts +11 -0
  24. package/dist/lib/docs.d.ts.map +1 -0
  25. package/dist/lib/docs.js +29 -0
  26. package/dist/lib/docs.js.map +1 -0
  27. package/dist/lib/expert-sections.d.ts +32 -0
  28. package/dist/lib/expert-sections.d.ts.map +1 -0
  29. package/dist/lib/expert-sections.js +130 -0
  30. package/dist/lib/expert-sections.js.map +1 -0
  31. package/dist/lib/grok.d.ts +15 -0
  32. package/dist/lib/grok.d.ts.map +1 -0
  33. package/dist/lib/grok.js +57 -0
  34. package/dist/lib/grok.js.map +1 -0
  35. package/dist/lib/help.d.ts +20 -0
  36. package/dist/lib/help.d.ts.map +1 -0
  37. package/dist/lib/help.js +163 -0
  38. package/dist/lib/help.js.map +1 -0
  39. package/dist/lib/recipes.d.ts +29 -0
  40. package/dist/lib/recipes.d.ts.map +1 -0
  41. package/dist/lib/recipes.js +133 -0
  42. package/dist/lib/recipes.js.map +1 -0
  43. package/dist/superkit.tar.gz +0 -0
  44. package/docs/grok-patterns.sup +89 -0
  45. package/docs/recipes/array.md +66 -0
  46. package/docs/recipes/array.spq +31 -0
  47. package/docs/recipes/character.md +110 -0
  48. package/docs/recipes/character.spq +57 -0
  49. package/docs/recipes/escape.md +159 -0
  50. package/docs/recipes/escape.spq +102 -0
  51. package/docs/recipes/format.md +51 -0
  52. package/docs/recipes/format.spq +24 -0
  53. package/docs/recipes/index.md +23 -0
  54. package/docs/recipes/integer.md +101 -0
  55. package/docs/recipes/integer.spq +53 -0
  56. package/docs/recipes/records.md +84 -0
  57. package/docs/recipes/records.spq +61 -0
  58. package/docs/recipes/string.md +177 -0
  59. package/docs/recipes/string.spq +105 -0
  60. package/docs/superdb-expert.md +929 -0
  61. package/docs/tutorials/bash_to_sup.md +123 -0
  62. package/docs/tutorials/chess-tiebreaks.md +233 -0
  63. package/docs/tutorials/debug.md +439 -0
  64. package/docs/tutorials/fork_for_window.md +296 -0
  65. package/docs/tutorials/grok.md +166 -0
  66. package/docs/tutorials/index.md +10 -0
  67. package/docs/tutorials/joins.md +79 -0
  68. package/docs/tutorials/moar_subqueries.md +35 -0
  69. package/docs/tutorials/subqueries.md +236 -0
  70. package/docs/tutorials/sup_to_bash.md +164 -0
  71. package/docs/tutorials/super_db_update.md +34 -0
  72. package/docs/tutorials/unnest.md +113 -0
  73. package/docs/zq-to-super-upgrades.md +549 -0
  74. package/package.json +46 -0
@@ -0,0 +1,123 @@
1
+ ---
2
+ title: "Getting Bash Text into SuperDB"
3
+ name: bash-to-sup
4
+ description: "Getting raw text safely from Bash into SuperDB without manual escaping."
5
+ layout: default
6
+ nav_order: 1
7
+ parent: Tutorials
8
+ superdb_version: "0.3.0"
9
+ last_updated: "2026-02-17"
10
+ ---
11
+
12
+ # Getting Bash Text into SuperDB
13
+
14
+ The companion to [sup_to_bash](sup_to_bash.md), this covers the reverse: safely
15
+ getting raw text from Bash into SuperDB.
16
+
17
+ ## The Problem
18
+
19
+ When building `.sup` records from Bash, you need to escape text before embedding
20
+ it in SUP strings. A common approach is manual escaping with sed:
21
+
22
+ ```bash
23
+ # Manual escaping — fragile and error-prone
24
+ escape_for_sup() {
25
+ echo "$1" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed "s/'/\'/g"
26
+ }
27
+
28
+ text='She said "hello" and it'\''s a back\slash'
29
+ record="{body:\"$(escape_for_sup "$text")\"}"
30
+ echo "$record" >> data.sup
31
+ ```
32
+
33
+ This works but is brittle — each special character needs its own sed pass, and
34
+ it's easy to miss edge cases or get the escaping order wrong.
35
+
36
+ ## Let Super Handle It
37
+
38
+ Instead of escaping text yourself, pipe it through `super` and let the
39
+ serializer do the work. The `-i line` flag reads each line of stdin as a string
40
+ value.
41
+
42
+ ### Single-Line Text
43
+
44
+ ```bash
45
+ text='She said "hello" and it'\''s a back\slash'
46
+ echo "$text" | super -s -i line -c "values this" -
47
+ # => "She said \"hello\" and it's a back\\slash"
48
+ ```
49
+
50
+ Super's SUP output automatically escapes backslashes and double quotes. No sed
51
+ needed.
52
+
53
+ ### Multiline Text to a Single String
54
+
55
+ With `-i line`, each input line becomes a separate string value. To collapse
56
+ multiline text into a single string with embedded `\n`:
57
+
58
+ ```bash
59
+ printf 'line one\nline two\nline three' |
60
+ super -s -i line -c 'aggregate s:=collect(this) | values join(s, "\n")' -
61
+ # => "line one\nline two\nline three"
62
+ ```
63
+
64
+ The pattern: `collect()` gathers all lines into an array, then `join()` merges
65
+ them with literal newline characters.
66
+
67
+ ### Building Records with Raw Text
68
+
69
+ Combine `-i line` with record construction to safely embed text into `.sup`
70
+ records:
71
+
72
+ ```bash
73
+ # Single-line field
74
+ msg='User said "goodbye" & left'
75
+ body=$(echo "$msg" | super -f line -i line -c "values this" -)
76
+ echo "{type:'message',body:'$body'}" | super -s -c "values this" -
77
+
78
+ # Or build the whole record in one shot
79
+ echo "$msg" | super -s -i line -c "values {type:'message', body: this}" -
80
+ ```
81
+
82
+ The second form is cleaner — super constructs the full record, so the text never
83
+ passes through Bash string interpolation at all.
84
+
85
+ ### Multiline Record Fields
86
+
87
+ ```bash
88
+ notes="first line
89
+ second line
90
+ third line"
91
+
92
+ echo "$notes" |
93
+ super -s -i line -c '
94
+ aggregate s:=collect(this)
95
+ | values {type: "note", body: join(s, "\n")}
96
+ ' -
97
+ # => {type:"note",body:"first line\nsecond line\nthird line"}
98
+ ```
99
+
100
+ ## Appending to .sup Files
101
+
102
+ A common pattern in scripts that maintain `.sup` files:
103
+
104
+ ```bash
105
+ append_record() {
106
+ local file="$1"
107
+ local msg="$2"
108
+ echo "$msg" |
109
+ super -s -i line -c "values {ts: now(), body: this}" - >> "$file"
110
+ }
111
+
112
+ append_record "log.sup" 'User clicked "submit" at /path?q=1&r=2'
113
+ ```
114
+
115
+ ## Why This Is Better
116
+
117
+ | Approach | Handles `"` | Handles `\` | Handles `'` | Handles newlines | Handles unicode |
118
+ |---|---|---|---|---|---|
119
+ | Manual sed chains | One sed per char | Easy to mis-order | Another sed | Yet another sed | Hope for the best |
120
+ | `super -i line` | Yes | Yes | Yes | With collect/join | Yes |
121
+
122
+ One pipeline replaces an entire family of escape functions. Super knows its own
123
+ serialization format — let it do the work.
@@ -0,0 +1,233 @@
1
+ ---
2
+ title: "Chess Tiebreaks"
3
+ name: chess-tiebreaks
4
+ description: "End-to-end tutorial parsing PGN chess data to find tie-break games."
5
+ layout: default
6
+ nav_order: 2
7
+ parent: Tutorials
8
+ superdb_version: "0.3.0"
9
+ last_updated: "2026-02-15"
10
+ ---
11
+
12
+ # Chess Tiebreaks
13
+
14
+ SuperDB isn't limited to JSON — it can ingest and transform plain text just as
15
+ easily. This tutorial demonstrates parsing raw text input using `grok` patterns,
16
+ reshaping records, and aggregating results to answer a real question.
17
+
18
+ PGN (Portable Game Notation) is a plain text format for recording chess games.
19
+ Each game has metadata in bracketed lines like `[White "LastName, FirstName"]`
20
+ followed by the moves. Here we'll parse a tournament's PGN file to find players
21
+ who faced each other more than once (indicating tie-break games).
22
+
23
+ The data comes from the Tata Steel Masters 2024 broadcast on Lichess, the
24
+ [tie-break games can be seen
25
+ here](https://lichess.org/broadcast/tata-steel-masters-2024/tiebreaks/L43YRQWv#boards).
26
+
27
+ ## The Complete Solution
28
+
29
+ ```mdtest-command
30
+ curl -sS https://lichess.org/api/broadcast/ycy5D2r8.pgn |
31
+ super -i line -s -c "
32
+ --
33
+ -- parse the pgn file
34
+ --
35
+ where this != ''
36
+ | grok('.*White \"%{WORD:last_name_white}|.*Black \"%{WORD:last_name_black}', this)
37
+ | where not is_error(this)
38
+
39
+ --
40
+ -- pair up each player in each game
41
+ --
42
+ | count
43
+ | put game_id:=((count - 1) / 2)::int64
44
+ | values {...that, game_id}
45
+ | aggregate
46
+ last_name_white:=max(last_name_white),
47
+ last_name_black:=max(last_name_black)
48
+ by game_id
49
+ | drop game_id
50
+
51
+ --
52
+ -- re-organize the data by player and opponent regardless of piece color
53
+ --
54
+ | [
55
+ {player: last_name_white, opponent: last_name_black},
56
+ {player: last_name_black, opponent: last_name_white}
57
+ ]
58
+ | unnest this
59
+
60
+ --
61
+ -- count each match-up of player and opponent and find any with counts > 1
62
+ --
63
+ -- this should be players who participated in tie-breaks, as the regular
64
+ -- tournament was only a single round-robin
65
+ --
66
+ | count(this) by player, opponent
67
+ | where count > 1
68
+ | sort player, opponent
69
+ " -
70
+ ```
71
+
72
+ ```mdtest-output
73
+ {player:"Abdusattorov",opponent:"Wei",count:3}
74
+ {player:"Giri",opponent:"Gukesh",count:4}
75
+ {player:"Gukesh",opponent:"Giri",count:4}
76
+ {player:"Gukesh",opponent:"Wei",count:3}
77
+ {player:"Wei",opponent:"Abdusattorov",count:3}
78
+ {player:"Wei",opponent:"Gukesh",count:3}
79
+ ```
80
+
81
+ ## Walkthrough
82
+
83
+ ### Line Input and Filtering
84
+
85
+ `-i line` tells super to treat each line as a separate string record. We filter
86
+ out empty lines with `where this != ''`.
87
+
88
+ ### Grok Parsing
89
+
90
+ The grok pattern `'.*White \"%{WORD:last_name_white}|.*Black \"%{WORD:last_name_black}'`
91
+ uses alternation (`|`) to match either a White or Black player line. The
92
+ `%{WORD:...}` capture extracts just the last name (the first word after the
93
+ opening quote).
94
+
95
+ Lines that don't match (like move notation or other metadata) produce errors,
96
+ which we filter with `where not is_error(this)`.
97
+
98
+ ### Pairing Records
99
+
100
+ PGN files list White and Black players on consecutive lines for each game. We
101
+ need to combine them into single records.
102
+
103
+ The `count` operator adds a sequential `count` field to each record (1, 2, 3, ...).
104
+ Integer division `(count - 1) / 2` maps pairs of rows to the same game_id:
105
+ rows 1,2 get game_id 0; rows 3,4 get game_id 1, etc. The `count` operator wraps
106
+ the input in a `that` field, so `{...that, game_id}` spreads the original fields
107
+ back out alongside the game_id.
108
+
109
+ Then `aggregate ... by game_id` with `max()` picks up the non-null value from
110
+ each field within each pair.
111
+
112
+ ### Reshaping with Arrays and Unnest
113
+
114
+ Each game record has `{last_name_white, last_name_black}`. To count matchups
115
+ regardless of color, we create an array with both perspectives:
116
+
117
+ ```
118
+ | [{player: last_name_white, opponent: last_name_black},
119
+ {player: last_name_black, opponent: last_name_white}]
120
+ | unnest this
121
+ ```
122
+
123
+ This doubles our records — each game now appears twice, once from each player's
124
+ perspective.
125
+
126
+ ### Final Aggregation
127
+
128
+ `count(this) by player, opponent` counts how many times each matchup occurred.
129
+ `where count > 1` filters to only matchups that happened more than once -
130
+ these are the tie-break games.
131
+
132
+ ## Nested `unnest ... into` Example
133
+
134
+ Here's a minimal dataset to demonstrate this next technique, based on similar
135
+ data we were just working with. Four players in a single round-robin (6 games),
136
+ plus one tie-breaker between Gukesh and Wei:
137
+
138
+ ```mdtest-input pairings.sup
139
+ {pairing:"Giri Gukesh"}
140
+ {pairing:"Giri Wei"}
141
+ {pairing:"Giri Abdusattorov"}
142
+ {pairing:"Gukesh Wei"}
143
+ {pairing:"Gukesh Abdusattorov"}
144
+ {pairing:"Wei Abdusattorov"}
145
+ {pairing:"Gukesh Wei"}
146
+ ```
147
+
148
+ ```mdtest-command
149
+ super -s -c "
150
+ split(pairing, ' ')
151
+ | unnest {pairing:this, player:this} into (
152
+ unnest {player: this.player, opponent: pairing} into (
153
+ where player != opponent
154
+ | cut player, opponent
155
+ )
156
+ )
157
+ " pairings.sup
158
+ ```
159
+
160
+ ```mdtest-output
161
+ {player:"Giri",opponent:"Gukesh"}
162
+ {player:"Gukesh",opponent:"Giri"}
163
+ {player:"Giri",opponent:"Wei"}
164
+ {player:"Wei",opponent:"Giri"}
165
+ {player:"Giri",opponent:"Abdusattorov"}
166
+ {player:"Abdusattorov",opponent:"Giri"}
167
+ {player:"Gukesh",opponent:"Wei"}
168
+ {player:"Wei",opponent:"Gukesh"}
169
+ {player:"Gukesh",opponent:"Abdusattorov"}
170
+ {player:"Abdusattorov",opponent:"Gukesh"}
171
+ {player:"Wei",opponent:"Abdusattorov"}
172
+ {player:"Abdusattorov",opponent:"Wei"}
173
+ {player:"Gukesh",opponent:"Wei"}
174
+ {player:"Wei",opponent:"Gukesh"}
175
+ ```
176
+
177
+ ### How Nested Unnest Works
178
+
179
+ The `unnest ... into` syntax is powerful but takes some unpacking:
180
+
181
+ 1. **`split(pairing, ' ')`** turns `"Giri Gukesh"` into `["Giri", "Gukesh"]`
182
+
183
+ 2. **First unnest**: `unnest {pairing:this, player:this} into (...)`
184
+ - Keeps the full array as `pairing`
185
+ - Unnests each element as `player`
186
+ - For `["Giri", "Gukesh"]` this produces two records:
187
+ - `{pairing: ["Giri", "Gukesh"], player: "Giri"}`
188
+ - `{pairing: ["Giri", "Gukesh"], player: "Gukesh"}`
189
+
190
+ 3. **Second unnest**: `unnest {player: this.player, opponent: pairing} into (...)`
191
+ - Preserves `player` from the outer record
192
+ - Unnests `pairing` array elements as `opponent`
193
+ - For `{pairing: ["Giri", "Gukesh"], player: "Giri"}` this produces:
194
+ - `{player: "Giri", opponent: "Giri"}`
195
+ - `{player: "Giri", opponent: "Gukesh"}`
196
+
197
+ 4. **`where player != opponent`** filters out self-matches
198
+
199
+ The result: each pairing expands into both player/opponent perspectives.
200
+
201
+ ### Finding Tie-Breaks
202
+
203
+ Now we can append the same count query to find players who faced each other
204
+ more than once:
205
+
206
+ ```mdtest-command
207
+ super -s -c "
208
+ split(pairing, ' ')
209
+ | unnest {all_pairings:this, player:this} into (
210
+ unnest {player: this.player, opponent: all_pairings} into (
211
+ where player != opponent
212
+ | cut player, opponent
213
+ )
214
+ )
215
+ | count(this) by player, opponent
216
+ | where count > 1
217
+ | sort player, opponent
218
+ " pairings.sup
219
+ ```
220
+
221
+ ```mdtest-output
222
+ {player:"Gukesh",opponent:"Wei",count:2}
223
+ {player:"Wei",opponent:"Gukesh",count:2}
224
+ ```
225
+
226
+ ## as of versions
227
+
228
+ ```mdtest-command
229
+ super --version
230
+ ```
231
+ ```mdtest-output
232
+ Version: v0.2.0
233
+ ```