@desmat/redis-store 1.3.0 → 1.4.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 +49 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +12 -3
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ Leans into Redis’ strong suits to bring relational aspects to the simple but p
|
|
|
12
12
|
|
|
13
13
|
Plays well with Upstash and Vercel but will work with any Redis instance via REST API.
|
|
14
14
|
|
|
15
|
+
Also ships with `MemoryStore`, a pure in-memory implementation of `RedisStore` — a drop-in swap for local development and tests that don't need (or want) a live Redis instance. See [In-memory store](#in-memory-store) below.
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
## Installation
|
|
17
19
|
|
|
@@ -171,3 +173,50 @@ await Promise.all([
|
|
|
171
173
|
// ZREM testthings:user:<USER_UUID> <UUID>
|
|
172
174
|
// ...
|
|
173
175
|
```
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
## In-memory store
|
|
179
|
+
|
|
180
|
+
`MemoryStore<T>` implements the exact same `Store<T>` interface as `RedisStore<T>` but keeps everything in plain JS `Map`s — no Redis connection, no environment variables, no network calls. Application code written against `Store<T>` can swap between the two without any changes.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { MemoryStore } from '@desmat/redis-store';
|
|
184
|
+
|
|
185
|
+
const store = {
|
|
186
|
+
users: new MemoryStore<User>({ key: "user" }),
|
|
187
|
+
things: new MemoryStore<Thing>({ key: "thing", options: ThingOptions }),
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const user = await store.users.create({ name: "User One" });
|
|
191
|
+
const things = await store.things.find({ user: user.id });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Useful for:
|
|
195
|
+
- Local development without provisioning a real Redis instance
|
|
196
|
+
- Test suites that need fast, isolated, disposable data (each `MemoryStore` instance is its own private state — nothing persists between processes, and nothing is shared unless you share the instance)
|
|
197
|
+
|
|
198
|
+
Constructor accepts an optional `seed` array to pre-populate records (and their lookup indices) at construction time, so seeded data is immediately queryable via `find`/`ids` without needing to call `.create()` for each one:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const things = new MemoryStore<Thing>({
|
|
202
|
+
key: "thing",
|
|
203
|
+
options: ThingOptions,
|
|
204
|
+
seed: [
|
|
205
|
+
{ id: "thing-1", createdAt: Date.now(), createdBy: "user-1", label: "Seeded thing" },
|
|
206
|
+
],
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Notes on parity with `RedisStore`:
|
|
211
|
+
- `create`/`update`/`delete`/lookup/counter semantics are ported 1:1, including Redis's rank-based (not score-range) windowing for `count`/`offset` in `ids()`/`find()`, and `queryCounter`'s lexicographic range-bound behavior for `incCounters`/`queryCounter`.
|
|
212
|
+
- `options.expire` is a no-op (data is ephemeral for the life of the process anyway).
|
|
213
|
+
- There's no equivalent to `RedisStore`'s raw `.redis` client escape hatch — code that reaches through it directly for one-off scripts (rather than going through the `Store<T>` methods) isn't portable to `MemoryStore`.
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
## Testing
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
npm test
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Runs the `MemoryStore` unit test suite (`test/*.test.ts`) via Node's built-in test runner (through `tsx`) — no live Redis needed. `RedisStore` itself has no automated tests; it's a thin wrapper around `@upstash/redis`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { Redis } from "@upstash/redis";
|
|
2
|
+
import type { Store } from "./store";
|
|
2
3
|
export type RedisStoreRecord = {
|
|
3
4
|
id: string;
|
|
4
5
|
createdAt: number;
|
|
5
6
|
updatedAt?: number;
|
|
6
7
|
deletedAt?: number;
|
|
7
8
|
};
|
|
8
|
-
export
|
|
9
|
+
export { Store } from "./store";
|
|
10
|
+
export { default as MemoryStore } from "./memory-store";
|
|
11
|
+
export default class RedisStore<T extends RedisStoreRecord> implements Store<T> {
|
|
9
12
|
redis: Redis;
|
|
10
13
|
key: string;
|
|
11
14
|
setKey: string;
|
|
@@ -37,7 +40,7 @@ export default class RedisStore<T extends RedisStoreRecord> {
|
|
|
37
40
|
lookups?: any;
|
|
38
41
|
}): Promise<T>;
|
|
39
42
|
update(value: any, options?: any): Promise<T>;
|
|
40
|
-
|
|
43
|
+
incCounters(values: Record<string, string | number>, delta: {
|
|
41
44
|
total: number;
|
|
42
45
|
count: number;
|
|
43
46
|
}): Promise<any>;
|
package/dist/index.js
CHANGED
|
@@ -19,9 +19,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
19
19
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
20
20
|
};
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.MemoryStore = void 0;
|
|
22
23
|
const moment_1 = __importDefault(require("moment"));
|
|
23
24
|
const utils_1 = require("@desmat/utils");
|
|
24
25
|
const redis_1 = require("@upstash/redis");
|
|
26
|
+
var memory_store_1 = require("./memory-store");
|
|
27
|
+
Object.defineProperty(exports, "MemoryStore", { enumerable: true, get: function () { return __importDefault(memory_store_1).default; } });
|
|
25
28
|
// polyfill Set.intersection
|
|
26
29
|
;
|
|
27
30
|
(function () {
|
|
@@ -260,10 +263,16 @@ class RedisStore {
|
|
|
260
263
|
this.debug && console.log(`RedisStore<${this.key}>.update`, { response });
|
|
261
264
|
return updatedValue;
|
|
262
265
|
}
|
|
263
|
-
async
|
|
266
|
+
async incCounters(values, delta) {
|
|
264
267
|
var _a;
|
|
265
|
-
this.debug && console.log(`RedisStore<${this.key}>.
|
|
268
|
+
this.debug && console.log(`RedisStore<${this.key}>.incCounters`, { values, delta });
|
|
266
269
|
const counters = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.counters) || [];
|
|
270
|
+
counters
|
|
271
|
+
.filter((counter) => !counter.split(":").every((d) => typeof values[d] != "undefined"))
|
|
272
|
+
.forEach((counter) => {
|
|
273
|
+
const missing = counter.split(":").filter((d) => typeof values[d] == "undefined");
|
|
274
|
+
console.warn(`RedisStore<${this.key}>.incCounters WARNING: skipping counter "${counter}": missing dimension(s) ${missing.join(", ")} in values`, { values });
|
|
275
|
+
});
|
|
267
276
|
const responses = await Promise.all(counters
|
|
268
277
|
.filter((counter) => counter.split(":").every((d) => typeof values[d] != "undefined"))
|
|
269
278
|
.flatMap((counter) => {
|
|
@@ -273,7 +282,7 @@ class RedisStore {
|
|
|
273
282
|
this.redis.zincrby(`${this.key}Counts:${counter}`, delta.count, member),
|
|
274
283
|
];
|
|
275
284
|
}));
|
|
276
|
-
this.debug && console.log(`RedisStore<${this.key}>.
|
|
285
|
+
this.debug && console.log(`RedisStore<${this.key}>.incCounters`, { responses });
|
|
277
286
|
return responses;
|
|
278
287
|
}
|
|
279
288
|
async queryCounter(kind, counter, exact, range) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desmat/redis-store",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc",
|
|
16
16
|
"prepare": "tsc",
|
|
17
|
-
"example": "ts-node --skipProject ./src/example.ts"
|
|
17
|
+
"example": "ts-node --skipProject ./src/example.ts",
|
|
18
|
+
"test": "tsx --test test/**/*.test.ts"
|
|
18
19
|
},
|
|
19
20
|
"repository": {
|
|
20
21
|
"type": "git",
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@types/node": "^22.8.4",
|
|
40
|
+
"tsx": "^4.21.0",
|
|
39
41
|
"typescript": "^4.0.0"
|
|
40
42
|
}
|
|
41
43
|
}
|