@rip-lang/api 0.5.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 (4) hide show
  1. package/README.md +776 -0
  2. package/api.rip +564 -0
  3. package/middleware.rip +394 -0
  4. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,776 @@
1
+ <img src="https://raw.githubusercontent.com/shreeve/rip-lang/main/docs/rip.svg" style="width:50px" /> <br>
2
+
3
+ # Rip API - @rip-lang/api
4
+
5
+ > **Pure Rip API framework — elegant, fast, zero dependencies**
6
+
7
+ ## Overview
8
+
9
+ `@rip-lang/api` is a complete API framework written entirely in Rip. It provides Hono-compatible routing, middleware composition, and powerful validation — all with no external dependencies.
10
+
11
+ - **`api.rip`** (~565 lines) — Core framework: routing, validation, `read()`, `session`, server
12
+ - **`middleware.rip`** (~395 lines) — Optional middleware: cors, logger, compress, cookies, sessions, etc.
13
+
14
+ **Core Philosophy**: API development should be intuitive, safe, and beautiful. Every function eliminates boilerplate, prevents common errors, and makes your intent crystal clear.
15
+
16
+ ### Key Features
17
+
18
+ - **Zero Dependencies** — Pure Rip implementation, no Hono or other frameworks
19
+ - **Sinatra-Style Handlers** — Return data directly, no ceremony
20
+ - **Magic `@` Access** — Use `@req`, `@json()`, `@session`, `@silent` like Sinatra
21
+ - **Powerful Validation** — 37 built-in validators with elegant `read()` function
22
+ - **Before/After Filters** — Sinatra-style request lifecycle hooks
23
+ - **AsyncLocalStorage** — Safe, race-condition-free request context
24
+ - **Hono-Compatible API** — Easy migration from existing Hono apps
25
+
26
+ > **See Also**: For complete Rip language documentation, see the [main rip-lang repository](https://github.com/shreeve/rip-lang) and [docs/GUIDE.md](https://github.com/shreeve/rip-lang/blob/main/docs/GUIDE.md).
27
+
28
+ ## Try it Now
29
+
30
+ Run the included example to see everything working:
31
+
32
+ ```bash
33
+ # Clone the repo (if you haven't)
34
+ git clone https://github.com/shreeve/rip-lang.git
35
+ cd rip-lang
36
+
37
+ # Run the example server
38
+ bun packages/api/test/example.rip
39
+ ```
40
+
41
+ **Test the endpoints:**
42
+
43
+ ```bash
44
+ # Basic routes
45
+ curl http://localhost:3000/
46
+ curl http://localhost:3000/json
47
+ curl http://localhost:3000/users/42
48
+
49
+ # Session demo (use -c/-b for cookies)
50
+ curl -c cookies.txt -b cookies.txt http://localhost:3000/session
51
+ # {"views":1,"loggedIn":false}
52
+
53
+ curl -c cookies.txt -b cookies.txt http://localhost:3000/session
54
+ # {"views":2,"loggedIn":false}
55
+
56
+ curl -c cookies.txt -b cookies.txt http://localhost:3000/login
57
+ # {"loggedIn":true,"userId":123}
58
+
59
+ curl -c cookies.txt -b cookies.txt http://localhost:3000/session
60
+ # {"views":4,"userId":123,"loggedIn":true}
61
+
62
+ curl -c cookies.txt -b cookies.txt http://localhost:3000/logout
63
+ # {"loggedOut":true}
64
+
65
+ # POST with validation
66
+ curl -X POST http://localhost:3000/signup \
67
+ -H "Content-Type: application/json" \
68
+ -d '{"email":"test@example.com","age":25}'
69
+ ```
70
+
71
+ ## Quick Start
72
+
73
+ ### Installation
74
+
75
+ ```bash
76
+ bun add @rip-lang/api
77
+ ```
78
+
79
+ ### Basic Usage
80
+
81
+ ```coffee
82
+ import { get, post, use, read, start } from '@rip-lang/api'
83
+
84
+ # Context-free handlers — just return data!
85
+ get '/', -> 'Hello, World!'
86
+
87
+ get '/json', -> { message: 'It works!', timestamp: Date.now() }
88
+
89
+ # Path parameters
90
+ get '/users/:id', ->
91
+ id = read 'id', 'id!'
92
+ { user: { id, name: "User #{id}" } }
93
+
94
+ # Form validation
95
+ post '/signup', ->
96
+ email = read 'email', 'email!'
97
+ phone = read 'phone', 'phone'
98
+ age = read 'age', 'int', [18, 120]
99
+ { success: true, email, phone, age }
100
+
101
+ start port: 3000
102
+ ```
103
+
104
+ ### With Context (when needed)
105
+
106
+ ```coffee
107
+ # Access full context for headers, redirects, etc.
108
+ get '/download/:id', (env) ->
109
+ env.header 'Content-Disposition', 'attachment'
110
+ env.body getFile!(read 'id', 'id!')
111
+
112
+ get '/redirect', (env) ->
113
+ env.redirect 'https://example.com'
114
+
115
+ get '/custom', (env) ->
116
+ env.json { created: true }, 201
117
+ ```
118
+
119
+ ## The `read()` Function
120
+
121
+ The crown jewel of `@rip-lang/api` — a validation and parsing powerhouse that eliminates 90% of API boilerplate.
122
+
123
+ ### Basic Patterns
124
+
125
+ ```coffee
126
+ # Required field (throws if missing)
127
+ email = read 'email', 'email!'
128
+
129
+ # Optional field (returns null if missing)
130
+ phone = read 'phone', 'phone'
131
+
132
+ # With default value
133
+ role = read 'role', ['admin', 'user'], 'user'
134
+
135
+ # Get entire payload
136
+ data = read()
137
+ ```
138
+
139
+ ### Range Validation
140
+
141
+ The `[min, max]` syntax works for both numbers and string lengths:
142
+
143
+ ```coffee
144
+ # Numbers: value range
145
+ age = read 'age', 'int', [18, 120] # Between 18 and 120
146
+ priority = read 'priority', 'int', [1, 10] # 1-10 range
147
+
148
+ # Strings: length range
149
+ username = read 'username', 'string', [3, 20] # 3-20 characters
150
+ bio = read 'bio', 'string', [0, 500] # Up to 500 chars
151
+
152
+ # Numbers: value range
153
+ views = read 'views', 'int', min: 0 # Non-negative integer
154
+ discount = read 'discount', 'number', max: 100 # Up to 100
155
+ ```
156
+
157
+ ### Enumeration Validation
158
+
159
+ ```coffee
160
+ # Must be one of these values
161
+ role = read 'role', ['admin', 'user', 'guest']
162
+ status = read 'status', ['pending', 'active', 'closed']
163
+ ```
164
+
165
+ ### Regex Validation
166
+
167
+ ```coffee
168
+ # Custom pattern matching
169
+ code = read 'code', /^[A-Z]{3,6}$/
170
+ ```
171
+
172
+ ## Built-in Validators
173
+
174
+ `@rip-lang/api` includes 37 validators for every common API need:
175
+
176
+ ### Numbers & Money
177
+ ```coffee
178
+ id = read 'user_id', 'id!' # Positive integer (1+)
179
+ count = read 'count', 'whole' # Non-negative integer (0+)
180
+ price = read 'price', 'decimal' # Decimal number
181
+ cost = read 'cost', 'money' # Cents (multiplies by 100)
182
+ ```
183
+
184
+ ### Text Processing
185
+ ```coffee
186
+ title = read 'title', 'string' # Collapses whitespace
187
+ bio = read 'bio', 'text' # Light cleanup
188
+ name = read 'name', 'name' # Trims and normalizes
189
+ ```
190
+
191
+ ### Contact Information
192
+ ```coffee
193
+ email = read 'email', 'email' # Valid email format
194
+ phone = read 'phone', 'phone' # US phone → (555) 123-4567
195
+ address = read 'address', 'address' # Trimmed address
196
+ ```
197
+
198
+ ### Geographic Data
199
+ ```coffee
200
+ state = read 'state', 'state' # Two-letter → uppercase
201
+ zip = read 'zip', 'zip' # 5-digit zip
202
+ zipplus4 = read 'zip', 'zipplus4' # 12345-6789 format
203
+ ```
204
+
205
+ ### Identity & Security
206
+ ```coffee
207
+ ssn = read 'ssn', 'ssn' # SSN → digits only
208
+ sex = read 'gender', 'sex' # m/f/o
209
+ username = read 'username', 'username' # 3-20 chars, lowercase
210
+ ```
211
+
212
+ ### Web & Technical
213
+ ```coffee
214
+ url = read 'website', 'url' # Valid URL
215
+ ip = read 'ip_address', 'ip' # IPv4 address
216
+ mac = read 'mac', 'mac' # MAC address
217
+ color = read 'color', 'color' # Hex color → #abc123
218
+ uuid = read 'user_id', 'uuid' # UUID format
219
+ semver = read 'version', 'semver' # Semantic version
220
+ ```
221
+
222
+ ### Time & Date
223
+ ```coffee
224
+ time = read 'time', 'time' # HH:MM or HH:MM:SS
225
+ date = read 'date', 'date' # YYYY-MM-DD
226
+ time24 = read 'time', 'time24' # 24-hour format
227
+ time12 = read 'time', 'time12' # 12-hour with am/pm
228
+ ```
229
+
230
+ ### Boolean & Collections
231
+ ```coffee
232
+ active = read 'active', 'truthy' # true/t/1/yes/y/on → true
233
+ inactive = read 'off', 'falsy' # false/f/0/no/n/off → true
234
+ flag = read 'flag', 'bool' # Either → boolean
235
+ tags = read 'tags', 'array' # Must be array
236
+ config = read 'config', 'hash' # Must be object
237
+ settings = read 'data', 'json' # Parse JSON string
238
+ ids = read 'ids', 'ids' # "1,2,3" → [1, 2, 3]
239
+ slug = read 'slug', 'slug' # URL-safe slug
240
+ ```
241
+
242
+ ### Custom Validators
243
+
244
+ Register your own validators:
245
+
246
+ ```coffee
247
+ import { registerValidator, read } from '@rip-lang/api'
248
+
249
+ registerValidator 'postalCode', (v) ->
250
+ if v =~ /^[A-Z]\d[A-Z] \d[A-Z]\d$/i
251
+ _[0].toUpperCase()
252
+ else
253
+ null
254
+
255
+ # Now use it
256
+ code = read 'postal', 'postalCode!'
257
+ ```
258
+
259
+ ## Routing
260
+
261
+ ### HTTP Methods
262
+
263
+ ```coffee
264
+ import { get, post, put, patch, del, all } from '@rip-lang/api'
265
+
266
+ get '/users', -> listUsers!
267
+ post '/users', -> createUser!
268
+ get '/users/:id', -> getUser!
269
+ put '/users/:id', -> updateUser!
270
+ patch '/users/:id', -> patchUser!
271
+ del '/users/:id', -> deleteUser!
272
+ all '/health', -> 'ok' # All methods
273
+ ```
274
+
275
+ ### Path Parameters
276
+
277
+ ```coffee
278
+ # Basic parameters
279
+ get '/users/:id', ->
280
+ id = read 'id', 'id!'
281
+ { id }
282
+
283
+ # Multiple parameters
284
+ get '/users/:userId/posts/:postId', ->
285
+ userId = read 'userId', 'id!'
286
+ postId = read 'postId', 'id!'
287
+ { userId, postId }
288
+
289
+ # Custom patterns
290
+ get '/files/:name{[a-z]+\\.txt}', ->
291
+ name = read 'name'
292
+ { file: name }
293
+
294
+ # Wildcards
295
+ get '/static/*', (env) ->
296
+ { path: env.req.path }
297
+ ```
298
+
299
+ ### Route Grouping
300
+
301
+ ```coffee
302
+ import { prefix } from '@rip-lang/api'
303
+
304
+ prefix '/api/v1', ->
305
+ get '/users', -> listUsers!
306
+ get '/posts', -> listPosts!
307
+
308
+ prefix '/api/v2', ->
309
+ get '/users', -> listUsersV2!
310
+ ```
311
+
312
+ ## Middleware
313
+
314
+ ### Built-in Middleware
315
+
316
+ Import from `@rip-lang/api/middleware`:
317
+
318
+ ```coffee
319
+ import { use } from '@rip-lang/api'
320
+ import { cors, logger, compress, cookies, sessions, secureHeaders, timeout, bodyLimit } from '@rip-lang/api/middleware'
321
+
322
+ # Logging
323
+ use logger()
324
+ use logger format: 'tiny' # Minimal output
325
+ use logger format: 'dev' # Colorized (default)
326
+ use logger skip: (c) -> c.req.path is '/health'
327
+
328
+ # CORS
329
+ use cors() # Allow all origins
330
+ use cors origin: 'https://myapp.com' # Specific origin
331
+ use cors origin: ['https://a.com', 'https://b.com']
332
+ use cors credentials: true, maxAge: 86400
333
+
334
+ # Compression (gzip/deflate)
335
+ use compress()
336
+ use compress threshold: 1024 # Min bytes to compress
337
+
338
+ # Cookies
339
+ use cookies()
340
+ use cookies secret: 'my-secret', secure: true, httpOnly: true
341
+
342
+ # Security headers
343
+ use secureHeaders()
344
+ use secureHeaders hsts: true, contentSecurityPolicy: "default-src 'self'"
345
+
346
+ # Request limits
347
+ use timeout ms: 30000 # 30 second timeout
348
+ use bodyLimit maxSize: 1024 * 1024 # 1MB max body
349
+ ```
350
+
351
+ ### Middleware Options
352
+
353
+ | Middleware | Options |
354
+ |------------|---------|
355
+ | `logger()` | `format`, `skip`, `stream` |
356
+ | `cors()` | `origin`, `methods`, `headers`, `credentials`, `maxAge` |
357
+ | `compress()` | `threshold`, `encodings` |
358
+ | `cookies()` | `secure`, `httpOnly`, `sameSite`, `maxAge`, `path` |
359
+ | `sessions()` | `name`, `maxAge`, `secure`, `httpOnly`, `sameSite` |
360
+ | `secureHeaders()` | `hsts`, `hstsMaxAge`, `contentSecurityPolicy`, `frameOptions` |
361
+ | `timeout()` | `ms`, `message`, `status` |
362
+ | `bodyLimit()` | `maxSize`, `message` |
363
+
364
+ ### Session Usage
365
+
366
+ ```coffee
367
+ import { get, use, before, session } from '@rip-lang/api'
368
+ import { cookies, sessions } from '@rip-lang/api/middleware'
369
+
370
+ use cookies()
371
+ use sessions()
372
+
373
+ before ->
374
+ session.views ?= 0
375
+ session.views += 1
376
+
377
+ get '/profile', ->
378
+ { userId: session.userId, views: session.views }
379
+
380
+ get '/login', ->
381
+ session.userId = 123
382
+ { loggedIn: true }
383
+
384
+ get '/logout', ->
385
+ delete session.userId
386
+ { loggedOut: true }
387
+ ```
388
+
389
+ The `session` import works anywhere via AsyncLocalStorage — no `@` needed, works in helpers and nested callbacks.
390
+
391
+ ### Cookie Usage (low-level)
392
+
393
+ ```coffee
394
+ import { get, use } from '@rip-lang/api'
395
+ import { cookies } from '@rip-lang/api/middleware'
396
+
397
+ use cookies()
398
+
399
+ get '/set-cookie', ->
400
+ @cookie 'theme', 'dark', maxAge: 3600
401
+ { success: true }
402
+
403
+ get '/get-cookie', ->
404
+ theme = @cookie 'theme'
405
+ { theme }
406
+
407
+ get '/logout', ->
408
+ @clearCookie 'theme'
409
+ { cleared: true }
410
+ ```
411
+
412
+ ### Custom Middleware
413
+
414
+ ```coffee
415
+ # Authentication middleware
416
+ use (c, next) ->
417
+ token = @req.header 'Authorization'
418
+ unless token
419
+ return @json { error: 'Unauthorized' }, 401
420
+ @user = validateToken!(token)
421
+ await next()
422
+
423
+ # Timing middleware
424
+ use (c, next) ->
425
+ start = Date.now()
426
+ await next()
427
+ @header 'X-Response-Time', "#{Date.now() - start}ms"
428
+ ```
429
+
430
+ ### Middleware Chain
431
+
432
+ Middleware runs in registration order with Koa-style `next()`:
433
+
434
+ ```coffee
435
+ use (c, next) ->
436
+ console.log 'Before handler'
437
+ await next()
438
+ console.log 'After handler'
439
+ ```
440
+
441
+ ### Before/After Filters (Sinatra-style)
442
+
443
+ For simpler cases, use `before` and `after` filters. Use `@` to access context:
444
+
445
+ ```coffee
446
+ import { before, after, get } from '@rip-lang/api'
447
+
448
+ skipPaths = ['/favicon.ico', '/ping', '/health']
449
+
450
+ # Runs before every request
451
+ before ->
452
+ @start = Date.now()
453
+ @silent = @req.path in skipPaths
454
+ # Return a Response to short-circuit (e.g., for auth)
455
+ unless @req.header 'Authorization'
456
+ return @json { error: 'Unauthorized' }, 401
457
+
458
+ # Runs after every request
459
+ after ->
460
+ return if @silent
461
+ console.log "#{@req.method} #{@req.path} - #{Date.now() - @start}ms"
462
+ ```
463
+
464
+ **How `@` works:** Handlers are called with `this` bound to the context, so `@foo` is `this.foo`. This gives you Sinatra-like magic access to:
465
+ - `@req` — Request object
466
+ - `@json()`, `@text()`, `@html()`, `@redirect()` — Response helpers
467
+ - `@header()` — Response header modifier
468
+ - `@anything` — Custom per-request state
469
+
470
+ **Imports that work anywhere** (via AsyncLocalStorage or Proxy):
471
+ - `read` — Validated request parameters
472
+ - `session` — Session data (if middleware enabled)
473
+ - `env` — `process.env` shortcut (e.g., `env.DATABASE_URL`)
474
+
475
+ ```coffee
476
+ import { get, read, session } from '@rip-lang/api'
477
+
478
+ get '/profile', ->
479
+ id = read 'id', 'id!' # Works anywhere
480
+ { id, user: session.userId } # No @ needed
481
+ ```
482
+
483
+ **Note:** In nested callbacks, use fat arrow `=>` to preserve `@`:
484
+
485
+ ```coffee
486
+ get '/delayed', ->
487
+ @user = 'alice'
488
+ setTimeout => # Fat arrow preserves @
489
+ console.log @user # Works!
490
+ , 100
491
+ ```
492
+
493
+ ## Context Object
494
+
495
+ When you need full control, handlers receive a context object:
496
+
497
+ ### Response Helpers
498
+
499
+ ```coffee
500
+ get '/demo', (c) ->
501
+ # JSON response
502
+ c.json { data: 'value' }
503
+ c.json { data: 'value' }, 201 # With status
504
+ c.json { data: 'value' }, 200, { 'X-Custom': 'header' }
505
+
506
+ # Text response
507
+ c.text 'Hello'
508
+ c.text 'Created', 201
509
+
510
+ # HTML response
511
+ c.html '<h1>Hello</h1>'
512
+
513
+ # Redirect
514
+ c.redirect '/new-location'
515
+ c.redirect '/new-location', 301 # Permanent
516
+
517
+ # Raw body
518
+ c.body data, 200, { 'Content-Type': 'application/octet-stream' }
519
+ ```
520
+
521
+ ### Request Helpers
522
+
523
+ ```coffee
524
+ get '/info', (c) ->
525
+ # Path parameters
526
+ id = c.req.param 'id'
527
+ allParams = c.req.param()
528
+
529
+ # Query parameters
530
+ q = c.req.query 'q'
531
+ allQuery = c.req.query()
532
+
533
+ # Headers
534
+ auth = c.req.header 'Authorization'
535
+ allHeaders = c.req.header()
536
+
537
+ # Body (async)
538
+ json = c.req.json!
539
+ text = c.req.text!
540
+ form = c.req.formData!
541
+ parsed = c.req.parseBody!
542
+
543
+ # Raw request
544
+ c.req.raw # Native Request object
545
+ c.req.method # 'GET', 'POST', etc.
546
+ c.req.url # Full URL
547
+ c.req.path # Path only
548
+ ```
549
+
550
+ ### Request-Scoped State
551
+
552
+ Use `@` to store per-request state (each request gets a fresh context):
553
+
554
+ ```coffee
555
+ # Store data for later middleware/handlers
556
+ use (c, next) ->
557
+ @user = { id: 1, name: 'Alice' }
558
+ @startTime = Date.now()
559
+ await next()
560
+
561
+ get '/profile', ->
562
+ @json @user
563
+ ```
564
+
565
+ ## Error Handling
566
+
567
+ ### Custom Error Handler
568
+
569
+ ```coffee
570
+ import { onError } from '@rip-lang/api'
571
+
572
+ onError (err, c) ->
573
+ console.error 'Error:', err
574
+ c.json { error: err.message }, err.status or 500
575
+ ```
576
+
577
+ ### Custom 404 Handler
578
+
579
+ ```coffee
580
+ import { notFound } from '@rip-lang/api'
581
+
582
+ notFound (c) ->
583
+ c.json { error: 'Not found', path: c.req.path }, 404
584
+ ```
585
+
586
+ ## Server Options
587
+
588
+ ### Basic Server
589
+
590
+ ```coffee
591
+ import { start } from '@rip-lang/api'
592
+
593
+ start port: 3000
594
+ start port: 3000, host: '0.0.0.0'
595
+ ```
596
+
597
+ ### Handler Only (for @rip-lang/server)
598
+
599
+ ```coffee
600
+ import { startHandler } from '@rip-lang/api'
601
+
602
+ export default startHandler()
603
+ ```
604
+
605
+ ### App Pattern
606
+
607
+ ```coffee
608
+ import { App, get, post } from '@rip-lang/api'
609
+
610
+ export default App ->
611
+ get '/', -> 'Hello'
612
+ post '/echo', -> read()
613
+ ```
614
+
615
+ ## Utility Functions
616
+
617
+ ### isBlank
618
+
619
+ ```coffee
620
+ import { isBlank } from '@rip-lang/api'
621
+
622
+ isBlank null # true
623
+ isBlank undefined # true
624
+ isBlank '' # true
625
+ isBlank ' ' # true
626
+ isBlank [] # true
627
+ isBlank {} # true
628
+ isBlank false # true
629
+ isBlank 'hello' # false
630
+ isBlank [1, 2] # false
631
+ ```
632
+
633
+ ### toName
634
+
635
+ Advanced name formatting with intelligent capitalization:
636
+
637
+ ```coffee
638
+ import { toName } from '@rip-lang/api'
639
+
640
+ toName 'john doe' # 'John Doe'
641
+ toName 'JANE SMITH' # 'Jane Smith'
642
+ toName "o'brien" # "O'Brien"
643
+ toName 'mcdonald' # 'McDonald'
644
+ toName 'los angeles', 'address' # 'Los Angeles'
645
+ ```
646
+
647
+ ### toPhone
648
+
649
+ US phone number formatting:
650
+
651
+ ```coffee
652
+ import { toPhone } from '@rip-lang/api'
653
+
654
+ toPhone '5551234567' # '(555) 123-4567'
655
+ toPhone '555-123-4567' # '(555) 123-4567'
656
+ toPhone '555.123.4567 x99' # '(555) 123-4567, ext. 99'
657
+ toPhone '+1 555 123 4567' # '(555) 123-4567'
658
+ ```
659
+
660
+ ## Migration from Hono
661
+
662
+ `@rip-lang/api` provides a Hono-compatible API surface:
663
+
664
+ ### Before (Hono)
665
+
666
+ ```coffee
667
+ import { Hono } from 'hono'
668
+
669
+ app = new Hono()
670
+ app.get '/users/:id', (c) ->
671
+ id = c.req.param 'id'
672
+ c.json { id }
673
+
674
+ export default app
675
+ ```
676
+
677
+ ### After (@rip-lang/api)
678
+
679
+ ```coffee
680
+ import { get, read, startHandler } from '@rip-lang/api'
681
+
682
+ get '/users/:id', ->
683
+ id = read 'id', 'id!'
684
+ { id }
685
+
686
+ export default startHandler()
687
+ ```
688
+
689
+ ### API Compatibility
690
+
691
+ | Hono | @rip-lang/api |
692
+ |------|---------------|
693
+ | `app.get(path, handler)` | `get path, handler` |
694
+ | `app.post(path, handler)` | `post path, handler` |
695
+ | `app.use(middleware)` | `use middleware` |
696
+ | `app.basePath(path)` | `prefix path, -> ...` |
697
+ | `c.json(data)` | `@json(data)` or return `{ data }` |
698
+ | `c.req.param('id')` | `@req.param('id')` or `read 'id'` |
699
+ | `c.req.query('q')` | `@req.query('q')` or `read 'q'` |
700
+
701
+ ## Real-World Example
702
+
703
+ ```coffee
704
+ import { get, post, put, del, use, read, start, before, after, onError } from '@rip-lang/api'
705
+ import { logger } from '@rip-lang/api/middleware'
706
+
707
+ # Middleware
708
+ use logger()
709
+
710
+ # Filters
711
+ before ->
712
+ @start = Date.now()
713
+
714
+ after ->
715
+ console.log "#{@req.method} #{@req.path} - #{Date.now() - @start}ms"
716
+
717
+ # Error handling
718
+ onError (err) ->
719
+ @json { error: err.message }, err.status or 500
720
+
721
+ # Routes
722
+ get '/', ->
723
+ { name: 'My API', version: '1.0' }
724
+
725
+ get '/users', ->
726
+ page = read 'page', 'int', [1, 100]
727
+ limit = read 'limit', 'int', [1, 50]
728
+ users = db.listUsers! page or 1, limit or 10
729
+ { users, page, limit }
730
+
731
+ get '/users/:id', ->
732
+ id = read 'id', 'id!'
733
+ user = db.getUser!(id)
734
+ unless user
735
+ throw { message: 'User not found', status: 404 }
736
+ { user }
737
+
738
+ post '/users', ->
739
+ email = read 'email', 'email!'
740
+ name = read 'name', 'string', [1, 100]
741
+ phone = read 'phone', 'phone'
742
+ user = db.createUser! { email, name, phone }
743
+ { user, created: true }
744
+
745
+ put '/users/:id', ->
746
+ id = read 'id', 'id!'
747
+ email = read 'email', 'email'
748
+ name = read 'name', 'string', [1, 100]
749
+ user = db.updateUser! id, { email, name }
750
+ { user, updated: true }
751
+
752
+ del '/users/:id', ->
753
+ id = read 'id', 'id!'
754
+ db.deleteUser!(id)
755
+ { deleted: true }
756
+
757
+ start port: 3000
758
+ ```
759
+
760
+ ## Performance
761
+
762
+ - **Minimal footprint** — Core is ~565 lines, optional middleware ~395 lines
763
+ - **Zero dependencies** — No external packages to load
764
+ - **Compiled patterns** — Route regexes compiled once at startup
765
+ - **Smart response wrapping** — Minimal overhead for return-value handlers
766
+ - **AsyncLocalStorage** — Industry-standard, zero-copy context propagation
767
+
768
+ ## Contributing
769
+
770
+ Contributions that enhance developer productivity and code clarity are welcome. See the [main rip-lang repository](https://github.com/shreeve/rip-lang) for contribution guidelines.
771
+
772
+ ---
773
+
774
+ **Transform your API development from verbose boilerplate to clear, elegant code.**
775
+
776
+ *"90% less code, 100% more clarity"*