@tsed/adapters-redis 8.28.7 → 8.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/adapters/OIDCRedisAdapter.js +32 -36
- package/lib/esm/adapters/RedisAdapter.js +66 -52
- package/lib/types/adapters/OIDCRedisAdapter.d.ts +9 -8
- package/lib/types/adapters/RedisAdapter.d.ts +10 -7
- package/lib/types/domain/RedisAdaptersSettings.d.ts +2 -2
- package/package.json +11 -9
- package/tests/setup/TestContainersRedis.ts +69 -0
- package/tests/setup/vi.setup.ts +9 -0
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import { __decorate, __metadata, __param } from "tslib";
|
|
2
|
-
import {
|
|
3
|
-
import { IOREDIS_CONNECTIONS } from "@tsed/ioredis";
|
|
2
|
+
import { Opts } from "@tsed/di";
|
|
4
3
|
import { RedisAdapter } from "./RedisAdapter.js";
|
|
5
4
|
const GRANTABLE = new Set(["AccessToken", "AuthorizationCode", "RefreshToken", "DeviceCode", "BackchannelAuthenticationRequest"]);
|
|
6
5
|
const CONSUMABLE = new Set(["AuthorizationCode", "RefreshToken", "DeviceCode", "BackchannelAuthenticationRequest"]);
|
|
7
|
-
function grantKeyFor(id) {
|
|
8
|
-
return `$oidc:grant:${id}`;
|
|
9
|
-
}
|
|
10
|
-
function userCodeKeyFor(userCode) {
|
|
11
|
-
return `$oidc:userCode:${userCode}`;
|
|
12
|
-
}
|
|
13
|
-
function uidKeyFor(uid) {
|
|
14
|
-
return `$oidc:uid:${uid}`;
|
|
15
|
-
}
|
|
16
6
|
let OIDCRedisAdapter = class OIDCRedisAdapter extends RedisAdapter {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
grantKeyFor(id) {
|
|
8
|
+
return this.prefix(`$oidc:grant:${id}`);
|
|
9
|
+
}
|
|
10
|
+
userCodeKeyFor(userCode) {
|
|
11
|
+
return this.prefix(`$oidc:userCode:${userCode}`);
|
|
12
|
+
}
|
|
13
|
+
uidKeyFor(uid) {
|
|
14
|
+
return this.prefix(`$oidc:uid:${uid}`);
|
|
15
|
+
}
|
|
16
|
+
constructor(options) {
|
|
17
|
+
super(options);
|
|
20
18
|
this.useHash = CONSUMABLE.has(this.collectionName);
|
|
21
19
|
this.isGrantable = GRANTABLE.has(this.collectionName);
|
|
22
20
|
this.hooks.on("insert", this.onInsert.bind(this));
|
|
@@ -25,55 +23,53 @@ let OIDCRedisAdapter = class OIDCRedisAdapter extends RedisAdapter {
|
|
|
25
23
|
const id = payload._id;
|
|
26
24
|
const key = this.key(id);
|
|
27
25
|
if (this.isGrantable && payload.grantId) {
|
|
28
|
-
const grantKey = grantKeyFor(payload.grantId);
|
|
29
|
-
multi
|
|
26
|
+
const grantKey = this.grantKeyFor(payload.grantId);
|
|
27
|
+
this.callCmd(multi, ["rPush", "rpush"], grantKey, key);
|
|
30
28
|
// if you're seeing grant key lists growing out of acceptable proportions consider using LTRIM
|
|
31
29
|
// here to trim the list to an appropriate length
|
|
32
|
-
const ttl = await this.db
|
|
30
|
+
const ttl = await this.callCmd(this.db, ["ttl"], grantKey);
|
|
33
31
|
if (expiresIn && expiresIn > ttl) {
|
|
34
|
-
multi
|
|
32
|
+
this.callCmd(multi, ["expire"], grantKey, expiresIn);
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
if (payload.userCode) {
|
|
38
|
-
const userCodeKey = userCodeKeyFor(payload.userCode);
|
|
39
|
-
multi
|
|
40
|
-
expiresIn && multi
|
|
36
|
+
const userCodeKey = this.userCodeKeyFor(payload.userCode);
|
|
37
|
+
this.callCmd(multi, ["set"], userCodeKey, id);
|
|
38
|
+
expiresIn && this.callCmd(multi, ["expire"], userCodeKey, expiresIn);
|
|
41
39
|
}
|
|
42
40
|
if (payload.uid) {
|
|
43
|
-
const uidKey = uidKeyFor(payload.uid);
|
|
44
|
-
multi
|
|
45
|
-
expiresIn && multi
|
|
41
|
+
const uidKey = this.uidKeyFor(payload.uid);
|
|
42
|
+
this.callCmd(multi, ["set"], uidKey, id);
|
|
43
|
+
expiresIn && this.callCmd(multi, ["expire"], uidKey, expiresIn);
|
|
46
44
|
}
|
|
47
45
|
return multi;
|
|
48
46
|
}
|
|
49
47
|
async findByUid(uid) {
|
|
50
|
-
const id = await this.db.
|
|
48
|
+
const id = await this.callCmd(this.db, ["get"], this.uidKeyFor(uid));
|
|
51
49
|
return id && this.findById(id);
|
|
52
50
|
}
|
|
53
51
|
async findByUserCode(userCode) {
|
|
54
|
-
const id = await this.db.
|
|
52
|
+
const id = await this.callCmd(this.db, ["get"], this.userCodeKeyFor(userCode));
|
|
55
53
|
return id && this.findById(id);
|
|
56
54
|
}
|
|
57
55
|
async destroy(id) {
|
|
58
56
|
const key = this.key(id);
|
|
59
|
-
await this.db
|
|
57
|
+
await this.callCmd(this.db, ["del"], key);
|
|
60
58
|
}
|
|
61
59
|
async revokeByGrantId(grantId) {
|
|
62
60
|
const multi = this.db.multi();
|
|
63
|
-
const key = grantKeyFor(grantId);
|
|
64
|
-
const tokens = await this.db
|
|
65
|
-
tokens.forEach((token) => multi
|
|
66
|
-
multi.
|
|
67
|
-
await multi
|
|
61
|
+
const key = this.grantKeyFor(grantId);
|
|
62
|
+
const tokens = await this.callCmd(this.db, ["lRange", "lrange"], key, 0, -1);
|
|
63
|
+
tokens.forEach((token) => this.callCmd(multi, ["del"], token));
|
|
64
|
+
this.callCmd(multi, ["del"], this.grantKeyFor(grantId));
|
|
65
|
+
await this.callCmd(multi, ["exec"]);
|
|
68
66
|
}
|
|
69
67
|
async consume(id) {
|
|
70
|
-
await this.db
|
|
68
|
+
await this.callCmd(this.db, ["hSet", "hset"], this.key(id), "consumed", Math.floor(Date.now() / 1000));
|
|
71
69
|
}
|
|
72
70
|
};
|
|
73
71
|
OIDCRedisAdapter = __decorate([
|
|
74
72
|
__param(0, Opts),
|
|
75
|
-
|
|
76
|
-
__param(2, Configuration()),
|
|
77
|
-
__metadata("design:paramtypes", [Object, Array, Object])
|
|
73
|
+
__metadata("design:paramtypes", [Object])
|
|
78
74
|
], OIDCRedisAdapter);
|
|
79
75
|
export { OIDCRedisAdapter };
|
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
import { __decorate, __metadata, __param } from "tslib";
|
|
2
2
|
import { Adapter } from "@tsed/adapters";
|
|
3
3
|
import { cleanObject, isObject, isString } from "@tsed/core";
|
|
4
|
-
import {
|
|
4
|
+
import { injectMany, Opts } from "@tsed/di";
|
|
5
5
|
import { Hooks } from "@tsed/hooks";
|
|
6
|
-
import {
|
|
6
|
+
import { REDIS_CONNECTIONS } from "@tsed/redis";
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
8
|
-
const getId = (key) => key.split(":")[2];
|
|
9
|
-
const flatKeys = (keys) => {
|
|
10
|
-
return keys.flatMap(([, result]) => result).filter(Boolean);
|
|
11
|
-
};
|
|
12
8
|
let RedisAdapter = class RedisAdapter extends Adapter {
|
|
13
|
-
constructor(options
|
|
14
|
-
super(options
|
|
15
|
-
this.configuration = configuration;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
16
11
|
this.hooks = new Hooks();
|
|
17
12
|
this.useHash = false;
|
|
18
13
|
this.useHash = Boolean(options.useHash);
|
|
19
14
|
this.connectionName = options.connectionName || "default";
|
|
20
|
-
this.
|
|
15
|
+
this.keyPrefix = options.keyPrefix || "";
|
|
16
|
+
this.connection = injectMany(REDIS_CONNECTIONS).find((connection) => {
|
|
17
|
+
return connection.name === this.connectionName;
|
|
18
|
+
}); // || connections[0];
|
|
21
19
|
}
|
|
22
20
|
get db() {
|
|
23
21
|
return this.connection;
|
|
24
22
|
}
|
|
25
23
|
key(id) {
|
|
26
|
-
return `${this.collectionName}:${id}
|
|
24
|
+
return this.prefix(`${this.collectionName}:${id}`);
|
|
27
25
|
}
|
|
28
26
|
create(payload, expiresAt) {
|
|
29
27
|
delete payload._id;
|
|
@@ -60,14 +58,16 @@ let RedisAdapter = class RedisAdapter extends Adapter {
|
|
|
60
58
|
if (foundKeys.length < keys.length) {
|
|
61
59
|
return undefined;
|
|
62
60
|
}
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
const id = this.extractIdFromIndexedKey(foundKeys[0]);
|
|
62
|
+
if (!id) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
foundKeys = foundKeys.filter((key) => id === this.extractIdFromIndexedKey(key));
|
|
66
66
|
return keys.length === foundKeys.length ? this.findById(id) : undefined;
|
|
67
67
|
}
|
|
68
68
|
async findById(_id) {
|
|
69
69
|
const key = this.key(_id);
|
|
70
|
-
const item = this.useHash ? await this.db
|
|
70
|
+
const item = this.useHash ? await this.callCmd(this.db, ["hGetAll", "hgetall"], key) : await this.callCmd(this.db, ["get"], key);
|
|
71
71
|
// hgetall returns an empty object when there are no results, so we need to check for that.
|
|
72
72
|
if (!item || (this.useHash && isObject(item) && Object.keys(item).length === 0)) {
|
|
73
73
|
return undefined;
|
|
@@ -98,7 +98,7 @@ let RedisAdapter = class RedisAdapter extends Adapter {
|
|
|
98
98
|
if (item) {
|
|
99
99
|
const id = this.key(item._id);
|
|
100
100
|
const indexIds = await this.getAllIndex(item._id);
|
|
101
|
-
await this.db
|
|
101
|
+
await this.callCmd(this.db, ["del"], id, ...indexIds);
|
|
102
102
|
return this.deserialize(item);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -107,29 +107,43 @@ let RedisAdapter = class RedisAdapter extends Adapter {
|
|
|
107
107
|
}
|
|
108
108
|
async deleteMany(predicate) {
|
|
109
109
|
const items = await this.findAll(predicate);
|
|
110
|
-
const pipeline = this.db.pipeline();
|
|
111
110
|
const ids = [];
|
|
112
111
|
const results = items.map((item) => {
|
|
113
112
|
ids.push(this.key(item._id));
|
|
114
|
-
this.getAllIndex(item._id, pipeline);
|
|
115
113
|
return this.deserialize(item);
|
|
116
114
|
});
|
|
117
|
-
const
|
|
118
|
-
|
|
115
|
+
const indexesByItem = await Promise.all(items.map((item) => this.getAllIndex(item._id)));
|
|
116
|
+
const indexKeys = indexesByItem.flat();
|
|
117
|
+
if (ids.length || indexKeys.length) {
|
|
118
|
+
await this.callCmd(this.db, ["del"], ...ids, ...indexKeys);
|
|
119
|
+
}
|
|
119
120
|
return results;
|
|
120
121
|
}
|
|
122
|
+
prefix(key) {
|
|
123
|
+
return this.keyPrefix ? `${this.keyPrefix}:${key}` : key;
|
|
124
|
+
}
|
|
125
|
+
callCmd(target, methods, ...args) {
|
|
126
|
+
const method = methods.map((name) => target?.[name]).find((fn) => typeof fn === "function");
|
|
127
|
+
if (!method) {
|
|
128
|
+
throw new Error(`Redis command not available: ${methods.join(" or ")}`);
|
|
129
|
+
}
|
|
130
|
+
return method.call(target, ...args);
|
|
131
|
+
}
|
|
132
|
+
extractIdFromIndexedKey(key) {
|
|
133
|
+
const parts = key.split(":");
|
|
134
|
+
const collectionIndex = parts.lastIndexOf(this.collectionName);
|
|
135
|
+
return collectionIndex > -1 ? parts[collectionIndex + 1] : undefined;
|
|
136
|
+
}
|
|
121
137
|
async findKeys(props) {
|
|
122
138
|
const keys = Object.keys(props);
|
|
123
|
-
const
|
|
124
|
-
this.indexes
|
|
139
|
+
const patterns = this.indexes
|
|
125
140
|
.filter(({ propertyKey }) => keys.includes(propertyKey))
|
|
126
|
-
.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return flatKeys(results);
|
|
141
|
+
.map(({ propertyKey }) => this.getIndexedKey("*", propertyKey, props[propertyKey]));
|
|
142
|
+
if (!patterns.length) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
const results = await Promise.all(patterns.map((pattern) => this.callCmd(this.db, ["keys"], pattern)));
|
|
146
|
+
return results.flat().filter(Boolean).sort();
|
|
133
147
|
}
|
|
134
148
|
async insert(payload, expiresAt) {
|
|
135
149
|
const id = (payload._id = payload._id || uuid());
|
|
@@ -138,41 +152,40 @@ let RedisAdapter = class RedisAdapter extends Adapter {
|
|
|
138
152
|
const multi = this.db.multi();
|
|
139
153
|
const key = this.key(id);
|
|
140
154
|
const strPayload = JSON.stringify(this.serialize(payload));
|
|
141
|
-
this.useHash
|
|
155
|
+
if (this.useHash) {
|
|
156
|
+
this.callCmd(multi, ["hSet", "hset"], key, { payload: strPayload });
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.callCmd(multi, ["set"], key, strPayload);
|
|
160
|
+
}
|
|
142
161
|
if (expiresIn) {
|
|
143
|
-
multi
|
|
162
|
+
this.callCmd(multi, ["expire"], key, expiresIn);
|
|
144
163
|
}
|
|
145
164
|
this.indexes.forEach(({ propertyKey }) => {
|
|
146
165
|
const value = payload[propertyKey];
|
|
147
166
|
const indexedKey = this.getIndexedKey(id, propertyKey, value);
|
|
148
|
-
multi
|
|
149
|
-
expiresIn && multi
|
|
167
|
+
this.callCmd(multi, ["set"], indexedKey, id);
|
|
168
|
+
expiresIn && this.callCmd(multi, ["expire"], indexedKey, expiresIn);
|
|
150
169
|
});
|
|
151
170
|
await this.hooks.asyncAlter("insert", multi, [payload, expiresIn]);
|
|
152
|
-
await multi
|
|
171
|
+
await this.callCmd(multi, ["exec"]);
|
|
153
172
|
const result = this.deserialize(payload, { useAlias: false });
|
|
154
173
|
await this.hooks.asyncEmit("afterInsert", [result]);
|
|
155
174
|
return result;
|
|
156
175
|
}
|
|
157
|
-
getAllIndex(id
|
|
158
|
-
const key = ["$idx", this.
|
|
159
|
-
return (
|
|
176
|
+
getAllIndex(id) {
|
|
177
|
+
const key = this.prefix(["$idx", `${this.collectionName}:${id}`, "*"].join(":"));
|
|
178
|
+
return this.callCmd(this.db, ["keys"], key);
|
|
160
179
|
}
|
|
161
180
|
getIndexedKey(id, propertyKey, value) {
|
|
162
|
-
const key = this.
|
|
163
|
-
return ["$idx", key, `${propertyKey}(${value})`].map(String).join(":");
|
|
181
|
+
const key = `${this.collectionName}:${id}`;
|
|
182
|
+
return this.prefix(["$idx", key, `${propertyKey}(${value})`].map(String).join(":"));
|
|
164
183
|
}
|
|
165
184
|
async getAll() {
|
|
166
|
-
const keys = await this.db.
|
|
167
|
-
const
|
|
168
|
-
keys.forEach((key) => {
|
|
169
|
-
pipeline.get(key);
|
|
170
|
-
});
|
|
171
|
-
const result = await pipeline.exec();
|
|
185
|
+
const keys = (await this.callCmd(this.db, ["keys"], this.prefix(`${this.collectionName}:*`))).sort();
|
|
186
|
+
const result = await Promise.all(keys.map((key) => this.callCmd(this.db, ["get"], key)));
|
|
172
187
|
return (result || [])
|
|
173
|
-
.map((
|
|
174
|
-
return typeof data === "string" ? this.deserialize(JSON.parse(data)) : undefined;
|
|
175
|
-
})
|
|
188
|
+
.map((data) => (typeof data === "string" ? this.deserialize(JSON.parse(data)) : undefined))
|
|
176
189
|
.filter(Boolean);
|
|
177
190
|
}
|
|
178
191
|
async findAllBy(props) {
|
|
@@ -182,7 +195,10 @@ let RedisAdapter = class RedisAdapter extends Adapter {
|
|
|
182
195
|
return [];
|
|
183
196
|
}
|
|
184
197
|
const map = foundKeys.reduce((map, key) => {
|
|
185
|
-
const id =
|
|
198
|
+
const id = this.extractIdFromIndexedKey(key);
|
|
199
|
+
if (!id) {
|
|
200
|
+
return map;
|
|
201
|
+
}
|
|
186
202
|
const value = map.get(id) || 0;
|
|
187
203
|
return map.set(id, value + 1);
|
|
188
204
|
}, new Map());
|
|
@@ -193,8 +209,6 @@ let RedisAdapter = class RedisAdapter extends Adapter {
|
|
|
193
209
|
};
|
|
194
210
|
RedisAdapter = __decorate([
|
|
195
211
|
__param(0, Opts),
|
|
196
|
-
|
|
197
|
-
__param(2, Configuration()),
|
|
198
|
-
__metadata("design:paramtypes", [Object, Array, Object])
|
|
212
|
+
__metadata("design:paramtypes", [Object])
|
|
199
213
|
], RedisAdapter);
|
|
200
214
|
export { RedisAdapter };
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { AdapterModel } from "@tsed/adapters";
|
|
2
|
-
import { Configuration } from "@tsed/di";
|
|
3
|
-
import { IORedis } from "@tsed/ioredis";
|
|
4
|
-
import { ChainableCommander } from "ioredis";
|
|
5
2
|
import { RedisAdapter, RedisAdapterConstructorOptions } from "./RedisAdapter.js";
|
|
3
|
+
type ChainableCommander = any;
|
|
6
4
|
export declare class OIDCRedisAdapter<T extends AdapterModel> extends RedisAdapter<T> {
|
|
7
|
-
protected configuration: Configuration;
|
|
8
5
|
protected isGrantable: boolean;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
protected grantKeyFor(id: string): string;
|
|
7
|
+
protected userCodeKeyFor(userCode: string): string;
|
|
8
|
+
protected uidKeyFor(uid: string): string;
|
|
9
|
+
constructor(options: RedisAdapterConstructorOptions);
|
|
10
|
+
onInsert(multi: ChainableCommander, payload: T, expiresIn: number): Promise<any>;
|
|
11
|
+
findByUid(uid: string): Promise<any>;
|
|
12
|
+
findByUserCode(userCode: string): Promise<any>;
|
|
13
13
|
destroy(id: string): Promise<void>;
|
|
14
14
|
revokeByGrantId(grantId: string): Promise<void>;
|
|
15
15
|
consume(id: string): Promise<void>;
|
|
16
16
|
}
|
|
17
|
+
export {};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Adapter, AdapterConstructorOptions, AdapterModel } from "@tsed/adapters";
|
|
2
|
-
import { Configuration } from "@tsed/di";
|
|
3
2
|
import { Hooks } from "@tsed/hooks";
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import { RedisConnection } from "@tsed/redis";
|
|
4
|
+
type Redis = any;
|
|
6
5
|
export interface RedisAdapterConstructorOptions extends AdapterConstructorOptions {
|
|
7
6
|
connectionName?: string;
|
|
7
|
+
keyPrefix?: string;
|
|
8
8
|
useHash?: boolean;
|
|
9
9
|
}
|
|
10
10
|
export declare class RedisAdapter<Model extends AdapterModel> extends Adapter<Model> {
|
|
11
|
-
protected configuration: Configuration;
|
|
12
11
|
readonly hooks: Hooks;
|
|
13
12
|
readonly connectionName: string;
|
|
14
|
-
readonly connection:
|
|
13
|
+
readonly connection: RedisConnection;
|
|
14
|
+
readonly keyPrefix: string;
|
|
15
15
|
protected useHash: boolean;
|
|
16
|
-
constructor(options: RedisAdapterConstructorOptions
|
|
16
|
+
constructor(options: RedisAdapterConstructorOptions);
|
|
17
17
|
get db(): Redis;
|
|
18
18
|
key(id: string): string;
|
|
19
19
|
create(payload: Partial<Model>, expiresAt?: Date): Promise<Model>;
|
|
@@ -26,11 +26,14 @@ export declare class RedisAdapter<Model extends AdapterModel> extends Adapter<Mo
|
|
|
26
26
|
deleteOne(predicate: Partial<Model & any>): Promise<Model | undefined>;
|
|
27
27
|
deleteById(_id: string): Promise<Model | undefined>;
|
|
28
28
|
deleteMany(predicate: Partial<Model>): Promise<Model[]>;
|
|
29
|
+
prefix(key: string): string;
|
|
30
|
+
protected callCmd(target: any, methods: string[], ...args: any[]): any;
|
|
31
|
+
protected extractIdFromIndexedKey(key: string): string | undefined;
|
|
29
32
|
protected findKeys(props: any): Promise<string[]>;
|
|
30
33
|
protected insert(payload: Partial<Model>, expiresAt?: Date): Promise<any>;
|
|
31
34
|
protected getAllIndex(id: string): Promise<string[]>;
|
|
32
|
-
protected getAllIndex(id: string, pipeline: ChainableCommander): ChainableCommander;
|
|
33
35
|
protected getIndexedKey(id: string, propertyKey: string, value: any): string;
|
|
34
36
|
protected getAll(): Promise<Model[]>;
|
|
35
37
|
protected findAllBy(props: Partial<Model & any>): Promise<Model[]>;
|
|
36
38
|
}
|
|
39
|
+
export {};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AdaptersSettings } from "@tsed/adapters";
|
|
2
|
-
import {
|
|
2
|
+
import { RedisClientOptions } from "redis";
|
|
3
3
|
declare global {
|
|
4
4
|
namespace TsED {
|
|
5
5
|
interface AdaptersOptions extends AdaptersSettings {
|
|
6
|
-
redis:
|
|
6
|
+
redis: RedisClientOptions & {
|
|
7
7
|
indexes?: {
|
|
8
8
|
[propertyKey: string]: Record<string, any>;
|
|
9
9
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@tsed/adapters-redis",
|
|
3
3
|
"description": "Redis adapter for Ts.ED",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "8.
|
|
5
|
+
"version": "8.29.0",
|
|
6
6
|
"source": "./src/index.ts",
|
|
7
7
|
"main": "./lib/esm/index.js",
|
|
8
8
|
"module": "./lib/esm/index.js",
|
|
@@ -23,16 +23,19 @@
|
|
|
23
23
|
"test:ci": "vitest run --coverage.thresholds.autoUpdate=true"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@tsed/adapters": "8.
|
|
27
|
-
"@tsed/
|
|
26
|
+
"@tsed/adapters": "8.29.0",
|
|
27
|
+
"@tsed/redis": "8.29.0",
|
|
28
28
|
"tslib": ">=2.7.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@
|
|
32
|
-
"@tsed/
|
|
33
|
-
"@tsed/
|
|
34
|
-
"@tsed/
|
|
31
|
+
"@testcontainers/redis": "^11.7.2",
|
|
32
|
+
"@tsed/barrels": "8.29.0",
|
|
33
|
+
"@tsed/core": "8.29.0",
|
|
34
|
+
"@tsed/di": "8.29.0",
|
|
35
|
+
"@tsed/hooks": "8.29.0",
|
|
36
|
+
"@tsed/typescript": "8.29.0",
|
|
35
37
|
"eslint": "9.12.0",
|
|
38
|
+
"testcontainers": "11.7.2",
|
|
36
39
|
"typescript": "6.0.2",
|
|
37
40
|
"vitest": "4.1.4"
|
|
38
41
|
},
|
|
@@ -42,8 +45,7 @@
|
|
|
42
45
|
"@tsed/di": ">=8.0.0",
|
|
43
46
|
"@tsed/hooks": ">=8.0.0",
|
|
44
47
|
"@tsed/platform-http": ">=8.0.0",
|
|
45
|
-
"
|
|
46
|
-
"ioredis-mock": ">=8.2.2",
|
|
48
|
+
"redis": ">=5.1.0",
|
|
47
49
|
"uuid": "^14.0.0"
|
|
48
50
|
},
|
|
49
51
|
"repository": "https://github.com/tsedio/tsed",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {RedisContainer, StartedRedisContainer} from "@testcontainers/redis";
|
|
2
|
+
|
|
3
|
+
const KEY = "TEST_CONTAINER_REDIS";
|
|
4
|
+
|
|
5
|
+
function getGlobal<T>(key: string): T | null {
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
return global[key];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function setGlobal(key: string, environment: unknown) {
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
global[key] = environment;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class TestContainersRedis {
|
|
16
|
+
static createContainer(image = "redis:8.0.1") {
|
|
17
|
+
return new RedisContainer(image).start();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static async startContainer(image?: string) {
|
|
21
|
+
// await stopMockServer()
|
|
22
|
+
const container = getGlobal<StartedRedisContainer>(KEY) || (await this.createContainer(image));
|
|
23
|
+
|
|
24
|
+
setGlobal(KEY, container);
|
|
25
|
+
|
|
26
|
+
process.env.REDIS_URL = container.getConnectionUrl();
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
container,
|
|
30
|
+
url: process.env.REDIS_URL
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static async stopContainer() {
|
|
35
|
+
const container = getGlobal<StartedRedisContainer>(KEY);
|
|
36
|
+
|
|
37
|
+
process.stdout.write("Stopping redis database\n");
|
|
38
|
+
|
|
39
|
+
if (container) {
|
|
40
|
+
await this.reset();
|
|
41
|
+
await container.stop();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static async reset() {
|
|
46
|
+
const container = getGlobal<StartedRedisContainer>(KEY);
|
|
47
|
+
await container?.exec("redis-cli flushall");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static getUrl(): string {
|
|
51
|
+
return process.env.REDIS_URL as string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static getRedisOptions() {
|
|
55
|
+
const url = new URL(this.getUrl());
|
|
56
|
+
return {
|
|
57
|
+
host: url.hostname,
|
|
58
|
+
password: url.password,
|
|
59
|
+
port: url.port ? Number(url.port) : 6379,
|
|
60
|
+
username: url.username,
|
|
61
|
+
tls:
|
|
62
|
+
url.protocol === "rediss:"
|
|
63
|
+
? {
|
|
64
|
+
rejectUnauthorized: false
|
|
65
|
+
}
|
|
66
|
+
: undefined
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|