This commit is contained in:
2026-01-28 00:55:06 +03:00
parent fc033be6a5
commit 25fc82c14a
1642 changed files with 101122 additions and 0 deletions

26
node_modules/connect-redis/.eslintrc generated vendored Normal file
View File

@ -0,0 +1,26 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"env": {
"node": true,
"es6": true
},
"parserOptions": {
"sourceType": "module",
"project": "./tsconfig.json",
"ecmaVersion": 2020
},
"rules": {
"prefer-const": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-unused-vars": [2, {"argsIgnorePattern": "^_"}]
}
}

30
node_modules/connect-redis/.github/release-drafter.yml generated vendored Normal file
View File

@ -0,0 +1,30 @@
name-template: "v$RESOLVED_VERSION"
tag-template: "v$RESOLVED_VERSION"
template: |
$CHANGES
category-template: "#### $TITLE"
change-template: "* #$NUMBER - $TITLE (@$AUTHOR)"
categories:
- title: "Breaking changes"
label: "breaking"
- title: "Enhancements"
label: "enhancement"
- title: "Bug fixes"
label: "bug"
- title: "Maintenance"
label: "chore"
version-resolver:
major:
labels:
- "breaking"
minor:
labels:
- "enhancement"
patch:
labels:
- "bug"
- "chore"
exclude-labels:
- "skip-changelog"

23
node_modules/connect-redis/.github/workflows/build.yml generated vendored Normal file
View File

@ -0,0 +1,23 @@
name: build
on:
push:
branches:
- master
pull_request:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node: [16, 18, 20]
name: Node v${{ matrix.node }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: sudo apt-get install -y redis-server
- run: npm install
- run: npm run fmt-check
- run: npm run lint
- run: npm test

View File

@ -0,0 +1,13 @@
name: release-draft
on:
workflow_dispatch:
push:
branches:
- master
jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

17
node_modules/connect-redis/.github/workflows/stale.yml generated vendored Normal file
View File

@ -0,0 +1,17 @@
name: stale-issues-prs
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
with:
stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days."
stale-pr-message: "This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days."
close-issue-message: "This issue was closed because it has been stalled for 5 days with no activity."
days-before-stale: 30
days-before-close: 5
stale-issue-label: stale

5
node_modules/connect-redis/.prettierrc generated vendored Normal file
View File

@ -0,0 +1,5 @@
{
"semi": false,
"bracketSpacing": false,
"plugins": ["prettier-plugin-organize-imports"]
}

48
node_modules/connect-redis/dist/cjs/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,48 @@
import { SessionData, Store } from "express-session";
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 {
parse(s: string): SessionData | Promise<SessionData>;
stringify(s: SessionData): string;
}
interface RedisStoreOptions {
client: any;
prefix?: string;
scanCount?: number;
serializer?: Serializer;
ttl?: number | {
(sess: SessionData): number;
};
disableTTL?: boolean;
disableTouch?: boolean;
}
declare class RedisStore extends Store {
client: NormalizedRedisClient;
prefix: string;
scanCount: number;
serializer: Serializer;
ttl: number | {
(sess: SessionData): number;
};
disableTTL: boolean;
disableTouch: boolean;
constructor(opts: RedisStoreOptions);
private normalizeClient;
get(sid: string, cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
set(sid: string, sess: SessionData, cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
touch(sid: string, sess: SessionData, cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
destroy(sid: string, cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
clear(cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
length(cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
ids(cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
all(cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
private _getTTL;
private _getAllKeys;
}
export default RedisStore;

179
node_modules/connect-redis/dist/cjs/index.js generated vendored Normal file
View File

@ -0,0 +1,179 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const express_session_1 = require("express-session");
const noop = (_err, _data) => { };
class RedisStore extends express_session_1.Store {
constructor(opts) {
super();
this.prefix = opts.prefix == null ? "sess:" : opts.prefix;
this.scanCount = opts.scanCount || 100;
this.serializer = opts.serializer || JSON;
this.ttl = opts.ttl || 86400; // One day in seconds.
this.disableTTL = opts.disableTTL || false;
this.disableTouch = opts.disableTouch || false;
this.client = this.normalizeClient(opts.client);
}
// Create a redis and ioredis compatible client
normalizeClient(client) {
let isRedis = "scanIterator" 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 (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, cb = noop) {
let key = this.prefix + sid;
try {
let data = await this.client.get(key);
if (!data)
return cb();
return cb(null, await this.serializer.parse(data));
}
catch (err) {
return cb(err);
}
}
async set(sid, sess, cb = noop) {
let key = this.prefix + sid;
let ttl = this._getTTL(sess);
try {
let val = this.serializer.stringify(sess);
if (ttl > 0) {
if (this.disableTTL)
await this.client.set(key, val);
else
await this.client.set(key, val, ttl);
return cb();
}
else {
return this.destroy(sid, cb);
}
}
catch (err) {
return cb(err);
}
}
async touch(sid, sess, cb = noop) {
let key = this.prefix + sid;
if (this.disableTouch || this.disableTTL)
return cb();
try {
await this.client.expire(key, this._getTTL(sess));
return cb();
}
catch (err) {
return cb(err);
}
}
async destroy(sid, cb = noop) {
let key = this.prefix + sid;
try {
await this.client.del([key]);
return cb();
}
catch (err) {
return cb(err);
}
}
async clear(cb = noop) {
try {
let keys = await this._getAllKeys();
if (!keys.length)
return cb();
await this.client.del(keys);
return cb();
}
catch (err) {
return cb(err);
}
}
async length(cb = noop) {
try {
let keys = await this._getAllKeys();
return cb(null, keys.length);
}
catch (err) {
return cb(err);
}
}
async ids(cb = noop) {
let len = this.prefix.length;
try {
let keys = await this._getAllKeys();
return cb(null, keys.map((k) => k.substring(len)));
}
catch (err) {
return cb(err);
}
}
async all(cb = noop) {
let len = this.prefix.length;
try {
let keys = await this._getAllKeys();
if (keys.length === 0)
return cb(null, []);
let data = await this.client.mget(keys);
let results = data.reduce((acc, raw, idx) => {
if (!raw)
return acc;
let sess = this.serializer.parse(raw);
sess.id = keys[idx].substring(len);
acc.push(sess);
return acc;
}, []);
return cb(null, results);
}
catch (err) {
return cb(err);
}
}
_getTTL(sess) {
if (typeof this.ttl === "function") {
return this.ttl(sess);
}
let ttl;
if (sess && sess.cookie && sess.cookie.expires) {
let ms = Number(new Date(sess.cookie.expires)) - Date.now();
ttl = Math.ceil(ms / 1000);
}
else {
ttl = this.ttl;
}
return ttl;
}
async _getAllKeys() {
let pattern = this.prefix + "*";
let keys = [];
for await (let key of this.client.scanIterator(pattern, this.scanCount)) {
keys.push(key);
}
return keys;
}
}
exports.default = RedisStore;

1
node_modules/connect-redis/dist/cjs/index_test.d.ts generated vendored Normal file
View File

@ -0,0 +1 @@
export {};

124
node_modules/connect-redis/dist/cjs/index_test.js generated vendored Normal file
View File

@ -0,0 +1,124 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const blue_tape_1 = __importDefault(require("blue-tape"));
const express_session_1 = require("express-session");
const ioredis_1 = require("ioredis");
const node_util_1 = require("node:util");
const redis_1 = require("redis");
const _1 = __importDefault(require("./"));
const redisSrv = __importStar(require("./testdata/server"));
(0, blue_tape_1.default)("setup", async () => {
await redisSrv.connect();
});
(0, blue_tape_1.default)("defaults", async (t) => {
let client = (0, redis_1.createClient)({ url: `redis://localhost:${redisSrv.port}` });
await client.connect();
let store = new _1.default({ client });
t.ok(store.client, "stores client");
t.equal(store.prefix, "sess:", "defaults to sess:");
t.equal(store.ttl, 86400, "defaults to one day");
t.equal(store.scanCount, 100, "defaults SCAN count to 100");
t.equal(store.serializer, JSON, "defaults to JSON serialization");
t.equal(store.disableTouch, false, "defaults to having `touch` enabled");
t.equal(store.disableTTL, false, "defaults to having `ttl` enabled");
await client.disconnect();
});
(0, blue_tape_1.default)("redis", async (t) => {
let client = (0, redis_1.createClient)({ url: `redis://localhost:${redisSrv.port}` });
await client.connect();
let store = new _1.default({ client });
await lifecycleTest(store, client, t);
await client.disconnect();
});
(0, blue_tape_1.default)("ioredis", async (t) => {
let client = new ioredis_1.Redis(`redis://localhost:${redisSrv.port}`);
let store = new _1.default({ client });
await lifecycleTest(store, client, t);
client.disconnect();
});
(0, blue_tape_1.default)("teardown", redisSrv.disconnect);
async function lifecycleTest(store, client, t) {
const P = (f) => (0, node_util_1.promisify)(f).bind(store);
let res = await P(store.clear)();
let sess = { foo: "bar" };
await P(store.set)("123", sess);
res = await P(store.get)("123");
t.same(res, sess, "store.get");
let ttl = await client.ttl("sess:123");
t.ok(ttl >= 86399, "check one day ttl");
ttl = 60;
let expires = new Date(Date.now() + ttl * 1000).toISOString();
await P(store.set)("456", { cookie: { expires } });
ttl = await client.ttl("sess:456");
t.ok(ttl <= 60, "check expires ttl");
ttl = 90;
let expires2 = new Date(Date.now() + ttl * 1000).toISOString();
await P(store.touch)("456", { cookie: { expires: expires2 } });
ttl = await client.ttl("sess:456");
t.ok(ttl > 60, "check expires ttl touch");
res = await P(store.length)();
t.equal(res, 2, "stored two keys length");
res = await P(store.ids)();
res.sort();
t.same(res, ["123", "456"], "stored two keys ids");
res = await P(store.all)();
res.sort((a, b) => (a.id > b.id ? 1 : -1));
t.same(res, [
{ id: "123", foo: "bar" },
{ id: "456", cookie: { expires } },
], "stored two keys data");
await P(store.destroy)("456");
res = await P(store.length)();
t.equal(res, 1, "one key remains");
res = await P(store.clear)();
res = await P(store.length)();
t.equal(res, 0, "no keys remain");
let count = 1000;
await load(store, count);
res = await P(store.length)();
t.equal(res, count, "bulk count");
await P(store.clear)();
res = await P(store.length)();
t.equal(res, 0, "bulk clear");
expires = new Date(Date.now() + ttl * 1000).toISOString(); // expires in the future
res = await P(store.set)("789", { cookie: { expires } });
res = await P(store.length)();
t.equal(res, 1, "one key exists (session 789)");
expires = new Date(Date.now() - ttl * 1000).toISOString(); // expires in the past
await P(store.set)("789", { cookie: { expires } });
res = await P(store.length)();
t.equal(res, 0, "no key remains and that includes session 789");
}
async function load(store, count) {
let cookie = new express_session_1.Cookie();
for (let sid = 0; sid < count; sid++) {
cookie.expires = new Date(Date.now() + 1000);
await store.set("s" + sid, { cookie });
}
}

48
node_modules/connect-redis/dist/esm/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,48 @@
import { SessionData, Store } from "express-session";
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 {
parse(s: string): SessionData | Promise<SessionData>;
stringify(s: SessionData): string;
}
interface RedisStoreOptions {
client: any;
prefix?: string;
scanCount?: number;
serializer?: Serializer;
ttl?: number | {
(sess: SessionData): number;
};
disableTTL?: boolean;
disableTouch?: boolean;
}
declare class RedisStore extends Store {
client: NormalizedRedisClient;
prefix: string;
scanCount: number;
serializer: Serializer;
ttl: number | {
(sess: SessionData): number;
};
disableTTL: boolean;
disableTouch: boolean;
constructor(opts: RedisStoreOptions);
private normalizeClient;
get(sid: string, cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
set(sid: string, sess: SessionData, cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
touch(sid: string, sess: SessionData, cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
destroy(sid: string, cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
clear(cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
length(cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
ids(cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
all(cb?: (_err?: unknown, _data?: any) => void): Promise<void>;
private _getTTL;
private _getAllKeys;
}
export default RedisStore;

177
node_modules/connect-redis/dist/esm/index.js generated vendored Normal file
View File

@ -0,0 +1,177 @@
import { Store } from "express-session";
const noop = (_err, _data) => { };
class RedisStore extends Store {
constructor(opts) {
super();
this.prefix = opts.prefix == null ? "sess:" : opts.prefix;
this.scanCount = opts.scanCount || 100;
this.serializer = opts.serializer || JSON;
this.ttl = opts.ttl || 86400; // One day in seconds.
this.disableTTL = opts.disableTTL || false;
this.disableTouch = opts.disableTouch || false;
this.client = this.normalizeClient(opts.client);
}
// Create a redis and ioredis compatible client
normalizeClient(client) {
let isRedis = "scanIterator" 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 (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, cb = noop) {
let key = this.prefix + sid;
try {
let data = await this.client.get(key);
if (!data)
return cb();
return cb(null, await this.serializer.parse(data));
}
catch (err) {
return cb(err);
}
}
async set(sid, sess, cb = noop) {
let key = this.prefix + sid;
let ttl = this._getTTL(sess);
try {
let val = this.serializer.stringify(sess);
if (ttl > 0) {
if (this.disableTTL)
await this.client.set(key, val);
else
await this.client.set(key, val, ttl);
return cb();
}
else {
return this.destroy(sid, cb);
}
}
catch (err) {
return cb(err);
}
}
async touch(sid, sess, cb = noop) {
let key = this.prefix + sid;
if (this.disableTouch || this.disableTTL)
return cb();
try {
await this.client.expire(key, this._getTTL(sess));
return cb();
}
catch (err) {
return cb(err);
}
}
async destroy(sid, cb = noop) {
let key = this.prefix + sid;
try {
await this.client.del([key]);
return cb();
}
catch (err) {
return cb(err);
}
}
async clear(cb = noop) {
try {
let keys = await this._getAllKeys();
if (!keys.length)
return cb();
await this.client.del(keys);
return cb();
}
catch (err) {
return cb(err);
}
}
async length(cb = noop) {
try {
let keys = await this._getAllKeys();
return cb(null, keys.length);
}
catch (err) {
return cb(err);
}
}
async ids(cb = noop) {
let len = this.prefix.length;
try {
let keys = await this._getAllKeys();
return cb(null, keys.map((k) => k.substring(len)));
}
catch (err) {
return cb(err);
}
}
async all(cb = noop) {
let len = this.prefix.length;
try {
let keys = await this._getAllKeys();
if (keys.length === 0)
return cb(null, []);
let data = await this.client.mget(keys);
let results = data.reduce((acc, raw, idx) => {
if (!raw)
return acc;
let sess = this.serializer.parse(raw);
sess.id = keys[idx].substring(len);
acc.push(sess);
return acc;
}, []);
return cb(null, results);
}
catch (err) {
return cb(err);
}
}
_getTTL(sess) {
if (typeof this.ttl === "function") {
return this.ttl(sess);
}
let ttl;
if (sess && sess.cookie && sess.cookie.expires) {
let ms = Number(new Date(sess.cookie.expires)) - Date.now();
ttl = Math.ceil(ms / 1000);
}
else {
ttl = this.ttl;
}
return ttl;
}
async _getAllKeys() {
let pattern = this.prefix + "*";
let keys = [];
for await (let key of this.client.scanIterator(pattern, this.scanCount)) {
keys.push(key);
}
return keys;
}
}
export default RedisStore;

1
node_modules/connect-redis/dist/esm/index_test.d.ts generated vendored Normal file
View File

@ -0,0 +1 @@
export {};

96
node_modules/connect-redis/dist/esm/index_test.js generated vendored Normal file
View File

@ -0,0 +1,96 @@
import test from "blue-tape";
import { Cookie } from "express-session";
import { Redis } from "ioredis";
import { promisify } from "node:util";
import { createClient } from "redis";
import RedisStore from "./";
import * as redisSrv from "./testdata/server";
test("setup", async () => {
await redisSrv.connect();
});
test("defaults", async (t) => {
let client = createClient({ url: `redis://localhost:${redisSrv.port}` });
await client.connect();
let store = new RedisStore({ client });
t.ok(store.client, "stores client");
t.equal(store.prefix, "sess:", "defaults to sess:");
t.equal(store.ttl, 86400, "defaults to one day");
t.equal(store.scanCount, 100, "defaults SCAN count to 100");
t.equal(store.serializer, JSON, "defaults to JSON serialization");
t.equal(store.disableTouch, false, "defaults to having `touch` enabled");
t.equal(store.disableTTL, false, "defaults to having `ttl` enabled");
await client.disconnect();
});
test("redis", async (t) => {
let client = createClient({ url: `redis://localhost:${redisSrv.port}` });
await client.connect();
let store = new RedisStore({ client });
await lifecycleTest(store, client, t);
await client.disconnect();
});
test("ioredis", async (t) => {
let client = new Redis(`redis://localhost:${redisSrv.port}`);
let store = new RedisStore({ client });
await lifecycleTest(store, client, t);
client.disconnect();
});
test("teardown", redisSrv.disconnect);
async function lifecycleTest(store, client, t) {
const P = (f) => promisify(f).bind(store);
let res = await P(store.clear)();
let sess = { foo: "bar" };
await P(store.set)("123", sess);
res = await P(store.get)("123");
t.same(res, sess, "store.get");
let ttl = await client.ttl("sess:123");
t.ok(ttl >= 86399, "check one day ttl");
ttl = 60;
let expires = new Date(Date.now() + ttl * 1000).toISOString();
await P(store.set)("456", { cookie: { expires } });
ttl = await client.ttl("sess:456");
t.ok(ttl <= 60, "check expires ttl");
ttl = 90;
let expires2 = new Date(Date.now() + ttl * 1000).toISOString();
await P(store.touch)("456", { cookie: { expires: expires2 } });
ttl = await client.ttl("sess:456");
t.ok(ttl > 60, "check expires ttl touch");
res = await P(store.length)();
t.equal(res, 2, "stored two keys length");
res = await P(store.ids)();
res.sort();
t.same(res, ["123", "456"], "stored two keys ids");
res = await P(store.all)();
res.sort((a, b) => (a.id > b.id ? 1 : -1));
t.same(res, [
{ id: "123", foo: "bar" },
{ id: "456", cookie: { expires } },
], "stored two keys data");
await P(store.destroy)("456");
res = await P(store.length)();
t.equal(res, 1, "one key remains");
res = await P(store.clear)();
res = await P(store.length)();
t.equal(res, 0, "no keys remain");
let count = 1000;
await load(store, count);
res = await P(store.length)();
t.equal(res, count, "bulk count");
await P(store.clear)();
res = await P(store.length)();
t.equal(res, 0, "bulk clear");
expires = new Date(Date.now() + ttl * 1000).toISOString(); // expires in the future
res = await P(store.set)("789", { cookie: { expires } });
res = await P(store.length)();
t.equal(res, 1, "one key exists (session 789)");
expires = new Date(Date.now() - ttl * 1000).toISOString(); // expires in the past
await P(store.set)("789", { cookie: { expires } });
res = await P(store.length)();
t.equal(res, 0, "no key remains and that includes session 789");
}
async function load(store, count) {
let cookie = new Cookie();
for (let sid = 0; sid < count; sid++) {
cookie.expires = new Date(Date.now() + 1000);
await store.set("s" + sid, { cookie });
}
}

1
node_modules/connect-redis/dist/esm/package.json generated vendored Normal file
View File

@ -0,0 +1 @@
{"type":"module"}

208
node_modules/connect-redis/index.ts generated vendored Normal file
View File

@ -0,0 +1,208 @@
import {SessionData, Store} from "express-session"
const noop = (_err?: unknown, _data?: 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 {
parse(s: string): SessionData | Promise<SessionData>
stringify(s: SessionData): string
}
interface RedisStoreOptions {
client: any
prefix?: string
scanCount?: number
serializer?: Serializer
ttl?: number | {(sess: SessionData): number}
disableTTL?: boolean
disableTouch?: boolean
}
class RedisStore extends Store {
client: NormalizedRedisClient
prefix: string
scanCount: number
serializer: Serializer
ttl: number | {(sess: SessionData): number}
disableTTL: boolean
disableTouch: boolean
constructor(opts: RedisStoreOptions) {
super()
this.prefix = opts.prefix == null ? "sess:" : opts.prefix
this.scanCount = opts.scanCount || 100
this.serializer = opts.serializer || JSON
this.ttl = opts.ttl || 86400 // One day in seconds.
this.disableTTL = opts.disableTTL || false
this.disableTouch = opts.disableTouch || false
this.client = this.normalizeClient(opts.client)
}
// Create a redis and ioredis compatible client
private normalizeClient(client: any): NormalizedRedisClient {
let isRedis = "scanIterator" 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 (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 = noop) {
let key = this.prefix + sid
try {
let data = await this.client.get(key)
if (!data) return cb()
return cb(null, await this.serializer.parse(data))
} catch (err) {
return cb(err)
}
}
async set(sid: string, sess: SessionData, cb = noop) {
let key = this.prefix + sid
let ttl = this._getTTL(sess)
try {
let val = this.serializer.stringify(sess)
if (ttl > 0) {
if (this.disableTTL) await this.client.set(key, val)
else await this.client.set(key, val, ttl)
return cb()
} else {
return this.destroy(sid, cb)
}
} catch (err) {
return cb(err)
}
}
async touch(sid: string, sess: SessionData, cb = noop) {
let key = this.prefix + sid
if (this.disableTouch || this.disableTTL) return cb()
try {
await this.client.expire(key, this._getTTL(sess))
return cb()
} catch (err) {
return cb(err)
}
}
async destroy(sid: string, cb = noop) {
let key = this.prefix + sid
try {
await this.client.del([key])
return cb()
} catch (err) {
return cb(err)
}
}
async clear(cb = noop) {
try {
let keys = await this._getAllKeys()
if (!keys.length) return cb()
await this.client.del(keys)
return cb()
} catch (err) {
return cb(err)
}
}
async length(cb = noop) {
try {
let keys = await this._getAllKeys()
return cb(null, keys.length)
} catch (err) {
return cb(err)
}
}
async ids(cb = noop) {
let len = this.prefix.length
try {
let keys = await this._getAllKeys()
return cb(
null,
keys.map((k) => k.substring(len)),
)
} catch (err) {
return cb(err)
}
}
async all(cb = noop) {
let len = this.prefix.length
try {
let keys = await this._getAllKeys()
if (keys.length === 0) return cb(null, [])
let data = await this.client.mget(keys)
let results = data.reduce((acc, raw, idx) => {
if (!raw) return acc
let sess = this.serializer.parse(raw) as any
sess.id = keys[idx].substring(len)
acc.push(sess)
return acc
}, [] as SessionData[])
return cb(null, results)
} catch (err) {
return cb(err)
}
}
private _getTTL(sess: SessionData) {
if (typeof this.ttl === "function") {
return this.ttl(sess)
}
let ttl
if (sess && sess.cookie && sess.cookie.expires) {
let ms = Number(new Date(sess.cookie.expires)) - Date.now()
ttl = Math.ceil(ms / 1000)
} else {
ttl = this.ttl
}
return ttl
}
private async _getAllKeys() {
let pattern = this.prefix + "*"
let keys = []
for await (let key of this.client.scanIterator(pattern, this.scanCount)) {
keys.push(key)
}
return keys
}
}
export default RedisStore

131
node_modules/connect-redis/index_test.ts generated vendored Normal file
View File

@ -0,0 +1,131 @@
import test from "blue-tape"
import {Cookie} from "express-session"
import {Redis} from "ioredis"
import {promisify} from "node:util"
import {createClient} from "redis"
import RedisStore from "./"
import * as redisSrv from "./testdata/server"
test("setup", async () => {
await redisSrv.connect()
})
test("defaults", async (t) => {
let client = createClient({url: `redis://localhost:${redisSrv.port}`})
await client.connect()
let store = new RedisStore({client})
t.ok(store.client, "stores client")
t.equal(store.prefix, "sess:", "defaults to sess:")
t.equal(store.ttl, 86400, "defaults to one day")
t.equal(store.scanCount, 100, "defaults SCAN count to 100")
t.equal(store.serializer, JSON, "defaults to JSON serialization")
t.equal(store.disableTouch, false, "defaults to having `touch` enabled")
t.equal(store.disableTTL, false, "defaults to having `ttl` enabled")
await client.disconnect()
})
test("redis", async (t) => {
let client = createClient({url: `redis://localhost:${redisSrv.port}`})
await client.connect()
let store = new RedisStore({client})
await lifecycleTest(store, client, t)
await client.disconnect()
})
test("ioredis", async (t) => {
let client = new Redis(`redis://localhost:${redisSrv.port}`)
let store = new RedisStore({client})
await lifecycleTest(store, client, t)
client.disconnect()
})
test("teardown", redisSrv.disconnect)
async function lifecycleTest(
store: RedisStore,
client: any,
t: test.Test,
): Promise<void> {
const P = (f: any) => promisify(f).bind(store)
let res = await P(store.clear)()
let sess = {foo: "bar"}
await P(store.set)("123", sess)
res = await P(store.get)("123")
t.same(res, sess, "store.get")
let ttl = await client.ttl("sess:123")
t.ok(ttl >= 86399, "check one day ttl")
ttl = 60
let expires = new Date(Date.now() + ttl * 1000).toISOString()
await P(store.set)("456", {cookie: {expires}})
ttl = await client.ttl("sess:456")
t.ok(ttl <= 60, "check expires ttl")
ttl = 90
let expires2 = new Date(Date.now() + ttl * 1000).toISOString()
await P(store.touch)("456", {cookie: {expires: expires2}})
ttl = await client.ttl("sess:456")
t.ok(ttl > 60, "check expires ttl touch")
res = await P(store.length)()
t.equal(res, 2, "stored two keys length")
res = await P(store.ids)()
res.sort()
t.same(res, ["123", "456"], "stored two keys ids")
res = await P(store.all)()
res.sort((a: any, b: any) => (a.id > b.id ? 1 : -1))
t.same(
res,
[
{id: "123", foo: "bar"},
{id: "456", cookie: {expires}},
],
"stored two keys data",
)
await P(store.destroy)("456")
res = await P(store.length)()
t.equal(res, 1, "one key remains")
res = await P(store.clear)()
res = await P(store.length)()
t.equal(res, 0, "no keys remain")
let count = 1000
await load(store, count)
res = await P(store.length)()
t.equal(res, count, "bulk count")
await P(store.clear)()
res = await P(store.length)()
t.equal(res, 0, "bulk clear")
expires = new Date(Date.now() + ttl * 1000).toISOString() // expires in the future
res = await P(store.set)("789", {cookie: {expires}})
res = await P(store.length)()
t.equal(res, 1, "one key exists (session 789)")
expires = new Date(Date.now() - ttl * 1000).toISOString() // expires in the past
await P(store.set)("789", {cookie: {expires}})
res = await P(store.length)()
t.equal(res, 0, "no key remains and that includes session 789")
}
async function load(store: RedisStore, count: number) {
let cookie = new Cookie()
for (let sid = 0; sid < count; sid++) {
cookie.expires = new Date(Date.now() + 1000)
await store.set("s" + sid, {cookie})
}
}

21
node_modules/connect-redis/license generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2010-2023 TJ Holowaychuk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

64
node_modules/connect-redis/package.json generated vendored Normal file
View File

@ -0,0 +1,64 @@
{
"name": "connect-redis",
"description": "Redis session store for Connect",
"version": "7.1.1",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
"Marc Harter <wavded@gmail.com>"
],
"license": "MIT",
"main": "./dist/esm/index.js",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"default": "./dist/esm/index.js"
}
},
"types": "./dist/esm/index.d.ts",
"scripts": {
"prepublishOnly": "rm -rf dist && tsc & tsc --project tsconfig.esm.json && echo '{\"type\":\"module\"}' > dist/esm/package.json",
"build": "npm run prepublishOnly",
"test": "nyc ts-node node_modules/blue-tape/bin/blue-tape \"**/*_test.ts\"",
"lint": "tsc --noemit && eslint --max-warnings 0 --ext ts testdata *.ts",
"fmt": "prettier --write .",
"fmt-check": "prettier --check ."
},
"repository": {
"type": "git",
"url": "git@github.com:tj/connect-redis.git"
},
"devDependencies": {
"@types/blue-tape": "^0.1.36",
"@types/express-session": "^1.17.10",
"@types/node": "^20.11.5",
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"blue-tape": "^1.0.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"express-session": "^1.17.3",
"ioredis": "^5.3.2",
"nyc": "^15.1.0",
"prettier": "^3.2.4",
"prettier-plugin-organize-imports": "^3.2.4",
"redis": "^4.6.12",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"peerDependencies": {
"express-session": ">=1"
},
"engines": {
"node": ">=16"
},
"bugs": {
"url": "https://github.com/tj/connect-redis/issues"
},
"keywords": [
"connect",
"redis",
"session",
"express"
]
}

139
node_modules/connect-redis/readme.md generated vendored Normal file
View File

@ -0,0 +1,139 @@
![Build Status](https://github.com/tj/connect-redis/workflows/build/badge.svg?branch=master) [![npm](https://img.shields.io/npm/v/connect-redis.svg)](https://npmjs.com/package/connect-redis) [![code-style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://gitter.im/jlongster/prettier) ![Downloads](https://img.shields.io/npm/dm/connect-redis.svg)
**connect-redis** provides Redis session storage for Express.
## Installation
**connect-redis** requires `express-session` to installed and one of the following compatible Redis clients:
- [`redis`][1]
- [`ioredis`][2]
Install with `redis`:
```sh
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").default
```
## API
Full setup using [`redis`][1] package:
```js
import RedisStore from "connect-redis"
import session from "express-session"
import {createClient} from "redis"
// Initialize client.
let redisClient = createClient()
redisClient.connect().catch(console.error)
// Initialize store.
let redisStore = new RedisStore({
client: redisClient,
prefix: "myapp:",
})
// Initialize session storage.
app.use(
session({
store: redisStore,
resave: false, // required: force lightweight session keep alive (touch)
saveUninitialized: false, // recommended: only save session when data exists
secret: "keyboard cat",
}),
)
```
### RedisStore(options)
#### Options
##### client
An instance of [`redis`][1] or [`ioredis`][2].
##### prefix
Key prefix in Redis (default: `sess:`).
**Note**: This prefix appends to whatever prefix you may have set on the `client` itself.
**Note**: You may need unique prefixes for different applications sharing the same Redis instance. This limits bulk commands exposed in `express-session` (like `length`, `all`, `keys`, and `clear`) to a single application's data.
##### ttl
If the session cookie has a `expires` date, `connect-redis` will use it as the TTL.
Otherwise, it will expire the session using the `ttl` option (default: `86400` seconds or one day).
```ts
interface RedisStoreOptions {
...
ttl?: number | {(sess: SessionData): number}
}
```
`ttl` also has external callback support. You can use it for dynamic TTL generation. It has access to `session` data.
**Note**: The TTL is reset every time a user interacts with the server. You can disable this behavior in _some_ instances by using `disableTouch`.
**Note**: `express-session` does not update `expires` until the end of the request life cycle. _Calling `session.save()` manually beforehand will have the previous value_.
##### disableTouch
Disables resetting the TTL when using `touch` (default: `false`)
The `express-session` package uses `touch` to signal to the store that the user has interacted with the session but hasn't changed anything in its data. Typically, this helps keep the users session alive if session changes are infrequent but you may want to disable it to cut down the extra calls or to prevent users from keeping sessions open too long. Also consider enabling if you store a lot of data on the session.
Ref: <https://github.com/expressjs/session#storetouchsid-session-callback>
##### disableTTL
Disables key expiration completely (default: `false`)
This option disables key expiration requiring the user to manually manage key cleanup outside of `connect-redis`. Only use if you know what you are doing and have an exceptional case where you need to manage your own expiration in Redis.
**Note**: This has no effect on `express-session` setting cookie expiration.
##### serializer
Provide a custom encoder/decoder to use when storing and retrieving session data from Redis (default: `JSON.parse` and `JSON.stringify`).
Optionally `parse` method can be async if need be.
```ts
interface Serializer {
parse(string): object | Promise<object>
stringify(object): string
}
```
##### scanCount
Value used for _count_ parameter in [Redis `SCAN` command](https://redis.io/commands/scan#the-count-option). Used for `ids()` and `all()` methods (default: `100`).
[1]: https://github.com/NodeRedis/node-redis
[2]: https://github.com/luin/ioredis

7
node_modules/connect-redis/tsconfig.esm.json generated vendored Normal file
View File

@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "es2020",
"outDir": "./dist/esm"
}
}

18
node_modules/connect-redis/tsconfig.json generated vendored Normal file
View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"isolatedModules": true,
"strict": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"declaration": true,
"outDir": "./dist/cjs",
"esModuleInterop": true,
"resolveJsonModule": true,
},
"exclude": ["node_modules", "dist"],
}