rip-lang 3.10.5 → 3.10.7

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/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)
@@ -322,6 +322,7 @@ Multiple lines
322
322
  | `*` | Merge assign | `*obj = {a: 1}` | `Object.assign(obj, {a: 1})` |
323
323
  | `not in` | Not in | `x not in arr` | Negated membership test |
324
324
  | `not of` | Not of | `k not of obj` | Negated key existence |
325
+ | `<=>` | Two-way bind | `value <=> name` | Bidirectional reactive binding (render blocks) |
325
326
 
326
327
  ## Assignment Operators
327
328
 
@@ -663,6 +664,19 @@ counter = Counter.new(initial: 5)
663
664
  # Same as: new Counter({initial: 5})
664
665
  ```
665
666
 
667
+ ## Implicit Commas
668
+
669
+ When a literal value is followed by an arrow function, Rip inserts a comma automatically:
670
+
671
+ ```coffee
672
+ # Clean route handlers
673
+ get '/users' -> User.all!
674
+ get '/users/:id' -> User.find params.id
675
+ post '/users' -> User.create body
676
+ ```
677
+
678
+ This enables Sinatra-style routing and other DSLs where functions take a value and a callback.
679
+
666
680
  ---
667
681
 
668
682
  # 5. Classes
@@ -902,6 +916,110 @@ declare const count: Signal<number>;
902
916
  declare const doubled: Computed<number>;
903
917
  ```
904
918
 
919
+ ## Two-Way Binding (`<=>`)
920
+
921
+ The `<=>` operator creates bidirectional reactive bindings inside render blocks.
922
+ It connects a parent's reactive state to a child element or component — changes
923
+ flow in both directions automatically. This is a Rip original.
924
+
925
+ ### With HTML Elements
926
+
927
+ ```coffee
928
+ export Form = component
929
+ @name := ''
930
+ @age := 25
931
+ @agree := false
932
+
933
+ render
934
+ input value <=> @name # text input
935
+ input type: "number", value <=> @age # number input
936
+ input type: "checkbox", checked <=> @agree # checkbox
937
+ p "#{@name}, age #{@age}, agreed: #{@agree}"
938
+ ```
939
+
940
+ `value <=> @name` compiles to two things:
941
+ 1. **State → DOM**: an effect that sets `el.value = name` whenever `name` changes
942
+ 2. **DOM → State**: an event listener that sets `name = e.target.value` on input
943
+
944
+ The compiler auto-detects types:
945
+ - Text inputs use the `input` event and `e.target.value`
946
+ - Number/range inputs use `e.target.valueAsNumber`
947
+ - Checkboxes use the `change` event and `e.target.checked`
948
+
949
+ ### With Components
950
+
951
+ `<=>` works with custom components using the same syntax:
952
+
953
+ ```coffee
954
+ export App = component
955
+ @selected := 'viewer'
956
+ @showDialog := false
957
+ @darkMode := false
958
+
959
+ render
960
+ Select value <=> @selected
961
+ Option value: "viewer", "Viewer"
962
+ Option value: "editor", "Editor"
963
+ Option value: "admin", "Admin"
964
+ Switch checked <=> @darkMode, "Dark mode"
965
+ Dialog open <=> @showDialog
966
+ p "Are you sure?"
967
+ p "Role: #{@selected}"
968
+ ```
969
+
970
+ The parent owns the state. The child reads it and writes back to it. No
971
+ callback props, no `onChange` handlers, no `onOpenChange`, no `setValue`.
972
+
973
+ ### Why This Matters
974
+
975
+ React requires explicit `value` + `onChange` pairs for every bindable property.
976
+ This is the "controlled component" pattern — the single most tedious aspect of
977
+ React development:
978
+
979
+ ```jsx
980
+ // React: 8 lines of wiring for 4 controls
981
+ const [name, setName] = useState('');
982
+ const [role, setRole] = useState('viewer');
983
+ const [notify, setNotify] = useState(true);
984
+ const [show, setShow] = useState(false);
985
+
986
+ <input value={name} onChange={e => setName(e.target.value)} />
987
+ <Select value={role} onValueChange={setRole} />
988
+ <Switch checked={notify} onCheckedChange={setNotify} />
989
+ <Dialog open={show} onOpenChange={setShow} />
990
+ ```
991
+
992
+ Rip eliminates all of it:
993
+
994
+ ```coffee
995
+ # Rip: 4 state declarations, 4 bindings, zero callbacks
996
+ @name := ''
997
+ @role := 'viewer'
998
+ @notify := true
999
+ @show := false
1000
+
1001
+ input value <=> @name
1002
+ Select value <=> @role
1003
+ Switch checked <=> @notify
1004
+ Dialog open <=> @show
1005
+ ```
1006
+
1007
+ Vue has `v-model`. Svelte has `bind:`. But Rip's `<=>` is cleaner — it works
1008
+ uniformly across HTML elements and custom components with the same syntax, the
1009
+ same operator, and the same mental model. No framework-specific directives, no
1010
+ special component protocol. Just a reactive binding that flows both ways.
1011
+
1012
+ ### Auto-Detection
1013
+
1014
+ Even without `<=>`, the compiler auto-detects when `value:` or `checked:` is
1015
+ bound to a reactive expression and generates two-way binding automatically:
1016
+
1017
+ ```coffee
1018
+ # These are equivalent:
1019
+ input value <=> @name # explicit
1020
+ input value: @name # auto-detected (name is reactive)
1021
+ ```
1022
+
905
1023
  ---
906
1024
 
907
1025
  # 7. Async Patterns
@@ -1087,98 +1205,218 @@ text =~ /line2/m # Works with /m flag
1087
1205
 
1088
1206
  ---
1089
1207
 
1090
- # 10. Server-Side Development
1208
+ # 10. Packages
1091
1209
 
1092
- ## HTTP Server
1210
+ Rip includes optional packages for full-stack development. All are written in Rip, have zero dependencies, and run on Bun.
1211
+
1212
+ ```bash
1213
+ bun add @rip-lang/api # Web framework
1214
+ bun add @rip-lang/server # Production server
1215
+ bun add @rip-lang/ui # Reactive web UI
1216
+ bun add @rip-lang/db # DuckDB server + client
1217
+ bun add @rip-lang/schema # ORM + validation
1218
+ bun add @rip-lang/swarm # Parallel job runner
1219
+ bun add @rip-lang/csv # CSV parser + writer
1220
+ ```
1221
+
1222
+ ## @rip-lang/api — Web Framework
1223
+
1224
+ Sinatra-style routing with `@` context magic and built-in validators.
1093
1225
 
1094
1226
  ```coffee
1095
- import { serve } from "bun"
1227
+ import { get, post, use, read, start, notFound } from '@rip-lang/api'
1096
1228
 
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)
1229
+ # Routes — return data directly
1230
+ get '/' -> { message: 'Hello!' }
1231
+ get '/users/:id' -> User.find!(read 'id', 'id!')
1232
+
1233
+ # Form validation with read()
1234
+ post '/signup' ->
1235
+ email = read 'email', 'email!' # required email
1236
+ age = read 'age', 'int', [18, 120] # integer between 18-120
1237
+ role = read 'role', ['admin', 'user'] # enum
1238
+ { success: true, email, age, role }
1108
1239
 
1109
- console.log "Server running on http://localhost:3000"
1240
+ # File serving
1241
+ get '/css/*' -> @send "public/#{@req.path.slice(5)}"
1242
+ notFound -> @send 'index.html', 'text/html; charset=UTF-8'
1243
+
1244
+ # Middleware
1245
+ import { cors, logger, sessions } from '@rip-lang/api/middleware'
1246
+
1247
+ use logger()
1248
+ use cors origin: '*'
1249
+ use sessions secret: process.env.SECRET
1250
+
1251
+ # Lifecycle hooks
1252
+ before -> @start = Date.now()
1253
+ after -> console.log "#{@req.method} #{@req.path} - #{Date.now() - @start}ms"
1254
+
1255
+ start port: 3000
1110
1256
  ```
1111
1257
 
1112
- ## REST API
1258
+ ### read() Validators
1113
1259
 
1114
1260
  ```coffee
1115
- import { serve } from "bun"
1261
+ id = read 'id', 'id!' # positive integer (required)
1262
+ count = read 'count', 'whole' # non-negative integer
1263
+ price = read 'price', 'money' # cents (multiplies by 100)
1264
+ name = read 'name', 'string' # collapses whitespace
1265
+ email = read 'email', 'email' # valid email format
1266
+ phone = read 'phone', 'phone' # US phone → (555) 123-4567
1267
+ state = read 'state', 'state' # two-letter → uppercase
1268
+ zip = read 'zip', 'zip' # 5-digit zip
1269
+ url = read 'url', 'url' # valid URL
1270
+ uuid = read 'id', 'uuid' # UUID format
1271
+ date = read 'date', 'date' # YYYY-MM-DD
1272
+ time = read 'time', 'time' # HH:MM or HH:MM:SS
1273
+ flag = read 'flag', 'bool' # boolean
1274
+ tags = read 'tags', 'array' # must be array
1275
+ ids = read 'ids', 'ids' # "1,2,3" → [1, 2, 3]
1276
+ slug = read 'slug', 'slug' # URL-safe slug
1277
+ ```
1278
+
1279
+ ## @rip-lang/server — Production Server
1280
+
1281
+ Multi-worker process manager with hot reload, automatic HTTPS, and mDNS.
1116
1282
 
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
- }
1283
+ ```bash
1284
+ rip-server # Start (uses ./index.rip)
1285
+ rip-server -w # With file watching + hot-reload
1286
+ rip-server myapp # Named (accessible at myapp.local)
1287
+ rip-server http:3000 # HTTP on specific port
1288
+ ```
1124
1289
 
1125
- def parseBody(req)
1126
- try
1127
- req.json!
1128
- catch
1129
- null
1290
+ ## @rip-lang/ui — Reactive Web Framework
1130
1291
 
1131
- serve
1132
- port: 3000
1133
- fetch: (req) ->
1134
- {pathname} = new URL(req.url)
1135
- method = req.method
1292
+ 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.
1136
1293
 
1137
- switch "#{method} #{pathname}"
1138
- when "GET /api/users"
1139
- Response.json(db.users)
1294
+ ```coffee
1295
+ # Server setup (index.rip)
1296
+ import { get, use, start, notFound } from '@rip-lang/api'
1297
+ import { ripUI } from '@rip-lang/ui/serve'
1298
+
1299
+ dir = import.meta.dir
1300
+ use ripUI dir: dir, components: 'routes', includes: ['ui'], watch: true
1301
+ get '/css/*' -> @send "#{dir}/css/#{@req.path.slice(5)}"
1302
+ notFound -> @send "#{dir}/index.html", 'text/html; charset=UTF-8'
1303
+ start port: 3000
1304
+ ```
1140
1305
 
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)
1306
+ ```coffee
1307
+ # Component (routes/counter.rip)
1308
+ Counter = component
1309
+ @count := 0
1310
+ doubled ~= @count * 2
1146
1311
 
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)
1312
+ increment: -> @count += 1
1152
1313
 
1153
- else
1154
- Response.json({error: "Not found"}, status: 404)
1314
+ render
1315
+ div.counter
1316
+ h1 "Count: #{@count}"
1317
+ p "Doubled: #{doubled}"
1318
+ button @click: @increment, "+"
1155
1319
  ```
1156
1320
 
1157
- ## WebSocket Server
1321
+ ## @rip-lang/db — DuckDB Server + Client
1322
+
1323
+ HTTP server for DuckDB with the official DuckDB UI built in, plus an ActiveRecord-style client library.
1324
+
1325
+ ```bash
1326
+ rip-db # In-memory database
1327
+ rip-db mydata.duckdb # File-based database
1328
+ ```
1158
1329
 
1159
1330
  ```coffee
1160
- import { serve } from "bun"
1331
+ # Client library
1332
+ import { connect, query, findAll, Model } from '@rip-lang/db/client'
1161
1333
 
1162
- clients = new Set()
1334
+ connect 'http://localhost:4213'
1163
1335
 
1164
- serve
1165
- port: 3000
1166
- fetch: (req, server) ->
1167
- if server.upgrade(req)
1168
- return
1169
- new Response("WebSocket server")
1336
+ users = findAll! "SELECT * FROM users WHERE role = $1", ['admin']
1170
1337
 
1171
- websocket:
1172
- open: (ws) ->
1173
- clients.add(ws)
1174
- console.log "Client connected (#{clients.size} total)"
1338
+ User = Model 'users'
1339
+ user = User.find! 42
1340
+ User.where(active: true).order('name').limit(10).all!
1341
+ ```
1342
+
1343
+ ## @rip-lang/swarm — Parallel Job Runner
1175
1344
 
1176
- message: (ws, message) ->
1177
- for client in clients
1178
- client.send(message) unless client is ws
1345
+ ```coffee
1346
+ import { swarm, init, retry, todo } from '@rip-lang/swarm'
1179
1347
 
1180
- close: (ws) ->
1181
- clients.delete(ws)
1348
+ setup = ->
1349
+ unless retry()
1350
+ init()
1351
+ for i in [1..100] then todo(i)
1352
+
1353
+ perform = (task, ctx) ->
1354
+ await Bun.sleep(Math.random() * 1000)
1355
+
1356
+ swarm { setup, perform }
1357
+ ```
1358
+
1359
+ ## @rip-lang/csv — CSV Parser + Writer
1360
+
1361
+ ```coffee
1362
+ import { CSV } from '@rip-lang/csv'
1363
+
1364
+ # Parse
1365
+ rows = CSV.read "name,age\nAlice,30\nBob,25\n", headers: true
1366
+ # [{name: 'Alice', age: '30'}, {name: 'Bob', age: '25'}]
1367
+
1368
+ # Write
1369
+ CSV.save! 'output.csv', rows
1370
+ ```
1371
+
1372
+ ## @rip-lang/schema — ORM + Validation
1373
+
1374
+ ```coffee
1375
+ import { Model } from '@rip-lang/schema'
1376
+
1377
+ class User extends Model
1378
+ @table = 'users'
1379
+ @schema
1380
+ name: { type: 'string', required: true }
1381
+ email: { type: 'email', unique: true }
1382
+
1383
+ user = User.find!(25)
1384
+ user.name = 'Alice'
1385
+ user.save!()
1386
+ ```
1387
+
1388
+ ## Full-Stack Example
1389
+
1390
+ A complete API server in Rip:
1391
+
1392
+ ```coffee
1393
+ import { get, post, use, read, start, notFound } from '@rip-lang/api'
1394
+ import { cors, logger } from '@rip-lang/api/middleware'
1395
+
1396
+ use logger()
1397
+ use cors origin: '*'
1398
+
1399
+ # In-memory store
1400
+ users = []
1401
+ nextId = 1
1402
+
1403
+ get '/api/users' -> users
1404
+
1405
+ get '/api/users/:id' ->
1406
+ id = read 'id', 'id!'
1407
+ user = users.find (u) -> u.id is id
1408
+ user or throw { status: 404, message: 'Not found' }
1409
+
1410
+ post '/api/users' ->
1411
+ name = read 'name', 'string!'
1412
+ email = read 'email', 'email!'
1413
+ user = { id: nextId++, name, email }
1414
+ users.push user
1415
+ user
1416
+
1417
+ notFound -> { error: 'Not found' }
1418
+
1419
+ start port: 3000
1182
1420
  ```
1183
1421
 
1184
1422
  ---
@@ -1412,6 +1650,10 @@ a =~ /pat/ # regex match, captures in _
1412
1650
  a[/pat/, 1] # regex extract
1413
1651
  a? # existence check (a != null)
1414
1652
  a ?? b # nullish coalescing
1653
+
1654
+ # Two-way binding (render blocks)
1655
+ input value <=> @name # bidirectional reactive binding
1656
+ Dialog open <=> @show # works with components too
1415
1657
  ```
1416
1658
 
1417
1659
  ## File Templates
@@ -1539,4 +1781,19 @@ Each would need design discussion before building.
1539
1781
 
1540
1782
  ---
1541
1783
 
1542
- *Rip 3.9 — 1,241 tests passing — Zero dependencies — Self-hosting — ~11,000 LOC*
1784
+ ## Resources
1785
+
1786
+ - [Rip Playground](https://shreeve.github.io/rip-lang/) — Try Rip in the browser
1787
+ - [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=rip-lang.rip) — IDE support
1788
+ - [GitHub](https://github.com/shreeve/rip-lang) — Source code
1789
+
1790
+ | Document | Purpose |
1791
+ |----------|---------|
1792
+ | **[RIP-LANG.md](RIP-LANG.md)** | Full language reference (this file) |
1793
+ | **[RIP-TYPES.md](RIP-TYPES.md)** | Type system specification |
1794
+ | **[RIP-INTERNALS.md](RIP-INTERNALS.md)** | Compiler architecture & design decisions |
1795
+ | **[AGENT.md](../AGENT.md)** | AI agent guide for working on the compiler |
1796
+
1797
+ ---
1798
+
1799
+ *Rip 3.10 — 1,243 tests — Zero dependencies — Self-hosting — ~13,500 LOC*