🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
Book a DemoInstallSign in
Socket

connect-redis

Package Overview
Dependencies
Maintainers
3
Versions
78
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

connect-redis - npm Package Compare versions

Comparing version

to
9.0.0

biome.json

27

dist/connect-redis.d.ts

@@ -0,1 +1,3 @@

import { RedisClientType } from 'redis';
import { RedisClusterType } from 'redis';
import { SessionData } from 'express-session';

@@ -6,23 +8,11 @@ import { Store } from 'express-session';

declare interface NormalizedRedisClient {
get(key: string): Promise<string | null>;
set(key: string, value: string, ttl?: number): Promise<string | null>;
expire(key: string, ttl: number): Promise<number | boolean>;
scanIterator(match: string, count: number): AsyncIterable<string>;
del(key: string[]): Promise<number>;
mget(key: string[]): Promise<(string | null)[]>;
}
export declare class RedisStore extends Store {
client: NormalizedRedisClient;
client: RedisClientType | RedisClusterType;
prefix: string;
scanCount: number;
serializer: Serializer;
ttl: number | {
(sess: SessionData): number;
};
ttl: number | ((sess: SessionData) => number);
disableTTL: boolean;
disableTouch: boolean;
constructor(opts: RedisStoreOptions);
private normalizeClient;
get(sid: string, cb?: Callback): Promise<any>;

@@ -36,4 +26,5 @@ set(sid: string, sess: SessionData, cb?: Callback): Promise<any>;

all(cb?: Callback): Promise<any>;
private _getTTL;
private _getAllKeys;
private getTTL;
private getAllKeys;
private scanIterator;
}

@@ -46,5 +37,3 @@

serializer?: Serializer;
ttl?: number | {
(sess: SessionData): number;
};
ttl?: number | ((sess: SessionData) => number);
disableTTL?: boolean;

@@ -51,0 +40,0 @@ disableTouch?: boolean;

@@ -23,46 +23,4 @@ import { Store } from "express-session";

this.disableTouch = opts.disableTouch || false;
this.client = this.normalizeClient(opts.client);
this.client = opts.client;
}
// Create a redis and ioredis compatible client
normalizeClient(client) {
let isRedis = "scanIterator" in client || "masters" in client;
let isRedisCluster = "masters" in client;
return {
get: (key) => client.get(key),
set: (key, val, ttl) => {
if (ttl) {
return isRedis ? client.set(key, val, { EX: ttl }) : client.set(key, val, "EX", ttl);
}
return client.set(key, val);
},
del: (key) => client.del(key),
expire: (key, ttl) => client.expire(key, ttl),
mget: (keys) => isRedis ? client.mGet(keys) : client.mget(keys),
scanIterator: (match, count) => {
if (isRedisCluster) {
return async function* () {
for (const master of client.masters) {
const nodeClient = await client.nodeClient(master);
for await (const key of nodeClient.scanIterator({
COUNT: count,
MATCH: match
})) {
yield key;
}
}
}();
}
if (isRedis) return client.scanIterator({ MATCH: match, COUNT: count });
return async function* () {
let [c, xs] = await client.scan("0", "MATCH", match, "COUNT", count);
for (let key of xs) yield key;
while (c !== "0") {
;
[c, xs] = await client.scan(c, "MATCH", match, "COUNT", count);
for (let key of xs) yield key;
}
}();
}
};
}
async get(sid, cb) {

@@ -80,3 +38,3 @@ let key = this.prefix + sid;

let key = this.prefix + sid;
let ttl = this._getTTL(sess);
let ttl = this.getTTL(sess);
try {

@@ -86,7 +44,9 @@ if (ttl > 0) {

if (this.disableTTL) await this.client.set(key, val);
else await this.client.set(key, val, ttl);
else
await this.client.set(key, val, {
expiration: { type: "EX", value: ttl }
});
return optionalCb(null, null, cb);
} else {
return this.destroy(sid, cb);
}
return this.destroy(sid, cb);
} catch (err) {

@@ -100,3 +60,3 @@ return optionalCb(err, null, cb);

try {
await this.client.expire(key, this._getTTL(sess));
await this.client.expire(key, this.getTTL(sess));
return optionalCb(null, null, cb);

@@ -118,3 +78,3 @@ } catch (err) {

try {
let keys = await this._getAllKeys();
let keys = await this.getAllKeys();
if (!keys.length) return optionalCb(null, null, cb);

@@ -129,3 +89,3 @@ await this.client.del(keys);

try {
let keys = await this._getAllKeys();
let keys = await this.getAllKeys();
return optionalCb(null, keys.length, cb);

@@ -139,3 +99,3 @@ } catch (err) {

try {
let keys = await this._getAllKeys();
let keys = await this.getAllKeys();
return optionalCb(

@@ -153,5 +113,5 @@ null,

try {
let keys = await this._getAllKeys();
let keys = await this.getAllKeys();
if (keys.length === 0) return optionalCb(null, [], cb);
let data = await this.client.mget(keys);
let data = await this.client.mGet(keys);
let results = data.reduce((acc, raw, idx) => {

@@ -169,3 +129,3 @@ if (!raw) return acc;

}
_getTTL(sess) {
getTTL(sess) {
if (typeof this.ttl === "function") {

@@ -175,3 +135,3 @@ return this.ttl(sess);

let ttl;
if (sess && sess.cookie && sess.cookie.expires) {
if (sess?.cookie?.expires) {
let ms = Number(new Date(sess.cookie.expires)) - Date.now();

@@ -184,10 +144,29 @@ ttl = Math.ceil(ms / 1e3);

}
async _getAllKeys() {
async getAllKeys() {
let pattern = this.prefix + "*";
let keys = [];
for await (let key of this.client.scanIterator(pattern, this.scanCount)) {
keys.push(key);
let set = /* @__PURE__ */ new Set();
for await (let keys of this.scanIterator(pattern, this.scanCount)) {
for (let key of keys) {
set.add(key);
}
}
return keys;
return set.size > 0 ? Array.from(set) : [];
}
scanIterator(match, count) {
let client = this.client;
if (!("masters" in client)) {
return client.scanIterator({ MATCH: match, COUNT: count });
}
return async function* () {
for (let master of client.masters) {
let c = await client.nodeClient(master);
for await (let keys of c.scanIterator({
COUNT: count,
MATCH: match
})) {
yield keys;
}
}
}();
}
}

@@ -194,0 +173,0 @@ export {

import {Cookie} from "express-session"
import {Redis} from "ioredis"
import {createClient} from "redis"

@@ -25,3 +24,3 @@ import {expect, test} from "vitest"

expect(store.disableTTL).toBe(false)
await client.disconnect()
client.destroy()
})

@@ -34,12 +33,5 @@

await lifecycleTest(store, client)
await client.disconnect()
client.destroy()
})
test("ioredis", async () => {
let client = new Redis(`redis://localhost:${redisSrv.port}`)
let store = new RedisStore({client})
await lifecycleTest(store, client)
client.disconnect()
})
test("teardown", redisSrv.disconnect)

@@ -46,0 +38,0 @@

@@ -1,2 +0,3 @@

import {SessionData, Store} from "express-session"
import {type SessionData, Store} from "express-session"
import type {RedisClientType, RedisClusterType} from "redis"

@@ -11,11 +12,2 @@ type Callback = (_err?: unknown, _data?: any) => any

interface NormalizedRedisClient {
get(key: string): Promise<string | null>
set(key: string, value: string, ttl?: number): Promise<string | null>
expire(key: string, ttl: number): Promise<number | boolean>
scanIterator(match: string, count: number): AsyncIterable<string>
del(key: string[]): Promise<number>
mget(key: string[]): Promise<(string | null)[]>
}
interface Serializer {

@@ -31,3 +23,3 @@ parse(s: string): SessionData | Promise<SessionData>

serializer?: Serializer
ttl?: number | {(sess: SessionData): number}
ttl?: number | ((sess: SessionData) => number)
disableTTL?: boolean

@@ -38,7 +30,7 @@ disableTouch?: boolean

export class RedisStore extends Store {
client: NormalizedRedisClient
client: RedisClientType | RedisClusterType
prefix: string
scanCount: number
serializer: Serializer
ttl: number | {(sess: SessionData): number}
ttl: number | ((sess: SessionData) => number)
disableTTL: boolean

@@ -55,55 +47,5 @@ disableTouch: boolean

this.disableTouch = opts.disableTouch || false
this.client = this.normalizeClient(opts.client)
this.client = opts.client
}
// Create a redis and ioredis compatible client
private normalizeClient(client: any): NormalizedRedisClient {
let isRedis = "scanIterator" in client || "masters" in client
let isRedisCluster = "masters" in client
return {
get: (key) => client.get(key),
set: (key, val, ttl) => {
if (ttl) {
return isRedis
? client.set(key, val, {EX: ttl})
: client.set(key, val, "EX", ttl)
}
return client.set(key, val)
},
del: (key) => client.del(key),
expire: (key, ttl) => client.expire(key, ttl),
mget: (keys) => (isRedis ? client.mGet(keys) : client.mget(keys)),
scanIterator: (match, count) => {
// node-redis createCluster impl.
if (isRedisCluster) {
return (async function* () {
for (const master of client.masters) {
const nodeClient = await client.nodeClient(master)
for await (const key of nodeClient.scanIterator({
COUNT: count,
MATCH: match,
})) {
yield key
}
}
})()
}
if (isRedis) return client.scanIterator({MATCH: match, COUNT: count})
// ioredis impl.
return (async function* () {
let [c, xs] = await client.scan("0", "MATCH", match, "COUNT", count)
for (let key of xs) yield key
while (c !== "0") {
;[c, xs] = await client.scan(c, "MATCH", match, "COUNT", count)
for (let key of xs) yield key
}
})()
},
}
}
async get(sid: string, cb?: Callback) {

@@ -122,3 +64,3 @@ let key = this.prefix + sid

let key = this.prefix + sid
let ttl = this._getTTL(sess)
let ttl = this.getTTL(sess)
try {

@@ -128,7 +70,9 @@ if (ttl > 0) {

if (this.disableTTL) await this.client.set(key, val)
else await this.client.set(key, val, ttl)
else
await this.client.set(key, val, {
expiration: {type: "EX", value: ttl},
})
return optionalCb(null, null, cb)
} else {
return this.destroy(sid, cb)
}
return this.destroy(sid, cb)
} catch (err) {

@@ -143,3 +87,3 @@ return optionalCb(err, null, cb)

try {
await this.client.expire(key, this._getTTL(sess))
await this.client.expire(key, this.getTTL(sess))
return optionalCb(null, null, cb)

@@ -163,3 +107,3 @@ } catch (err) {

try {
let keys = await this._getAllKeys()
let keys = await this.getAllKeys()
if (!keys.length) return optionalCb(null, null, cb)

@@ -175,3 +119,3 @@ await this.client.del(keys)

try {
let keys = await this._getAllKeys()
let keys = await this.getAllKeys()
return optionalCb(null, keys.length, cb)

@@ -186,3 +130,3 @@ } catch (err) {

try {
let keys = await this._getAllKeys()
let keys = await this.getAllKeys()
return optionalCb(

@@ -201,6 +145,6 @@ null,

try {
let keys = await this._getAllKeys()
let keys = await this.getAllKeys()
if (keys.length === 0) return optionalCb(null, [], cb)
let data = await this.client.mget(keys)
let data = await this.client.mGet(keys)
let results = data.reduce((acc, raw, idx) => {

@@ -219,3 +163,3 @@ if (!raw) return acc

private _getTTL(sess: SessionData) {
private getTTL(sess: SessionData) {
if (typeof this.ttl === "function") {

@@ -226,3 +170,3 @@ return this.ttl(sess)

let ttl
if (sess && sess.cookie && sess.cookie.expires) {
if (sess?.cookie?.expires) {
let ms = Number(new Date(sess.cookie.expires)) - Date.now()

@@ -236,10 +180,32 @@ ttl = Math.ceil(ms / 1000)

private async _getAllKeys() {
private async getAllKeys() {
let pattern = this.prefix + "*"
let keys = []
for await (let key of this.client.scanIterator(pattern, this.scanCount)) {
keys.push(key)
let set = new Set<string>()
for await (let keys of this.scanIterator(pattern, this.scanCount)) {
for (let key of keys) {
set.add(key)
}
}
return keys
return set.size > 0 ? Array.from(set) : []
}
private scanIterator(match: string, count: number) {
let client = this.client
if (!("masters" in client)) {
return client.scanIterator({MATCH: match, COUNT: count})
}
return (async function* () {
for (let master of client.masters) {
let c = await client.nodeClient(master)
for await (let keys of c.scanIterator({
COUNT: count,
MATCH: match,
})) {
yield keys
}
}
})()
}
}
{
"name": "connect-redis",
"description": "Redis session store for Connect",
"version": "8.1.0",
"version": "9.0.0",
"author": "TJ Holowaychuk <tj@vision-media.ca>",

@@ -31,22 +31,15 @@ "contributors": [

"devDependencies": {
"@eslint/js": "^9.24.0",
"@types/express-session": "^1.18.1",
"@types/node": "^22.14.1",
"@vitest/coverage-v8": "^3.1.1",
"eslint": "^9.24.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"@biomejs/biome": "^1.9.4",
"@types/express-session": "^1.18.2",
"@types/node": "^22.15.31",
"@vitest/coverage-v8": "^3.2.3",
"express-session": "^1.18.1",
"ioredis": "^5.6.1",
"prettier": "^3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"redis": "^4.7.0",
"ts-node": "^10.9.2",
"typescript": "^5.8.3",
"typescript-eslint": "^8.30.1",
"vite": "^6.3.1",
"vite-plugin-dts": "^4.5.3",
"vitest": "^3.1.1"
"vite": "^6.3.5",
"vite-plugin-dts": "^4.5.4",
"vitest": "^3.2.3"
},
"peerDependencies": {
"redis": ">=5",
"express-session": ">=1"

@@ -68,7 +61,6 @@ },

"build": "vite build",
"test": "vitest run --silent --coverage",
"lint": "tsc --noemit && eslint --max-warnings 0 testdata *.ts",
"fmt": "prettier --write .",
"fmt-check": "prettier --check ."
"test": "vitest run --coverage",
"lint": "tsc --noemit && biome check .",
"fix": "biome check --write ."
}
}

@@ -7,9 +7,4 @@ [![build](https://github.com/tj/connect-redis/actions/workflows/build.yml/badge.svg)](https://github.com/tj/connect-redis/actions/workflows/build.yml) [![npm](https://img.shields.io/npm/v/connect-redis.svg)](https://npmjs.com/package/connect-redis) ![Downloads](https://img.shields.io/npm/dm/connect-redis.svg)

**connect-redis** requires `express-session` to be installed and one of the following compatible Redis clients:
**connect-redis** requires `express-session` and [`redis`][1]:
- [`redis`][1]
- [`ioredis`][2]
Install with `redis`:
```sh

@@ -19,27 +14,5 @@ npm install redis connect-redis express-session

Install with `ioredis`:
```sh
npm install ioredis connect-redis express-session
```
## Importing
**connect-redis** supports both CommonJS (`require`) and ESM (`import`) modules.
Import using ESM/Typescript:
```js
import {RedisStore} from "connect-redis"
```
Require using CommonJS:
```js
const {RedisStore} = require("connect-redis")
```
## API
Full setup using [`redis`][1] package:
Full setup:

@@ -78,3 +51,3 @@ ```js

An instance of [`redis`][1] or [`ioredis`][2].
An instance of [`redis`][1]

@@ -142,2 +115,1 @@ ##### prefix

[1]: https://github.com/NodeRedis/node-redis
[2]: https://github.com/luin/ioredis

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet