@desmat/redis-store 1.2.0 → 1.3.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.
@@ -0,0 +1,60 @@
1
+ import { Redis } from "@upstash/redis";
2
+ export type RedisStoreRecord = {
3
+ id: string;
4
+ createdAt: number;
5
+ updatedAt?: number;
6
+ deletedAt?: number;
7
+ };
8
+ export default class RedisStore<T extends RedisStoreRecord> {
9
+ redis: Redis;
10
+ key: string;
11
+ setKey: string;
12
+ valueKey: (id: string) => string;
13
+ options: any;
14
+ debug: boolean;
15
+ constructor({ url, token, key, setKey, options, debug, }: {
16
+ key: string;
17
+ setKey?: string;
18
+ options?: any;
19
+ url?: string;
20
+ token?: string;
21
+ debug?: boolean;
22
+ });
23
+ lookupKeys(value: any, options?: {
24
+ noLookup?: boolean;
25
+ lookups?: any;
26
+ }): any[][];
27
+ exists(id: string): Promise<boolean>;
28
+ get(id: string, options?: any): Promise<T | undefined>;
29
+ scan(query?: any): Promise<Set<string>>;
30
+ ids(query?: any, options?: {
31
+ withScores?: boolean;
32
+ aggregate?: string;
33
+ }): Promise<Set<string>>;
34
+ find(query?: any): Promise<T[]>;
35
+ create(value: any, options?: {
36
+ expire?: number;
37
+ noIndex?: boolean;
38
+ score?: number;
39
+ noLookup?: boolean;
40
+ lookups?: any;
41
+ }): Promise<T>;
42
+ update(value: any, options?: any): Promise<T>;
43
+ incrementCounters(values: Record<string, string | number>, delta: {
44
+ total: number;
45
+ count: number;
46
+ }): Promise<any>;
47
+ queryCounter(kind: "count" | "counts" | "totals", dims: string[], exact: Record<string, string | number>, range?: {
48
+ field: string;
49
+ min?: string;
50
+ max?: string;
51
+ }): Promise<number | Array<{
52
+ member: string;
53
+ score: number;
54
+ }>>;
55
+ delete(id: string, options?: {
56
+ hardDelete?: boolean;
57
+ noLookup?: boolean;
58
+ lookups?: any;
59
+ }): Promise<T | undefined>;
60
+ }
@@ -0,0 +1,356 @@
1
+ "use strict";
2
+ /*
3
+ Some useful commands
4
+
5
+ keys *
6
+ scan 0 match thing:*
7
+ del thing1 thing2 etc
8
+ json.get things $
9
+ json.get things '$[?((@.deletedAt > 0) == false)]'
10
+ json.get things '$[?((@.deletedAt > 0) == true)]'
11
+ json.get things '$[?(@.createdBy == "UID")]'
12
+ json.get things '$[?(@.content ~= "(?i)lorem")]'
13
+ json.get things '$[?(@.id ~= "(ID1)|(ID2)")]
14
+ json.set thing:UUID '$.foos[5].bar' '{"car": 42}'
15
+ json.set thing:UUID '$.foos[1].bar.car' '42'
16
+ json.get userhaikus '$[?(@.haikuId == "ID" && (@.likedAt > 0) == true)]'
17
+ */
18
+ var __importDefault = (this && this.__importDefault) || function (mod) {
19
+ return (mod && mod.__esModule) ? mod : { "default": mod };
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ const moment_1 = __importDefault(require("moment"));
23
+ const utils_1 = require("@desmat/utils");
24
+ const redis_1 = require("@upstash/redis");
25
+ // polyfill Set.intersection
26
+ ;
27
+ (function () {
28
+ // @ts-ignore
29
+ if (!Set.prototype.intersection) {
30
+ // @ts-ignore
31
+ Set.prototype.intersection = function (other) {
32
+ return new Set(Array.from(this).filter((value) => other.has(value)));
33
+ };
34
+ }
35
+ })();
36
+ class RedisStore {
37
+ constructor({ url, token, key, setKey, options, debug, }) {
38
+ const _url = url || process.env.KV_REST_API_URL; //|| "https://crisp-skylark-47483.upstash.io";
39
+ const _token = token || process.env.KV_REST_API_TOKEN; //|| "Abl7AAIncDFiZDM3ZjVjOTEyMmI0MWYxOWRkYTlhMWVkYWQ3ZGRjN3AxNDc0ODM";
40
+ if (!_url)
41
+ throw 'Error creating RedisStore: `url` is required: either provide in constructor or via environment variable `KV_REST_API_URL`';
42
+ if (!_token)
43
+ throw 'Error creating RedisStore: `token` is required: either provide in constructor or via environment variable `KV_REST_API_TOKEN`';
44
+ this.redis = new redis_1.Redis({
45
+ url: _url,
46
+ token: _token
47
+ });
48
+ this.key = key;
49
+ this.setKey = setKey || key + "s";
50
+ this.valueKey = (id) => `${key}:${id}`;
51
+ this.options = options;
52
+ this.debug = !!debug;
53
+ }
54
+ lookupKeys(value, options) {
55
+ options = { ...this.options, ...options };
56
+ this.debug && console.log(`RedisStore.lookupKeys<${this.key}>.lookupKeys`, { value, options });
57
+ /*
58
+ create index and lookup sets based on options.lookups
59
+
60
+ given a likedhaiku record:
61
+ {
62
+ id: 123:456,
63
+ userId: 123,
64
+ haikuId 456,
65
+ }
66
+
67
+ and lookups:
68
+ {
69
+ user: { userId: "haikuId"},
70
+ haiku: { haikuId: "userId" }
71
+ }
72
+
73
+ we want indexes:
74
+
75
+ likedhaiku:123:456 -> value (JSON, the rest are sorted sets, not handled here)
76
+ likedhaikus -> all likedhaiku id's (ie 123:456, etc, not handled here)
77
+ // NOT SUPPORTED FOR NOW // likedhaikus:users -> all user ids (ie 123, etc) NOTE: this should be a sorted set of user ids with its score as number of haikus liked
78
+ likedhaikus:user:123 -> all likedhaiku id's for the given user (ie 123:456, etc)
79
+ // NOT SUPPORTED FOR NOW // likedhaikus:haikus -> NOTE: this should be a sorted set of haiku ids with its score as number of users who liked it
80
+ likedhaikus:haiku:456 -> all likedhaiku id's for the given haiku (ie 123:456, etc)
81
+
82
+ */
83
+ const lookupKeys = !(options === null || options === void 0 ? void 0 : options.noLookup) && Object
84
+ .entries((options === null || options === void 0 ? void 0 : options.lookups) || {})
85
+ .map((entry) => {
86
+ const id = value.id;
87
+ const lookupName = entry[0];
88
+ const lookupKey = entry[1];
89
+ // TODO validate and log errors
90
+ // @ts-ignore
91
+ const lookupId = value[lookupKey];
92
+ // foos:bar:123 -> 123:456
93
+ return [`${this.setKey}:${lookupName}:${lookupId}`, id];
94
+ }) || [];
95
+ this.debug && console.log(`RedisStore<${this.key}>.lookupKeys`, { options, lookupKeys });
96
+ return lookupKeys;
97
+ }
98
+ async exists(id) {
99
+ this.debug && console.log(`RedisStore<${this.key}>.exists`, { id });
100
+ const response = await this.redis.exists(this.valueKey(id));
101
+ this.debug && console.log(`RedisStore<${this.key}>.exists`, { response });
102
+ return response > 0;
103
+ }
104
+ async get(id, options) {
105
+ this.debug && console.log(`RedisStore<${this.key}>.get`, { id });
106
+ const response = await this.redis.json.get(this.valueKey(id), "$");
107
+ this.debug && console.log(`RedisStore<${this.key}>.get`, { response });
108
+ let value;
109
+ if (response && response[0] && !(response[0].deletedAt && !(options === null || options === void 0 ? void 0 : options.deleted))) {
110
+ value = response[0];
111
+ }
112
+ return value;
113
+ }
114
+ async scan(query = {}) {
115
+ this.debug && console.log(`RedisStore<${this.key}>.scan`, { query });
116
+ !query.count && console.warn(`RedisStore.RedisStore<${this.key}>.find WARNING: scan command with no count provided: setting count at 999`);
117
+ const count = query.count || 999;
118
+ const match = this.valueKey(query.scan);
119
+ let keys = new Set();
120
+ let nextCursor = "0";
121
+ do {
122
+ const ret = await this.redis.scan(nextCursor, { match, type: "json", count: count - keys.size });
123
+ this.debug && console.log(`RedisStore<${this.key}>.scan`, { ret });
124
+ nextCursor = ret[0];
125
+ ret[1].forEach((key) => keys.size < count && keys.add(key.substring(key.indexOf(':') + 1)));
126
+ } while (keys.size < count && nextCursor && nextCursor != "0");
127
+ this.debug && console.log(`RedisStore<${this.key}>.scan`, { keys });
128
+ return keys;
129
+ }
130
+ async ids(query = {}, options) {
131
+ this.debug && console.log(`RedisStore<${this.key}>.ids`, { query });
132
+ if (query.scan) {
133
+ return this.scan(query);
134
+ }
135
+ let count = query.count;
136
+ delete query.count;
137
+ if (typeof (count) != "undefined" && typeof (count) != "number") {
138
+ console.warn(`RedisStore.RedisStore<${this.key}>.id WARNING: query.count is not a number`);
139
+ count = undefined;
140
+ }
141
+ let offset = query.offset;
142
+ delete query.offset;
143
+ if (typeof (offset) != "undefined" && typeof (offset) != "number") {
144
+ console.warn(`RedisStore.RedisStore<${this.key}>.id WARNING: query.offset must be a number`);
145
+ offset = undefined;
146
+ }
147
+ const min = offset || 0;
148
+ const max = min + (count || 0) - 1;
149
+ const queryEntries = query && Object.entries(query);
150
+ if (options === null || options === void 0 ? void 0 : options.withScores) {
151
+ // const setOfIds = await Promise.all(
152
+ // queryEntries.map(([queryKey, queryVal]: [string, string]) => {
153
+ // if (queryVal) {
154
+ // // lookup keys via the foos:bar:123 lookup set
155
+ // return this.redis.zrange(`${this.setKey}:${queryKey}:${queryVal}`, min, max, { rev: true, withScores: !!options?.withScores });
156
+ // } else {
157
+ // throw `redis.find(query) query must have key and value`;
158
+ // }
159
+ // })
160
+ // );
161
+ const keys = queryEntries.map(([queryKey, queryVal]) => `${queryKey}:${queryVal}`);
162
+ const overallKey = `${this.setKey}:${keys.join(":")}`;
163
+ const zinterstoreRet = await this.redis.zinterstore(overallKey, keys.length, keys.map((k) => `${this.setKey}:${k}`), { aggregate: "min" });
164
+ const zrangeRet = await this.redis.zrange(overallKey, min, max, { rev: true, withScores: !!(options === null || options === void 0 ? void 0 : options.withScores) });
165
+ console.warn(`RedisStore.RedisStore<${this.key}>.ids`, { overallKey, keys, zinterstoreRet, zrangeRet });
166
+ // const ids = setOfIds.reduce((prev: Set<any> | undefined, curr: any[]) => {
167
+ // new Set(curr).intersection(prev || new Set(curr)), undefined)
168
+ // throw 'NOT IMPLEMENTED';
169
+ }
170
+ // .ids() or .ids({})
171
+ if (!(queryEntries === null || queryEntries === void 0 ? void 0 : queryEntries.length)) {
172
+ // get all keys via the index set
173
+ return new Set(await this.redis.zrange(`${this.setKey}`, min, max, { rev: true /* , withScores: !!options?.withScores */ }));
174
+ }
175
+ // .ids({ foo: "FOO", bar: "BAR", ... })
176
+ const setOfIds = await Promise.all(queryEntries.map(([queryKey, queryVal]) => {
177
+ if (queryVal) {
178
+ // lookup keys via the foos:bar:123 lookup set
179
+ return this.redis.zrange(`${this.setKey}:${queryKey}:${queryVal}`, min, max, { rev: true /* , withScores: !!options?.withScores */ });
180
+ }
181
+ else {
182
+ throw `redis.find(query) query must have key and value`;
183
+ }
184
+ }));
185
+ // @ts-ignore
186
+ const ids = setOfIds.reduce((prev, curr) => new Set(curr).intersection(prev || new Set(curr)), undefined);
187
+ this.debug && console.log(`RedisStore<${this.key}>.ids queried lookup key`, { query, setOfIds, ids });
188
+ return ids;
189
+ }
190
+ async find(query = {}) {
191
+ this.debug && console.log(`RedisStore<${this.key}>.find`, { query });
192
+ const keys = Array.isArray(query.id)
193
+ ? query.id
194
+ .map((id) => id && this.valueKey(id))
195
+ .filter(Boolean)
196
+ : Array.from(await this.ids(query))
197
+ // @ts-ignore
198
+ .map((key) => `${this.key}:${key}`);
199
+ if (keys.length > 100) {
200
+ console.warn(`RedisStore.RedisStore<${this.key}>.find WARNING: json.mget more than 100 values`, { keys });
201
+ }
202
+ else {
203
+ this.debug && console.log(`RedisStore<${this.key}>.find`, { keys });
204
+ }
205
+ // don't mget too many at once otherwise 💥
206
+ const blockSize = 256;
207
+ const blocks = keys && keys.length && Array
208
+ .apply(null, Array(Math.ceil(keys.length / blockSize)))
209
+ .map((v, block) => (keys || [])
210
+ .slice(blockSize * block, blockSize * (block + 1)));
211
+ this.debug && console.log(`RedisStore<${this.key}>.find`, { blocks });
212
+ const values = blocks && blocks.length > 0
213
+ ? (await Promise.all(blocks
214
+ .map(async (keys) => (await this.redis.json.mget(keys, "$"))
215
+ .flat())))
216
+ .flat()
217
+ .filter((value) => value && !value.deletedAt)
218
+ : [];
219
+ this.debug && console.log(`RedisStore<${this.key}>.find`, { values });
220
+ return values;
221
+ }
222
+ async create(value, options) {
223
+ this.debug && console.log(`RedisStore<${this.key}>.create`, { value, options, this_options: this.options });
224
+ const now = (0, moment_1.default)().valueOf();
225
+ options = { ...this.options, ...options };
226
+ const createdValue = {
227
+ id: value.id || (0, utils_1.uuid)(),
228
+ createdAt: value.createdAt || now,
229
+ ...value,
230
+ };
231
+ this.debug && console.log(`RedisStore<${this.key}>.create`, { createdValue });
232
+ const lookupKeys = this.lookupKeys(createdValue, options);
233
+ this.debug && console.log(`RedisStore<${this.key}>.create`, { lookupKeys });
234
+ const responses = await Promise.all([
235
+ this.redis.json.set(this.valueKey(createdValue.id), "$", createdValue),
236
+ (options === null || options === void 0 ? void 0 : options.expire) && this.redis.expire(this.valueKey(createdValue.id), options === null || options === void 0 ? void 0 : options.expire),
237
+ // !options?.noIndex && this.redis.zadd(this.setKey, { score: options?.score || createdValue.createdAt, member: createdValue.id }),
238
+ // ...(lookupKeys ? lookupKeys.map((lookupKey: any) => this.redis.zadd(lookupKey[0], { score: options?.score || createdValue.createdAt, member: lookupKey[1] })) : []),
239
+ ...(lookupKeys ? lookupKeys.map((lookupKey) => this.redis.zincrby(lookupKey[0], (options === null || options === void 0 ? void 0 : options.score) || createdValue.createdAt, lookupKey[1])) : []),
240
+ ]);
241
+ this.debug && console.log(`RedisStore<${this.key}>.create`, { responses });
242
+ return createdValue;
243
+ }
244
+ async update(value, options) {
245
+ this.debug && console.log(`RedisStore<${this.key}>.update`, { value, options });
246
+ if (!value.id) {
247
+ throw `Cannot update ${this.key}: null id`;
248
+ }
249
+ const prevValue = await this.get(value.id);
250
+ if (!prevValue) {
251
+ throw `Cannot update ${this.key}: does not exist: ${value.id}`;
252
+ }
253
+ const now = (0, moment_1.default)().valueOf();
254
+ options = { ...this.options, ...options };
255
+ const updatedValue = {
256
+ ...value,
257
+ updatedAt: now,
258
+ };
259
+ // optionally update lookups
260
+ // @ts-ignore
261
+ const prevLookupKeys = new Map(this.lookupKeys(prevValue, options));
262
+ // @ts-ignore
263
+ const lookupKeys = new Map(this.lookupKeys(updatedValue, options));
264
+ const lookupsToRemove = prevLookupKeys && Array.from(prevLookupKeys)
265
+ .filter(([k, v]) => !lookupKeys || lookupKeys.get(k) != v);
266
+ const lookupsToAdd = lookupKeys && Array.from(lookupKeys)
267
+ .filter(([k, v]) => !prevLookupKeys || prevLookupKeys.get(k) != v);
268
+ this.debug && console.log(`RedisStore<${this.key}>.update`, { prevLookupKeys, lookupKeys, prevLookupKeyMap: prevLookupKeys, keysToRemove: lookupsToRemove, keysToAdd: lookupsToAdd });
269
+ if (lookupsToRemove && lookupsToRemove.length) {
270
+ this.debug && console.log(`RedisStore<${this.key}>.update deleting previous lookup keys`, { prevLookupKeys });
271
+ const response = await Promise.all([
272
+ ...lookupsToRemove.map((lookupKey) => this.redis.zrem(lookupKey[0], lookupKey[1]))
273
+ ]);
274
+ this.debug && console.log(`RedisStore<${this.key}>.update deleted previous lookup keys`, { response });
275
+ }
276
+ const response = await Promise.all([
277
+ this.redis.json.set(this.valueKey(value.id), "$", updatedValue),
278
+ options.expire && this.redis.expire(this.valueKey(value.id), options.expire),
279
+ ...(lookupsToAdd ? lookupsToAdd.map((lookupKey) => this.redis.zadd(lookupKey[0], { score: updatedValue.createdAt || updatedValue.updatedAt, member: lookupKey[1] })) : []),
280
+ ]);
281
+ this.debug && console.log(`RedisStore<${this.key}>.update`, { response });
282
+ return updatedValue;
283
+ }
284
+ async incrementCounters(values, delta) {
285
+ var _a;
286
+ this.debug && console.log(`RedisStore<${this.key}>.incrementCounters`, { values, delta });
287
+ const counters = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.counters) || [];
288
+ const responses = await Promise.all(counters
289
+ .filter((dims) => dims.every((d) => typeof values[d] != "undefined"))
290
+ .flatMap((dims) => {
291
+ const member = dims.map((d) => `${d}=${values[d]}`).join(":");
292
+ return [
293
+ this.redis.zincrby(`${this.key}Totals:${dims.join(":")}`, delta.total, member),
294
+ this.redis.zincrby(`${this.key}Counts:${dims.join(":")}`, delta.count, member),
295
+ ];
296
+ }));
297
+ this.debug && console.log(`RedisStore<${this.key}>.incrementCounters`, { responses });
298
+ return responses;
299
+ }
300
+ async queryCounter(kind, dims, exact, range) {
301
+ this.debug && console.log(`RedisStore<${this.key}>.queryCounter`, { kind, dims, exact, range });
302
+ const setKey = `${this.key}${kind == "totals" ? "Totals" : "Counts"}:${dims.join(":")}`;
303
+ const prefix = dims
304
+ .filter((d) => typeof exact[d] != "undefined")
305
+ .map((d) => `${d}=${exact[d]}`)
306
+ .join(":");
307
+ // the trailing sentinel scopes the range to the "prefix:" family regardless of whether
308
+ // the range field itself is the last dim, matching how the original hand-rolled queries worked
309
+ const hasMore = dims.length > Object.keys(exact).length;
310
+ const minPrefix = (range === null || range === void 0 ? void 0 : range.min) != null ? `${prefix}${prefix ? ":" : ""}${range.field}=${range.min}` : prefix;
311
+ const maxPrefix = (range === null || range === void 0 ? void 0 : range.max) != null ? `${prefix}${prefix ? ":" : ""}${range.field}=${range.max}` : prefix;
312
+ const min = `[${minPrefix}${hasMore ? ":\x00" : ""}`;
313
+ const max = `[${maxPrefix}${hasMore ? ":\x7f" : ""}`;
314
+ this.debug && console.log(`RedisStore<${this.key}>.queryCounter`, { setKey, min, max });
315
+ if (kind == "count") {
316
+ const count = await this.redis.zlexcount(setKey, min, max);
317
+ this.debug && console.log(`RedisStore<${this.key}>.queryCounter`, { count });
318
+ return count;
319
+ }
320
+ const raw = await this.redis.zrange(setKey, min, max, { byLex: true, withScores: true });
321
+ this.debug && console.log(`RedisStore<${this.key}>.queryCounter`, { raw });
322
+ // members are returned as-is ("field=value:field=value...") rather than parsed into an
323
+ // object: some field values (eg namespaced ids) may themselves contain the ":" delimiter,
324
+ // which only the caller can safely account for when splitting a member back into fields
325
+ const results = [];
326
+ for (let i = 0; i < raw.length / 2; i++) {
327
+ results.push({ member: `${raw[2 * i]}`, score: raw[2 * i + 1] });
328
+ }
329
+ this.debug && console.log(`RedisStore<${this.key}>.queryCounter`, { results });
330
+ return results;
331
+ }
332
+ async delete(id, options) {
333
+ this.debug && console.log(`RedisStore<${this.key}>.delete`, { id, options });
334
+ if (!id) {
335
+ throw `Cannot delete ${this.key}: null id`;
336
+ }
337
+ options = { ...this.options, ...options };
338
+ const value = await this.get(id, { deleted: true });
339
+ if (!value) {
340
+ console.warn(`RedisStore<${this.key}>.delete WARNING: does not exist: ${id}`);
341
+ }
342
+ const lookupKeys = value && this.lookupKeys(value, options);
343
+ this.debug && console.log(`RedisStore<${this.key}>.delete`, { lookupKeys });
344
+ const deletedAt = (0, moment_1.default)().valueOf();
345
+ const response = await Promise.all([
346
+ (options === null || options === void 0 ? void 0 : options.hardDelete)
347
+ ? this.redis.json.del(this.valueKey(id), "$")
348
+ : this.redis.json.set(this.valueKey(id), "$.deletedAt", deletedAt),
349
+ this.redis.zrem(this.setKey, id),
350
+ ...(lookupKeys ? lookupKeys.map((lookupKey) => this.redis.zrem(lookupKey[0], lookupKey[1])) : []),
351
+ ]);
352
+ this.debug && console.log(`RedisStore<${this.key}>.delete`, { response });
353
+ return value ? { ...value, deletedAt } : undefined;
354
+ }
355
+ }
356
+ exports.default = RedisStore;
package/dist/index.d.ts CHANGED
@@ -20,13 +20,38 @@ export default class RedisStore<T extends RedisStoreRecord> {
20
20
  token?: string;
21
21
  debug?: boolean;
22
22
  });
23
- lookupKeys(value: any, options?: any): any[][];
23
+ lookupKeys(value: any, options?: {
24
+ noLookup?: boolean;
25
+ lookups?: any;
26
+ }): any[][];
24
27
  exists(id: string): Promise<boolean>;
25
28
  get(id: string, options?: any): Promise<T | undefined>;
26
29
  scan(query?: any): Promise<Set<string>>;
27
30
  ids(query?: any): Promise<Set<string>>;
28
31
  find(query?: any): Promise<T[]>;
29
- create(value: any, options?: any): Promise<T>;
32
+ create(value: any, options?: {
33
+ expire?: number;
34
+ noIndex?: boolean;
35
+ score?: number;
36
+ noLookup?: boolean;
37
+ lookups?: any;
38
+ }): Promise<T>;
30
39
  update(value: any, options?: any): Promise<T>;
31
- delete(id: string, options?: any): Promise<T | undefined>;
40
+ incrementCounters(values: Record<string, string | number>, delta: {
41
+ total: number;
42
+ count: number;
43
+ }): Promise<any>;
44
+ queryCounter(kind: "count" | "counts" | "totals", counter: string, exact: Record<string, string | number>, range?: {
45
+ field: string;
46
+ min?: string;
47
+ max?: string;
48
+ }): Promise<number | Array<{
49
+ member: string;
50
+ score: number;
51
+ }>>;
52
+ delete(id: string, options?: {
53
+ hardDelete?: boolean;
54
+ noLookup?: boolean;
55
+ lookups?: any;
56
+ }): Promise<T | undefined>;
32
57
  }
package/dist/index.js CHANGED
@@ -135,13 +135,13 @@ class RedisStore {
135
135
  let count = query.count;
136
136
  delete query.count;
137
137
  if (typeof (count) != "undefined" && typeof (count) != "number") {
138
- console.warn(`RedisStore.RedisStore<${this.key}>.id WARNING: query.count is not a numberXX`);
138
+ console.warn(`RedisStore.RedisStore<${this.key}>.id WARNING: query.count is not a number`);
139
139
  count = undefined;
140
140
  }
141
141
  let offset = query.offset;
142
142
  delete query.offset;
143
143
  if (typeof (offset) != "undefined" && typeof (offset) != "number") {
144
- console.warn(`RedisStore.RedisStore<${this.key}>.id WARNING: query.offset must be a numberYY`);
144
+ console.warn(`RedisStore.RedisStore<${this.key}>.id WARNING: query.offset must be a number`);
145
145
  offset = undefined;
146
146
  }
147
147
  const min = offset || 0;
@@ -213,8 +213,8 @@ class RedisStore {
213
213
  this.debug && console.log(`RedisStore<${this.key}>.create`, { lookupKeys });
214
214
  const responses = await Promise.all([
215
215
  this.redis.json.set(this.valueKey(createdValue.id), "$", createdValue),
216
- options.expire && this.redis.expire(this.valueKey(createdValue.id), options.expire),
217
- !options.noIndex && this.redis.zadd(this.setKey, { score: createdValue.createdAt, member: createdValue.id }),
216
+ (options === null || options === void 0 ? void 0 : options.expire) && this.redis.expire(this.valueKey(createdValue.id), options === null || options === void 0 ? void 0 : options.expire),
217
+ !(options === null || options === void 0 ? void 0 : options.noIndex) && this.redis.zadd(this.setKey, { score: createdValue.createdAt, member: createdValue.id }),
218
218
  ...(lookupKeys ? lookupKeys.map((lookupKey) => this.redis.zadd(lookupKey[0], { score: createdValue.createdAt, member: lookupKey[1] })) : []),
219
219
  ]);
220
220
  this.debug && console.log(`RedisStore<${this.key}>.create`, { responses });
@@ -260,7 +260,56 @@ class RedisStore {
260
260
  this.debug && console.log(`RedisStore<${this.key}>.update`, { response });
261
261
  return updatedValue;
262
262
  }
263
- async delete(id, options = {}) {
263
+ async incrementCounters(values, delta) {
264
+ var _a;
265
+ this.debug && console.log(`RedisStore<${this.key}>.incrementCounters`, { values, delta });
266
+ const counters = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.counters) || [];
267
+ const responses = await Promise.all(counters
268
+ .filter((counter) => counter.split(":").every((d) => typeof values[d] != "undefined"))
269
+ .flatMap((counter) => {
270
+ const member = counter.split(":").map((d) => `${d}=${values[d]}`).join(":");
271
+ return [
272
+ this.redis.zincrby(`${this.key}Totals:${counter}`, delta.total, member),
273
+ this.redis.zincrby(`${this.key}Counts:${counter}`, delta.count, member),
274
+ ];
275
+ }));
276
+ this.debug && console.log(`RedisStore<${this.key}>.incrementCounters`, { responses });
277
+ return responses;
278
+ }
279
+ async queryCounter(kind, counter, exact, range) {
280
+ this.debug && console.log(`RedisStore<${this.key}>.queryCounter`, { kind, counter, exact, range });
281
+ const dims = counter.split(":");
282
+ const setKey = `${this.key}${kind == "totals" ? "Totals" : "Counts"}:${counter}`;
283
+ const prefix = dims
284
+ .filter((d) => typeof exact[d] != "undefined")
285
+ .map((d) => `${d}=${exact[d]}`)
286
+ .join(":");
287
+ // the trailing sentinel scopes the range to the "prefix:" family regardless of whether
288
+ // the range field itself is the last dim, matching how the original hand-rolled queries worked
289
+ const hasMore = dims.length > Object.keys(exact).length;
290
+ const minPrefix = (range === null || range === void 0 ? void 0 : range.min) != null ? `${prefix}${prefix ? ":" : ""}${range.field}=${range.min}` : prefix;
291
+ const maxPrefix = (range === null || range === void 0 ? void 0 : range.max) != null ? `${prefix}${prefix ? ":" : ""}${range.field}=${range.max}` : prefix;
292
+ const min = `[${minPrefix}${hasMore ? ":\x00" : ""}`;
293
+ const max = `[${maxPrefix}${hasMore ? ":\x7f" : ""}`;
294
+ this.debug && console.log(`RedisStore<${this.key}>.queryCounter`, { setKey, min, max });
295
+ if (kind == "count") {
296
+ const count = await this.redis.zlexcount(setKey, min, max);
297
+ this.debug && console.log(`RedisStore<${this.key}>.queryCounter`, { count });
298
+ return count;
299
+ }
300
+ const raw = await this.redis.zrange(setKey, min, max, { byLex: true, withScores: true });
301
+ this.debug && console.log(`RedisStore<${this.key}>.queryCounter`, { raw });
302
+ // members are returned as-is ("field=value:field=value...") rather than parsed into an
303
+ // object: some field values (eg namespaced ids) may themselves contain the ":" delimiter,
304
+ // which only the caller can safely account for when splitting a member back into fields
305
+ const results = [];
306
+ for (let i = 0; i < raw.length / 2; i++) {
307
+ results.push({ member: `${raw[2 * i]}`, score: raw[2 * i + 1] });
308
+ }
309
+ this.debug && console.log(`RedisStore<${this.key}>.queryCounter`, { results });
310
+ return results;
311
+ }
312
+ async delete(id, options) {
264
313
  this.debug && console.log(`RedisStore<${this.key}>.delete`, { id, options });
265
314
  if (!id) {
266
315
  throw `Cannot delete ${this.key}: null id`;
@@ -274,7 +323,7 @@ class RedisStore {
274
323
  this.debug && console.log(`RedisStore<${this.key}>.delete`, { lookupKeys });
275
324
  const deletedAt = (0, moment_1.default)().valueOf();
276
325
  const response = await Promise.all([
277
- options.hardDelete
326
+ (options === null || options === void 0 ? void 0 : options.hardDelete)
278
327
  ? this.redis.json.del(this.valueKey(id), "$")
279
328
  : this.redis.json.set(this.valueKey(id), "$.deletedAt", deletedAt),
280
329
  this.redis.zrem(this.setKey, id),
@@ -0,0 +1,54 @@
1
+ import type { RedisStoreRecord } from "./index";
2
+ import type { Store } from "./store";
3
+ export default class MemoryStore<T extends RedisStoreRecord> implements Store<T> {
4
+ key: string;
5
+ setKey: string;
6
+ options: any;
7
+ debug: boolean;
8
+ private records;
9
+ private index;
10
+ private lookups;
11
+ private counters;
12
+ constructor({ key, setKey, options, debug, seed, }: {
13
+ key: string;
14
+ setKey?: string;
15
+ options?: any;
16
+ debug?: boolean;
17
+ seed?: T[];
18
+ });
19
+ lookupKeys(value: any, options?: {
20
+ noLookup?: boolean;
21
+ lookups?: any;
22
+ }): any[][];
23
+ private _index;
24
+ exists(id: string): Promise<boolean>;
25
+ get(id: string, options?: any): Promise<T | undefined>;
26
+ scan(query?: any): Promise<Set<string>>;
27
+ ids(query?: any): Promise<Set<string>>;
28
+ find(query?: any): Promise<T[]>;
29
+ create(value: any, options?: {
30
+ expire?: number;
31
+ noIndex?: boolean;
32
+ score?: number;
33
+ noLookup?: boolean;
34
+ lookups?: any;
35
+ }): Promise<T>;
36
+ update(value: any, options?: any): Promise<T>;
37
+ incCounters(values: Record<string, string | number>, delta: {
38
+ total: number;
39
+ count: number;
40
+ }): Promise<any>;
41
+ queryCounter(kind: "count" | "counts" | "totals", counter: string, exact: Record<string, string | number>, range?: {
42
+ field: string;
43
+ min?: string;
44
+ max?: string;
45
+ }): Promise<number | Array<{
46
+ member: string;
47
+ score: number;
48
+ }>>;
49
+ delete(id: string, options?: {
50
+ hardDelete?: boolean;
51
+ noLookup?: boolean;
52
+ lookups?: any;
53
+ }): Promise<T | undefined>;
54
+ }
@@ -0,0 +1,279 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@desmat/utils");
4
+ // mimics a Redis sorted set: members ordered by score, ties broken by member
5
+ // string ascending (Redis zset default ordering), with ZRANGE's index semantics
6
+ // (negative indices count from the end, e.g. -1 is the last element).
7
+ class SortedIdList {
8
+ constructor() {
9
+ this.entries = [];
10
+ }
11
+ add(id, score) {
12
+ this.remove(id);
13
+ this.entries.push({ id, score });
14
+ }
15
+ remove(id) {
16
+ this.entries = this.entries.filter((e) => e.id !== id);
17
+ }
18
+ sorted() {
19
+ return [...this.entries].sort((a, b) => a.score - b.score || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
20
+ }
21
+ range(min, max, rev) {
22
+ const ordered = rev ? this.sorted().reverse() : this.sorted();
23
+ const len = ordered.length;
24
+ const resolve = (i) => (i < 0 ? len + i : i);
25
+ const start = Math.max(resolve(min), 0);
26
+ const stop = Math.min(resolve(max), len - 1);
27
+ if (len === 0 || start > stop)
28
+ return [];
29
+ return ordered.slice(start, stop + 1).map((e) => e.id);
30
+ }
31
+ }
32
+ function globToRegExp(pattern) {
33
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
34
+ return new RegExp(`^${escaped}$`);
35
+ }
36
+ // intersect() polyfills Set.prototype.intersection so this module has no load-order
37
+ // dependency on index.ts's IIFE that installs it globally.
38
+ function intersect(a, b) {
39
+ return new Set(Array.from(a).filter((value) => b.has(value)));
40
+ }
41
+ class MemoryStore {
42
+ constructor({ key, setKey, options, debug, seed, }) {
43
+ this.records = new Map();
44
+ this.index = new SortedIdList();
45
+ this.lookups = new Map();
46
+ this.counters = new Map();
47
+ this.key = key;
48
+ this.setKey = setKey || key + "s";
49
+ this.options = options;
50
+ this.debug = !!debug;
51
+ (seed || []).forEach((value) => this._index(value));
52
+ }
53
+ lookupKeys(value, options) {
54
+ options = { ...this.options, ...options };
55
+ this.debug && console.log(`MemoryStore<${this.key}>.lookupKeys`, { value, options });
56
+ const lookupKeys = !(options === null || options === void 0 ? void 0 : options.noLookup) && Object
57
+ .entries((options === null || options === void 0 ? void 0 : options.lookups) || {})
58
+ .map((entry) => {
59
+ const id = value.id;
60
+ const lookupName = entry[0];
61
+ const lookupKey = entry[1];
62
+ // @ts-ignore
63
+ const lookupId = value[lookupKey];
64
+ return [`${this.setKey}:${lookupName}:${lookupId}`, id];
65
+ }) || [];
66
+ this.debug && console.log(`MemoryStore<${this.key}>.lookupKeys`, { options, lookupKeys });
67
+ return lookupKeys;
68
+ }
69
+ // shared by the constructor's `seed` and create(): adds a record to the main
70
+ // index and lookup indices without re-deriving id/createdAt defaults.
71
+ _index(value, options) {
72
+ this.records.set(value.id, value);
73
+ !(options === null || options === void 0 ? void 0 : options.noIndex) && this.index.add(value.id, value.createdAt);
74
+ const lookupKeys = this.lookupKeys(value, options);
75
+ (lookupKeys || []).forEach(([lookupKey, id]) => {
76
+ if (!this.lookups.has(lookupKey))
77
+ this.lookups.set(lookupKey, new SortedIdList());
78
+ this.lookups.get(lookupKey).add(id, value.createdAt);
79
+ });
80
+ }
81
+ async exists(id) {
82
+ this.debug && console.log(`MemoryStore<${this.key}>.exists`, { id });
83
+ return this.records.has(id);
84
+ }
85
+ async get(id, options) {
86
+ this.debug && console.log(`MemoryStore<${this.key}>.get`, { id });
87
+ const value = this.records.get(id);
88
+ return value && !(value.deletedAt && !(options === null || options === void 0 ? void 0 : options.deleted)) ? value : undefined;
89
+ }
90
+ async scan(query = {}) {
91
+ var _a;
92
+ this.debug && console.log(`MemoryStore<${this.key}>.scan`, { query });
93
+ const count = query.count || 999;
94
+ const pattern = globToRegExp((_a = query.scan) !== null && _a !== void 0 ? _a : "*");
95
+ const keys = new Set();
96
+ for (const id of this.records.keys()) {
97
+ if (keys.size >= count)
98
+ break;
99
+ if (pattern.test(id))
100
+ keys.add(id);
101
+ }
102
+ this.debug && console.log(`MemoryStore<${this.key}>.scan`, { keys });
103
+ return keys;
104
+ }
105
+ async ids(query = {}) {
106
+ this.debug && console.log(`MemoryStore<${this.key}>.ids`, { query });
107
+ if (query.scan) {
108
+ return this.scan(query);
109
+ }
110
+ let count = query.count;
111
+ delete query.count;
112
+ if (typeof (count) != "undefined" && typeof (count) != "number") {
113
+ console.warn(`MemoryStore<${this.key}>.ids WARNING: query.count is not a number`);
114
+ count = undefined;
115
+ }
116
+ let offset = query.offset;
117
+ delete query.offset;
118
+ if (typeof (offset) != "undefined" && typeof (offset) != "number") {
119
+ console.warn(`MemoryStore<${this.key}>.ids WARNING: query.offset must be a number`);
120
+ offset = undefined;
121
+ }
122
+ const min = offset || 0;
123
+ const max = min + (count || 0) - 1;
124
+ const queryEntries = query && Object.entries(query);
125
+ if (!(queryEntries === null || queryEntries === void 0 ? void 0 : queryEntries.length)) {
126
+ return new Set(this.index.range(min, max, true));
127
+ }
128
+ const setOfIds = queryEntries.map(([queryKey, queryVal]) => {
129
+ var _a;
130
+ if (queryVal) {
131
+ const lookupKey = `${this.setKey}:${queryKey}:${queryVal}`;
132
+ return new Set(((_a = this.lookups.get(lookupKey)) === null || _a === void 0 ? void 0 : _a.range(min, max, true)) || []);
133
+ }
134
+ else {
135
+ throw `MemoryStore.ids(query) query must have key and value`;
136
+ }
137
+ });
138
+ const ids = setOfIds.reduce((prev, curr) => intersect(curr, prev || curr), undefined) || new Set();
139
+ this.debug && console.log(`MemoryStore<${this.key}>.ids queried lookup key`, { query, setOfIds, ids });
140
+ return ids;
141
+ }
142
+ async find(query = {}) {
143
+ this.debug && console.log(`MemoryStore<${this.key}>.find`, { query });
144
+ const ids = Array.isArray(query.id)
145
+ ? query.id.filter(Boolean)
146
+ : Array.from(await this.ids(query));
147
+ const values = ids
148
+ .map((id) => this.records.get(id))
149
+ .filter((value) => !!value && !value.deletedAt);
150
+ this.debug && console.log(`MemoryStore<${this.key}>.find`, { values });
151
+ return values;
152
+ }
153
+ async create(value, options) {
154
+ this.debug && console.log(`MemoryStore<${this.key}>.create`, { value, options, this_options: this.options });
155
+ const now = Date.now();
156
+ options = { ...this.options, ...options };
157
+ const createdValue = {
158
+ id: value.id || (0, utils_1.uuid)(),
159
+ createdAt: value.createdAt || now,
160
+ ...value,
161
+ };
162
+ this.debug && console.log(`MemoryStore<${this.key}>.create`, { createdValue });
163
+ this._index(createdValue, options);
164
+ (options === null || options === void 0 ? void 0 : options.expire) && this.debug && console.log(`MemoryStore<${this.key}>.create ignoring options.expire (memory store is ephemeral)`);
165
+ return createdValue;
166
+ }
167
+ async update(value, options) {
168
+ this.debug && console.log(`MemoryStore<${this.key}>.update`, { value, options });
169
+ if (!value.id) {
170
+ throw `Cannot update ${this.key}: null id`;
171
+ }
172
+ const prevValue = await this.get(value.id);
173
+ if (!prevValue) {
174
+ throw `Cannot update ${this.key}: does not exist: ${value.id}`;
175
+ }
176
+ const now = Date.now();
177
+ options = { ...this.options, ...options };
178
+ const updatedValue = {
179
+ ...value,
180
+ updatedAt: now,
181
+ };
182
+ // @ts-ignore
183
+ const prevLookupKeys = new Map(this.lookupKeys(prevValue, options));
184
+ // @ts-ignore
185
+ const lookupKeys = new Map(this.lookupKeys(updatedValue, options));
186
+ const lookupsToRemove = prevLookupKeys && Array.from(prevLookupKeys)
187
+ .filter(([k, v]) => !lookupKeys || lookupKeys.get(k) != v);
188
+ const lookupsToAdd = lookupKeys && Array.from(lookupKeys)
189
+ .filter(([k, v]) => !prevLookupKeys || prevLookupKeys.get(k) != v);
190
+ this.debug && console.log(`MemoryStore<${this.key}>.update`, { prevLookupKeys, lookupKeys, keysToRemove: lookupsToRemove, keysToAdd: lookupsToAdd });
191
+ lookupsToRemove.forEach(([lookupKey, id]) => { var _a; return (_a = this.lookups.get(lookupKey)) === null || _a === void 0 ? void 0 : _a.remove(id); });
192
+ this.records.set(value.id, updatedValue);
193
+ lookupsToAdd.forEach(([lookupKey, id]) => {
194
+ if (!this.lookups.has(lookupKey))
195
+ this.lookups.set(lookupKey, new SortedIdList());
196
+ this.lookups.get(lookupKey).add(id, updatedValue.createdAt || updatedValue.updatedAt);
197
+ });
198
+ return updatedValue;
199
+ }
200
+ async incCounters(values, delta) {
201
+ var _a;
202
+ this.debug && console.log(`MemoryStore<${this.key}>.incCounters`, { values, delta });
203
+ const counters = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.counters) || [];
204
+ counters
205
+ .filter((counter) => !counter.split(":").every((d) => typeof values[d] != "undefined"))
206
+ .forEach((counter) => {
207
+ const missing = counter.split(":").filter((d) => typeof values[d] == "undefined");
208
+ console.warn(`MemoryStore<${this.key}>.incCounters WARNING: skipping counter "${counter}": missing dimension(s) ${missing.join(", ")} in values`, { values });
209
+ });
210
+ counters
211
+ .filter((counter) => counter.split(":").every((d) => typeof values[d] != "undefined"))
212
+ .forEach((counter) => {
213
+ const member = counter.split(":").map((d) => `${d}=${values[d]}`).join(":");
214
+ [
215
+ [`${this.key}Totals:${counter}`, delta.total],
216
+ [`${this.key}Counts:${counter}`, delta.count],
217
+ ].forEach(([setKey, d]) => {
218
+ if (!this.counters.has(setKey))
219
+ this.counters.set(setKey, new Map());
220
+ const counterMap = this.counters.get(setKey);
221
+ counterMap.set(member, (counterMap.get(member) || 0) + d);
222
+ });
223
+ });
224
+ }
225
+ async queryCounter(kind, counter, exact, range) {
226
+ this.debug && console.log(`MemoryStore<${this.key}>.queryCounter`, { kind, counter, exact, range });
227
+ const dims = counter.split(":");
228
+ const setKey = `${this.key}${kind == "totals" ? "Totals" : "Counts"}:${counter}`;
229
+ const counterMap = this.counters.get(setKey) || new Map();
230
+ const prefix = dims
231
+ .filter((d) => typeof exact[d] != "undefined")
232
+ .map((d) => `${d}=${exact[d]}`)
233
+ .join(":");
234
+ const hasMore = dims.length > Object.keys(exact).length;
235
+ const minPrefix = (range === null || range === void 0 ? void 0 : range.min) != null ? `${prefix}${prefix ? ":" : ""}${range.field}=${range.min}` : prefix;
236
+ const maxPrefix = (range === null || range === void 0 ? void 0 : range.max) != null ? `${prefix}${prefix ? ":" : ""}${range.field}=${range.max}` : prefix;
237
+ // matches RedisStore's `[`-prefixed (always inclusive) ZRANGEBYLEX bounds, minus the
238
+ // Redis-specific bound-type marker -- comparisons below are plain string comparisons.
239
+ const min = `${minPrefix}${hasMore ? ":\x00" : ""}`;
240
+ const max = `${maxPrefix}${hasMore ? ":\x7f" : ""}`;
241
+ this.debug && console.log(`MemoryStore<${this.key}>.queryCounter`, { setKey, min, max });
242
+ // sorted lexicographically to mirror ZRANGEBYLEX's byte-order member comparison
243
+ const members = Array.from(counterMap.keys()).sort();
244
+ const filtered = members.filter((m) => m >= min && m <= max);
245
+ if (kind == "count") {
246
+ this.debug && console.log(`MemoryStore<${this.key}>.queryCounter`, { count: filtered.length });
247
+ return filtered.length;
248
+ }
249
+ const results = filtered.map((member) => ({ member, score: counterMap.get(member) }));
250
+ this.debug && console.log(`MemoryStore<${this.key}>.queryCounter`, { results });
251
+ return results;
252
+ }
253
+ async delete(id, options) {
254
+ this.debug && console.log(`MemoryStore<${this.key}>.delete`, { id, options });
255
+ if (!id) {
256
+ throw `Cannot delete ${this.key}: null id`;
257
+ }
258
+ options = { ...this.options, ...options };
259
+ const value = await this.get(id, { deleted: true });
260
+ if (!value) {
261
+ console.warn(`MemoryStore<${this.key}>.delete WARNING: does not exist: ${id}`);
262
+ }
263
+ const lookupKeys = value && this.lookupKeys(value, options);
264
+ this.debug && console.log(`MemoryStore<${this.key}>.delete`, { lookupKeys });
265
+ const deletedAt = Date.now();
266
+ if (value) {
267
+ if (options === null || options === void 0 ? void 0 : options.hardDelete) {
268
+ this.records.delete(id);
269
+ }
270
+ else {
271
+ this.records.set(id, { ...value, deletedAt });
272
+ }
273
+ }
274
+ this.index.remove(id);
275
+ (lookupKeys || []).forEach(([lookupKey, lookupId]) => { var _a; return (_a = this.lookups.get(lookupKey)) === null || _a === void 0 ? void 0 : _a.remove(lookupId); });
276
+ return value ? { ...value, deletedAt } : undefined;
277
+ }
278
+ }
279
+ exports.default = MemoryStore;
@@ -0,0 +1,37 @@
1
+ import type { RedisStoreRecord } from "./index";
2
+ export interface Store<T extends RedisStoreRecord> {
3
+ key: string;
4
+ setKey: string;
5
+ options: any;
6
+ debug: boolean;
7
+ exists(id: string): Promise<boolean>;
8
+ get(id: string, options?: any): Promise<T | undefined>;
9
+ scan(query?: any): Promise<Set<string>>;
10
+ ids(query?: any): Promise<Set<string>>;
11
+ find(query?: any): Promise<T[]>;
12
+ create(value: any, options?: {
13
+ expire?: number;
14
+ noIndex?: boolean;
15
+ score?: number;
16
+ noLookup?: boolean;
17
+ lookups?: any;
18
+ }): Promise<T>;
19
+ update(value: any, options?: any): Promise<T>;
20
+ incCounters(values: Record<string, string | number>, delta: {
21
+ total: number;
22
+ count: number;
23
+ }): Promise<any>;
24
+ queryCounter(kind: "count" | "counts" | "totals", counter: string, exact: Record<string, string | number>, range?: {
25
+ field: string;
26
+ min?: string;
27
+ max?: string;
28
+ }): Promise<number | Array<{
29
+ member: string;
30
+ score: number;
31
+ }>>;
32
+ delete(id: string, options?: {
33
+ hardDelete?: boolean;
34
+ noLookup?: boolean;
35
+ lookups?: any;
36
+ }): Promise<T | undefined>;
37
+ }
package/dist/store.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desmat/redis-store",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {
@@ -13,6 +13,7 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "build": "tsc",
16
+ "prepare": "tsc",
16
17
  "example": "ts-node --skipProject ./src/example.ts"
17
18
  },
18
19
  "repository": {
@@ -30,7 +31,7 @@
30
31
  "license": "MIT",
31
32
  "dependencies": {
32
33
  "@desmat/utils": "^1.0.0",
33
- "@upstash/redis": "^1.34.3",
34
+ "@upstash/redis": "^1.38.0",
34
35
  "dotenv": "^16.4.5"
35
36
  },
36
37
  "devDependencies": {