@rip-lang/api 0.5.1 → 0.7.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
@@ -8,8 +8,8 @@
8
8
 
9
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
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.
11
+ - **`api.rip`** (~595 lines) — Core framework: routing, validation, `read()`, `session`, server
12
+ - **`middleware.rip`** (~390 lines) — Optional middleware: cors, logger, compress, sessions, etc.
13
13
 
14
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
15
 
@@ -19,7 +19,7 @@
19
19
  - **Sinatra-Style Handlers** — Return data directly, no ceremony
20
20
  - **Magic `@` Access** — Use `@req`, `@json()`, `@session`, `@silent` like Sinatra
21
21
  - **Powerful Validation** — 37 built-in validators with elegant `read()` function
22
- - **Before/After Filters** — Sinatra-style request lifecycle hooks
22
+ - **Lifecycle Filters** — `raw` `before` → handler → `after` hooks
23
23
  - **AsyncLocalStorage** — Safe, race-condition-free request context
24
24
  - **Hono-Compatible API** — Easy migration from existing Hono apps
25
25
 
@@ -27,15 +27,32 @@
27
27
 
28
28
  ## Try it Now
29
29
 
30
- Run the included example to see everything working:
30
+ Create `app.rip`:
31
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
32
+ ```coffee
33
+ import { get, post, read, session, start, use, before } from '@rip-lang/api'
34
+ import { sessions } from '@rip-lang/api/middleware'
35
+
36
+ use sessions() # Add secret: 'your-secret' for production
37
+
38
+ before ->
39
+ session.views ?= 0
40
+ session.views += 1
41
+
42
+ get '/', -> 'Hello, World!'
43
+ get '/json', -> { message: 'It works!', timestamp: Date.now() }
44
+ get '/users/:id', -> { user: { id: read('id', 'id!') } }
45
+ get '/session', -> { views: session.views, loggedIn: session.userId? }
46
+ get '/login', -> session.userId = 123; { loggedIn: true, userId: 123 }
47
+ get '/logout', -> delete session.userId; { loggedOut: true }
36
48
 
37
- # Run the example server
38
- bun packages/api/test/example.rip
49
+ start port: 3000
50
+ ```
51
+
52
+ Run it:
53
+
54
+ ```bash
55
+ rip app.rip
39
56
  ```
40
57
 
41
58
  **Test the endpoints:**
@@ -317,7 +334,7 @@ Import from `@rip-lang/api/middleware`:
317
334
 
318
335
  ```coffee
319
336
  import { use } from '@rip-lang/api'
320
- import { cors, logger, compress, cookies, sessions, secureHeaders, timeout, bodyLimit } from '@rip-lang/api/middleware'
337
+ import { cors, logger, compress, sessions, secureHeaders, timeout, bodyLimit } from '@rip-lang/api/middleware'
321
338
 
322
339
  # Logging
323
340
  use logger()
@@ -335,10 +352,6 @@ use cors credentials: true, maxAge: 86400
335
352
  use compress()
336
353
  use compress threshold: 1024 # Min bytes to compress
337
354
 
338
- # Cookies
339
- use cookies()
340
- use cookies secret: 'my-secret', secure: true, httpOnly: true
341
-
342
355
  # Security headers
343
356
  use secureHeaders()
344
357
  use secureHeaders hsts: true, contentSecurityPolicy: "default-src 'self'"
@@ -353,11 +366,10 @@ use bodyLimit maxSize: 1024 * 1024 # 1MB max body
353
366
  | Middleware | Options |
354
367
  |------------|---------|
355
368
  | `logger()` | `format`, `skip`, `stream` |
356
- | `cors()` | `origin`, `methods`, `headers`, `credentials`, `maxAge` |
369
+ | `cors()` | `origin`, `methods`, `headers`, `credentials`, `maxAge`, `exposeHeaders`, `preflight` |
357
370
  | `compress()` | `threshold`, `encodings` |
358
- | `cookies()` | `secure`, `httpOnly`, `sameSite`, `maxAge`, `path` |
359
- | `sessions()` | `name`, `maxAge`, `secure`, `httpOnly`, `sameSite` |
360
- | `secureHeaders()` | `hsts`, `hstsMaxAge`, `contentSecurityPolicy`, `frameOptions` |
371
+ | `sessions()` | `secret`, `name`, `maxAge`, `secure`, `httpOnly`, `sameSite` |
372
+ | `secureHeaders()` | `hsts`, `hstsMaxAge`, `contentSecurityPolicy`, `frameOptions`, `referrerPolicy` |
361
373
  | `timeout()` | `ms`, `message`, `status` |
362
374
  | `bodyLimit()` | `maxSize`, `message` |
363
375
 
@@ -365,10 +377,10 @@ use bodyLimit maxSize: 1024 * 1024 # 1MB max body
365
377
 
366
378
  ```coffee
367
379
  import { get, use, before, session } from '@rip-lang/api'
368
- import { cookies, sessions } from '@rip-lang/api/middleware'
380
+ import { sessions } from '@rip-lang/api/middleware'
369
381
 
370
- use cookies()
371
- use sessions()
382
+ # Sessions parses cookies directly from request headers
383
+ use sessions secret: process.env.SESSION_SECRET
372
384
 
373
385
  before ->
374
386
  session.views ?= 0
@@ -388,25 +400,18 @@ get '/logout', ->
388
400
 
389
401
  The `session` import works anywhere via AsyncLocalStorage — no `@` needed, works in helpers and nested callbacks.
390
402
 
391
- ### Cookie Usage (low-level)
392
-
393
- ```coffee
394
- import { get, use } from '@rip-lang/api'
395
- import { cookies } from '@rip-lang/api/middleware'
403
+ **Security note:** Without `secret`, sessions use plain base64 (dev only). With `secret`, sessions are HMAC-SHA256 signed (tamper-proof). Always set `secret` in production.
396
404
 
397
- use cookies()
405
+ ### CORS with Preflight
398
406
 
399
- get '/set-cookie', ->
400
- @cookie 'theme', 'dark', maxAge: 3600
401
- { success: true }
407
+ For APIs that need to handle OPTIONS preflight requests before route matching:
402
408
 
403
- get '/get-cookie', ->
404
- theme = @cookie 'theme'
405
- { theme }
409
+ ```coffee
410
+ import { use } from '@rip-lang/api'
411
+ import { cors } from '@rip-lang/api/middleware'
406
412
 
407
- get '/logout', ->
408
- @clearCookie 'theme'
409
- { cleared: true }
413
+ # Handle OPTIONS early (before routes are matched)
414
+ use cors origin: 'https://myapp.com', preflight: true
410
415
  ```
411
416
 
412
417
  ### Custom Middleware
@@ -438,16 +443,22 @@ use (c, next) ->
438
443
  console.log 'After handler'
439
444
  ```
440
445
 
441
- ### Before/After Filters (Sinatra-style)
446
+ ### Request Lifecycle Filters
442
447
 
443
- For simpler cases, use `before` and `after` filters. Use `@` to access context:
448
+ Three filters run at different stages: `raw` `before` handler `after`
444
449
 
445
450
  ```coffee
446
- import { before, after, get } from '@rip-lang/api'
451
+ import { raw, before, after, get } from '@rip-lang/api'
452
+
453
+ # Runs first — modify raw request before body parsing
454
+ raw (req) ->
455
+ # Fix content-type for specific clients
456
+ if req.headers.get('X-Raw-SQL') is 'true'
457
+ req.headers.set 'content-type', 'text/plain'
447
458
 
448
459
  skipPaths = ['/favicon.ico', '/ping', '/health']
449
460
 
450
- # Runs before every request
461
+ # Runs before handler (after body parsing)
451
462
  before ->
452
463
  @start = Date.now()
453
464
  @silent = @req.path in skipPaths
@@ -455,12 +466,14 @@ before ->
455
466
  unless @req.header 'Authorization'
456
467
  return @json { error: 'Unauthorized' }, 401
457
468
 
458
- # Runs after every request
469
+ # Runs after handler
459
470
  after ->
460
471
  return if @silent
461
472
  console.log "#{@req.method} #{@req.path} - #{Date.now() - @start}ms"
462
473
  ```
463
474
 
475
+ **Note:** `raw` receives the native `Request` object (before parsing). `before` and `after` use `@` to access the context.
476
+
464
477
  **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
478
  - `@req` — Request object
466
479
  - `@json()`, `@text()`, `@html()`, `@redirect()` — Response helpers
@@ -612,6 +625,41 @@ export default App ->
612
625
  post '/echo', -> read()
613
626
  ```
614
627
 
628
+ ## Context Utilities
629
+
630
+ ### ctx()
631
+
632
+ Get the current request context from anywhere (via AsyncLocalStorage):
633
+
634
+ ```coffee
635
+ import { ctx } from '@rip-lang/api'
636
+
637
+ # In a helper function
638
+ logRequest = ->
639
+ c = ctx()
640
+ console.log "#{c.req.method} #{c.req.path}" if c
641
+
642
+ # Works in callbacks, helpers, anywhere during request
643
+ get '/demo', ->
644
+ logRequest()
645
+ { ok: true }
646
+ ```
647
+
648
+ ### resetGlobals()
649
+
650
+ Reset all global state (routes, middleware, filters). Useful for testing:
651
+
652
+ ```coffee
653
+ import { resetGlobals, get, start } from '@rip-lang/api'
654
+
655
+ # In test setup
656
+ beforeEach ->
657
+ resetGlobals()
658
+
659
+ # Now define fresh routes for this test
660
+ get '/test', -> { test: true }
661
+ ```
662
+
615
663
  ## Utility Functions
616
664
 
617
665
  ### isBlank
@@ -759,7 +807,7 @@ start port: 3000
759
807
 
760
808
  ## Performance
761
809
 
762
- - **Minimal footprint** — Core is ~565 lines, optional middleware ~395 lines
810
+ - **Minimal footprint** — Core is ~595 lines, optional middleware ~390 lines
763
811
  - **Zero dependencies** — No external packages to load
764
812
  - **Compiled patterns** — Route regexes compiled once at startup
765
813
  - **Smart response wrapping** — Minimal overhead for return-value handlers
package/api.rip CHANGED
@@ -8,13 +8,12 @@
8
8
  # Public exports:
9
9
  # DSL: get, post, put, patch, del, all, use, prefix
10
10
  # Server: start, startHandler, fetch, App
11
- # Filters: before, after
11
+ # Filters: raw, before, after
12
12
  # Handlers: onError, notFound
13
13
  # Validation: read, validators, registerValidator, getValidator
14
14
  # Context: ctx, session, env
15
15
  # Utilities: isBlank, toName, toPhone
16
16
  # Dev: resetGlobals
17
- #
18
17
  # ==============================================================================
19
18
 
20
19
  import { AsyncLocalStorage } from 'node:async_hooks'
@@ -26,20 +25,27 @@ import { AsyncLocalStorage } from 'node:async_hooks'
26
25
  _ = null # Match capture holder for Rip's =~
27
26
  _errorHandler = null # Custom error handler
28
27
  _notFoundHandler = null # Custom 404 handler
28
+ _rawFilter = null # Raw request filter (before body parsing)
29
29
  _beforeFilters = [] # Before request filters
30
30
  _afterFilters = [] # After request filters
31
31
  _routes = [] # Route definitions
32
32
  _middlewares = [] # Global middleware functions
33
33
  _prefix = '' # Current route prefix for grouping
34
+ _corsPreflight = false # Enable early OPTIONS handling (set by cors middleware)
34
35
 
35
36
  export resetGlobals = ->
36
37
  _errorHandler = null
37
38
  _notFoundHandler = null
39
+ _rawFilter = null
38
40
  _beforeFilters = []
39
41
  _afterFilters = []
40
42
  _routes = []
41
43
  _middlewares = []
42
44
  _prefix = ''
45
+ _corsPreflight = false
46
+
47
+ # Enable early OPTIONS handling (called by cors middleware with preflight: true)
48
+ export enableCorsPreflight = -> _corsPreflight = true
43
49
 
44
50
  # AsyncLocalStorage for request context (enables sync read() in handlers)
45
51
  export requestContext = new AsyncLocalStorage()
@@ -237,6 +243,7 @@ export prefix = (base, fn) ->
237
243
  export onError = (handler) -> _errorHandler = handler
238
244
  export notFound = (handler) -> _notFoundHandler = handler
239
245
 
246
+ export raw = (fn) -> _rawFilter = fn
240
247
  export before = (fn) -> _beforeFilters.push fn
241
248
  export after = (fn) -> _afterFilters.push fn
242
249
 
@@ -253,6 +260,18 @@ export fetch = (req) ->
253
260
  res = fetch!(new Request(req.url, { method: 'GET', headers: req.headers }))
254
261
  return new Response(null, { status: res.status, headers: res.headers })
255
262
 
263
+ # Handle CORS preflight (OPTIONS) requests early, before route matching
264
+ # Enabled when cors middleware is used with preflight: true
265
+ if method is 'OPTIONS' and _corsPreflight
266
+ return new Response null,
267
+ status: 204
268
+ headers:
269
+ 'Access-Control-Allow-Origin': req.headers.get('origin') or '*'
270
+ 'Access-Control-Allow-Credentials': 'true'
271
+ 'Access-Control-Allow-Methods': 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS'
272
+ 'Access-Control-Allow-Headers': req.headers.get('access-control-request-headers') or 'Content-Type,Authorization'
273
+ 'Access-Control-Max-Age': '86400'
274
+
256
275
  match = matchRoute(method, pathname)
257
276
 
258
277
  unless match
@@ -263,6 +282,9 @@ export fetch = (req) ->
263
282
 
264
283
  c = createContext(req, match.params)
265
284
 
285
+ # Run raw filter before body parsing (e.g., fix content-type)
286
+ _rawFilter?(req) if method in ['POST', 'PUT', 'PATCH']
287
+
266
288
  # Pre-parse body and query for sync read() — baked in, zero ceremony
267
289
  data = {}
268
290
  try
@@ -272,6 +294,8 @@ export fetch = (req) ->
272
294
  data = c.req.json!
273
295
  else if ct =~ /x-www-form-urlencoded|form-data/i
274
296
  data = c.req.parseBody!
297
+ else if ct =~ /text\//i
298
+ data = { body: c.req.text! }
275
299
  catch
276
300
  data = {}
277
301
  merged = { ...data, ...c.req.query(), ...match.params }
@@ -300,10 +324,14 @@ runHandler = (c, handler) ->
300
324
  export startHandler = -> fetch
301
325
 
302
326
  export start = (opts = {}) ->
303
- # Skip if running under rip-server (worker sets these env vars)
304
- return if process.env.WORKER_ID? or process.env.SOCKET_PATH?
305
-
306
327
  handler = startHandler()
328
+
329
+ # If running under rip-server, set global handler and return
330
+ if process.env.WORKER_ID? or process.env.SOCKET_PATH?
331
+ globalThis.__ripHandler = handler
332
+ return handler
333
+
334
+ # Otherwise start standalone server
307
335
  host = opts.host or '0.0.0.0'
308
336
  port = opts.port or 3000
309
337
  server = Bun.serve { hostname: host, port: port, fetch: handler }
package/middleware.rip CHANGED
@@ -3,13 +3,12 @@
3
3
  # ==============================================================================
4
4
  #
5
5
  # Usage:
6
- # import { cors, logger, compress, cookies, sessions } from '@rip-lang/api/middleware'
6
+ # import { cors, logger, compress, sessions } from '@rip-lang/api/middleware'
7
7
  # import { session } from '@rip-lang/api'
8
8
  #
9
9
  # use logger()
10
10
  # use cors origin: 'https://myapp.com'
11
11
  # use compress()
12
- # use cookies()
13
12
  # use sessions()
14
13
  #
15
14
  # before ->
@@ -30,8 +29,11 @@
30
29
  # credentials: boolean
31
30
  # maxAge: number (seconds)
32
31
  # exposeHeaders: string | string[]
32
+ # preflight: boolean — handle OPTIONS before route matching (default: false)
33
33
  #
34
34
 
35
+ import { enableCorsPreflight } from '@rip-lang/api'
36
+
35
37
  export cors = (opts = {}) ->
36
38
  origin = opts.origin or '*'
37
39
  methods = opts.methods or 'GET,POST,PUT,PATCH,DELETE,OPTIONS'
@@ -45,6 +47,9 @@ export cors = (opts = {}) ->
45
47
  headers = headers.join(',') if Array.isArray(headers)
46
48
  exposeHeaders = exposeHeaders.join(',') if Array.isArray(exposeHeaders)
47
49
 
50
+ # Enable early OPTIONS handling if preflight option is set
51
+ enableCorsPreflight() if opts.preflight
52
+
48
53
  (c, next) ->
49
54
  requestOrigin = c.req.header('Origin')
50
55
 
@@ -188,73 +193,11 @@ export compress = (opts = {}) ->
188
193
  return
189
194
 
190
195
  # ==============================================================================
191
- # cookiesCookie Parsing and Setting
192
- # ==============================================================================
193
- #
194
- # Options:
195
- # secret: string (for signed cookies)
196
- # secure: boolean (HTTPS only, default false)
197
- # httpOnly: boolean (default true)
198
- # sameSite: 'Strict' | 'Lax' | 'None' (default 'Lax')
199
- # maxAge: number (seconds)
200
- # path: string (default '/')
201
- #
202
- # Adds to context:
203
- # c.cookie(name) — get cookie value
204
- # c.cookie(name, value, opts) — set cookie
205
- # c.clearCookie(name) — delete cookie
206
- #
207
-
208
- export cookies = (opts = {}) ->
209
- defaults =
210
- secure: opts.secure or false
211
- httpOnly: opts.httpOnly ? true
212
- sameSite: opts.sameSite or 'Lax'
213
- path: opts.path or '/'
214
- maxAge: opts.maxAge
215
-
216
- (c, next) ->
217
- # Parse cookies from header
218
- cookieHeader = c.req.header('Cookie') or ''
219
- parsed = {}
220
- for pair in cookieHeader.split(';')
221
- [key, val] = pair.trim().split('=')
222
- parsed[key] = decodeURIComponent(val) if key and val
223
-
224
- # Store pending Set-Cookie headers
225
- setCookies = []
226
-
227
- # Get cookie value
228
- c.cookie = (name, value, cookieOpts) ->
229
- if value is undefined
230
- # Getter
231
- return parsed[name]
232
-
233
- # Setter
234
- cookieOpts = { ...defaults, ...cookieOpts }
235
- parts = ["#{encodeURIComponent(name)}=#{encodeURIComponent(value)}"]
236
- parts.push "Path=#{cookieOpts.path}" if cookieOpts.path
237
- parts.push "Max-Age=#{cookieOpts.maxAge}" if cookieOpts.maxAge?
238
- parts.push "HttpOnly" if cookieOpts.httpOnly
239
- parts.push "Secure" if cookieOpts.secure
240
- parts.push "SameSite=#{cookieOpts.sameSite}" if cookieOpts.sameSite
241
- setCookies.push parts.join('; ')
242
-
243
- # Clear cookie
244
- c.clearCookie = (name, cookieOpts = {}) ->
245
- c.cookie name, '', { ...cookieOpts, maxAge: 0 }
246
-
247
- await next()
248
-
249
- # Apply Set-Cookie headers
250
- for cookie in setCookies
251
- c.header 'Set-Cookie', cookie, append: true
252
-
253
- # ==============================================================================
254
- # session — Session Management (requires cookies middleware)
196
+ # sessionsSession Management
255
197
  # ==============================================================================
256
198
  #
257
199
  # Options:
200
+ # secret: string (REQUIRED for production — signs cookie to prevent tampering)
258
201
  # name: string (cookie name, default 'session')
259
202
  # maxAge: number (seconds, default 86400 = 24 hours)
260
203
  # secure: boolean (HTTPS only)
@@ -263,6 +206,9 @@ export cookies = (opts = {}) ->
263
206
  #
264
207
  # Usage:
265
208
  # import { session } from '@rip-lang/api'
209
+ # import { sessions } from '@rip-lang/api/middleware'
210
+ #
211
+ # use sessions secret: process.env.SESSION_SECRET
266
212
  #
267
213
  # before ->
268
214
  # session.userId = 123
@@ -270,21 +216,73 @@ export cookies = (opts = {}) ->
270
216
  # get '/profile', ->
271
217
  # { userId: session.userId }
272
218
  #
219
+ # Security:
220
+ # Without `secret`, sessions use plain base64 (INSECURE — dev only).
221
+ # With `secret`, sessions are HMAC-SHA256 signed (tamper-proof).
222
+ #
223
+ # Note: Self-contained — parses cookies directly from request headers.
224
+ #
225
+
226
+ # HMAC-SHA256 signing helpers
227
+ hmacSign = (data, secret) ->
228
+ encoder = new TextEncoder()
229
+ key = crypto.subtle.importKey! 'raw', encoder.encode(secret),
230
+ { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
231
+ signature = crypto.subtle.sign! 'HMAC', key, encoder.encode(data)
232
+ Array.from(new Uint8Array(signature)).map((b) -> b.toString(16).padStart(2, '0')).join('')
233
+
234
+ hmacVerify = (data, signature, secret) ->
235
+ expected = hmacSign! data, secret
236
+ # Constant-time comparison to prevent timing attacks
237
+ return false if expected.length isnt signature.length
238
+ result = 0
239
+ for i in [0...expected.length]
240
+ result |= expected.charCodeAt(i) ^ signature.charCodeAt(i)
241
+ result is 0
242
+
243
+ encodeSession = (data, secret) ->
244
+ json = JSON.stringify(data)
245
+ payload = btoa(unescape(encodeURIComponent(json)))
246
+ return payload unless secret
247
+ signature = hmacSign! payload, secret
248
+ "#{payload}--#{signature}"
249
+
250
+ decodeSession = (cookie, secret) ->
251
+ return {} unless cookie
252
+ try
253
+ if secret
254
+ [payload, signature] = cookie.split('--')
255
+ return {} unless payload and signature
256
+ return {} unless hmacVerify! payload, signature, secret
257
+ JSON.parse(decodeURIComponent(escape(atob(payload))))
258
+ else
259
+ JSON.parse(decodeURIComponent(escape(atob(cookie))))
260
+ catch
261
+ {}
273
262
 
274
263
  export sessions = (opts = {}) ->
264
+ secret = opts.secret
275
265
  cookieName = opts.name or 'session'
276
266
  maxAge = opts.maxAge or 86400
277
267
  secure = opts.secure or false
278
268
  httpOnly = opts.httpOnly ? true
279
269
  sameSite = opts.sameSite or 'Lax'
280
270
 
271
+ # Warn if no secret in production
272
+ if not secret and process.env.NODE_ENV is 'production'
273
+ console.warn 'WARNING: sessions() without secret is insecure. Set secret option for production.'
274
+
281
275
  (c, next) ->
282
- # Parse existing session from cookie
283
- raw = c.cookie?(cookieName)
284
- try
285
- c.session = if raw then JSON.parse(atob(raw)) else {}
286
- catch
287
- c.session = {}
276
+ # Parse cookie header
277
+ cookieHeader = c.req.header('Cookie') or ''
278
+ cookies = {}
279
+ for pair in cookieHeader.split(';')
280
+ [key, val] = pair.trim().split('=')
281
+ cookies[key] = decodeURIComponent(val) if key and val
282
+
283
+ # Decode existing session
284
+ raw = cookies[cookieName]
285
+ c.session = decodeSession raw, secret
288
286
 
289
287
  # Track original for change detection
290
288
  original = JSON.stringify(c.session)
@@ -294,8 +292,8 @@ export sessions = (opts = {}) ->
294
292
  # Save session if changed
295
293
  current = JSON.stringify(c.session)
296
294
  if current isnt original and c._response?
297
- encoded = btoa(current)
298
- parts = ["#{cookieName}=#{encoded}"]
295
+ encoded = encodeSession c.session, secret
296
+ parts = ["#{cookieName}=#{encodeURIComponent(encoded)}"]
299
297
  parts.push "Path=/"
300
298
  parts.push "Max-Age=#{maxAge}"
301
299
  parts.push "HttpOnly" if httpOnly
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/api",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "Pure Rip API framework — elegant, fast, zero dependencies",
5
5
  "type": "module",
6
6
  "main": "api.rip",
@@ -32,9 +32,6 @@
32
32
  },
33
33
  "author": "Steve Shreeve <steve.shreeve@gmail.com>",
34
34
  "license": "MIT",
35
- "peerDependencies": {
36
- "rip-lang": "^2.0.0"
37
- },
38
35
  "files": [
39
36
  "api.rip",
40
37
  "middleware.rip",