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.
- 🧹 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 && /^please sign in\.?$/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 wireSessionMutationLogger
MIT © CodeCorn™
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 CodeCorn™ 🚀