diff --git a/.autod.conf.js b/.autod.conf.js deleted file mode 100644 index 0f01bb9..0000000 --- a/.autod.conf.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -module.exports = { - write: true, - prefix: '^', - test: [ - 'test', - 'benchmark', - ], - devdep: [ - 'egg', - 'egg-ci', - 'egg-bin', - 'autod', - 'eslint', - 'eslint-config-egg', - 'supertest', - 'webstorm-disable-index', - ], - exclude: [ - './test/fixtures', - './docs', - './coverage', - ], -}; diff --git a/.eslintignore b/.eslintignore index a24e501..618ef2b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ test/fixtures coverage +__snapshots__ diff --git a/.eslintrc b/.eslintrc index c799fe5..9bcdb46 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,6 @@ { - "extends": "eslint-config-egg" + "extends": [ + "eslint-config-egg/typescript", + "eslint-config-egg/lib/rules/enforce-node-prefix" + ] } diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 48f9944..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,24 +0,0 @@ - - -##### Checklist - - -- [ ] `npm test` passes -- [ ] tests and/or benchmarks are included -- [ ] documentation is changed or added -- [ ] commit message follows commit guidelines - -##### Affected core subsystem(s) - - - -##### Description of change - diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..25bb2a4 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,16 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + Job: + name: Node.js + uses: node-modules/github-actions/.github/workflows/node-test-mysql.yml@master + with: + version: '18.19.0, 18, 20, 22' + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml new file mode 100644 index 0000000..bac3fac --- /dev/null +++ b/.github/workflows/pkg.pr.new.yml @@ -0,0 +1,23 @@ +name: Publish Any Commit +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - run: corepack enable + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install + + - name: Build + run: npm run prepublishOnly --if-present + + - run: npx pkg-pr-new publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a2bf04a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,13 @@ +name: Release + +on: + push: + branches: [ master ] + +jobs: + release: + name: Node.js + uses: eggjs/github-actions/.github/workflows/node-release.yml@master + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} diff --git a/.gitignore b/.gitignore index 1a5bf14..7e41e53 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,12 @@ logs/ npm-debug.log node_modules/ coverage/ -.idea/ -run/ +test/fixtures/**/run .DS_Store -*.swp - +.tshy* +.eslintcache +dist +package-lock.json +.package-lock.json +test/fixtures/**/*.d.ts +run/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3b0ca4e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -sudo: false -language: node_js -node_js: - - '8' - - '10' -install: - - npm i npminstall && npminstall -script: - - npm run ci -after_script: - - npminstall codecov && codecov -services: - - redis-server diff --git a/History.md b/CHANGELOG.md similarity index 100% rename from History.md rename to CHANGELOG.md diff --git a/README.md b/README.md index f7e86eb..607822d 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,45 @@ -# egg-session-redis +# @eggjs/session-redis [![NPM version][npm-image]][npm-url] -[![build status][travis-image]][travis-url] +[![Node.js CI](https://github.com/eggjs/session-redis/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/session-redis/actions/workflows/nodejs.yml) [![Test coverage][codecov-image]][codecov-url] -[![David deps][david-image]][david-url] [![Known Vulnerabilities][snyk-image]][snyk-url] [![npm download][download-image]][download-url] - -[npm-image]: https://img.shields.io/npm/v/egg-session-redis.svg?style=flat-square -[npm-url]: https://npmjs.org/package/egg-session-redis -[travis-image]: https://img.shields.io/travis/eggjs/egg-session-redis.svg?style=flat-square -[travis-url]: https://travis-ci.org/eggjs/egg-session-redis -[codecov-image]: https://img.shields.io/codecov/c/github/eggjs/egg-session-redis.svg?style=flat-square -[codecov-url]: https://codecov.io/github/eggjs/egg-session-redis?branch=master -[david-image]: https://img.shields.io/david/eggjs/egg-session-redis.svg?style=flat-square -[david-url]: https://david-dm.org/eggjs/egg-session-redis -[snyk-image]: https://snyk.io/test/npm/egg-session-redis/badge.svg?style=flat-square -[snyk-url]: https://snyk.io/test/npm/egg-session-redis -[download-image]: https://img.shields.io/npm/dm/egg-session-redis.svg?style=flat-square -[download-url]: https://npmjs.org/package/egg-session-redis +[![Node.js Version](https://img.shields.io/node/v/@eggjs/session-redis.svg?style=flat)](https://nodejs.org/en/download/) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) +![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/eggjs/session-redis) + +[npm-image]: https://img.shields.io/npm/v/@eggjs/session-redis.svg?style=flat-square +[npm-url]: https://npmjs.org/package/@eggjs/session-redis +[codecov-image]: https://img.shields.io/codecov/c/github/eggjs/session-redis.svg?style=flat-square +[codecov-url]: https://codecov.io/github/eggjs/session-redis?branch=master +[snyk-image]: https://snyk.io/test/npm/@eggjs/session-redis/badge.svg?style=flat-square +[snyk-url]: https://snyk.io/test/npm/@eggjs/session-redis +[download-image]: https://img.shields.io/npm/dm/@eggjs/session-redis.svg?style=flat-square +[download-url]: https://npmjs.org/package/@eggjs/session-redis A session extension for store session in redis. ## Install - ```bash -$ npm i egg-session-redis egg-redis --save +npm i @eggjs/session-redis @eggjs/redis ``` ## Usage -This module dependent on [egg-redis] plugin, so we must enable both. +This module dependent on [@eggjs/redis](https://github.com/eggjs/redis) plugin, so we must enable both. ```js // {app_root}/config/plugin.js exports.sessionRedis = { enable: true, - package: 'egg-session-redis', + package: '@eggjs/session-redis', }; exports.redis = { enable: true, - package: 'egg-redis', + package: '@eggjs/redis', }; ``` @@ -89,4 +86,8 @@ Please open an issue [here](https://github.com/eggjs/egg/issues). [MIT](LICENSE) -[egg-redis]: https://github.com/eggjs/egg-redis +## Contributors + +[![Contributors](https://contrib.rocks/image?repo=eggjs/session-redis)](https://github.com/eggjs/session-redis/graphs/contributors) + +Made with [contributors-img](https://contrib.rocks). diff --git a/__snapshots__/app.test.ts.js b/__snapshots__/app.test.ts.js new file mode 100644 index 0000000..94f40d5 --- /dev/null +++ b/__snapshots__/app.test.ts.js @@ -0,0 +1,3 @@ +exports['test/app.test.ts single should keep config stable 1'] = { + "name": "" +} diff --git a/app.js b/app.js deleted file mode 100644 index 31a05c7..0000000 --- a/app.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -const assert = require('assert'); - -const ONE_DAY = 1000 * 60 * 60 * 24; - -module.exports = app => { - const name = app.config.sessionRedis.name; - const redis = name ? app.redis.get(name) : app.redis; - assert(redis, `redis instance [${name}] not exists`); - - app.sessionStore = { - async get(key) { - const res = await redis.get(key); - if (!res) return null; - return JSON.parse(res); - }, - - async set(key, value, maxAge) { - maxAge = typeof maxAge === 'number' ? maxAge : ONE_DAY; - value = JSON.stringify(value); - await redis.set(key, value, 'PX', maxAge); - }, - - async destroy(key) { - await redis.del(key); - }, - }; -}; diff --git a/config/config.default.js b/config/config.default.js deleted file mode 100644 index 1f2ac6c..0000000 --- a/config/config.default.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/** - * session-redis default config - * @member Config#sessionRedis - * @property {String} name - redis instance name - */ -exports.sessionRedis = { - name: '', // if name present, use `app.redis[name]` for session store -}; diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index 868f7a2..0000000 --- a/index.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -declare module 'egg' { - export interface EggAppConfig { - sessionRedis: { - name: string - } - } - - export interface Application { - /** - * @member Application#sessionStore - * @property {Function} get - get redis session store - * @property {Function} set - set the redis session store - * @property {Function} destroy - destroy of redis session store - * @example - * ```js - * this.app.sessionStore.set('SESSION_KEY', { a: 1 }, 6000); - * this.app.sessionStore.get('SESSION_KEY'); - * this.app.sessionStore.destroy('SESSION_KEY'); - * ``` - */ - sessionStore: { - get: (key: string) => Promise; - set: (key: string, value: any, maxAge?: number | string) => Promise; - destroy: (key: string) => Promise; - }; - } -} diff --git a/package.json b/package.json index a17cc59..3b6a9cf 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,21 @@ { - "name": "egg-session-redis", + "name": "@eggjs/session-redis", "version": "2.1.0", - "description": "redis store for egg session", + "publishConfig": { + "access": "public" + }, + "description": "redis store plugin for egg session", "eggPlugin": { "name": "sessionRedis", "dependencies": [ - "redis" - ] + "redis", + "session" + ], + "exports": { + "import": "./dist/esm", + "require": "./dist/commonjs", + "typescript": "./src" + } }, "keywords": [ "egg", @@ -15,56 +24,75 @@ "session", "redis" ], - "dependencies": {}, - "devDependencies": { - "autod": "^3.0.1", - "egg": "^2.9.1", - "egg-bin": "^4.7.0", - "egg-ci": "^1.8.0", - "egg-mock": "^3.17.2", - "egg-redis": "^2.0.0", - "eslint": "^4.19.1", - "eslint-config-egg": "^7.0.0", - "mz-modules": "^2.1.0", - "runscript": "^1.3.0", - "supertest": "^3.1.0", - "typescript": "^3.0.3", - "webstorm-disable-index": "^1.2.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "scripts": { - "test": "npm run lint -- --fix && npm run test-local", - "test-local": "egg-bin test", - "cov": "egg-bin cov", - "lint": "eslint .", - "ci": "npm run lint && npm run cov", - "autod": "autod" - }, - "types": "index.d.ts", - "files": [ - "index.js", - "index.d.ts", - "app.js", - "agent.js", - "config", - "app", - "lib" - ], - "ci": { - "version": "8, 10", - "services": "redis-server", - "type": "travis" - }, "repository": { "type": "git", - "url": "git+https://github.com/eggjs/egg-session-redis.git" + "url": "git+https://github.com/eggjs/session-redis.git" }, "bugs": { "url": "https://github.com/eggjs/egg/issues" }, - "homepage": "https://github.com/eggjs/egg-session-redis#readme", + "homepage": "https://github.com/eggjs/session-redis#readme", "author": "dead-horse", - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "dependencies": { + "@eggjs/core": "^6.3.1" + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.17.3", + "@eggjs/bin": "7", + "@eggjs/mock": "^6.0.5", + "@eggjs/redis": "^3.0.0", + "@eggjs/supertest": "^8.2.0", + "@eggjs/tsconfig": "1", + "@types/mocha": "10", + "@types/node": "22", + "egg": "^4.0.3", + "eslint": "8", + "eslint-config-egg": "14", + "rimraf": "6", + "snap-shot-it": "^7.9.10", + "tshy": "3", + "tshy-after": "1", + "typescript": "5" + }, + "scripts": { + "lint": "eslint --cache src test --ext .ts", + "pretest": "npm run clean && npm run lint -- --fix", + "test": "egg-bin test", + "preci": "npm run clean && npm run lint", + "ci": "egg-bin cov", + "postci": "npm run prepublishOnly && npm run clean", + "clean": "rimraf dist", + "prepublishOnly": "tshy && tshy-after && attw --pack" + }, + "type": "module", + "tshy": { + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + } + }, + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ], + "types": "./dist/commonjs/index.d.ts", + "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js" } diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..9b03fe1 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,46 @@ +import type { EggCore, ILifecycleBoot } from '@eggjs/core'; + +const ONE_DAY = 1000 * 60 * 60 * 24; + +export default class AppBoot implements ILifecycleBoot { + constructor(private readonly app: EggCore) {} + + async didLoad() { + const { app } = this; + const name = app.config.sessionRedis.name; + const redis = name ? app.redis.getSingletonInstance(name) : app.redis; + if (!redis) { + throw new TypeError(`redis instance [${name}] not exists`); + } + + /** + * @member Application#sessionStore + * @property {Function} get - get redis session store + * @property {Function} set - set the redis session store + * @property {Function} destroy - destroy of redis session store + * @example + * ```js + * this.app.sessionStore.set('SESSION_KEY', { a: 1 }, 6000); + * this.app.sessionStore.get('SESSION_KEY'); + * this.app.sessionStore.destroy('SESSION_KEY'); + * ``` + */ + app.sessionStore = { + async get(key) { + const res = await redis.get(key); + if (!res) return null; + return JSON.parse(res); + }, + + async set(key, value, maxAge?) { + maxAge = typeof maxAge === 'number' ? maxAge : ONE_DAY; + value = JSON.stringify(value); + await redis.set(key, value, 'PX', maxAge); + }, + + async destroy(key) { + await redis.del(key); + }, + }; + } +} diff --git a/src/config/config.default.ts b/src/config/config.default.ts new file mode 100644 index 0000000..2490005 --- /dev/null +++ b/src/config/config.default.ts @@ -0,0 +1,21 @@ +/** + * session-redis default config + * @member Config#sessionRedis + * @property {String} name - redis instance name + */ +export interface SessionRedisConfig { + /** + * redis instance name + * + * Default to `''`, use `app.redis` for session store + * + * If name present, use `app.redis.getSingletonInstance(name)` for session store + */ + name: string; +} + +export default { + sessionRedis: { + name: '', + } as SessionRedisConfig, +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ce5fb25 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +import './types.js'; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..ac81837 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,8 @@ +import type { SessionRedisConfig } from './config/config.default.js'; + +declare module '@eggjs/core' { + // add EggAppConfig overrides types + interface EggAppConfig { + sessionRedis: SessionRedisConfig; + } +} diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts new file mode 100644 index 0000000..5407cb1 --- /dev/null +++ b/src/typings/index.d.ts @@ -0,0 +1,5 @@ +// make sure to import egg typings and let typescript know about it +// @see https://github.com/whxaxes/blog/issues/11 +// and https://www.typescriptlang.org/docs/handbook/declaration-merging.html +import 'egg'; +import '@eggjs/redis'; diff --git a/test/app.test.js b/test/app.test.ts similarity index 66% rename from test/app.test.js rename to test/app.test.ts index 7adde83..a5c13d0 100644 --- a/test/app.test.js +++ b/test/app.test.ts @@ -1,27 +1,18 @@ -'use strict'; - -const sleep = require('mz-modules/sleep'); -const request = require('supertest'); -const runscript = require('runscript'); -const assert = require('assert'); -const mm = require('egg-mock'); -const path = require('path'); -const tsBaseDir = path.join(__dirname, './fixtures/ts'); - -describe('app.test.js', () => { - // add typescript tsc - before(async () => { - await runscript(`tsc -p ${tsBaseDir}/tsconfig.json`, { cwd: tsBaseDir }); - }); +import { scheduler } from 'node:timers/promises'; +import { TestAgent } from '@eggjs/supertest'; +import assert from 'node:assert'; +import { mm, MockApplication } from '@eggjs/mock'; +import snapshot from 'snap-shot-it'; +describe('test/app.test.ts', () => { [ 'single', 'multi', 'ts', ].forEach(name => { describe(name, () => { - let app; - let agent; + let app: MockApplication; + let agent: TestAgent; before(() => { app = mm.app({ baseDir: name, @@ -29,22 +20,28 @@ describe('app.test.js', () => { return app.ready(); }); beforeEach(() => { - agent = request.agent(app.callback()); + agent = new TestAgent(app.callback()); }); afterEach(mm.restore); after(() => app.close()); - it('should get empty session and do not set cookie when session not populated', async function() { + if (name === 'single') { + it('should keep config stable', () => { + snapshot(app.config.sessionRedis); + }); + } + + it('should get empty session and do not set cookie when session not populated', async () => { await agent .get('/get') .expect(200) .expect({}) .expect(res => { - assert(!res.header['set-cookie'].join('').match(/EGG_SESS/)); + assert(!res.get('Set-Cookie')!.join('').match(/EGG_SESS/)); }); }); - it('should ctx.session= change the session', async function() { + it('should ctx.session= change the session', async () => { await agent .get('/set?foo=bar') .expect(200) @@ -52,7 +49,7 @@ describe('app.test.js', () => { .expect('set-cookie', /EGG_SESS=.*?;/); }); - it('should ctx.session.key= change the session', async function() { + it('should ctx.session.key= change the session', async () => { await agent .get('/set?key=foo&foo=bar') .expect(200) @@ -66,7 +63,7 @@ describe('app.test.js', () => { .expect('set-cookie', /EGG_SESS=.*?;/); }); - it('should ctx.session=null remove the session', async function() { + it('should ctx.session=null remove the session', async () => { await agent .get('/set?key=foo&foo=bar') .expect(200) @@ -84,33 +81,33 @@ describe('app.test.js', () => { .expect({}); }); - it('should ctx.session.maxAge= change maxAge', async function() { + it('should ctx.session.maxAge= change maxAge', async () => { await agent .get('/set?key=foo&foo=bar') .expect(200) .expect({ key: 'foo', foo: 'bar' }) .expect('set-cookie', /EGG_SESS=.*?;/); - let cookie; + let cookie = ''; await agent .get('/maxAge?maxAge=100') .expect(200) .expect({ key: 'foo', foo: 'bar' }) .expect(res => { - cookie = res.headers['set-cookie'].join(';'); + cookie = res.get('Set-Cookie')!.join(';'); assert(cookie.match(/EGG_SESS=.*?;/)); assert(cookie.match(/expires=/)); }); - await sleep(200); + await scheduler.wait(200); await agent .get('/get') .expect(200) .expect({}); - await request(app.callback()) + await app.httpRequest() .get('/get') .set('cookie', cookie) .expect(200) diff --git a/test/fixtures/multi/config/plugin.js b/test/fixtures/multi/config/plugin.js index 7a9ce28..c0057b5 100644 --- a/test/fixtures/multi/config/plugin.js +++ b/test/fixtures/multi/config/plugin.js @@ -1,6 +1,4 @@ -'use strict'; - exports.redis = { enable: true, - package: 'egg-redis', + package: '@eggjs/redis', }; diff --git a/test/fixtures/single/config/plugin.js b/test/fixtures/single/config/plugin.js index 7a9ce28..c0057b5 100644 --- a/test/fixtures/single/config/plugin.js +++ b/test/fixtures/single/config/plugin.js @@ -1,6 +1,4 @@ -'use strict'; - exports.redis = { enable: true, - package: 'egg-redis', + package: '@eggjs/redis', }; diff --git a/test/fixtures/ts/app/controller/home.ts b/test/fixtures/ts/app/controller/home.ts index 76f063e..ae779b2 100644 --- a/test/fixtures/ts/app/controller/home.ts +++ b/test/fixtures/ts/app/controller/home.ts @@ -5,6 +5,10 @@ declare module 'egg' { interface IController { home: HomeController; } + + interface Context { + [key: string | symbol]: any; + } } export default class HomeController extends Controller { diff --git a/test/fixtures/ts/config/plugin.ts b/test/fixtures/ts/config/plugin.ts index ac4fd44..118020a 100644 --- a/test/fixtures/ts/config/plugin.ts +++ b/test/fixtures/ts/config/plugin.ts @@ -3,7 +3,7 @@ import { EggPlugin } from 'egg'; const plugin: EggPlugin = { redis: { enable: true, - package: 'egg-redis', + package: '@eggjs/redis', } }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ff41b73 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +}