rip-lang 3.15.4 → 3.16.1

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 (112) hide show
  1. package/README.md +6 -4
  2. package/bin/rip +167 -12
  3. package/docs/AGENTS.md +1 -1
  4. package/docs/RIP-APP.md +808 -0
  5. package/docs/RIP-DUCKDB.md +477 -0
  6. package/docs/RIP-INTRO.md +396 -0
  7. package/docs/RIP-LANG.md +59 -5
  8. package/docs/RIP-SCHEMA.md +191 -8
  9. package/docs/RIP-TYPES.md +74 -103
  10. package/docs/demo/README.md +4 -3
  11. package/docs/dist/rip.js +3627 -1470
  12. package/docs/dist/rip.min.js +671 -244
  13. package/docs/dist/rip.min.js.br +0 -0
  14. package/docs/example/index.json +7 -7
  15. package/docs/example/index.json.br +0 -0
  16. package/docs/extensions/duckdb/manifest.json +1 -1
  17. package/docs/extensions/duckdb/v1.5.2/linux_amd64/ripdb.duckdb_extension.gz +0 -0
  18. package/docs/extensions/duckdb/v1.5.2/osx_arm64/ripdb.duckdb_extension.gz +0 -0
  19. package/docs/extensions/vscode/print/index.html +2 -1
  20. package/docs/extensions/vscode/print/print-1.0.13.vsix +0 -0
  21. package/docs/extensions/vscode/print/print-1.0.14.vsix +0 -0
  22. package/docs/extensions/vscode/print/print-latest.vsix +0 -0
  23. package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
  24. package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
  25. package/docs/ui/bundle.json +61 -0
  26. package/docs/ui/bundle.json.br +0 -0
  27. package/docs/ui/hljs-rip.js +0 -7
  28. package/docs/ui/index.css +66 -23
  29. package/docs/ui/index.html +6 -6
  30. package/package.json +9 -3
  31. package/rip-loader.js +64 -2
  32. package/src/AGENTS.md +63 -36
  33. package/src/browser.js +96 -14
  34. package/src/compiler.js +960 -143
  35. package/src/components.js +794 -88
  36. package/src/{types-emit.js → dts.js} +181 -71
  37. package/src/grammar/README.md +1 -1
  38. package/src/grammar/grammar.rip +111 -97
  39. package/src/lexer.js +132 -18
  40. package/src/parser.js +203 -205
  41. package/src/repl.js +74 -6
  42. package/src/schema/runtime-orm.js +168 -4
  43. package/src/schema/runtime-validate.js +146 -2
  44. package/src/schema/runtime.generated.js +314 -6
  45. package/src/schema/schema.js +5 -5
  46. package/src/sourcemaps.js +277 -1
  47. package/src/stdlib.js +253 -0
  48. package/src/typecheck.js +2023 -106
  49. package/src/types.js +127 -7
  50. package/docs/ui/accordion.rip +0 -103
  51. package/docs/ui/alert-dialog.rip +0 -53
  52. package/docs/ui/autocomplete.rip +0 -115
  53. package/docs/ui/avatar.rip +0 -37
  54. package/docs/ui/badge.rip +0 -15
  55. package/docs/ui/breadcrumb.rip +0 -47
  56. package/docs/ui/button-group.rip +0 -26
  57. package/docs/ui/button.rip +0 -23
  58. package/docs/ui/card.rip +0 -25
  59. package/docs/ui/carousel.rip +0 -110
  60. package/docs/ui/checkbox-group.rip +0 -61
  61. package/docs/ui/checkbox.rip +0 -33
  62. package/docs/ui/collapsible.rip +0 -50
  63. package/docs/ui/combobox.rip +0 -130
  64. package/docs/ui/context-menu.rip +0 -88
  65. package/docs/ui/date-picker.rip +0 -206
  66. package/docs/ui/dialog.rip +0 -60
  67. package/docs/ui/drawer.rip +0 -58
  68. package/docs/ui/editable-value.rip +0 -82
  69. package/docs/ui/field.rip +0 -53
  70. package/docs/ui/fieldset.rip +0 -22
  71. package/docs/ui/form.rip +0 -39
  72. package/docs/ui/grid.rip +0 -901
  73. package/docs/ui/input-group.rip +0 -28
  74. package/docs/ui/input.rip +0 -36
  75. package/docs/ui/label.rip +0 -16
  76. package/docs/ui/menu.rip +0 -134
  77. package/docs/ui/menubar.rip +0 -151
  78. package/docs/ui/meter.rip +0 -36
  79. package/docs/ui/multi-select.rip +0 -203
  80. package/docs/ui/native-select.rip +0 -33
  81. package/docs/ui/nav-menu.rip +0 -126
  82. package/docs/ui/number-field.rip +0 -162
  83. package/docs/ui/otp-field.rip +0 -89
  84. package/docs/ui/pagination.rip +0 -123
  85. package/docs/ui/popover.rip +0 -93
  86. package/docs/ui/preview-card.rip +0 -75
  87. package/docs/ui/progress.rip +0 -25
  88. package/docs/ui/radio-group.rip +0 -57
  89. package/docs/ui/resizable.rip +0 -123
  90. package/docs/ui/scroll-area.rip +0 -145
  91. package/docs/ui/select.rip +0 -151
  92. package/docs/ui/separator.rip +0 -17
  93. package/docs/ui/skeleton.rip +0 -22
  94. package/docs/ui/slider.rip +0 -165
  95. package/docs/ui/spinner.rip +0 -17
  96. package/docs/ui/table.rip +0 -27
  97. package/docs/ui/tabs.rip +0 -113
  98. package/docs/ui/textarea.rip +0 -48
  99. package/docs/ui/toast.rip +0 -87
  100. package/docs/ui/toggle-group.rip +0 -71
  101. package/docs/ui/toggle.rip +0 -24
  102. package/docs/ui/toolbar.rip +0 -38
  103. package/docs/ui/tooltip.rip +0 -85
  104. package/src/app.rip +0 -1571
  105. package/src/sourcemap-merge.js +0 -287
  106. /package/docs/demo/{components → routes}/_layout.rip +0 -0
  107. /package/docs/demo/{components → routes}/about.rip +0 -0
  108. /package/docs/demo/{components → routes}/card.rip +0 -0
  109. /package/docs/demo/{components → routes}/counter.rip +0 -0
  110. /package/docs/demo/{components → routes}/index.rip +0 -0
  111. /package/docs/demo/{components → routes}/todos.rip +0 -0
  112. /package/src/schema/{dts-emit.js → dts.js} +0 -0
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="https://github.com/shreeve/rip-lang/commits/main"><img src="https://img.shields.io/badge/version-3.15.4-blue.svg" alt="Version"></a>
12
+ <a href="https://github.com/shreeve/rip-lang/commits/main"><img src="https://img.shields.io/badge/version-3.16.1-blue.svg" alt="Version"></a>
13
13
  <a href="#zero-dependencies"><img src="https://img.shields.io/badge/dependencies-ZERO-brightgreen.svg" alt="Dependencies"></a>
14
14
  <a href="#"><img src="https://img.shields.io/badge/tests-1%2C436%2F1%2C436-brightgreen.svg" alt="Tests"></a>
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
@@ -414,13 +414,13 @@ That's it. All `<script type="text/rip">` tags share scope — `export` makes na
414
414
  <script type="text/rip" src="app.rip"></script>
415
415
 
416
416
  <!-- Bundle — fetch all components from a server endpoint -->
417
- <script defer src="/rip/rip.min.js" data-src="bundle" data-mount="App"></script>
417
+ <script defer src="/rip/rip.min.js" data-src="app" data-mount="App"></script>
418
418
 
419
419
  <!-- Bundle with stash persistence (sessionStorage) -->
420
- <script defer src="/rip/rip.min.js" data-src="bundle" data-mount="App" data-persist></script>
420
+ <script defer src="/rip/rip.min.js" data-src="app" data-mount="App" data-persist></script>
421
421
 
422
422
  <!-- Mix bundles and individual files -->
423
- <script defer src="/rip/rip.min.js" data-src="/rip/ui bundle header.rip" data-mount="App"></script>
423
+ <script defer src="/rip/rip.min.js" data-src="/rip/ui app header.rip" data-mount="App"></script>
424
424
  ```
425
425
 
426
426
  Every component has a static `mount(target)` method — `App.mount '#app'` is shorthand for `App.new().mount('#app')`. Target defaults to `'body'`.
@@ -561,6 +561,8 @@ bun run bump major
561
561
  | [docs/RIP-LANG.md](docs/RIP-LANG.md) | Full language reference (syntax, operators, reactivity, types, components) |
562
562
  | [docs/RIP-TYPES.md](docs/RIP-TYPES.md) | Type system specification |
563
563
  | [docs/RIP-SCHEMA.md](docs/RIP-SCHEMA.md) | Schema keyword — validators, models, ORM, DDL, algebra |
564
+ | [docs/RIP-DUCKDB.md](docs/RIP-DUCKDB.md) | DuckDB foreign-key constraints — what works, what doesn't, how the ORM keeps you safe |
565
+ | [docs/RIP-APP.md](docs/RIP-APP.md) | Rip App — browser framework architecture, subsystems, lifecycle invariants, gotchas |
564
566
  | [AGENTS.md](AGENTS.md) | AI agents — compiler architecture, subsystems, conventions |
565
567
 
566
568
  ---
package/bin/rip CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { readFileSync, readdirSync, writeFileSync, existsSync, statSync, unlinkSync } from 'fs';
3
+ import { readFileSync, readdirSync, writeFileSync, existsSync, statSync, unlinkSync, lstatSync, readlinkSync, rmSync, mkdirSync, symlinkSync, realpathSync } from 'fs';
4
4
  import { execSync, spawnSync, spawn } from 'child_process';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { dirname, basename, join } from 'path';
@@ -8,7 +8,7 @@ import { Compiler, formatError } from '../src/compiler.js';
8
8
  // Side-effect imports — register CLI-side runtime providers. The browser
9
9
  // bundle imports a different schema loader so server-only fragments
10
10
  // (db-naming/orm/ddl) tree-shake out of docs/dist/rip.min.js.
11
- import '../src/types-emit.js'; // registers emitTypes for .d.ts output
11
+ import '../src/dts.js'; // registers emitTypes for .d.ts output
12
12
  import '../src/schema/loader-server.js'; // registers full schema runtime
13
13
  import packageJson from '../package.json' with { type: 'json' };
14
14
 
@@ -60,7 +60,12 @@ Options:
60
60
 
61
61
  Subcommands:
62
62
  rip check [dir] Type-check all .rip files in directory
63
- rip check --strict [dir] Require type annotations in all .rip files
63
+ rip check --audit [pkgDir] Audit a typed package's public API surface for 'any' leaks
64
+ rip check --sourcemap [dir] Verify source-map round-trip for every identifier
65
+ (hover/go-to-def integrity). Useful for compiler work.
66
+ rip link [--quiet] After 'bun install', symlink @rip-lang/* deps to
67
+ local source (opt-in, for framework devs;
68
+ undo with 'bun install --force')
64
69
  rip <name> [args] Run rip-<name> (repo bin/, node_modules, or PATH)
65
70
 
66
71
  Configure via rip.json or package.json:
@@ -95,6 +100,121 @@ Shebang support:
95
100
  `);
96
101
  }
97
102
 
103
+ // `rip link` — opt-in override for framework maintainers, layered on top of a
104
+ // normal install. The canonical setup for any rip project is `bun install`
105
+ // (published packages, lockfile, transitive deps); `rip link` then redirects
106
+ // the @rip-lang/* deps to this CLI's own source checkout, by symlinking them
107
+ // into ./node_modules/@rip-lang/* (plus matching .bin shims). The `rip-lang`
108
+ // compiler/toolchain is pointed at source too, so the loader and VS Code LSP
109
+ // load the matching toolchain instead of a shadowing published tarball. Lets
110
+ // you edit framework source live without publishing.
111
+ //
112
+ // Requires `bun install` first — it overrides an existing install, it is not a
113
+ // substitute for one. Reversible: the symlinks live in node_modules (gitignored)
114
+ // and package.json stays clean semver, so `bun install --force` restores the
115
+ // published packages (a plain `bun install` sees the lockfile as satisfied and
116
+ // leaves the symlinks in place). No-op when no source tree is found (e.g. `rip`
117
+ // installed from npm without a dev checkout) — projects then just use installed
118
+ // packages.
119
+ //
120
+ // Source is this CLI's repo root (so it Just Works after `link-global`),
121
+ // overridable via RIP_SRC.
122
+ function ripLink(args) {
123
+ const quiet = args.includes('--quiet');
124
+ const log = (msg) => { if (!quiet) console.log(`[rip] link: ${msg}`); };
125
+
126
+ const rawSrc = process.env.RIP_SRC || join(__dirname, '..');
127
+ if (!existsSync(join(rawSrc, 'packages'))) {
128
+ log(`no rip-lang source at ${rawSrc} — using installed packages (set RIP_SRC to override)`);
129
+ return;
130
+ }
131
+ const src = realpathSync(rawSrc);
132
+
133
+ const cwd = process.cwd();
134
+ const pkgPath = join(cwd, 'package.json');
135
+ if (!existsSync(pkgPath)) {
136
+ console.error('[rip] link: no package.json in current directory');
137
+ process.exit(1);
138
+ }
139
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
140
+ const deps = Object.keys(pkg.dependencies || {}).filter((d) => d.startsWith('@rip-lang/'));
141
+ if (deps.length === 0) {
142
+ log('no @rip-lang/* dependencies declared — nothing to link');
143
+ return;
144
+ }
145
+
146
+ const nodeModules = join(cwd, 'node_modules');
147
+ if (!existsSync(nodeModules)) {
148
+ console.error('[rip] link: no node_modules found — run `bun install` first, then `rip link`.');
149
+ process.exit(1);
150
+ }
151
+ const binDir = join(nodeModules, '.bin');
152
+
153
+ const linkTo = (path, target, type = 'dir') => {
154
+ try {
155
+ if (lstatSync(path).isSymbolicLink() && readlinkSync(path) === target) return false;
156
+ rmSync(path, { recursive: true, force: true });
157
+ } catch {}
158
+ mkdirSync(dirname(path), { recursive: true });
159
+ symlinkSync(target, path, type);
160
+ return true;
161
+ };
162
+
163
+ const linked = []; // { dep, bins: [names] } for each resolved package
164
+ let changed = false;
165
+
166
+ for (const dep of deps) {
167
+ const short = dep.slice('@rip-lang/'.length);
168
+ const target = join(src, 'packages', short);
169
+ if (!existsSync(target)) { log(`skip ${dep} — not found at ${target}`); continue; }
170
+ if (linkTo(join(nodeModules, '@rip-lang', short), target)) changed = true;
171
+
172
+ // Re-create .bin shims from the package's own `bin` field so `rip server`
173
+ // and friends resolve source binaries from this project's node_modules.
174
+ let meta = {};
175
+ try { meta = JSON.parse(readFileSync(join(target, 'package.json'), 'utf-8')); } catch {}
176
+ const binMap = typeof meta.bin === 'string' ? { [short]: meta.bin } : (meta.bin || {});
177
+ const bins = [];
178
+ for (const [name, rel] of Object.entries(binMap)) {
179
+ const binTarget = join(target, rel);
180
+ if (!existsSync(binTarget)) continue;
181
+ if (linkTo(join(binDir, name), binTarget, 'file')) changed = true;
182
+ bins.push(name);
183
+ }
184
+ linked.push({ dep, bins });
185
+ }
186
+
187
+ // The compiler/toolchain itself. Every @rip-lang/* package depends on it,
188
+ // and the loader + VS Code LSP resolve it from the project's node_modules —
189
+ // so it must point at source too, otherwise a published `rip-lang` tarball
190
+ // shadows it (e.g. the LSP loading a typecheck.js without the latest API).
191
+ if (existsSync(join(nodeModules, 'rip-lang'))) {
192
+ if (linkTo(join(nodeModules, 'rip-lang'), src)) changed = true;
193
+ linked.push({ dep: 'rip-lang', bins: [], label: 'compiler' });
194
+ }
195
+
196
+ if (quiet || linked.length === 0) {
197
+ if (linked.length === 0) log('no @rip-lang/* packages resolved to source');
198
+ return;
199
+ }
200
+
201
+ const home = process.env.HOME || process.env.USERPROFILE || '';
202
+ const tilde = (p) => home && p.startsWith(home) ? '~' + p.slice(home.length) : p;
203
+ const tty = process.stdout.isTTY;
204
+ const dim = (s) => tty ? `\x1b[2m${s}\x1b[0m` : s;
205
+ const bold = (s) => tty ? `\x1b[1m${s}\x1b[0m` : s;
206
+
207
+ const n = linked.length;
208
+ const verb = changed ? 'linked' : 'already linked';
209
+ console.log(`${bold('rip link')} ${dim('·')} ${verb} ${n} package${n === 1 ? '' : 's'} ${dim('→ ' + tilde(src))}`);
210
+ const width = Math.max(...linked.map((l) => l.dep.length));
211
+ for (const { dep, bins, label } of linked) {
212
+ const tag = label || (bins.length ? `bin: ${bins.join(', ')}` : '');
213
+ const line = tag ? `${dep.padEnd(width)}${dim(` ${tag}`)}` : dep;
214
+ console.log(` ${dim('•')} ${line}`);
215
+ }
216
+ }
217
+
98
218
  async function main() {
99
219
  const args = process.argv.slice(2);
100
220
 
@@ -140,14 +260,27 @@ async function main() {
140
260
  // --- Built-in subcommands ---
141
261
 
142
262
  if (args[0] === 'check') {
143
- const { runCheck } = await import('../src/typecheck.js');
144
263
  const checkArgs = args.slice(1);
264
+ const VALID_FLAGS = new Set(['--audit', '--sourcemap']);
265
+ const unknown = checkArgs.filter(a => a.startsWith('-') && !VALID_FLAGS.has(a));
266
+ if (unknown.length > 0) {
267
+ console.error(`rip check: unknown flag${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}`);
268
+ console.error(`Valid flags: ${[...VALID_FLAGS].join(', ')}`);
269
+ process.exit(2);
270
+ }
145
271
  const dir = checkArgs.find(a => !a.startsWith('-')) || '.';
146
- const exitCode = await runCheck(dir, {
147
- quiet: checkArgs.includes('-q') || checkArgs.includes('--quiet'),
148
- strict: checkArgs.includes('--strict'),
149
- });
150
- process.exit(exitCode);
272
+ const wantAudit = checkArgs.includes('--audit');
273
+ const wantSourcemap = checkArgs.includes('--sourcemap');
274
+ const { runCheck, runAudit } = await import('../src/typecheck.js');
275
+ const checkCode = await runCheck(dir, { sourceMapAudit: wantSourcemap });
276
+ if (!wantAudit) process.exit(checkCode);
277
+ const auditCode = await runAudit(dir);
278
+ process.exit(checkCode || auditCode);
279
+ }
280
+
281
+ if (args[0] === 'link') {
282
+ ripLink(args.slice(1));
283
+ process.exit(0);
151
284
  }
152
285
 
153
286
  // --- Subcommand dispatch: rip <name> → rip-<name> ---
@@ -197,13 +330,32 @@ async function main() {
197
330
  }
198
331
  }
199
332
 
200
- // 5. Global PATH: rip-<name>
333
+ // 5. Nearest node_modules/.bin walking up from the cwd. `getRepoRoot`
334
+ // uses `git rev-parse --show-toplevel`, which returns the OUTERMOST git
335
+ // repo — wrong when a project lives in a subdirectory of a larger git
336
+ // repo: step 4 then probes the outer repo's node_modules and misses the
337
+ // project's own declared bins. Resolve the bin the way Node resolves a
338
+ // dependency — nearest node_modules up the tree from the cwd — which is
339
+ // correct for nested projects and standalone consumers alike.
340
+ let nmDir = process.cwd();
341
+ while (true) {
342
+ const nmBin = join(nmDir, 'node_modules', '.bin', `rip-${name}`);
343
+ if (existsSync(nmBin)) {
344
+ const r = spawnSync(nmBin, subArgs, { stdio: 'inherit', env: process.env });
345
+ process.exit(r.status ?? 1);
346
+ }
347
+ const parent = dirname(nmDir);
348
+ if (parent === nmDir) break;
349
+ nmDir = parent;
350
+ }
351
+
352
+ // 6. Global PATH: rip-<name>
201
353
  const pathResult = spawnSync(`rip-${name}`, subArgs, { stdio: 'inherit', env: process.env });
202
354
  if (pathResult.error?.code !== 'ENOENT') {
203
355
  process.exit(pathResult.status ?? 1);
204
356
  }
205
357
 
206
- // 6. Not found
358
+ // 7. Not found
207
359
  console.error(`rip: unknown command '${name}'\n\nRun 'rip --help' for usage.`);
208
360
  process.exit(1);
209
361
  }
@@ -213,7 +365,10 @@ async function main() {
213
365
 
214
366
  if ((args.length === 0 && process.stdin.isTTY) || ripOptions.includes('-r') || ripOptions.includes('--repl')) {
215
367
  const replModule = join(__dirname, '../src/repl.js');
216
- const replProcess = spawn('bun', ['--experimental-vm-modules', '-e', `import('${replModule}').then(m => m.startREPL())`], {
368
+ // --preload installs the rip-loader plugin so `import '@rip-lang/foo'`
369
+ // (and any other .rip dependency) compiles when the REPL is run from a
370
+ // directory without a bunfig.toml.
371
+ const replProcess = spawn('bun', ['--experimental-vm-modules', '--preload', loaderPath, '-e', `import('${replModule}').then(m => m.startREPL())`], {
217
372
  stdio: 'inherit',
218
373
  env: process.env
219
374
  });
package/docs/AGENTS.md CHANGED
@@ -38,6 +38,6 @@ App.mount()
38
38
  - `demo.html` and `charts.html` — dashboard demos
39
39
  - `sierpinski.html` — CDN demo
40
40
  - `example/index.html` and `results/index.html` — app launchers / examples. `example/index.json` is generated from `docs/demo/` via `bun run bundle:demo` (the source-of-truth lives in `docs/demo/`, the JSON is the deployable artifact).
41
- - `ui/index.html` — widget gallery
41
+ - `ui/index.html` — widget gallery. `ui/bundle.json` is generated from `packages/ui/browser/components/` via `bun run bundle:ui` (auto-runs as part of `bun run build`). The source-of-truth is the workspace package; the JSON is the deployable artifact. The gallery loads the bundle at boot via `data-src="bundle.json"` and reads view-source text synchronously from the in-memory components store (`window.__RIP__.components.read("_pkg/ui/<id>.rip")`) — no per-component fetches.
42
42
 
43
43
  Static demos can be opened via `file://`. The playground and example app require `bun run serve`.