Tiny middleware to prune or debug empty sessions (cookie-only + your rules) and keep Prisma/Redis stores lean.
Agnostic core β no app-specific keys inside. Bring your own policy via options.
npm downloads stars issues CI coverage umbrella license
- π§Ή Prune sessions that are "empty" under your rules.
- π Debug with dry-run + logger; optional session mutation logger.
- π§© Agnostic: pass allowlists, predicates, denylists β no hardcoded keys.
- π§ͺ Store-agnostic: Prisma/SQL, Redis, etc.
- βοΈ Composable predicates (
emptyObject,equals,oneOf,and,or,flashEmptyOrOneOf). - π§° Optional preset:
cookieFlash()β beginner-friendly. - π§― Safe by design: no env access, tiny footprint.
npm i @codecorn/empty-session-reaper
// β CommonJS (require) const { wireEmptySessionReaper, predicates, buildAllowedKeys, wireSessionMutationLogger, // optional: logs added/removed keys } = require("@codecorn/empty-session-reaper"); const { cookieFlash } = require("@codecorn/empty-session-reaper/presets");
// β ESM / TypeScript import { wireEmptySessionReaper, predicates as P, buildAllowedKeys, wireSessionMutationLogger, // optional } from "@codecorn/empty-session-reaper"; import { cookieFlash } from "@codecorn/empty-session-reaper/presets";
// Meaning of buildAllowedKeys(input, expandBase, base): // - base: starting list (default: ['cookie']) // - input: extra keys to allow (e.g., ['flash']) // - expandBase: // true => merge base + input (e.g., ['cookie'] + ['flash'] -> ['cookie','flash']) // false => use input only (e.g., ['flash']) wireEmptySessionReaper(app, { logger: (m, meta) => console.debug(m, meta), allowedKeys: buildAllowedKeys(["flash"], true, ["cookie"]), // β allowlist = merge base ['cookie'] + ['flash'] β ['cookie','flash'] maxKeys: 2, keyPredicates: { // flash is harmless if it's an empty object {} flash: P.emptyObject, }, });
const isLoginFlash = (flash: any) => { if (!flash || typeof flash !== "object") return false; const ks = Object.keys(flash); if (ks.length !== 1) return false; const arr = Array.isArray((flash as any)[ks[0]]) ? (flash as any)[ks[0]] : []; return arr.length === 1 && /^pleasesignin\.?$/i.test(String(arr[0] || "")); }; wireEmptySessionReaper(app, { logger: (m, meta) => console.debug(m, meta), allowedKeys: ["cookie", "flash", "url", "flag"], maxKeys: 4, disallowedKeyPatterns: [/^csrf/i, /^token/i, /^user/i], keyPredicates: { flash: (v) => P.emptyObject(v) || isLoginFlash(v), url: (v) => ["/", "/login", "/signin"].includes(String(v || "")), flag: P.oneOf([false, "auto"]), }, isSessionPrunable: (s) => { const url = String((s as any).url || ""); return !(/\.(env|git)\b/i.test(url) || /\/upload\/\./i.test(url)); }, });
const preset = cookieFlash({ // flashKey: 'flash', // flashField: 'error', // loginMessages: [/^please sign in\.?$/i, /^access denied$/i], // extraAllowedKeys: ['url'], // maxKeys: 3, // disallowedKeyPatterns: [/^csrf/i, /^token/i], // extraPredicates: { url: (v) => ['/', '/login'].includes(String(v || '')) }, // finalCheck: (s) => !/\.env\b/i.test(String((s as any).url || '')), }); wireEmptySessionReaper(app, { logger: console.debug, ...preset });
// Place AFTER session(...) and BEFORE the reaper: wireSessionMutationLogger(app, { logger: (label, meta) => console.debug(label, meta), includeValues: false, // true to also log shallow values (use redact to mask) redact: (k, v) => (/(token|secret|pass)/i.test(k) ? "[redacted]" : v), label: "session mutation", });
Logs
{ path, added: [...], removed: [...] }on each response where the session keys changed.
createEmptySessionReaper(opts) -> (req, res, next) => void
Create the middleware with your pruning policy.
wireEmptySessionReaper(app, opts) -> middleware
Mounts the middleware on the app and returns it.
buildAllowedKeys(input?: string[], expandBase?: boolean, base?: string[]) -> string[]
Helper to compose allowlists.
- base: starting list (default: ["cookie"])
- input: extra allowed keys (e.g., ["flash"])
- expandBase:
true -> merge base + input
false -> use input only
predicates:
- emptyObject(v)
- equals(x)
- oneOf([a,b,c])
- and(p1,p2,...)
- or(p1,p2,...)
- flashEmptyOrOneOf(field='error', messages=[/^please sign in\.?$/i])
createSessionMutationLogger(opts) -> middleware
wireSessionMutationLogger(app, opts) -> middleware
lookUpSessMutation(app, opts) -> alias of wireSessionMutationLoggerMIT Β© CodeCornTM
Distributed under the MIT license.
- CousΓ¬n (co-author & review) β PYTORCHIA FOR LIFE
- Federico Girolami (CodeCorn) β Maintainer
π¨βπ» Federico Girolami
Full Stack Developer | System Integrator | Digital Solution Architect π
π« Get in Touch
π Website: codecorn.it *(Under Construction)*
π§ Email: f.girolami@codecorn.it
π GitHub: github.com/fgirolami29
Pull requests are welcome. For major changes, please open an issue first to discuss what youβd like to change.
Powered by CodeCornTM π