@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 +20 -18
- package/api.rip +65 -75
- package/middleware.rip +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -2,28 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
# Rip API - @rip-lang/api
|
|
4
4
|
|
|
5
|
-
> **
|
|
5
|
+
> **A fast, elegant API framework with zero dependencies — written entirely in Rip**
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
13
|
+
## Features
|
|
10
14
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ~
|
|
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 = (
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
526
|
-
|
|
527
|
-
return
|
|
528
|
-
return
|
|
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
|
-
|
|
532
|
-
if typeof
|
|
533
|
-
required = true
|
|
534
|
-
tag = tag.slice(0, -1)
|
|
530
|
+
# String: apply validator function
|
|
531
|
+
else if typeof type is 'string'
|
|
535
532
|
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
#
|
|
540
|
-
if
|
|
541
|
-
|
|
542
|
-
v =
|
|
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(
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
v = if
|
|
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
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
|
563
|
-
|
|
564
|
-
#
|
|
565
|
-
else if
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rip-lang/api",
|
|
3
|
-
"version": "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": "
|
|
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",
|