@samveys/session
Dari packages/session/README.md di repository framework.
Halaman ini belum diterjemahkan. Kamu sedang membaca versi bahasa Inggris.
Server-side sessions for Samveys. The browser holds an opaque id in a cookie; the data lives in a store (the database by default).
- Id: 32 random bytes, base64url (43 characters). A cookie value of any other shape is ignored before it reaches the store.
- Cookie defaults:
svs_session,Secure; HttpOnly; SameSite=Lax; Path=/, 2 hour lifetime. - A request that never writes to the session sets no cookie and writes no row.
- The idle timeout slides: a used session is refreshed once half of it has passed.
1. Install the plugin
// samveys.config.ts
import { database } from '@samveys/database'
import { http } from '@samveys/http'
import { session } from '@samveys/session'
import dbConfig from './config/database.ts'
export default {
plugins: [http(), database(dbConfig), session({ cookie: { maxAgeMs: 8 * 60 * 60 * 1000 } })],
}The plugin registers the named middleware session. Put use: ['session'] on a route or group (or list it in the global middleware). Reading ctx.session on a route without it fails with SVS_E090.
| Option | Default | Meaning |
|---|---|---|
store | 'database' | 'database', 'memory' (tests), or your own SessionStore |
cookie.name | svs_session | |
cookie.secure | true | Turn off only for plain-HTTP local development |
cookie.httpOnly | true | |
cookie.sameSite | 'Lax' | 'None' requires secure |
cookie.path | / | |
cookie.maxAgeMs | 2 hours | |
idleTimeoutMs | cookie.maxAgeMs |
The database store keeps samveys_sessions(id, data, expires_at) and creates it on first use.
2. Use it
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { http } from '@samveys/http'
import { session } from '@samveys/session'
import { createTestApp } from '@samveys/testing'
test('a counter that survives requests, and a login that changes the id', async () => {
await using app = await createTestApp({
plugins: [http(), session({ store: 'memory' })],
routes: (router) => {
router.get('/count', { use: ['session'] }, ({ session: s }) => {
const count = s.get<number>('count', 0) + 1
s.put('count', count)
return { count }
})
router.post('/login', { use: ['session'] }, async ({ session: s }) => {
s.put('userId', 7)
await s.regenerate() // always on login: stops session fixation
return { ok: true }
})
},
})
const first = await app.http.get('/count')
const header = first.headers.get('set-cookie') ?? ''
assert.match(header, /Secure/)
assert.match(header, /HttpOnly/)
assert.match(header, /SameSite=Lax/)
const cookie = header.split(';')[0] ?? ''
assert.equal((await app.http.get('/count', { headers: { cookie } })).json<{ count: number }>().count, 2)
const login = await app.http.post('/login', { headers: { cookie } })
assert.notEqual((login.headers.get('set-cookie') ?? '').split(';')[0], cookie)
})3. The ctx.session API
| Call | Does |
|---|---|
get(key, fallback?) | Reads a value (including flash data from the previous request) |
put(key, value) | Writes a JSON-serializable value |
forget(key) | Removes a value |
flash(key, value) | Readable with get() on the next request only |
all() | A copy of every value |
regenerate() | New id, same data; the old id is deleted. Call it on login |
invalidate() | Logout: deletes the data now and clears the cookie |
id, isNew | The current id; whether the request arrived without a valid session |
Notes and limits
- Two concurrent requests of one browser that both change the session race; the later save wins. Keep sessions small and avoid writing on read-only routes.
- If a handler throws, nothing is saved.
SessionStoreis three methods (read,write,destroy). Stores hold a record and anexpiresAt; the middleware does the clock arithmetic.- The cookie value is the id and nothing else. It is not signed, because the store lookup is the check and a guessed id is a 256-bit guess.