@rip-lang/api 0.8.0 → 1.0.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.
package/README.md CHANGED
@@ -2,28 +2,30 @@
2
2
 
3
3
  # Rip API - @rip-lang/api
4
4
 
5
- > **Pure Rip API framework — elegant, fast, zero dependencies**
5
+ > **A fast, elegant API framework with zero dependencies written entirely in Rip**
6
6
 
7
- ## Overview
7
+ Rip API is a complete HTTP framework for building APIs with Bun. It provides
8
+ Sinatra-style routing, Koa-style middleware composition, 37 built-in validators,
9
+ and powerful session management — all in two files with no external dependencies.
10
+ It powers [@rip-lang/db](https://github.com/shreeve/rip-lang/tree/main/packages/db)
11
+ and is designed for APIs that are clear, concise, and correct.
8
12
 
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.
13
+ ## Features
10
14
 
11
- - **`api.rip`** (~595 lines) Core framework: routing, validation, `read()`, `session`, server
12
- - **`middleware.rip`** (~390 lines) Optional middleware: cors, logger, compress, sessions, etc.
15
+ - **Zero dependencies**Pure Rip implementation, no external frameworks
16
+ - **Sinatra-style handlers**Return data directly, no ceremony
17
+ - **Magic `@` access** — `@req`, `@json()`, `@session` like Sinatra
18
+ - **37 built-in validators** — Elegant `read()` function for parsing and validation
19
+ - **Lifecycle filters** — `raw` → `before` → handler → `after` hooks
20
+ - **AsyncLocalStorage context** — `read()` and `session` work anywhere, no prop drilling
21
+ - **Hono-compatible API** — Easy migration from existing Hono apps
13
22
 
14
- **Core Philosophy**: API development should be intuitive, safe, and beautiful. Every function eliminates boilerplate, prevents common errors, and makes your intent crystal clear.
23
+ | File | Lines | Role |
24
+ |------|-------|------|
25
+ | `api.rip` | ~590 | Core framework: routing, validation, `read()`, `session`, server |
26
+ | `middleware.rip` | ~465 | Built-in middleware: cors, logger, sessions, compression, security |
15
27
 
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
- - **Lifecycle Filters** — `raw` → `before` → handler → `after` 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).
28
+ > **See Also**: For 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
29
 
28
30
  ## Try it Now
29
31
 
@@ -806,7 +808,7 @@ start port: 3000
806
808
 
807
809
  ## Performance
808
810
 
809
- - **Minimal footprint** — Core is ~595 lines, optional middleware ~390 lines
811
+ - **Minimal footprint** — Core is ~590 lines, middleware ~465 lines
810
812
  - **Zero dependencies** — No external packages to load
811
813
  - **Compiled patterns** — Route regexes compiled once at startup
812
814
  - **Smart response wrapping** — Minimal overhead for return-value handlers
package/api.rip CHANGED
@@ -508,91 +508,81 @@ export getValidator = (name) -> validators[name]
508
508
  # read() — Sinatra-style Parameter Reading
509
509
  # ==============================================================================
510
510
 
511
- export read = (keyOrTag = null, tagOrMiss = null, missOrNil = null) ->
512
- store = requestContext.getStore()
513
- unless store?
514
- throw new Error 'read() called outside request context'
511
+ export read = (name = null, type = null, miss = null) ->
512
+ store = requestContext.getStore() or throw new Error 'no context for read()'
515
513
 
516
- data = store.data or {}
517
- key = null
518
- tag = null
519
- miss = null
514
+ # missing value helper
515
+ done = (must = false) ->
516
+ return miss() if typeof miss is 'function'
517
+ throw new Error "Missing required field: #{name}" if must
518
+ return miss ?? null
520
519
 
521
- [key, tag, miss] = typeof keyOrTag is 'string' \
522
- ? [keyOrTag, tagOrMiss, missOrNil] \
523
- : [null , keyOrTag , tagOrMiss]
520
+ # get value from store
521
+ v = store.data or {}
522
+ v = v[name] if name?
523
+ v = v.trim() if typeof v is 'string'
524
524
 
525
- raw = if key? then data[key] else data
526
- unless tag?
527
- return raw if raw?
528
- return miss() if typeof miss is 'function'
529
- return if miss? and miss isnt '' then miss else null
525
+ # value only, no validator
526
+ if !type?
527
+ return v if v?
528
+ return done()
530
529
 
531
- required = false
532
- if typeof tag is 'string' and tag.endsWith('!')
533
- required = true
534
- tag = tag.slice(0, -1)
530
+ # String: apply validator function
531
+ else if typeof type is 'string'
535
532
 
536
- v = raw
537
- v = v.trim() if typeof v is 'string'
533
+ # detect required value (trailing '!')
534
+ if type.endsWith '!'
535
+ must = true
536
+ type = type.slice(0, -1)
537
+
538
+ # apply validator function
539
+ f = getValidator(type)
540
+ v = String(v ?? '') unless type in ['array', 'hash', 'json']
541
+ v = if f then f(v) else null
538
542
 
539
- # Named validator
540
- if typeof tag is 'string'
541
- fn = getValidator(tag)
542
- v = if fn then fn(String(v or '')) else null
543
+ # Regex: apply regex pattern
544
+ else if type instanceof RegExp
545
+ s = String(v ?? '')
546
+ v = s.match(type)?[0] or null
543
547
 
544
548
  # Array: [min, max] constraint or enumeration
545
- else if Array.isArray(tag)
546
- if typeof tag[0] is 'number'
547
- minVal = tag[0]
548
- maxVal = tag[1]
549
- if typeof v is 'number' or (String(v) =~ /^[-+]?\d+$/)
550
- n = if typeof v is 'number' then v else parseInt(String(v))
551
- ok = not isNaN(n)
552
- ok = false if minVal? and n < minVal
553
- ok = false if maxVal? and n > maxVal
554
- v = if ok then n else null
549
+ else if Array.isArray(type)
550
+ s = String(v ?? '')
551
+ y = true
552
+ if typeof type[0] is 'number'
553
+ [min, max] = type
554
+ if typeof v is 'number' or (s =~ /^[-+]?\d+$/)
555
+ n = if typeof v is 'number' then v else +s
556
+ y = false if min? and n < min
557
+ y = false if max? and n > max
558
+ v = if y then n else null
555
559
  else
556
- s = String(v or '')
557
- ok = true
558
- ok = false if minVal? and s.length < minVal
559
- ok = false if maxVal? and s.length > maxVal
560
- v = if ok then s else null
560
+ y = false if min? and s.length < min
561
+ y = false if max? and s.length > max
562
+ v = if y then s else null
561
563
  else
562
- v = if tag.includes(String(v)) then String(v) else null
563
-
564
- # Regex
565
- else if tag instanceof RegExp
566
- v = String(v or '').match(tag)?[0] or null
567
-
568
- # Object constraints
569
- else if typeof tag is 'object'
570
- if tag.start? or tag.end?
571
- n = parseInt(v)
572
- v = if not isNaN(n) and (not tag.start? or n >= tag.start) and (not tag.end? or n <= tag.end) then n else null
573
- else if tag.min? or tag.max?
574
- if typeof v is 'number' or (String(v or '') =~ /^[-+]?\d+$/)
575
- n = if typeof v is 'number' then v else parseInt(String(v))
576
- ok = not isNaN(n)
577
- ok = false if tag.min? and n < tag.min
578
- ok = false if tag.max? and n > tag.max
579
- v = if ok then n else null
564
+ v = if type.includes(s) then s else null
565
+
566
+ # Object: start/end, min/max
567
+ else if type? and typeof type is 'object'
568
+ s = String(v ?? '')
569
+ if type.start? or type.end?
570
+ n = if s =~ /^[-+]?\d+$/ then +s else NaN
571
+ v = if not isNaN(n) and (not type.start? or n >= type.start) and (not type.end? or n <= type.end) then n else null
572
+ else if type.min? or type.max?
573
+ y = true
574
+ if typeof v is 'number' or (s =~ /^[-+]?\d+$/)
575
+ n = if typeof v is 'number' then v else +s
576
+ y = false if type.min? and n < type.min
577
+ y = false if type.max? and n > type.max
578
+ v = if y then n else null
580
579
  else
581
- s = String(v or '')
582
- ok = true
583
- ok = false if tag.min? and s.length < tag.min
584
- ok = false if tag.max? and s.length > tag.max
585
- v = if ok then s else null
586
-
587
- blank = v is null or v is undefined or (typeof v is 'string' and v.trim() is '')
588
-
589
- if blank
590
- if required
591
- return miss?() if typeof miss is 'function'
592
- throw new Error "Missing required field: #{key}"
593
- else
594
- return miss?() if typeof miss is 'function'
595
- return null if miss is '' or not miss? # Empty string or null/undefined → null
596
- return miss # Return 0, false, etc. as-is
580
+ y = false if type.min? and s.length < type.min
581
+ y = false if type.max? and s.length > type.max
582
+ v = if y then s else null
583
+
584
+ # blank / missing value handling
585
+ if not v? or (typeof v is 'string' and v.trim() is '')
586
+ return done(must)
597
587
 
598
588
  v
package/middleware.rip CHANGED
@@ -415,7 +415,7 @@ export prettyJson = (c, next) ->
415
415
  return _renderHtmlJson(data, status)
416
416
  originalJson(data, status, headers)
417
417
 
418
- next!()
418
+ next!
419
419
 
420
420
  _shouldRenderHtmlJson = (req) ->
421
421
  ua = req.headers.get('user-agent') or ''
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/api",
3
- "version": "0.8.0",
3
+ "version": "1.0.0",
4
4
  "description": "Pure Rip API framework — elegant, fast, zero dependencies",
5
5
  "type": "module",
6
6
  "main": "api.rip",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "scripts": {
12
12
  "build": "rip api.rip",
13
- "test": "echo \"Tests coming soon\" && exit 0"
13
+ "test": "rip tests/read.test.rip"
14
14
  },
15
15
  "keywords": [
16
16
  "api",
@@ -32,6 +32,9 @@
32
32
  },
33
33
  "author": "Steve Shreeve <steve.shreeve@gmail.com>",
34
34
  "license": "MIT",
35
+ "dependencies": {
36
+ "rip-lang": "^2.9.0"
37
+ },
35
38
  "files": [
36
39
  "api.rip",
37
40
  "middleware.rip",