@travetto/model-redis 2.0.3 → 2.1.2
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/README.md +2 -1
- package/package.json +4 -5
- package/src/config.ts +2 -1
- package/src/service.ts +53 -44
package/README.md
CHANGED
|
@@ -49,11 +49,12 @@ import { Field } from '@travetto/schema';
|
|
|
49
49
|
export class RedisModelConfig {
|
|
50
50
|
|
|
51
51
|
@Field(Object)
|
|
52
|
-
client: redis.
|
|
52
|
+
client: redis.RedisClientOptions = {};
|
|
53
53
|
namespace?: string;
|
|
54
54
|
autoCreate?: boolean;
|
|
55
55
|
|
|
56
56
|
postConstruct() {
|
|
57
|
+
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-redis",
|
|
3
3
|
"displayName": "Redis Model Support",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.1.2",
|
|
5
5
|
"description": "Redis backing for the travetto model module.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"typescript",
|
|
@@ -26,10 +26,9 @@
|
|
|
26
26
|
"directory": "module/model-redis"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/config": "^2.
|
|
30
|
-
"@travetto/model": "^2.
|
|
31
|
-
"
|
|
32
|
-
"redis": "^3.1.2"
|
|
29
|
+
"@travetto/config": "^2.1.2",
|
|
30
|
+
"@travetto/model": "^2.1.2",
|
|
31
|
+
"redis": "^4.1.0"
|
|
33
32
|
},
|
|
34
33
|
"publishConfig": {
|
|
35
34
|
"access": "public"
|
package/src/config.ts
CHANGED
|
@@ -7,10 +7,11 @@ import { Field } from '@travetto/schema';
|
|
|
7
7
|
export class RedisModelConfig {
|
|
8
8
|
|
|
9
9
|
@Field(Object)
|
|
10
|
-
client: redis.
|
|
10
|
+
client: redis.RedisClientOptions = {};
|
|
11
11
|
namespace?: string;
|
|
12
12
|
autoCreate?: boolean;
|
|
13
13
|
|
|
14
14
|
postConstruct() {
|
|
15
|
+
|
|
15
16
|
}
|
|
16
17
|
}
|
package/src/service.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as redis from 'redis';
|
|
2
|
-
import * as util from 'util';
|
|
3
2
|
|
|
4
3
|
import { Class, ShutdownManager, Util } from '@travetto/base';
|
|
5
4
|
import { DeepPartial } from '@travetto/schema';
|
|
@@ -17,6 +16,8 @@ import { ModelStorageUtil } from '@travetto/model/src/internal/service/storage';
|
|
|
17
16
|
import { RedisModelConfig } from './config';
|
|
18
17
|
|
|
19
18
|
type RedisScan = { key: string } | { match: string };
|
|
19
|
+
type RedisClient = ReturnType<typeof redis.createClient>;
|
|
20
|
+
type RedisMulti = ReturnType<RedisClient['multi']>;
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* A model service backed by redis
|
|
@@ -24,12 +25,10 @@ type RedisScan = { key: string } | { match: string };
|
|
|
24
25
|
@Injectable()
|
|
25
26
|
export class RedisModelService implements ModelCrudSupport, ModelExpirySupport, ModelStorageSupport, ModelIndexedSupport {
|
|
26
27
|
|
|
27
|
-
client:
|
|
28
|
+
client: RedisClient;
|
|
28
29
|
|
|
29
30
|
constructor(public readonly config: RedisModelConfig) { }
|
|
30
31
|
|
|
31
|
-
#wrap = <T>(fn: T): T => (fn as unknown as Function).bind(this.client) as T;
|
|
32
|
-
|
|
33
32
|
#resolveKey(cls: Class | string, id?: string, extra?: string) {
|
|
34
33
|
let key = typeof cls === 'string' ? cls : ModelRegistry.getStore(cls);
|
|
35
34
|
if (id) {
|
|
@@ -44,26 +43,27 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
44
43
|
return key;
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
async * #streamValues(op: 'scan' | '
|
|
48
|
-
let prevCursor
|
|
46
|
+
async * #streamValues(op: 'scan' | 'sScan' | 'zScan', search: RedisScan, count = 100): AsyncIterable<string[]> {
|
|
47
|
+
let prevCursor = 0;
|
|
49
48
|
let done = false;
|
|
50
49
|
|
|
51
|
-
const flags = 'match' in search ?
|
|
52
|
-
const key = 'key' in search ?
|
|
50
|
+
const flags = { COUNT: count, ...('match' in search ? { MATCH: search.match } : {}) };
|
|
51
|
+
const key = 'key' in search ? search.key : '';
|
|
53
52
|
|
|
54
53
|
while (!done) {
|
|
55
|
-
const [cursor, results] = await
|
|
56
|
-
|
|
54
|
+
const [cursor, results] = await (
|
|
55
|
+
op === 'scan' ?
|
|
56
|
+
this.client.scan(prevCursor, flags).then(x => [x.cursor, x.keys] as const) :
|
|
57
|
+
op === 'sScan' ?
|
|
58
|
+
this.client.sScan(key, prevCursor, flags).then(x => [x.cursor, x.members] as const) :
|
|
59
|
+
this.client.zScan(key, prevCursor, flags).then(x => [x.cursor, x.members.map(y => y.value)] as const)
|
|
57
60
|
);
|
|
61
|
+
|
|
58
62
|
prevCursor = cursor;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
yield results;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (cursor === '0') {
|
|
63
|
+
|
|
64
|
+
yield results;
|
|
65
|
+
|
|
66
|
+
if (cursor === 0) {
|
|
67
67
|
done = true;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -73,25 +73,25 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
73
73
|
return this.#streamValues('scan', { match: `${this.#resolveKey(prefix)}*` });
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
#removeIndices<T extends ModelType>(cls: Class, item: T, multi:
|
|
76
|
+
#removeIndices<T extends ModelType>(cls: Class, item: T, multi: RedisMulti) {
|
|
77
77
|
for (const idx of ModelRegistry.getIndices(cls, ['sorted', 'unsorted'])) {
|
|
78
78
|
const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, item);
|
|
79
79
|
const fullKey = this.#resolveKey(cls, idx.name, key);
|
|
80
80
|
switch (idx.type) {
|
|
81
|
-
case 'unsorted': multi.
|
|
82
|
-
case 'sorted': multi.
|
|
81
|
+
case 'unsorted': multi.sRem(fullKey, item.id); break;
|
|
82
|
+
case 'sorted': multi.zRem(fullKey, item.id); break;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
#addIndices<T extends ModelType>(cls: Class, item: T, multi:
|
|
87
|
+
#addIndices<T extends ModelType>(cls: Class, item: T, multi: RedisMulti) {
|
|
88
88
|
for (const idx of ModelRegistry.getIndices(cls, ['sorted', 'unsorted'])) {
|
|
89
89
|
const { key, sort } = ModelIndexedUtil.computeIndexKey(cls, idx, item);
|
|
90
90
|
const fullKey = this.#resolveKey(cls, idx.name, key);
|
|
91
91
|
|
|
92
92
|
switch (idx.type) {
|
|
93
|
-
case 'unsorted': multi.
|
|
94
|
-
case 'sorted': multi.
|
|
93
|
+
case 'unsorted': multi.sAdd(fullKey, item.id); break;
|
|
94
|
+
case 'sorted': multi.zAdd(fullKey, { score: +sort!, value: item.id }); break;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -119,15 +119,15 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
119
119
|
break;
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
await
|
|
122
|
+
await multi.exec();
|
|
123
123
|
} else {
|
|
124
124
|
switch (action) {
|
|
125
125
|
case 'write': {
|
|
126
|
-
await this
|
|
126
|
+
await this.client.set(key, JSON.stringify(item));
|
|
127
127
|
break;
|
|
128
128
|
}
|
|
129
129
|
case 'delete': {
|
|
130
|
-
const count = await this
|
|
130
|
+
const count = await this.client.del(this.#resolveKey(cls, item.id));
|
|
131
131
|
if (!count) {
|
|
132
132
|
throw new NotFoundError(cls, item.id);
|
|
133
133
|
}
|
|
@@ -140,11 +140,11 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
140
140
|
const expiry = ModelExpiryUtil.getExpiryState(cls, item);
|
|
141
141
|
if (expiry.expiresAt !== undefined) {
|
|
142
142
|
if (expiry.expiresAt) {
|
|
143
|
-
await this
|
|
143
|
+
await this.client.pExpireAt(
|
|
144
144
|
this.#resolveKey(cls, item.id), expiry.expiresAt.getTime()
|
|
145
145
|
);
|
|
146
146
|
} else {
|
|
147
|
-
await this
|
|
147
|
+
await this.client.persist(this.#resolveKey(cls, item.id));
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
}
|
|
@@ -158,11 +158,11 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
158
158
|
const fullKey = this.#resolveKey(cls, idxCfg.name, key);
|
|
159
159
|
let id: string | undefined;
|
|
160
160
|
if (idxCfg.type === 'unsorted') {
|
|
161
|
-
id = await this
|
|
161
|
+
id = (await this.client.sRandMember(fullKey))!;
|
|
162
162
|
} else {
|
|
163
|
-
const res =
|
|
163
|
+
const res = await this.client.zRangeByScore(
|
|
164
164
|
fullKey, +sort!, +sort!
|
|
165
|
-
)
|
|
165
|
+
);
|
|
166
166
|
id = res[0];
|
|
167
167
|
}
|
|
168
168
|
if (id) {
|
|
@@ -172,14 +172,15 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
async postConstruct() {
|
|
175
|
-
this.client =
|
|
175
|
+
this.client = redis.createClient(this.config.client);
|
|
176
|
+
await this.client.connect();
|
|
176
177
|
await ModelStorageUtil.registerModelChangeListener(this);
|
|
177
|
-
ShutdownManager.onShutdown(this.constructor.ᚕid, () => this.client.
|
|
178
|
+
ShutdownManager.onShutdown(this.constructor.ᚕid, () => this.client.disconnect());
|
|
178
179
|
for (const el of ModelRegistry.getClasses()) {
|
|
179
180
|
for (const idx of ModelRegistry.get(el).indices ?? []) {
|
|
180
181
|
switch (idx.type) {
|
|
181
182
|
case 'unique': {
|
|
182
|
-
console.error('Unique
|
|
183
|
+
console.error('Unique indices are not supported in redis for', { cls: el.ᚕid, idx: idx.name });
|
|
183
184
|
break;
|
|
184
185
|
}
|
|
185
186
|
}
|
|
@@ -192,7 +193,7 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
async has<T extends ModelType>(cls: Class<T>, id: string, error?: 'notfound' | 'data') {
|
|
195
|
-
const res = await this
|
|
196
|
+
const res = await this.client.exists(this.#resolveKey(cls, id));
|
|
196
197
|
if (res === 0 && error === 'notfound') {
|
|
197
198
|
throw new NotFoundError(cls, id);
|
|
198
199
|
} else if (res === 1 && error === 'data') {
|
|
@@ -201,7 +202,7 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
async get<T extends ModelType>(cls: Class<T>, id: string) {
|
|
204
|
-
const payload = await this
|
|
205
|
+
const payload = await this.client.get(this.#resolveKey(cls, id));
|
|
205
206
|
if (payload) {
|
|
206
207
|
const item = await ModelCrudUtil.load(cls, payload);
|
|
207
208
|
if (item) {
|
|
@@ -249,7 +250,11 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
249
250
|
async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
|
|
250
251
|
for await (const ids of this.#iterate(cls)) {
|
|
251
252
|
|
|
252
|
-
|
|
253
|
+
if (!ids.length) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const bodies = (await this.client.mGet(ids))
|
|
253
258
|
.filter(x => !!x) as string[];
|
|
254
259
|
|
|
255
260
|
for (const body of bodies) {
|
|
@@ -277,11 +282,11 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
277
282
|
|
|
278
283
|
async deleteStorage() {
|
|
279
284
|
if (!this.config.namespace) {
|
|
280
|
-
await this
|
|
285
|
+
await this.client.flushDb();
|
|
281
286
|
} else {
|
|
282
287
|
for await (const ids of this.#iterate('')) {
|
|
283
288
|
if (ids.length) {
|
|
284
|
-
await this
|
|
289
|
+
await this.client.del(ids);
|
|
285
290
|
}
|
|
286
291
|
}
|
|
287
292
|
}
|
|
@@ -290,7 +295,7 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
290
295
|
async truncateModel<T extends ModelType>(model: Class<T>) {
|
|
291
296
|
for await (const ids of this.#iterate(model)) {
|
|
292
297
|
if (ids.length) {
|
|
293
|
-
await this
|
|
298
|
+
await this.client.del(ids);
|
|
294
299
|
}
|
|
295
300
|
}
|
|
296
301
|
}
|
|
@@ -319,13 +324,17 @@ export class RedisModelService implements ModelCrudSupport, ModelExpirySupport,
|
|
|
319
324
|
const fullKey = this.#resolveKey(cls, idx, key);
|
|
320
325
|
|
|
321
326
|
if (idxCfg.type === 'unsorted') {
|
|
322
|
-
stream = this.#streamValues('
|
|
327
|
+
stream = this.#streamValues('sScan', { key: fullKey });
|
|
323
328
|
} else {
|
|
324
|
-
stream = this.#streamValues('
|
|
329
|
+
stream = this.#streamValues('zScan', { key: fullKey });
|
|
325
330
|
}
|
|
326
331
|
|
|
327
332
|
for await (const ids of stream) {
|
|
328
|
-
|
|
333
|
+
if (!ids.length) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const bodies = (await this.client.mGet(
|
|
329
338
|
ids.map(x => this.#resolveKey(cls, x))
|
|
330
339
|
))
|
|
331
340
|
.filter(x => !!x) as string[];
|