Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .eslintrc

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ coverage
.tshy*
.eslintcache
dist
/tsconfig.tsbuildinfo
60 changes: 60 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Reuse legacy eslint-config-egg via FlatCompat on ESLint v9 flat config
import { FlatCompat } from '@eslint/eslintrc';
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import nodePlugin from 'eslint-plugin-node';

const compat = new FlatCompat({ baseDirectory: import.meta.dirname });

export default [
js.configs.recommended,
// TypeScript recommendations (no type-checking for speed)
...tseslint.configs.recommended,
// Bring in the Egg preset (legacy) so behavior stays identical
...compat.extends('eslint-config-egg'),
// Project-specific tweaks
{
ignores: [ 'dist/**', 'node_modules/**' ],
},
{
files: [ 'src/**/*.ts', 'test/**/*.ts' ],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
parser: tseslint.parser,
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: (() => {
// Disable all node/* rules to avoid ESLint v9 incompatibilities from legacy plugin versions pulled by eslint-config-egg
const disabled = Object.fromEntries(
Object.keys(nodePlugin.rules ?? {}).map(ruleName => [ `node/${ruleName}`, 'off' ])
);
// Align with previous ESLint 8 behavior from eslint-config-egg in this repo
Object.assign(disabled, {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-unused-vars': 'off',
'comma-dangle': 'off',
'no-prototype-builtins': 'off',
'no-useless-catch': 'off',
});
return disabled;
})(),
},
// Relax rules specifically for tests to mirror prior setup
{
files: [ 'test/**/*.ts' ],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-unused-vars': 'off',
},
},
];


27 changes: 15 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
{
"name": "koa-session",
"name": "@bp/koa-session",
"description": "Koa cookie session middleware with external store support",
"repository": {
"type": "git",
"url": "[email protected]:koajs/session.git"
},
"version": "7.0.2",
"version": "8.0.0",
"keywords": [
"koa",
"middleware",
"session",
"cookie"
],
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.1",
"@arethetypeswrong/cli": "^0.18.2",
"@eggjs/bin": "7",
"@eggjs/supertest": "8",
"@eggjs/tsconfig": "1",
"@types/crc": "^3.8.3",
"@types/koa": "^2.15.0",
"@eggjs/tsconfig": "3",
"@types/koa": "^3.0.0",
"@types/mocha": "10",
"@types/node": "22",
"eslint": "8",
"@types/node": "24",
"eslint": "9",
"eslint-config-egg": "14",
"koa": "2",
"koa": "3",
"mm": "4",
"rimraf": "6",
"snap-shot-it": "^7.9.10",
"tshy": "3",
"tshy-after": "1",
"typescript": "5"
"typescript": "5",
"typescript-eslint": "^8.46.1"
},
"license": "MIT",
"dependencies": {
"crc": "^3.8.0",
"crc": "^4.3.2",
"is-type-of": "^2.2.0",
"zod": "^3.24.1"
"zod": "^4.1.12"
},
"engines": {
"node": ">= 18.19.0"
Expand Down Expand Up @@ -70,6 +70,9 @@
},
"./package.json": "./package.json"
},
"publishConfig": {
"registry": "https://code.tks.eu/api/v4/projects/173/packages/npm/"
},
"files": [
"dist",
"src"
Expand Down
52 changes: 32 additions & 20 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export class ContextSession {
prevHash?: number;

/**
* context session constructor
* Context session constructor.
* @param {any} ctx Koa context instance for the current request lifecycle
* @param {SessionOptions} opts Resolved session options applied to this context
*/
constructor(ctx: any, opts: SessionOptions) {
this.ctx = ctx;
Expand All @@ -28,8 +30,8 @@ export class ContextSession {
}

/**
* internal logic of `ctx.session`
* @return {Session} session object
* Internal logic of `ctx.session` getter.
* @return {Session|null} Session object when present, otherwise null
*/
get(): Session | null {
// already retrieved
Expand All @@ -43,8 +45,8 @@ export class ContextSession {
}

/**
* internal logic of `ctx.session=`
* @param {Object} val session object
* Internal logic of `ctx.session=` setter.
* @param {Record<string, unknown>|null} val Session data object to set, or null to clear session
*/
set(val: Record<string, unknown> | null) {
if (val === null) {
Expand All @@ -60,10 +62,9 @@ export class ContextSession {
}

/**
* init session from external store
* will be called in the front of session middleware
*
* Initialize session from external store; invoked at middleware entry.
* @public
* @return {Promise<void>}
*/

async initFromExternal() {
Expand Down Expand Up @@ -101,8 +102,9 @@ export class ContextSession {
}

/**
* init session from cookie
* Initialize session from cookie.
* @private
* @return {void}
*/
initFromCookie() {
debug('init from cookie');
Expand Down Expand Up @@ -153,10 +155,11 @@ export class ContextSession {
}

/**
* verify session(expired or custom verification)
* @param {Object} sessionData session data
* @param {Object} [key] session externalKey(optional)
* Verify session payload (expiry or custom validation).
* @param {Record<string, unknown>} sessionData Parsed session payload from store or cookie
* @param {string} [key] Optional external session key when using a store
* @private
* @return {boolean} True if session is valid, false otherwise
*/
protected valid(sessionData: Record<string, unknown>, key?: string) {
const ctx = this.ctx;
Expand All @@ -182,9 +185,11 @@ export class ContextSession {
}

/**
* @param {String} event event name
* @param {Object} data event data
* Emit session lifecycle events on the Koa app.
* @param {string} event Event name (e.g. "expired", "invalid", "missed")
* @param {unknown} data Arbitrary event payload
* @private
* @return {void}
*/
emit(event: string, data: unknown) {
setImmediate(() => {
Expand All @@ -193,10 +198,10 @@ export class ContextSession {
}

/**
* create a new session and attach to ctx.sess
*
* @param {Object} [sessionData] session data
* @param {String} [externalKey] session external key
* Create a new session and attach to the context.
* @param {Record<string, unknown>} [sessionData] Optional session data to initialize with
* @param {string} [externalKey] Optional external session key (for store-backed sessions)
* @return {void}
*/
protected create(sessionData?: Record<string, unknown>, externalKey?: string) {
debug('create session with data: %j, externalKey: %s', sessionData, externalKey);
Expand All @@ -208,6 +213,10 @@ export class ContextSession {

/**
* Commit the session changes or removal.
* @param {{save?: boolean, regenerate?: boolean}} [root0] Options for commit behavior
* @param {boolean} [root0.save] Force save the session regardless of change detection
* @param {boolean} [root0.regenerate] Regenerate external key (store-backed) before saving
* @return {Promise<void>}
*/
async commit({ save = false, regenerate = false } = {}) {
const session = this.session;
Expand Down Expand Up @@ -283,8 +292,9 @@ export class ContextSession {
}

/**
* remove session
* Remove the current session (cookie and external store if applicable).
* @private
* @return {Promise<void>}
*/
async remove() {
// Override the default options so that we can properly expire the session cookies
Expand All @@ -304,8 +314,10 @@ export class ContextSession {
}

/**
* save session
* Persist session to cookie or external store.
* @param {boolean} changed Whether the session content changed since last save
* @private
* @return {Promise<void>}
*/
async save(changed: boolean) {
const opts = this.opts;
Expand Down
Loading