@travetto/model-redis 2.0.3 → 2.1.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/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.ClientOpts = {};
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.0.3",
4
+ "version": "2.1.0",
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.0.3",
30
- "@travetto/model": "^2.0.3",
31
- "@types/redis": "^2.8.29",
32
- "redis": "^3.1.2"
29
+ "@travetto/config": "^2.1.0",
30
+ "@travetto/model": "^2.1.0",
31
+ "redis": "^4.0.3"
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.ClientOpts = {};
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: redis.RedisClient;
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' | 'sscan' | 'zscan', search: RedisScan, count = 100): AsyncIterable<string[]> {
48
- let prevCursor: string | undefined;
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 ? ['MATCH', search.match] : [];
52
- const key = 'key' in search ? [search.key] : [];
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 this.#wrap(util.promisify(this.client[op]) as ((...rest: string[]) => Promise<[string, string[]]>))(
56
- ...key, prevCursor ?? '0', ...flags, 'COUNT', `${count}`
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
- if (results.length) {
60
- if (op === 'zscan') {
61
- yield results.filter((x, i) => i % 2 === 0); // Drop scores
62
- } else {
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: redis.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.srem(fullKey, item.id); break;
82
- case 'sorted': multi.zrem(fullKey, item.id); break;
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: redis.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.sadd(fullKey, item.id); break;
94
- case 'sorted': multi.zadd(fullKey, +sort!, item.id); break;
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 new Promise<void>((resolve, reject) => multi.exec(err => err ? reject(err) : resolve()));
122
+ await multi.exec();
123
123
  } else {
124
124
  switch (action) {
125
125
  case 'write': {
126
- await this.#wrap(util.promisify(this.client.set))(key, JSON.stringify(item));
126
+ await this.client.set(key, JSON.stringify(item));
127
127
  break;
128
128
  }
129
129
  case 'delete': {
130
- const count = await this.#wrap(util.promisify(this.client.del as (key2: string, cb: redis.Callback<number>) => void))(this.#resolveKey(cls, item.id));
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.#wrap(util.promisify(this.client.pexpireat))(
143
+ await this.client.pExpireAt(
144
144
  this.#resolveKey(cls, item.id), expiry.expiresAt.getTime()
145
145
  );
146
146
  } else {
147
- await this.#wrap(util.promisify(this.client.persist))(this.#resolveKey(cls, item.id));
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.#wrap(util.promisify(this.client.srandmember) as (k: string) => Promise<string>)(fullKey);
161
+ id = (await this.client.sRandMember(fullKey))!;
162
162
  } else {
163
- const res = (await this.#wrap(util.promisify(this.client.zrangebyscore) as (k: string, start: string | number, end: string | number, type?: string) => Promise<string[]>)(
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 = new redis.RedisClient(this.config.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.quit());
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 inidices are not supported in redis for', { cls: el.ᚕid, idx: idx.name });
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.#wrap(util.promisify(this.client.exists as (key: string, cb: redis.Callback<number>) => void))(this.#resolveKey(cls, id));
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.#wrap(util.promisify(this.client.get))(this.#resolveKey(cls, id));
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
- const bodies = (await this.#wrap(util.promisify(this.client.mget as (keys: string[], cb: redis.Callback<(string | null)[]>) => void))(ids))
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.#wrap(util.promisify(this.client.flushdb))();
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.#wrap(util.promisify(this.client.del) as (...keys: string[]) => Promise<number>)(...ids);
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.#wrap(util.promisify(this.client.del) as (...keys: string[]) => Promise<number>)(...ids);
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('sscan', { key: fullKey });
327
+ stream = this.#streamValues('sScan', { key: fullKey });
323
328
  } else {
324
- stream = this.#streamValues('zscan', { key: fullKey });
329
+ stream = this.#streamValues('zScan', { key: fullKey });
325
330
  }
326
331
 
327
332
  for await (const ids of stream) {
328
- const bodies = (await this.#wrap(util.promisify(this.client.mget as (keys: string[], cb: redis.Callback<(string | null)[]>) => void))(
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[];