-first commit
This commit is contained in:
30
node_modules/connect-redis/.github/release-drafter.yml
generated
vendored
Normal file
30
node_modules/connect-redis/.github/release-drafter.yml
generated
vendored
Normal 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"
|
||||
22
node_modules/connect-redis/.github/workflows/build.yml
generated
vendored
Normal file
22
node_modules/connect-redis/.github/workflows/build.yml
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [18, 20, 22, 24]
|
||||
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 lint
|
||||
- run: npm test
|
||||
13
node_modules/connect-redis/.github/workflows/release.yml
generated
vendored
Normal file
13
node_modules/connect-redis/.github/workflows/release.yml
generated
vendored
Normal 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
17
node_modules/connect-redis/.github/workflows/stale.yml
generated
vendored
Normal 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
|
||||
40
node_modules/connect-redis/biome.json
generated
vendored
Normal file
40
node_modules/connect-redis/biome.json
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"files": {
|
||||
"ignore": ["dist", "pnpm-*.yaml", "coverage"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"bracketSpacing": false
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"a11y": {
|
||||
"all": false
|
||||
},
|
||||
"style": {
|
||||
"useConst": "off",
|
||||
"useTemplate": "off",
|
||||
"noParameterAssign": "off",
|
||||
"useSingleVarDeclarator": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedImports": "error"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off",
|
||||
"noArrayIndexKey": "off",
|
||||
"noImplicitAnyLet": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"semicolons": "asNeeded"
|
||||
}
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
164
node_modules/connect-redis/dist/connect-redis.cjs
generated
vendored
Normal file
164
node_modules/connect-redis/dist/connect-redis.cjs
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
||||
const expressSession = require("express-session");
|
||||
function optionalCb(err, data, cb) {
|
||||
if (cb) return cb(err, data);
|
||||
if (err) throw err;
|
||||
return data;
|
||||
}
|
||||
class RedisStore extends expressSession.Store {
|
||||
client;
|
||||
prefix;
|
||||
scanCount;
|
||||
serializer;
|
||||
ttl;
|
||||
disableTTL;
|
||||
disableTouch;
|
||||
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;
|
||||
this.disableTTL = opts.disableTTL || false;
|
||||
this.disableTouch = opts.disableTouch || false;
|
||||
this.client = opts.client;
|
||||
}
|
||||
async get(sid, cb) {
|
||||
let key = this.prefix + sid;
|
||||
try {
|
||||
let data = await this.client.get(key);
|
||||
if (!data) return optionalCb(null, null, cb);
|
||||
return optionalCb(null, await this.serializer.parse(data), cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async set(sid, sess, cb) {
|
||||
let key = this.prefix + sid;
|
||||
let ttl = this.getTTL(sess);
|
||||
try {
|
||||
if (ttl > 0) {
|
||||
let val = this.serializer.stringify(sess);
|
||||
if (this.disableTTL) await this.client.set(key, val);
|
||||
else
|
||||
await this.client.set(key, val, {
|
||||
expiration: { type: "EX", value: ttl }
|
||||
});
|
||||
return optionalCb(null, null, cb);
|
||||
}
|
||||
return this.destroy(sid, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async touch(sid, sess, cb) {
|
||||
let key = this.prefix + sid;
|
||||
if (this.disableTouch || this.disableTTL) return optionalCb(null, null, cb);
|
||||
try {
|
||||
await this.client.expire(key, this.getTTL(sess));
|
||||
return optionalCb(null, null, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async destroy(sid, cb) {
|
||||
let key = this.prefix + sid;
|
||||
try {
|
||||
await this.client.del([key]);
|
||||
return optionalCb(null, null, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async clear(cb) {
|
||||
try {
|
||||
let keys = await this.getAllKeys();
|
||||
if (!keys.length) return optionalCb(null, null, cb);
|
||||
await this.client.del(keys);
|
||||
return optionalCb(null, null, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async length(cb) {
|
||||
try {
|
||||
let keys = await this.getAllKeys();
|
||||
return optionalCb(null, keys.length, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async ids(cb) {
|
||||
let len = this.prefix.length;
|
||||
try {
|
||||
let keys = await this.getAllKeys();
|
||||
return optionalCb(
|
||||
null,
|
||||
keys.map((k) => k.substring(len)),
|
||||
cb
|
||||
);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async all(cb) {
|
||||
let len = this.prefix.length;
|
||||
try {
|
||||
let keys = await this.getAllKeys();
|
||||
if (keys.length === 0) return optionalCb(null, [], cb);
|
||||
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 optionalCb(null, results, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
getTTL(sess) {
|
||||
if (typeof this.ttl === "function") {
|
||||
return this.ttl(sess);
|
||||
}
|
||||
let ttl;
|
||||
if (sess?.cookie?.expires) {
|
||||
let ms = Number(new Date(sess.cookie.expires)) - Date.now();
|
||||
ttl = Math.ceil(ms / 1e3);
|
||||
} else {
|
||||
ttl = this.ttl;
|
||||
}
|
||||
return ttl;
|
||||
}
|
||||
async getAllKeys() {
|
||||
let pattern = this.prefix + "*";
|
||||
let set = /* @__PURE__ */ new Set();
|
||||
for await (let keys of this.scanIterator(pattern, this.scanCount)) {
|
||||
for (let key of keys) {
|
||||
set.add(key);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}();
|
||||
}
|
||||
}
|
||||
exports.RedisStore = RedisStore;
|
||||
45
node_modules/connect-redis/dist/connect-redis.d.cts
generated
vendored
Normal file
45
node_modules/connect-redis/dist/connect-redis.d.cts
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import { RedisClientType } from 'redis';
|
||||
import { RedisClusterType } from 'redis';
|
||||
import { SessionData } from 'express-session';
|
||||
import { Store } from 'express-session';
|
||||
|
||||
declare type Callback = (_err?: unknown, _data?: any) => any;
|
||||
|
||||
export declare class RedisStore extends Store {
|
||||
client: RedisClientType | RedisClusterType;
|
||||
prefix: string;
|
||||
scanCount: number;
|
||||
serializer: Serializer;
|
||||
ttl: number | ((sess: SessionData) => number);
|
||||
disableTTL: boolean;
|
||||
disableTouch: boolean;
|
||||
constructor(opts: RedisStoreOptions);
|
||||
get(sid: string, cb?: Callback): Promise<any>;
|
||||
set(sid: string, sess: SessionData, cb?: Callback): Promise<any>;
|
||||
touch(sid: string, sess: SessionData, cb?: Callback): Promise<any>;
|
||||
destroy(sid: string, cb?: Callback): Promise<any>;
|
||||
clear(cb?: Callback): Promise<any>;
|
||||
length(cb?: Callback): Promise<any>;
|
||||
ids(cb?: Callback): Promise<any>;
|
||||
all(cb?: Callback): Promise<any>;
|
||||
private getTTL;
|
||||
private getAllKeys;
|
||||
private scanIterator;
|
||||
}
|
||||
|
||||
declare interface RedisStoreOptions {
|
||||
client: any;
|
||||
prefix?: string;
|
||||
scanCount?: number;
|
||||
serializer?: Serializer;
|
||||
ttl?: number | ((sess: SessionData) => number);
|
||||
disableTTL?: boolean;
|
||||
disableTouch?: boolean;
|
||||
}
|
||||
|
||||
declare interface Serializer {
|
||||
parse(s: string): SessionData | Promise<SessionData>;
|
||||
stringify(s: SessionData): string;
|
||||
}
|
||||
|
||||
export { }
|
||||
45
node_modules/connect-redis/dist/connect-redis.d.ts
generated
vendored
Normal file
45
node_modules/connect-redis/dist/connect-redis.d.ts
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import { RedisClientType } from 'redis';
|
||||
import { RedisClusterType } from 'redis';
|
||||
import { SessionData } from 'express-session';
|
||||
import { Store } from 'express-session';
|
||||
|
||||
declare type Callback = (_err?: unknown, _data?: any) => any;
|
||||
|
||||
export declare class RedisStore extends Store {
|
||||
client: RedisClientType | RedisClusterType;
|
||||
prefix: string;
|
||||
scanCount: number;
|
||||
serializer: Serializer;
|
||||
ttl: number | ((sess: SessionData) => number);
|
||||
disableTTL: boolean;
|
||||
disableTouch: boolean;
|
||||
constructor(opts: RedisStoreOptions);
|
||||
get(sid: string, cb?: Callback): Promise<any>;
|
||||
set(sid: string, sess: SessionData, cb?: Callback): Promise<any>;
|
||||
touch(sid: string, sess: SessionData, cb?: Callback): Promise<any>;
|
||||
destroy(sid: string, cb?: Callback): Promise<any>;
|
||||
clear(cb?: Callback): Promise<any>;
|
||||
length(cb?: Callback): Promise<any>;
|
||||
ids(cb?: Callback): Promise<any>;
|
||||
all(cb?: Callback): Promise<any>;
|
||||
private getTTL;
|
||||
private getAllKeys;
|
||||
private scanIterator;
|
||||
}
|
||||
|
||||
declare interface RedisStoreOptions {
|
||||
client: any;
|
||||
prefix?: string;
|
||||
scanCount?: number;
|
||||
serializer?: Serializer;
|
||||
ttl?: number | ((sess: SessionData) => number);
|
||||
disableTTL?: boolean;
|
||||
disableTouch?: boolean;
|
||||
}
|
||||
|
||||
declare interface Serializer {
|
||||
parse(s: string): SessionData | Promise<SessionData>;
|
||||
stringify(s: SessionData): string;
|
||||
}
|
||||
|
||||
export { }
|
||||
164
node_modules/connect-redis/dist/connect-redis.js
generated
vendored
Normal file
164
node_modules/connect-redis/dist/connect-redis.js
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
import { Store } from "express-session";
|
||||
function optionalCb(err, data, cb) {
|
||||
if (cb) return cb(err, data);
|
||||
if (err) throw err;
|
||||
return data;
|
||||
}
|
||||
class RedisStore extends Store {
|
||||
client;
|
||||
prefix;
|
||||
scanCount;
|
||||
serializer;
|
||||
ttl;
|
||||
disableTTL;
|
||||
disableTouch;
|
||||
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;
|
||||
this.disableTTL = opts.disableTTL || false;
|
||||
this.disableTouch = opts.disableTouch || false;
|
||||
this.client = opts.client;
|
||||
}
|
||||
async get(sid, cb) {
|
||||
let key = this.prefix + sid;
|
||||
try {
|
||||
let data = await this.client.get(key);
|
||||
if (!data) return optionalCb(null, null, cb);
|
||||
return optionalCb(null, await this.serializer.parse(data), cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async set(sid, sess, cb) {
|
||||
let key = this.prefix + sid;
|
||||
let ttl = this.getTTL(sess);
|
||||
try {
|
||||
if (ttl > 0) {
|
||||
let val = this.serializer.stringify(sess);
|
||||
if (this.disableTTL) await this.client.set(key, val);
|
||||
else
|
||||
await this.client.set(key, val, {
|
||||
expiration: { type: "EX", value: ttl }
|
||||
});
|
||||
return optionalCb(null, null, cb);
|
||||
}
|
||||
return this.destroy(sid, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async touch(sid, sess, cb) {
|
||||
let key = this.prefix + sid;
|
||||
if (this.disableTouch || this.disableTTL) return optionalCb(null, null, cb);
|
||||
try {
|
||||
await this.client.expire(key, this.getTTL(sess));
|
||||
return optionalCb(null, null, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async destroy(sid, cb) {
|
||||
let key = this.prefix + sid;
|
||||
try {
|
||||
await this.client.del([key]);
|
||||
return optionalCb(null, null, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async clear(cb) {
|
||||
try {
|
||||
let keys = await this.getAllKeys();
|
||||
if (!keys.length) return optionalCb(null, null, cb);
|
||||
await this.client.del(keys);
|
||||
return optionalCb(null, null, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async length(cb) {
|
||||
try {
|
||||
let keys = await this.getAllKeys();
|
||||
return optionalCb(null, keys.length, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async ids(cb) {
|
||||
let len = this.prefix.length;
|
||||
try {
|
||||
let keys = await this.getAllKeys();
|
||||
return optionalCb(
|
||||
null,
|
||||
keys.map((k) => k.substring(len)),
|
||||
cb
|
||||
);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
async all(cb) {
|
||||
let len = this.prefix.length;
|
||||
try {
|
||||
let keys = await this.getAllKeys();
|
||||
if (keys.length === 0) return optionalCb(null, [], cb);
|
||||
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 optionalCb(null, results, cb);
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb);
|
||||
}
|
||||
}
|
||||
getTTL(sess) {
|
||||
if (typeof this.ttl === "function") {
|
||||
return this.ttl(sess);
|
||||
}
|
||||
let ttl;
|
||||
if (sess?.cookie?.expires) {
|
||||
let ms = Number(new Date(sess.cookie.expires)) - Date.now();
|
||||
ttl = Math.ceil(ms / 1e3);
|
||||
} else {
|
||||
ttl = this.ttl;
|
||||
}
|
||||
return ttl;
|
||||
}
|
||||
async getAllKeys() {
|
||||
let pattern = this.prefix + "*";
|
||||
let set = /* @__PURE__ */ new Set();
|
||||
for await (let keys of this.scanIterator(pattern, this.scanCount)) {
|
||||
for (let key of keys) {
|
||||
set.add(key);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}();
|
||||
}
|
||||
}
|
||||
export {
|
||||
RedisStore
|
||||
};
|
||||
197
node_modules/connect-redis/index.ts
generated
vendored
Normal file
197
node_modules/connect-redis/index.ts
generated
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
import {type SessionData, Store} from "express-session"
|
||||
import type {RedisClientType, RedisClusterType} from "redis"
|
||||
|
||||
type Callback = (_err?: unknown, _data?: any) => any
|
||||
|
||||
function optionalCb(err: unknown, data: unknown, cb?: Callback) {
|
||||
if (cb) return cb(err, data)
|
||||
if (err) throw err
|
||||
return data
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export class RedisStore extends Store {
|
||||
client: RedisClientType | RedisClusterType
|
||||
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 = opts.client
|
||||
}
|
||||
|
||||
async get(sid: string, cb?: Callback) {
|
||||
let key = this.prefix + sid
|
||||
try {
|
||||
let data = await this.client.get(key)
|
||||
if (!data) return optionalCb(null, null, cb)
|
||||
return optionalCb(null, await this.serializer.parse(data), cb)
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb)
|
||||
}
|
||||
}
|
||||
|
||||
async set(sid: string, sess: SessionData, cb?: Callback) {
|
||||
let key = this.prefix + sid
|
||||
let ttl = this.getTTL(sess)
|
||||
try {
|
||||
if (ttl > 0) {
|
||||
let val = this.serializer.stringify(sess)
|
||||
if (this.disableTTL) await this.client.set(key, val)
|
||||
else
|
||||
await this.client.set(key, val, {
|
||||
expiration: {type: "EX", value: ttl},
|
||||
})
|
||||
return optionalCb(null, null, cb)
|
||||
}
|
||||
return this.destroy(sid, cb)
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb)
|
||||
}
|
||||
}
|
||||
|
||||
async touch(sid: string, sess: SessionData, cb?: Callback) {
|
||||
let key = this.prefix + sid
|
||||
if (this.disableTouch || this.disableTTL) return optionalCb(null, null, cb)
|
||||
try {
|
||||
await this.client.expire(key, this.getTTL(sess))
|
||||
return optionalCb(null, null, cb)
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb)
|
||||
}
|
||||
}
|
||||
|
||||
async destroy(sid: string, cb?: Callback) {
|
||||
let key = this.prefix + sid
|
||||
try {
|
||||
await this.client.del([key])
|
||||
return optionalCb(null, null, cb)
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb)
|
||||
}
|
||||
}
|
||||
|
||||
async clear(cb?: Callback) {
|
||||
try {
|
||||
let keys = await this.getAllKeys()
|
||||
if (!keys.length) return optionalCb(null, null, cb)
|
||||
await this.client.del(keys)
|
||||
return optionalCb(null, null, cb)
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb)
|
||||
}
|
||||
}
|
||||
|
||||
async length(cb?: Callback) {
|
||||
try {
|
||||
let keys = await this.getAllKeys()
|
||||
return optionalCb(null, keys.length, cb)
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb)
|
||||
}
|
||||
}
|
||||
|
||||
async ids(cb?: Callback) {
|
||||
let len = this.prefix.length
|
||||
try {
|
||||
let keys = await this.getAllKeys()
|
||||
return optionalCb(
|
||||
null,
|
||||
keys.map((k) => k.substring(len)),
|
||||
cb,
|
||||
)
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb)
|
||||
}
|
||||
}
|
||||
|
||||
async all(cb?: Callback) {
|
||||
let len = this.prefix.length
|
||||
try {
|
||||
let keys = await this.getAllKeys()
|
||||
if (keys.length === 0) return optionalCb(null, [], cb)
|
||||
|
||||
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 optionalCb(null, results, cb)
|
||||
} catch (err) {
|
||||
return optionalCb(err, null, cb)
|
||||
}
|
||||
}
|
||||
|
||||
private getTTL(sess: SessionData) {
|
||||
if (typeof this.ttl === "function") {
|
||||
return this.ttl(sess)
|
||||
}
|
||||
|
||||
let ttl
|
||||
if (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 set = new Set<string>()
|
||||
for await (let keys of this.scanIterator(pattern, this.scanCount)) {
|
||||
for (let key of keys) {
|
||||
set.add(key)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
})()
|
||||
}
|
||||
}
|
||||
113
node_modules/connect-redis/index_test.ts
generated
vendored
Normal file
113
node_modules/connect-redis/index_test.ts
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
import {Cookie} from "express-session"
|
||||
import {createClient} from "redis"
|
||||
import {expect, test} from "vitest"
|
||||
import {RedisStore} from "./"
|
||||
import * as redisSrv from "./testdata/server"
|
||||
|
||||
test("setup", async () => {
|
||||
await redisSrv.connect()
|
||||
})
|
||||
|
||||
test("defaults", async () => {
|
||||
let client = createClient({url: `redis://localhost:${redisSrv.port}`})
|
||||
await client.connect()
|
||||
|
||||
let store = new RedisStore({client})
|
||||
|
||||
expect(store.client).toBeDefined()
|
||||
expect(store.prefix).toBe("sess:")
|
||||
expect(store.ttl).toBe(86400) // defaults to one day
|
||||
expect(store.scanCount).toBe(100)
|
||||
expect(store.serializer).toBe(JSON)
|
||||
expect(store.disableTouch).toBe(false)
|
||||
expect(store.disableTTL).toBe(false)
|
||||
client.destroy()
|
||||
})
|
||||
|
||||
test("redis", async () => {
|
||||
let client = createClient({url: `redis://localhost:${redisSrv.port}`})
|
||||
await client.connect()
|
||||
let store = new RedisStore({client})
|
||||
await lifecycleTest(store, client)
|
||||
client.destroy()
|
||||
})
|
||||
|
||||
test("teardown", redisSrv.disconnect)
|
||||
|
||||
async function lifecycleTest(store: RedisStore, client: any): Promise<void> {
|
||||
let res = await store.clear()
|
||||
|
||||
let sess = {foo: "bar", cookie: {originalMaxAge: null}}
|
||||
await store.set("123", sess)
|
||||
|
||||
res = await store.get("123")
|
||||
expect(res).toEqual(sess)
|
||||
|
||||
let ttl = await client.ttl("sess:123")
|
||||
expect(ttl).toBeGreaterThanOrEqual(86399)
|
||||
|
||||
ttl = 60
|
||||
let expires = new Date(Date.now() + ttl * 1000)
|
||||
await store.set("456", {cookie: {originalMaxAge: null, expires}})
|
||||
ttl = await client.ttl("sess:456")
|
||||
expect(ttl).toBeLessThanOrEqual(60)
|
||||
|
||||
ttl = 90
|
||||
let expires2 = new Date(Date.now() + ttl * 1000)
|
||||
await store.touch("456", {cookie: {originalMaxAge: null, expires: expires2}})
|
||||
ttl = await client.ttl("sess:456")
|
||||
expect(ttl).toBeGreaterThan(60)
|
||||
|
||||
res = await store.length()
|
||||
expect(res).toBe(2) // stored two keys length
|
||||
|
||||
res = await store.ids()
|
||||
res.sort()
|
||||
expect(res).toEqual(["123", "456"])
|
||||
|
||||
res = await store.all()
|
||||
res.sort((a: any, b: any) => (a.id > b.id ? 1 : -1))
|
||||
expect(res).toEqual([
|
||||
{id: "123", foo: "bar", cookie: {originalMaxAge: null}},
|
||||
{id: "456", cookie: {originalMaxAge: null, expires: expires.toISOString()}},
|
||||
])
|
||||
|
||||
await store.destroy("456")
|
||||
res = await store.length()
|
||||
expect(res).toBe(1) // one key remains
|
||||
|
||||
res = await store.clear()
|
||||
|
||||
res = await store.length()
|
||||
expect(res).toBe(0) // no keys remain
|
||||
|
||||
let count = 1000
|
||||
await load(store, count)
|
||||
|
||||
res = await store.length()
|
||||
expect(res).toBe(count)
|
||||
|
||||
await store.clear()
|
||||
res = await store.length()
|
||||
expect(res).toBe(0)
|
||||
|
||||
expires = new Date(Date.now() + ttl * 1000) // expires in the future
|
||||
res = await store.set("789", {cookie: {originalMaxAge: null, expires}})
|
||||
|
||||
res = await store.length()
|
||||
expect(res).toBe(1)
|
||||
|
||||
expires = new Date(Date.now() - ttl * 1000) // expires in the past
|
||||
await store.set("789", {cookie: {originalMaxAge: null, expires}})
|
||||
|
||||
res = await store.length()
|
||||
expect(res).toBe(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
21
node_modules/connect-redis/license
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2010-2025 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
64
node_modules/connect-redis/package.json
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "connect-redis",
|
||||
"description": "Redis session store for Connect",
|
||||
"version": "9.0.0",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca>",
|
||||
"contributors": [
|
||||
"Marc Harter <wavded@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "./dist/connect-redis.cjs",
|
||||
"module": "./dist/connect-redis.js",
|
||||
"types": "./dist/connect-redis.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/connect-redis.d.ts",
|
||||
"default": "./dist/connect-redis.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/connect-redis.d.cts",
|
||||
"default": "./dist/connect-redis.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/tj/connect-redis.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-dts": "^4.5.4",
|
||||
"vitest": "^3.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"redis": ">=5",
|
||||
"express-session": ">=1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/tj/connect-redis/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"connect",
|
||||
"redis",
|
||||
"session",
|
||||
"express"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"test": "vitest run --coverage",
|
||||
"lint": "tsc --noemit && biome check .",
|
||||
"fix": "biome check --write ."
|
||||
}
|
||||
}
|
||||
3
node_modules/connect-redis/pnpm-workspace.yaml
generated
vendored
Normal file
3
node_modules/connect-redis/pnpm-workspace.yaml
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
onlyBuiltDependencies:
|
||||
- '@biomejs/biome'
|
||||
- esbuild
|
||||
111
node_modules/connect-redis/readme.md
generated
vendored
Normal file
111
node_modules/connect-redis/readme.md
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
[](https://github.com/tj/connect-redis/actions/workflows/build.yml) [](https://npmjs.com/package/connect-redis) 
|
||||
|
||||
**connect-redis** provides Redis session storage for Express.
|
||||
|
||||
## Installation
|
||||
|
||||
**connect-redis** requires `express-session` and [`redis`][1]:
|
||||
|
||||
```sh
|
||||
npm install redis connect-redis express-session
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
Full setup:
|
||||
|
||||
```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]
|
||||
|
||||
##### 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
|
||||
16
node_modules/connect-redis/tsconfig.json
generated
vendored
Normal file
16
node_modules/connect-redis/tsconfig.json
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
37
node_modules/connect-redis/vite.config.ts
generated
vendored
Normal file
37
node_modules/connect-redis/vite.config.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import {copyFileSync} from "node:fs"
|
||||
import dts from "vite-plugin-dts"
|
||||
import {defineConfig} from "vitest/config"
|
||||
|
||||
// https://vitest.dev/config/
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: "index.ts",
|
||||
name: "connect-redis",
|
||||
formats: ["es", "cjs"],
|
||||
},
|
||||
emptyOutDir: true,
|
||||
minify: false,
|
||||
rollupOptions: {
|
||||
external: ["express-session"],
|
||||
treeshake: false,
|
||||
},
|
||||
target: "node18",
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
include: ["index.ts"],
|
||||
rollupTypes: true,
|
||||
insertTypesEntry: true,
|
||||
afterBuild: () => {
|
||||
copyFileSync("dist/connect-redis.d.ts", "dist/connect-redis.d.cts")
|
||||
},
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
include: ["**/*_test.[jt]s"],
|
||||
coverage: {
|
||||
reporter: ["text"],
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user