@chrismo/superkit 1.1.0 → 1.3.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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=recipes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipes.test.d.ts","sourceRoot":"","sources":["../src/recipes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,142 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execFileSync } from 'child_process';
3
+ import { readFileSync, readdirSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { superRecipes } from './lib/recipes.js';
6
+ const recipesDir = join(import.meta.dirname, '..', 'docs', 'recipes');
7
+ // Load all .spq files and strip skdoc blocks — they contain nested
8
+ // brackets in example fields that super can't parse as valid syntax.
9
+ // Keep only the actual fn/op implementations.
10
+ const allSpqFiles = readdirSync(recipesDir)
11
+ .filter(f => f.endsWith('.spq'))
12
+ .sort();
13
+ // Strip skdoc blocks and functions that can't be parsed by super -I
14
+ // (sk_shell_quote has nested quotes in f-strings that break the file parser)
15
+ const UNPARSEABLE_FNS = [
16
+ 'sk_shell_quote', // nested quotes in f-string
17
+ 'sk_merge_records', // string literal with braces
18
+ 'sk_chr', // uses `let` keyword (broken in current super)
19
+ 'sk_alpha', // depends on sk_chr
20
+ 'sk_seq', // depends on sk_chr (via sk_pad_left, but also broken)
21
+ 'sk_add_ids', // uses `that` (old syntax for `this`)
22
+ ];
23
+ function stripSkdocBlocks(content) {
24
+ const lines = content.split('\n');
25
+ const result = [];
26
+ let inSkdoc = false;
27
+ let inUnparseable = false;
28
+ let skipNextClosingParen = false;
29
+ let parenDepth = 0;
30
+ for (const line of lines) {
31
+ // Strip skdoc metadata blocks
32
+ if (!inSkdoc && !inUnparseable && /^(?:fn|op)\s+skdoc_/.test(line)) {
33
+ inSkdoc = true;
34
+ continue;
35
+ }
36
+ if (inSkdoc) {
37
+ if (line.includes('<skdoc>)')) {
38
+ inSkdoc = false;
39
+ skipNextClosingParen = true;
40
+ }
41
+ continue;
42
+ }
43
+ if (skipNextClosingParen) {
44
+ if (line.trim() === '') {
45
+ result.push(line);
46
+ continue;
47
+ }
48
+ if (line.trim() === ')') {
49
+ skipNextClosingParen = false;
50
+ continue;
51
+ }
52
+ skipNextClosingParen = false;
53
+ }
54
+ // Strip functions that cause parse errors when loaded via -I
55
+ if (!inUnparseable) {
56
+ const fnMatch = line.match(/^(?:fn|op)\s+(\w+)/);
57
+ if (fnMatch && UNPARSEABLE_FNS.includes(fnMatch[1])) {
58
+ inUnparseable = true;
59
+ parenDepth = 0;
60
+ for (const ch of line) {
61
+ if (ch === '(')
62
+ parenDepth++;
63
+ if (ch === ')')
64
+ parenDepth--;
65
+ }
66
+ if (parenDepth <= 0)
67
+ inUnparseable = false;
68
+ continue;
69
+ }
70
+ }
71
+ if (inUnparseable) {
72
+ for (const ch of line) {
73
+ if (ch === '(')
74
+ parenDepth++;
75
+ if (ch === ')')
76
+ parenDepth--;
77
+ }
78
+ if (parenDepth <= 0)
79
+ inUnparseable = false;
80
+ continue;
81
+ }
82
+ result.push(line);
83
+ }
84
+ return result.join('\n');
85
+ }
86
+ import { writeFileSync } from 'fs';
87
+ import { tmpdir } from 'os';
88
+ const allDefinitions = allSpqFiles
89
+ .map(f => stripSkdocBlocks(readFileSync(join(recipesDir, f), 'utf-8')))
90
+ .join('\n');
91
+ function normalizeOutput(s) {
92
+ return s
93
+ .replace(/^'(.*)'$/, '"$1"')
94
+ .trim();
95
+ }
96
+ // Write stripped defs to a file that super can load with -I
97
+ const defsFile = join(tmpdir(), 'superkit-test-defs.spq');
98
+ writeFileSync(defsFile, allDefinitions);
99
+ function runSuper(query) {
100
+ const result = execFileSync('super', ['-I', defsFile, '-s', '-c', query], {
101
+ encoding: 'utf-8',
102
+ timeout: 10_000,
103
+ });
104
+ return result.trim();
105
+ }
106
+ // Functions stripped from defs that can't be tested
107
+ const SKIPPED_FNS = new Set(UNPARSEABLE_FNS);
108
+ const { recipes } = superRecipes();
109
+ // Skip examples that are prose descriptions, not executable assertions
110
+ function isExecutableExample(example) {
111
+ const nonExecutable = [
112
+ 'quoted and wrapped', 'single-quoted', 'tabs replaced',
113
+ 'newlines replaced', 'safely embedded', 'properly escaped',
114
+ 'single record', 'arbitrary user', 'formatted',
115
+ ];
116
+ return !nonExecutable.some(s => example.o.toLowerCase().includes(s));
117
+ }
118
+ describe('recipe skdoc examples', () => {
119
+ for (const recipe of recipes) {
120
+ if (SKIPPED_FNS.has(recipe.name))
121
+ continue;
122
+ // Skip shell-pattern recipes (type: "shell") — not SuperDB functions
123
+ if (recipe.type === 'shell')
124
+ continue;
125
+ for (const example of recipe.examples) {
126
+ if (!isExecutableExample(example))
127
+ continue;
128
+ it(`${recipe.name}: ${example.i}`, () => {
129
+ // Some examples already include "values", don't double-prefix
130
+ const query = example.i.startsWith('values ')
131
+ ? example.i
132
+ : `values ${example.i}`;
133
+ const actual = runSuper(query);
134
+ const expected = normalizeOutput(example.o);
135
+ // Strip type decorators (e.g., "29::uint8" → "29") for comparison
136
+ const actualClean = actual.replace(/::\w+$/, '');
137
+ expect(actualClean).toBe(expected);
138
+ });
139
+ }
140
+ }
141
+ });
142
+ //# sourceMappingURL=recipes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipes.test.js","sourceRoot":"","sources":["../src/recipes.test.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@ op skdoc_seq: (
9
9
  examples:[{i:"sk_seq(3)",o:"0, 1, 2"}] }, <skdoc>)
10
10
  )
11
11
 
12
- op sk_seq(n): (
12
+ op sk_seq n: (
13
13
  split(sk_pad_left('', '0', n), '')
14
14
  | unnest this
15
15
  | count
@@ -21,7 +21,7 @@ fn skdoc_csv_row(): (
21
21
  type:"func",
22
22
  desc:"Builds a CSV row from an array of values. Each element is cast to string and escaped with sk_csv_field, then joined with commas.",
23
23
  args:[{name:"arr",desc:"Array of values to format as a CSV row"}],
24
- examples:[{i:"sk_csv_row(arr) where arr has commas",o:"fields with commas get quoted"}] }, <skdoc>)
24
+ examples:[{i:"sk_csv_row(['a', 'b,c', 'd'])",o:"'a,\"b,c\",d'"}] }, <skdoc>)
25
25
  )
26
26
 
27
27
  fn sk_csv_row(arr): (
@@ -24,8 +24,8 @@ Returns a slice of the string passed in, even if indexes are out of range.
24
24
  | `end` | Ending index, exclusive. |
25
25
 
26
26
  ```supersql
27
- sk_slice('howdy')
28
- -- => 'Howdy'
27
+ sk_slice('howdy', 0, 3)
28
+ -- => 'how'
29
29
  ```
30
30
 
31
31
  **Implementation:**
@@ -235,15 +235,15 @@ super -I string.spq -s -c 'values sk_urldecode("%2Ftavern%20test")' -
235
235
  op sk_decode_seg s: (
236
236
  len(s) == 0
237
237
  ? s
238
- : (is_error(hex(s[1:3]))
238
+ : (is_error(hex(s[0:2]))
239
239
  ? s
240
- : hex(s[1:3])::string + s[3:])
240
+ : f'{hex(s[0:2])::string}{s[2:]}')
241
241
  )
242
242
 
243
243
  op sk_urldecode url: (
244
- split(url, "%")
244
+ split(url, "%%")
245
245
  | unnest this
246
- | decode_seg this
246
+ | sk_decode_seg this
247
247
  | collect(this)
248
248
  | join(this, "")
249
249
  )
@@ -9,7 +9,7 @@ op skdoc_slice: (
9
9
  args:[{name:"s",desc:"The string to slice."}
10
10
  {name:"start",desc:"Starting index, zero-based, inclusive."}
11
11
  {name:"end",desc:"Ending index, exclusive."}],
12
- examples:[{i:"sk_slice('howdy')",o:"'Howdy'"}] }, <skdoc>)
12
+ examples:[{i:"sk_slice('howdy', 0, 3)",o:"'how'"}] }, <skdoc>)
13
13
  )
14
14
 
15
15
  -- This isn't necessary with zq, but during early releases of super, the
@@ -114,21 +114,20 @@ fn skdoc_mid(): (
114
114
 
115
115
  fn sk_mid(s, start, n): (sk_slice(s, sk_clamp(start, 0, len(s)), sk_clamp(start, 0, len(s)) + sk_clamp(n, 0, len(s))))
116
116
 
117
- -- TODO: skdoc_urldecode
118
-
119
- -- URL Decoder for SuperDB
120
- -- Usage: super -I urldecode.spq -s -c 'values sk_urldecode(this)' - <<< '"%%2Ftavern%%20test"'
121
- -- Or inline the definitions in your query
122
-
123
- -- Helper operator to decode a single segment after splitting on %%
124
- -- If the segment starts with valid 2-char hex, converts it to the character
125
- -- Otherwise returns the segment as-is
117
+ fn skdoc_urldecode(): (
118
+ cast(
119
+ {name:"sk_urldecode",
120
+ type:"op",
121
+ desc:"URL-decodes a percent-encoded string. Splits on %%, decodes each hex-encoded segment, and joins back together.",
122
+ args:[{name:"url",desc:"The URL-encoded string to decode."}],
123
+ examples:[{i:"'%%2Fhello%%20world' | sk_urldecode this",o:"'/hello world'"}]}, <skdoc>)
124
+ )
126
125
  op sk_decode_seg s: (
127
126
  len(s) == 0
128
127
  ? s
129
- : (is_error(hex(s[1:3]))
128
+ : (is_error(hex(s[0:2]))
130
129
  ? s
131
- : hex(s[1:3])::string + s[3:])
130
+ : f'{hex(s[0:2])::string}{s[2:]}')
132
131
  )
133
132
 
134
133
  -- Main URL decoder operator
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrismo/superkit",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "SuperDB toolkit — docs, recipes, grok patterns, and CLI tools for the super binary",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",