rip-lang 3.10.4 → 3.10.6

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ All notable changes to Rip will be documented in this file.
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8
8
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
 
10
+ ## [3.10.6] - 2026-02-20
11
+
12
+ ### CLI — Fix Multiline Argument Corruption
13
+
14
+ - **`execSync` → `spawnSync` in `bin/rip`** — The CLI runner was passing script arguments through a shell via `execSync` with string interpolation. Any argument containing a newline (JSON payloads, heredoc content, multiline strings) was silently split into separate shell commands. Replaced all three `execSync` call sites (`.rip` file execution, compiled temp file execution, and `bin/` script fallback) with `spawnSync` using argument arrays, bypassing the shell entirely. Exit codes use `?? 1` (nullish coalescing) instead of `|| 0` for correct signal-kill handling.
15
+
16
+ ### Utilities — `curl.rip` Improvements
17
+
18
+ - **Stdin TTY guard** — `curl.rip` no longer hangs when run interactively without a payload argument. Added `process.stdin.isTTY` check to only read from stdin when piped or heredoc'd.
19
+ - **Documented inline and heredoc modes** — Updated header comments to clearly show both usage patterns.
20
+
10
21
  ## [3.9.1] - 2026-02-18
11
22
 
12
23
  ### Runtime — Reactivity & Reconciliation
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.9.1-blue.svg" alt="Version"></a>
12
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.10.6-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%2C243%2F1%2C243-brightgreen.svg" alt="Tests"></a>
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
@@ -290,8 +290,8 @@ See [@rip-lang/ui](packages/ui/) for the full framework: file-based router, reac
290
290
  | **Dependencies** | Multiple | Zero |
291
291
  | **Self-hosting** | No | Yes |
292
292
  | **Lexer** | 3,558 LOC | 2,024 LOC |
293
- | **Compiler** | 10,346 LOC | 3,289 LOC |
294
- | **Total** | 17,760 LOC | ~9,500 LOC |
293
+ | **Compiler** | 10,346 LOC | 3,293 LOC |
294
+ | **Total** | 17,760 LOC | ~11,100 LOC |
295
295
 
296
296
  Smaller codebase, modern output, built-in reactivity.
297
297
 
@@ -326,7 +326,7 @@ await rip("res = fetch! 'https://api.example.com/todos/1'; res.json!") // → {
326
326
 
327
327
  ```
328
328
  Source -> Lexer -> emitTypes -> Parser -> S-Expressions -> Codegen -> JavaScript
329
- (1,761) (types.js) (359) ["=", "x", 42] (3,291) + source map
329
+ (1,761) (types.js) (359) ["=", "x", 42] (3,293) + source map
330
330
  ```
331
331
 
332
332
  Simple arrays (with `.loc`) instead of AST node classes. The compiler is self-hosting — `bun run parser` rebuilds from source.
@@ -334,17 +334,17 @@ Simple arrays (with `.loc`) instead of AST node classes. The compiler is self-ho
334
334
  | Component | File | Lines |
335
335
  |-----------|------|-------|
336
336
  | Lexer + Rewriter | `src/lexer.js` | 1,761 |
337
- | Compiler + Codegen | `src/compiler.js` | 3,291 |
337
+ | Compiler + Codegen | `src/compiler.js` | 3,293 |
338
338
  | Type System | `src/types.js` | 1,099 |
339
- | Component System | `src/components.js` | 1,645 |
340
- | Source Maps | `src/sourcemaps.js` | 121 |
339
+ | Component System | `src/components.js` | 1,750 |
340
+ | Source Maps | `src/sourcemaps.js` | 189 |
341
341
  | Parser (generated) | `src/parser.js` | 359 |
342
342
  | Grammar | `src/grammar/grammar.rip` | 944 |
343
343
  | Parser Generator | `src/grammar/solar.rip` | 929 |
344
344
  | REPL | `src/repl.js` | 601 |
345
- | Browser Entry | `src/browser.js` | 150 |
345
+ | Browser Entry | `src/browser.js` | 151 |
346
346
  | Tags | `src/tags.js` | 62 |
347
- | **Total** | | **~10,962** |
347
+ | **Total** | | **~11,138** |
348
348
 
349
349
  ---
350
350
 
@@ -354,15 +354,15 @@ Rip includes optional packages for full-stack development:
354
354
 
355
355
  | Package | Version | Purpose |
356
356
  |---------|---------|---------|
357
- | [rip-lang](https://www.npmjs.com/package/rip-lang) | 3.9.1 | Core language compiler |
358
- | [@rip-lang/api](packages/api/) | 1.1.6 | HTTP framework (Sinatra-style routing, 37 validators) |
359
- | [@rip-lang/server](packages/server/) | 1.1.5 | Multi-worker app server (hot reload, HTTPS, mDNS) |
360
- | [@rip-lang/db](packages/db/) | 1.1.5 | DuckDB server with official UI (pure Bun FFI) |
361
- | [@rip-lang/ui](packages/ui/) | 0.3.2 | Zero-build reactive web framework (stash, router, hash routing) |
362
- | [@rip-lang/swarm](packages/swarm/) | 1.1.3 | Parallel job runner with worker pool |
363
- | [@rip-lang/csv](packages/csv/) | 1.1.3 | CSV parser + writer |
364
- | [@rip-lang/schema](packages/schema/) | 0.2.0 | Unified schema → TypeScript types, SQL DDL, validation, ORM |
365
- | [VS Code Extension](packages/vscode/) | 0.3.2 | Syntax highlighting, type intelligence, source maps |
357
+ | [rip-lang](https://www.npmjs.com/package/rip-lang) | 3.10.6 | Core language compiler |
358
+ | [@rip-lang/api](packages/api/) | 1.1.10 | HTTP framework (Sinatra-style routing, 37 validators) |
359
+ | [@rip-lang/server](packages/server/) | 1.1.19 | Multi-worker app server (hot reload, HTTPS, mDNS) |
360
+ | [@rip-lang/db](packages/db/) | 1.2.0 | DuckDB server with official UI + ActiveRecord-style client |
361
+ | [@rip-lang/ui](packages/ui/) | 0.3.14 | Zero-build reactive web framework (stash, router, hash routing) |
362
+ | [@rip-lang/swarm](packages/swarm/) | 1.1.4 | Parallel job runner with worker pool |
363
+ | [@rip-lang/csv](packages/csv/) | 1.1.4 | CSV parser + writer |
364
+ | [@rip-lang/schema](packages/schema/) | 0.2.1 | Unified schema → TypeScript types, SQL DDL, validation, ORM |
365
+ | [VS Code Extension](packages/vscode/) | 0.5.0 | Syntax highlighting, type intelligence, source maps |
366
366
 
367
367
  ```bash
368
368
  bun add -g @rip-lang/db # Installs everything (rip-lang + api + db)
package/bin/rip CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { readFileSync, writeFileSync, existsSync, statSync, unlinkSync } from 'fs';
4
- import { execSync, spawn } from 'child_process';
4
+ import { execSync, spawnSync, spawn } from 'child_process';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { dirname, basename, join } from 'path';
7
7
  import { Compiler } from '../src/compiler.js';
@@ -180,25 +180,19 @@ async function main() {
180
180
  }
181
181
  }
182
182
 
183
- // If no compile flags execute the script instead of compiling it
184
- const hasCompileFlag = showCompiled || showTokens || showSExpr || generateDts || generateMap || outputFile;
185
- if (inputFile && !hasCompileFlag) {
186
- if (!existsSync(inputFile)) {
187
- console.error(`Error: File not found: ${inputFile}`);
188
- process.exit(1);
189
- }
183
+ // Helper to check if path is a regular file (not a directory)
184
+ const isFile = (path) => existsSync(path) && statSync(path).isFile();
190
185
 
186
+ // If no compile flags and file exists → execute the script instead of compiling it
187
+ const hasCompileFlag = showCompiled || showTokens || showSExpr || generateDts || generateMap || outputFile;
188
+ if (inputFile && !hasCompileFlag && isFile(inputFile)) {
191
189
  const loaderPath = join(__dirname, '../rip-loader.js');
192
190
 
193
191
  if (inputFile.endsWith('.rip')) {
194
- try {
195
- execSync(`bun --preload ${loaderPath} ${inputFile} ${scriptArgs.join(' ')}`, {
196
- stdio: 'inherit'
197
- });
198
- process.exit(0);
199
- } catch (error) {
200
- process.exit(error.status || 1);
201
- }
192
+ const result = spawnSync('bun', ['--preload', loaderPath, inputFile, ...scriptArgs], {
193
+ stdio: 'inherit'
194
+ });
195
+ process.exit(result.status ?? 1);
202
196
  } else {
203
197
  // Non-.rip files (e.g. shebang scripts): compile, write temp file, execute
204
198
  const source = readFileSync(inputFile, 'utf-8');
@@ -208,7 +202,8 @@ async function main() {
208
202
  let exitCode = 0;
209
203
  try {
210
204
  writeFileSync(tmp, result.code);
211
- execSync(`bun --preload ${loaderPath} "${tmp}" ${scriptArgs.join(' ')}`, { stdio: 'inherit' });
205
+ const r = spawnSync('bun', ['--preload', loaderPath, tmp, ...scriptArgs], { stdio: 'inherit' });
206
+ exitCode = r.status ?? 1;
212
207
  } catch (error) {
213
208
  exitCode = error.status || 1;
214
209
  }
@@ -217,9 +212,6 @@ async function main() {
217
212
  }
218
213
  }
219
214
 
220
- // Helper to check if path is a regular file (not a directory)
221
- const isFile = (path) => existsSync(path) && statSync(path).isFile();
222
-
223
215
  // Fallback: Check for bin/ script in git repo root
224
216
  // Allows `rip migrate --status` to find and run {repo}/bin/migrate
225
217
  // Also triggers if inputFile is a directory (not a compilable file)
@@ -235,16 +227,8 @@ async function main() {
235
227
  const binScript = join(repoRoot, 'bin', inputFile);
236
228
 
237
229
  if (existsSync(binScript)) {
238
- // Found it! Execute with remaining args
239
- try {
240
- execSync(`"${binScript}" ${scriptArgs.join(' ')}`, {
241
- stdio: 'inherit',
242
- shell: true
243
- });
244
- process.exit(0);
245
- } catch (error) {
246
- process.exit(error.status || 1);
247
- }
230
+ const r = spawnSync(binScript, scriptArgs, { stdio: 'inherit' });
231
+ process.exit(r.status ?? 1);
248
232
  }
249
233
  } catch {
250
234
  // Not in a git repo, or git not available - fall through to normal error
@@ -45,7 +45,7 @@ class BinaryOp {
45
45
  ["+", left, right] // That's it!
46
46
  ```
47
47
 
48
- **Result:** CoffeeScript's compiler is 17,760 LOC. Rip's is ~10,800 LOC — smaller, yet includes a complete reactive runtime, type system, component system, and source maps.
48
+ **Result:** CoffeeScript's compiler is 17,760 LOC. Rip's is ~10,400 LOC — smaller, yet includes a complete reactive runtime, type system, component system, and source maps.
49
49
 
50
50
  > **Transform the IR (s-expressions), not the output (strings).**
51
51
 
@@ -77,7 +77,7 @@ console.log(code);
77
77
  | Dependencies | Multiple | **Zero** |
78
78
  | Parser generator | External (Jison) | **Built-in (Solar)** |
79
79
  | Self-hosting | No | **Yes** |
80
- | Total LOC | 17,760 | ~10,800 |
80
+ | Total LOC | 17,760 | ~10,400 |
81
81
 
82
82
  ## Design Principles
83
83
 
@@ -93,7 +93,7 @@ console.log(code);
93
93
 
94
94
  ```
95
95
  Source Code → Lexer → emitTypes → Parser → S-Expressions → Codegen → JavaScript
96
- (1,958) (types.js) (359) (arrays + .loc) (3,378) + source map
96
+ (1,761) (types.js) (359) (arrays + .loc) (3,293) + source map
97
97
 
98
98
  file.d.ts (when types: "emit")
99
99
  ```
@@ -102,15 +102,15 @@ Source Code → Lexer → emitTypes → Parser → S-Expressions → C
102
102
 
103
103
  | File | Purpose | Lines | Modify? |
104
104
  |------|---------|-------|---------|
105
- | `src/lexer.js` | Lexer + Rewriter | 1,958 | Yes |
106
- | `src/compiler.js` | Compiler + Code Generator | 3,378 | Yes |
105
+ | `src/lexer.js` | Lexer + Rewriter | 1,761 | Yes |
106
+ | `src/compiler.js` | Compiler + Code Generator | 3,293 | Yes |
107
107
  | `src/types.js` | Type System (lexer sidecar) | 1,099 | Yes |
108
- | `src/components.js` | Component System (compiler sidecar) | 1,240 | Yes |
109
- | `src/sourcemaps.js` | Source Map V3 Generator | 122 | Yes |
110
- | `src/tags.js` | HTML Tag Classification | 63 | Yes |
111
- | `src/parser.js` | Generated parser | 357 | No (auto-gen) |
112
- | `src/grammar/grammar.rip` | Grammar specification | 945 | Yes (carefully) |
113
- | `src/grammar/solar.rip` | Parser generator | 916 | No |
108
+ | `src/components.js` | Component System (compiler sidecar) | 1,750 | Yes |
109
+ | `src/sourcemaps.js` | Source Map V3 Generator | 189 | Yes |
110
+ | `src/tags.js` | HTML Tag Classification | 62 | Yes |
111
+ | `src/parser.js` | Generated parser | 359 | No (auto-gen) |
112
+ | `src/grammar/grammar.rip` | Grammar specification | 944 | Yes (carefully) |
113
+ | `src/grammar/solar.rip` | Parser generator | 929 | No |
114
114
 
115
115
  ## Example Flow
116
116
 
@@ -275,7 +275,7 @@ S-expressions are simple arrays that serve as Rip's intermediate representation
275
275
 
276
276
  # 4. Lexer & Rewriter
277
277
 
278
- The lexer (`src/lexer.js`) is a clean reimplementation that replaces the old lexer (3,260 lines) with ~1,870 lines producing the same token vocabulary the parser expects.
278
+ The lexer (`src/lexer.js`) is a clean reimplementation that replaces the old lexer (3,260 lines) with ~1,760 lines producing the same token vocabulary the parser expects.
279
279
 
280
280
  ## Architecture
281
281
 
@@ -439,7 +439,7 @@ REGEX tokens store `delimiter` and optional `heregex` flags in `token.data`.
439
439
 
440
440
  # 6. Compiler
441
441
 
442
- The compiler (`src/compiler.js`) is a clean reimplementation replacing the old compiler (6,016 lines) with ~3,290 lines producing identical JavaScript output.
442
+ The compiler (`src/compiler.js`) is a clean reimplementation replacing the old compiler (6,016 lines) with ~3,293 lines producing identical JavaScript output.
443
443
 
444
444
  ## Structure
445
445
 
@@ -577,4 +577,4 @@ rip> .js # Toggle JS display
577
577
 
578
578
  ---
579
579
 
580
- *Rip 3.8 — 1,241 tests passing — Zero dependencies — Self-hosting — ~11,000 LOC*
580
+ *Rip 3.10 — 1,243 tests — Zero dependencies — Self-hosting — ~13,500 LOC*
package/docs/RIP-LANG.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Rip Language Reference
4
4
 
5
- Rip is a modern reactive language that compiles to ES2022 JavaScript. It combines CoffeeScript's elegant syntax with built-in reactivity primitives. Zero dependencies, self-hosting, ~10,800 LOC.
5
+ Rip is a modern reactive language that compiles to ES2022 JavaScript. It combines CoffeeScript's elegant syntax with built-in reactivity primitives. Zero dependencies, self-hosting, ~13,500 LOC.
6
6
 
7
7
  ---
8
8
 
@@ -17,7 +17,7 @@ Rip is a modern reactive language that compiles to ES2022 JavaScript. It combine
17
17
  7. [Async Patterns](#7-async-patterns)
18
18
  8. [Modules & Imports](#8-modules--imports)
19
19
  9. [Regex Features](#9-regex-features)
20
- 10. [Server-Side Development](#10-server-side-development)
20
+ 10. [Packages](#10-packages)
21
21
  11. [CLI Tools & Scripts](#11-cli-tools--scripts)
22
22
  12. [Types](#12-types)
23
23
  13. [JavaScript Interop](#13-javascript-interop)
@@ -663,6 +663,19 @@ counter = Counter.new(initial: 5)
663
663
  # Same as: new Counter({initial: 5})
664
664
  ```
665
665
 
666
+ ## Implicit Commas
667
+
668
+ When a literal value is followed by an arrow function, Rip inserts a comma automatically:
669
+
670
+ ```coffee
671
+ # Clean route handlers
672
+ get '/users' -> User.all!
673
+ get '/users/:id' -> User.find params.id
674
+ post '/users' -> User.create body
675
+ ```
676
+
677
+ This enables Sinatra-style routing and other DSLs where functions take a value and a callback.
678
+
666
679
  ---
667
680
 
668
681
  # 5. Classes
@@ -1087,98 +1100,218 @@ text =~ /line2/m # Works with /m flag
1087
1100
 
1088
1101
  ---
1089
1102
 
1090
- # 10. Server-Side Development
1103
+ # 10. Packages
1104
+
1105
+ Rip includes optional packages for full-stack development. All are written in Rip, have zero dependencies, and run on Bun.
1106
+
1107
+ ```bash
1108
+ bun add @rip-lang/api # Web framework
1109
+ bun add @rip-lang/server # Production server
1110
+ bun add @rip-lang/ui # Reactive web UI
1111
+ bun add @rip-lang/db # DuckDB server + client
1112
+ bun add @rip-lang/schema # ORM + validation
1113
+ bun add @rip-lang/swarm # Parallel job runner
1114
+ bun add @rip-lang/csv # CSV parser + writer
1115
+ ```
1091
1116
 
1092
- ## HTTP Server
1117
+ ## @rip-lang/api — Web Framework
1118
+
1119
+ Sinatra-style routing with `@` context magic and built-in validators.
1093
1120
 
1094
1121
  ```coffee
1095
- import { serve } from "bun"
1122
+ import { get, post, use, read, start, notFound } from '@rip-lang/api'
1096
1123
 
1097
- serve
1098
- port: 3000
1099
- fetch: (req) ->
1100
- url = new URL(req.url)
1101
- switch url.pathname
1102
- when "/"
1103
- new Response("Hello from Rip!")
1104
- when "/api/users"
1105
- Response.json([{id: 1, name: "Alice"}])
1106
- else
1107
- new Response("Not Found", status: 404)
1124
+ # Routes — return data directly
1125
+ get '/' -> { message: 'Hello!' }
1126
+ get '/users/:id' -> User.find!(read 'id', 'id!')
1127
+
1128
+ # Form validation with read()
1129
+ post '/signup' ->
1130
+ email = read 'email', 'email!' # required email
1131
+ age = read 'age', 'int', [18, 120] # integer between 18-120
1132
+ role = read 'role', ['admin', 'user'] # enum
1133
+ { success: true, email, age, role }
1134
+
1135
+ # File serving
1136
+ get '/css/*' -> @send "public/#{@req.path.slice(5)}"
1137
+ notFound -> @send 'index.html', 'text/html; charset=UTF-8'
1138
+
1139
+ # Middleware
1140
+ import { cors, logger, sessions } from '@rip-lang/api/middleware'
1141
+
1142
+ use logger()
1143
+ use cors origin: '*'
1144
+ use sessions secret: process.env.SECRET
1108
1145
 
1109
- console.log "Server running on http://localhost:3000"
1146
+ # Lifecycle hooks
1147
+ before -> @start = Date.now()
1148
+ after -> console.log "#{@req.method} #{@req.path} - #{Date.now() - @start}ms"
1149
+
1150
+ start port: 3000
1110
1151
  ```
1111
1152
 
1112
- ## REST API
1153
+ ### read() Validators
1113
1154
 
1114
1155
  ```coffee
1115
- import { serve } from "bun"
1156
+ id = read 'id', 'id!' # positive integer (required)
1157
+ count = read 'count', 'whole' # non-negative integer
1158
+ price = read 'price', 'money' # cents (multiplies by 100)
1159
+ name = read 'name', 'string' # collapses whitespace
1160
+ email = read 'email', 'email' # valid email format
1161
+ phone = read 'phone', 'phone' # US phone → (555) 123-4567
1162
+ state = read 'state', 'state' # two-letter → uppercase
1163
+ zip = read 'zip', 'zip' # 5-digit zip
1164
+ url = read 'url', 'url' # valid URL
1165
+ uuid = read 'id', 'uuid' # UUID format
1166
+ date = read 'date', 'date' # YYYY-MM-DD
1167
+ time = read 'time', 'time' # HH:MM or HH:MM:SS
1168
+ flag = read 'flag', 'bool' # boolean
1169
+ tags = read 'tags', 'array' # must be array
1170
+ ids = read 'ids', 'ids' # "1,2,3" → [1, 2, 3]
1171
+ slug = read 'slug', 'slug' # URL-safe slug
1172
+ ```
1173
+
1174
+ ## @rip-lang/server — Production Server
1175
+
1176
+ Multi-worker process manager with hot reload, automatic HTTPS, and mDNS.
1116
1177
 
1117
- db = {
1118
- users: [
1119
- {id: 1, name: "Alice", email: "alice@example.com"}
1120
- {id: 2, name: "Bob", email: "bob@example.com"}
1121
- ]
1122
- nextId: 3
1123
- }
1178
+ ```bash
1179
+ rip-server # Start (uses ./index.rip)
1180
+ rip-server -w # With file watching + hot-reload
1181
+ rip-server myapp # Named (accessible at myapp.local)
1182
+ rip-server http:3000 # HTTP on specific port
1183
+ ```
1124
1184
 
1125
- def parseBody(req)
1126
- try
1127
- req.json!
1128
- catch
1129
- null
1185
+ ## @rip-lang/ui — Reactive Web Framework
1130
1186
 
1131
- serve
1132
- port: 3000
1133
- fetch: (req) ->
1134
- {pathname} = new URL(req.url)
1135
- method = req.method
1187
+ Zero-build reactive framework. Ships the compiler to the browser and compiles `.rip` components on demand. File-based routing, unified reactive stash, and SSE hot reload.
1188
+
1189
+ ```coffee
1190
+ # Server setup (index.rip)
1191
+ import { get, use, start, notFound } from '@rip-lang/api'
1192
+ import { ripUI } from '@rip-lang/ui/serve'
1193
+
1194
+ dir = import.meta.dir
1195
+ use ripUI dir: dir, components: 'routes', includes: ['ui'], watch: true
1196
+ get '/css/*' -> @send "#{dir}/css/#{@req.path.slice(5)}"
1197
+ notFound -> @send "#{dir}/index.html", 'text/html; charset=UTF-8'
1198
+ start port: 3000
1199
+ ```
1200
+
1201
+ ```coffee
1202
+ # Component (routes/counter.rip)
1203
+ Counter = component
1204
+ @count := 0
1205
+ doubled ~= @count * 2
1206
+
1207
+ increment: -> @count += 1
1208
+
1209
+ render
1210
+ div.counter
1211
+ h1 "Count: #{@count}"
1212
+ p "Doubled: #{doubled}"
1213
+ button @click: @increment, "+"
1214
+ ```
1215
+
1216
+ ## @rip-lang/db — DuckDB Server + Client
1217
+
1218
+ HTTP server for DuckDB with the official DuckDB UI built in, plus an ActiveRecord-style client library.
1219
+
1220
+ ```bash
1221
+ rip-db # In-memory database
1222
+ rip-db mydata.duckdb # File-based database
1223
+ ```
1136
1224
 
1137
- switch "#{method} #{pathname}"
1138
- when "GET /api/users"
1139
- Response.json(db.users)
1225
+ ```coffee
1226
+ # Client library
1227
+ import { connect, query, findAll, Model } from '@rip-lang/db/client'
1140
1228
 
1141
- when /^GET \/api\/users\/(\d+)$/
1142
- id = parseInt(_[1])
1143
- user = db.users.find (u) -> u.id is id
1144
- if user then Response.json(user)
1145
- else Response.json({error: "Not found"}, status: 404)
1229
+ connect 'http://localhost:4213'
1146
1230
 
1147
- when "POST /api/users"
1148
- body = parseBody!(req)
1149
- user = {id: db.nextId++, ...body}
1150
- db.users.push(user)
1151
- Response.json(user, status: 201)
1231
+ users = findAll! "SELECT * FROM users WHERE role = $1", ['admin']
1152
1232
 
1153
- else
1154
- Response.json({error: "Not found"}, status: 404)
1233
+ User = Model 'users'
1234
+ user = User.find! 42
1235
+ User.where(active: true).order('name').limit(10).all!
1155
1236
  ```
1156
1237
 
1157
- ## WebSocket Server
1238
+ ## @rip-lang/swarm — Parallel Job Runner
1158
1239
 
1159
1240
  ```coffee
1160
- import { serve } from "bun"
1241
+ import { swarm, init, retry, todo } from '@rip-lang/swarm'
1161
1242
 
1162
- clients = new Set()
1243
+ setup = ->
1244
+ unless retry()
1245
+ init()
1246
+ for i in [1..100] then todo(i)
1163
1247
 
1164
- serve
1165
- port: 3000
1166
- fetch: (req, server) ->
1167
- if server.upgrade(req)
1168
- return
1169
- new Response("WebSocket server")
1248
+ perform = (task, ctx) ->
1249
+ await Bun.sleep(Math.random() * 1000)
1170
1250
 
1171
- websocket:
1172
- open: (ws) ->
1173
- clients.add(ws)
1174
- console.log "Client connected (#{clients.size} total)"
1251
+ swarm { setup, perform }
1252
+ ```
1253
+
1254
+ ## @rip-lang/csv CSV Parser + Writer
1175
1255
 
1176
- message: (ws, message) ->
1177
- for client in clients
1178
- client.send(message) unless client is ws
1256
+ ```coffee
1257
+ import { CSV } from '@rip-lang/csv'
1179
1258
 
1180
- close: (ws) ->
1181
- clients.delete(ws)
1259
+ # Parse
1260
+ rows = CSV.read "name,age\nAlice,30\nBob,25\n", headers: true
1261
+ # [{name: 'Alice', age: '30'}, {name: 'Bob', age: '25'}]
1262
+
1263
+ # Write
1264
+ CSV.save! 'output.csv', rows
1265
+ ```
1266
+
1267
+ ## @rip-lang/schema — ORM + Validation
1268
+
1269
+ ```coffee
1270
+ import { Model } from '@rip-lang/schema'
1271
+
1272
+ class User extends Model
1273
+ @table = 'users'
1274
+ @schema
1275
+ name: { type: 'string', required: true }
1276
+ email: { type: 'email', unique: true }
1277
+
1278
+ user = User.find!(25)
1279
+ user.name = 'Alice'
1280
+ user.save!()
1281
+ ```
1282
+
1283
+ ## Full-Stack Example
1284
+
1285
+ A complete API server in Rip:
1286
+
1287
+ ```coffee
1288
+ import { get, post, use, read, start, notFound } from '@rip-lang/api'
1289
+ import { cors, logger } from '@rip-lang/api/middleware'
1290
+
1291
+ use logger()
1292
+ use cors origin: '*'
1293
+
1294
+ # In-memory store
1295
+ users = []
1296
+ nextId = 1
1297
+
1298
+ get '/api/users' -> users
1299
+
1300
+ get '/api/users/:id' ->
1301
+ id = read 'id', 'id!'
1302
+ user = users.find (u) -> u.id is id
1303
+ user or throw { status: 404, message: 'Not found' }
1304
+
1305
+ post '/api/users' ->
1306
+ name = read 'name', 'string!'
1307
+ email = read 'email', 'email!'
1308
+ user = { id: nextId++, name, email }
1309
+ users.push user
1310
+ user
1311
+
1312
+ notFound -> { error: 'Not found' }
1313
+
1314
+ start port: 3000
1182
1315
  ```
1183
1316
 
1184
1317
  ---
@@ -1539,4 +1672,19 @@ Each would need design discussion before building.
1539
1672
 
1540
1673
  ---
1541
1674
 
1542
- *Rip 3.9 — 1,241 tests passing — Zero dependencies — Self-hosting — ~11,000 LOC*
1675
+ ## Resources
1676
+
1677
+ - [Rip Playground](https://shreeve.github.io/rip-lang/) — Try Rip in the browser
1678
+ - [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=rip-lang.rip) — IDE support
1679
+ - [GitHub](https://github.com/shreeve/rip-lang) — Source code
1680
+
1681
+ | Document | Purpose |
1682
+ |----------|---------|
1683
+ | **[RIP-LANG.md](RIP-LANG.md)** | Full language reference (this file) |
1684
+ | **[RIP-TYPES.md](RIP-TYPES.md)** | Type system specification |
1685
+ | **[RIP-INTERNALS.md](RIP-INTERNALS.md)** | Compiler architecture & design decisions |
1686
+ | **[AGENT.md](../AGENT.md)** | AI agent guide for working on the compiler |
1687
+
1688
+ ---
1689
+
1690
+ *Rip 3.10 — 1,243 tests — Zero dependencies — Self-hosting — ~13,500 LOC*