deepbase-redis 1.2.2 → 3.0.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Martin Clasen
3
+ Copyright (c) 2023 mclasen
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,100 +1,211 @@
1
- # 🌳 DeepBase Redis
1
+ # deepbase-redis
2
2
 
3
- DeepBaseRedis is an innovative and efficient module designed to seamlessly integrate with Redis Stack, providing a robust solution for managing and interacting with databases. With DeepBaseRedis, you can effortlessly perform CRUD operations and manipulate data stored in Redis Stack (RedisJSON Module) keys.
3
+ Redis Stack driver for DeepBase.
4
4
 
5
- For simplicity you may be interested in the version of DeepBase that persists in JSON files. https://www.npmjs.com/package/deepbase
5
+ ## Installation
6
6
 
7
- ## 📦 Installation
8
- ```shell
9
- # DeepBaseRedis requires Redis Stack, which includes the necessary RedisJSON module.
10
- docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest
7
+ ```bash
8
+ npm install deepbase deepbase-redis
9
+ ```
10
+
11
+ ## Prerequisites
12
+
13
+ Requires Redis Stack (includes RedisJSON module):
14
+
15
+ ```bash
16
+ docker run -d -p 6379:6379 --name redis redis/redis-stack-server:latest
17
+ ```
18
+
19
+ **Note:** Standard Redis won't work - you need Redis Stack for JSON support.
20
+
21
+ ## Description
22
+
23
+ Stores data in Redis using the RedisJSON module. Perfect for:
24
+
25
+ - ✅ High-performance caching
26
+ - ✅ Real-time applications
27
+ - ✅ Session storage
28
+ - ✅ Fast reads and writes
29
+ - ✅ In-memory speed with persistence
30
+
31
+ ## Usage
32
+
33
+ ```javascript
34
+ import DeepBase from 'deepbase';
35
+ import RedisDriver from 'deepbase-redis';
36
+
37
+ const db = new DeepBase(new RedisDriver({
38
+ url: 'redis://localhost:6379',
39
+ prefix: 'myapp'
40
+ }));
41
+
42
+ await db.connect();
43
+
44
+ await db.set('users', 'alice', { name: 'Alice', age: 30 });
45
+ const alice = await db.get('users', 'alice');
46
+ ```
47
+
48
+ ## Options
49
+
50
+ ```javascript
51
+ new RedisDriver({
52
+ url: 'redis://localhost:6379', // Redis connection URL
53
+ prefix: 'db', // Key prefix (or use 'name')
54
+ nidAlphabet: 'ABC...', // Alphabet for ID generation
55
+ nidLength: 10 // Length of generated IDs
56
+ })
57
+ ```
58
+
59
+ ## Data Structure
60
+
61
+ Data is stored as Redis JSON keys:
11
62
 
12
- npm install deepbase-redis
63
+ ```javascript
64
+ // Keys created
65
+ myapp:users -> { alice: {...}, bob: {...} }
66
+ myapp:config -> { theme: "dark", lang: "en" }
13
67
  ```
14
68
 
15
- ## 🔧 Usage
16
- ```js
17
- import DeepBase from "deepbase-redis"
18
- const mem = new DeepBase({ name: "db" }); // "db" redis prefix
19
- await mem.connect();
69
+ ## Features
70
+
71
+ ### RedisJSON Support
72
+
73
+ Uses Redis JSON module for native JSON operations:
74
+
75
+ ```javascript
76
+ await db.set('users', 'alice', 'address', { city: 'NYC' });
77
+ // Stored at JSON path: $.alice.address
78
+ ```
79
+
80
+ ### Atomic Increment
81
+
82
+ Uses Redis `JSON.NUMINCRBY` for atomic operations:
83
+
84
+ ```javascript
85
+ await db.inc('stats', 'views', 1);
86
+ await db.dec('stats', 'views', 1);
20
87
  ```
21
88
 
22
- ### ✍️ Setting Values
23
- ```js
24
- await mem.set("config", "lang", "en");
89
+ ### Key Scanning
90
+
91
+ Efficiently scans keys with patterns:
92
+
93
+ ```javascript
94
+ const allUsers = await db.get('users');
95
+ // Scans all keys matching prefix
96
+ ```
25
97
 
26
- const configLang = await mem.get("config", "lang");
27
- console.log(configLang); // "en"
98
+ ## Three-Tier Architecture
99
+
100
+ Use Redis as a cache layer:
101
+
102
+ ```javascript
103
+ import DeepBase from '@deepbase/core';
104
+ import MongoDriver from 'deepbase-mongodb';
105
+ import { JsonDriver } from 'deepbase';
106
+ import RedisDriver from '@deepbase/redis';
107
+
108
+ const db = new DeepBase([
109
+ new MongoDriver({ url: 'mongodb://localhost:27017' }), // Primary
110
+ new JsonDriver({ path: './backup' }), // Backup
111
+ new RedisDriver({ url: 'redis://localhost:6379' }) // Cache
112
+ ], {
113
+ writeAll: true, // Write to all three
114
+ readFirst: true, // Read from MongoDB first
115
+ failOnPrimaryError: false // Fallback through layers
116
+ });
28
117
  ```
29
118
 
30
- ### ✅ Adding Rows
31
- ```js
32
- const path = await mem.add("user", { name: "martin" });
119
+ **Read priority**: MongoDB → JSON → Redis
120
+ **Write replication**: All three updated simultaneously
33
121
 
34
- // add() will create a secure key (ie. "CqtOILTDUg")
35
- console.log(path) // [ 'user', 'CqtOILTDUg' ]
122
+ ## Connection String Formats
36
123
 
37
- const userName = await mem.get(...path, "name");
38
- console.log(userName); // "martin"
124
+ ```javascript
125
+ // Local
126
+ url: 'redis://localhost:6379'
127
+
128
+ // With password
129
+ url: 'redis://:password@localhost:6379'
130
+
131
+ // With database number
132
+ url: 'redis://localhost:6379/0'
133
+
134
+ // Redis Cloud
135
+ url: 'redis://username:password@host:port'
136
+
137
+ // TLS/SSL
138
+ url: 'rediss://host:port'
39
139
  ```
40
140
 
41
- ### 🔢 Increment fields
42
- ```js
43
- await mem.inc(...path, "balance", 160);
44
- await mem.inc(...path, "balance", 420);
141
+ ## Performance
142
+
143
+ Redis is extremely fast:
45
144
 
46
- const userBalance = await mem.get(...path, "balance");
47
- console.log(userBalance); // 580
145
+ - **Reads**: Sub-millisecond response times
146
+ - **Writes**: Thousands of operations per second
147
+ - **In-memory**: Data stored in RAM with optional persistence
148
+ - **Atomic operations**: Lock-free increments and updates
149
+
150
+ ## Use Cases
151
+
152
+ ### Session Storage
153
+ ```javascript
154
+ const sessions = new DeepBase(new RedisDriver({ prefix: 'session' }));
155
+ await sessions.set(sessionId, 'user', userData);
48
156
  ```
49
157
 
50
- ### ⚗️ Update
51
- ```js
52
- await mem.upd("config", "lang", v => v.toUpperCase());
53
- const lang = await mem.get("config", "lang"); // EN
158
+ ### Real-time Stats
159
+ ```javascript
160
+ const stats = new DeepBase(new RedisDriver({ prefix: 'stats' }));
161
+ await stats.inc('page', 'views', 1);
162
+ await stats.inc('page', 'unique_visitors', 1);
54
163
  ```
55
164
 
56
- ### 🔥 Finally
57
- ```js
58
- await mem.add("user", { name: "anya" });
59
-
60
- const userIds = await mem.keys("user")
61
- console.log(userIds) // [ 'CqtOILTDUg', 'MXOlTBSmEf' ]
62
-
63
- console.log(await mem.get())
64
- // {
65
- // config: { lang: 'EN' },
66
- // user: {
67
- // CqtOILTDUg: { name: 'martin', balance: 580 },
68
- // MXOlTBSmEf: { name: 'anya' }
69
- // }
70
- // }
165
+ ### Cache Layer
166
+ ```javascript
167
+ const cache = new DeepBase([
168
+ new RedisDriver({ prefix: 'cache' }),
169
+ new MongoDriver({ url: '...' })
170
+ ]);
171
+
172
+ // Fast reads from Redis, persistent in MongoDB
71
173
  ```
72
174
 
73
- ## 🤯 Features
74
- - 🔍 Easily access and modify nested objects in JSON storage.
75
- - 📁 Provides an easy-to-use interface for connecting to RedisJSON and performing data operations, saving development.
76
- - 🌱 Simple and intuitive API for managing complex JSON structures.
175
+ ## Best Practices
77
176
 
78
- ## 🤔 Why DeepBase
79
- - ⚡ Fastest and simplest way to add persistence to your projects.
80
- - 📖 Offers advanced querying capabilities, nested value retrieval, and seamless update processes.
81
- - 🧠 Easy to use and understand.
177
+ 1. **Use as cache layer** - Not as primary storage
178
+ 2. **Set appropriate TTLs** - Expire old data
179
+ 3. **Monitor memory usage** - Redis is in-memory
180
+ 4. **Enable persistence** - RDB or AOF for durability
181
+ 5. **Use with persistent drivers** - MongoDB or JSON backup
82
182
 
83
- ## 🤝 Contributing
84
- Contributions to DeepBase are welcome! If you have an idea or a bug to report, please open an issue. If you would like to contribute to the code, please open a pull request.
183
+ ## Persistence Options
85
184
 
86
- ## 🎬 Conclusion
87
- DeepBaseRedis is built with efficiency and performance in mind, leveraging the power of the Redis Stack driver and optimizing data access operations. Whether you're building a small-scale application or a complex system, DeepBaseRedis empowers you to interact with Redis Stack effortlessly, making your development process smoother and more efficient.
185
+ Redis Stack supports:
186
+ - **RDB**: Periodic snapshots
187
+ - **AOF**: Append-only file for durability
88
188
 
89
- 🚀 Try DeepBaseRedis today and experience the convenience and power it brings to your Redis Stack data management workflow!
189
+ Configure in Redis:
190
+ ```bash
191
+ docker run -d -p 6379:6379 \
192
+ -v redis-data:/data \
193
+ redis/redis-stack-server:latest \
194
+ --appendonly yes
195
+ ```
90
196
 
91
- ## 📄 License
92
- The MIT License (MIT)
197
+ ## Error Handling
93
198
 
94
- Copyright (c) Martin Clasen
199
+ ```javascript
200
+ try {
201
+ await db.connect();
202
+ } catch (error) {
203
+ console.error('Redis connection failed:', error);
204
+ // Fallback to other drivers
205
+ }
206
+ ```
95
207
 
96
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
208
+ ## License
97
209
 
98
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
210
+ MIT - Copyright (c) Martin Clasen
99
211
 
100
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/package.json CHANGED
@@ -1,54 +1,47 @@
1
1
  {
2
2
  "name": "deepbase-redis",
3
- "version": "1.2.2",
4
- "description": "⚡ Fastest and simplest way to add Redis Stack persistence to your projects.",
5
- "main": "index.js",
3
+ "version": "3.0.2",
4
+ "description": "⚡ DeepBase Redis Stack - driver",
6
5
  "type": "module",
7
- "module": "./index.js",
6
+ "main": "src/index.js",
7
+ "module": "src/index.js",
8
8
  "exports": {
9
9
  ".": {
10
- "require": "./index.cjs",
11
- "import": "./index.js"
10
+ "import": "./src/index.js"
12
11
  }
13
12
  },
14
13
  "dependencies": {
15
- "nanoid": "^5.1.5",
16
14
  "redis": "^4.7.0"
17
15
  },
16
+ "peerDependencies": {
17
+ "deepbase": "^3.0.0"
18
+ },
18
19
  "scripts": {
19
- "test": "mocha"
20
+ "test": "mocha test/test.js"
21
+ },
22
+ "devDependencies": {
23
+ "mocha": "^10.8.2"
20
24
  },
21
25
  "repository": {
22
26
  "type": "git",
23
- "url": "git+https://github.com/clasen/DeepBaseRedis.git"
27
+ "url": "git+https://github.com/clasen/DeepBase.git"
24
28
  },
25
29
  "keywords": [
30
+ "deepbase",
26
31
  "redis",
27
- "redis-json",
28
32
  "redis-stack",
29
- "json",
33
+ "redis-json",
34
+ "driver",
30
35
  "database",
31
36
  "persist",
32
37
  "nested",
33
- "objects",
34
- "user-friendly",
35
- "intuitive",
36
- "reliable",
37
- "synchronization",
38
- "structure",
39
- "lowdb",
40
- "clasen"
38
+ "objects"
41
39
  ],
42
40
  "author": "Martin Clasen",
43
41
  "license": "MIT",
44
42
  "bugs": {
45
- "url": "https://github.com/clasen/DeepBaseRedis/issues"
46
- },
47
- "homepage": "https://github.com/clasen/DeepBaseRedis#readme",
48
- "devDependencies": {
49
- "mocha": "^11.1.0"
43
+ "url": "https://github.com/clasen/DeepBase/issues"
50
44
  },
51
- "directories": {
52
- "test": "test"
53
- }
45
+ "homepage": "https://github.com/clasen/DeepBase#readme"
54
46
  }
47
+
@@ -0,0 +1,138 @@
1
+ import { DeepBaseDriver } from 'deepbase';
2
+ import { createClient } from 'redis';
3
+ import { customAlphabet } from 'nanoid';
4
+
5
+ export class RedisDriver extends DeepBaseDriver {
6
+ constructor({name, prefix, url, ...opts} = {}) {
7
+ super(opts);
8
+
9
+ this.name = name || prefix || "db";
10
+ this.url = url || "redis://localhost:6379";
11
+
12
+ this.nanoid = customAlphabet(this.nidAlphabet, this.nidLength);
13
+
14
+ this.client = createClient({ url: this.url });
15
+ }
16
+
17
+ async connect() {
18
+ await this.client.connect();
19
+ }
20
+
21
+ async disconnect() {
22
+ await this.client.disconnect();
23
+ }
24
+
25
+ async get(...args) {
26
+ if (args.length === 0) {
27
+ const dic = {};
28
+ for (let key of await this._zeroKeys()) {
29
+ dic[key] = await this.get(key);
30
+ }
31
+ return dic;
32
+ }
33
+
34
+ const key = args.shift();
35
+ const path = args.length == 0 ? "." : args.join(".");
36
+
37
+ try {
38
+ return await this.client.json.get(this.name + ":" + key, { path });
39
+ } catch (error) {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ async set(...args) {
45
+ if (args.length < 2) return;
46
+
47
+ const keys = args.slice(0, -1);
48
+ const value = args[args.length - 1];
49
+
50
+ const key = keys.shift();
51
+ const keyPath = keys.length == 0 ? "." : keys.join(".");
52
+ await this._set(key, keyPath, value);
53
+
54
+ return args.slice(0, -1);
55
+ }
56
+
57
+ async inc(...args) {
58
+ const i = args.pop();
59
+ const key = args.shift();
60
+ const path = args.length == 0 ? "." : args.join(".");
61
+
62
+ try {
63
+ await this.client.json.numIncrBy(this.name + ":" + key, path, i);
64
+ return [key, ...args];
65
+ } catch (error) {
66
+ args.unshift(key);
67
+ return this.upd(...args, n => n + i);
68
+ }
69
+ }
70
+
71
+ async dec(...args) {
72
+ const i = args.pop();
73
+ args.push(-i);
74
+ return this.inc(...args);
75
+ }
76
+
77
+ async del(...args) {
78
+ if (args.length === 0) {
79
+ for (let key of await this._zeroKeys()) {
80
+ await this.del(key);
81
+ }
82
+ return;
83
+ }
84
+
85
+ const key = args.shift();
86
+ const path = args.length == 0 ? "." : args.join(".");
87
+ await this.client.json.del(this.name + ":" + key, path);
88
+
89
+ return [key, ...args];
90
+ }
91
+
92
+ async add(...keys) {
93
+ const value = keys.pop();
94
+ const id = this.nanoid();
95
+ await this.set(...[...keys, id], value);
96
+ return [...keys, id];
97
+ }
98
+
99
+ async upd(...args) {
100
+ const func = args.pop();
101
+ return this.set(...args, func(await this.get(...args)));
102
+ }
103
+
104
+ async _set(key, path, value) {
105
+ if (value === undefined) {
106
+ await this.client.json.del(this.name + ":" + key, path);
107
+ return;
108
+ }
109
+
110
+ try {
111
+ await this.client.json.set(this.name + ":" + key, path, value);
112
+ } catch (error) {
113
+ const keys = path.split('.');
114
+ keys.pop();
115
+ const keyPath = keys.length == 0 ? "." : keys.join(".");
116
+ await this._set(key, keyPath, {});
117
+ await this._set(key, path, value);
118
+ }
119
+ }
120
+
121
+ async _zeroKeys() {
122
+ const scan = {
123
+ TYPE: "ReJSON-RL",
124
+ MATCH: this.name + ":*",
125
+ COUNT: 1000,
126
+ };
127
+
128
+ const keys = [];
129
+ for await (let key of this.client.scanIterator(scan)) {
130
+ keys.push(key.substring(this.name.length + 1));
131
+ }
132
+
133
+ return keys;
134
+ }
135
+ }
136
+
137
+ export default RedisDriver;
138
+
package/src/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { RedisDriver } from './RedisDriver.js';
2
+ export { RedisDriver } from './RedisDriver.js';
3
+ export default RedisDriver;
4
+
package/test/test.js CHANGED
@@ -1,158 +1,332 @@
1
- /* eslint-env mocha */
2
-
3
- import DeepBase from '../index.js';
4
1
  import assert from 'assert';
2
+ import { DeepBase } from '../../core/src/index.js';
3
+ import { RedisDriver } from '../src/RedisDriver.js';
5
4
 
6
- describe('DeepBaseRedis', () => {
7
- let db;
5
+ describe('RedisDriver', function() {
6
+ let db;
7
+ let testCounter = 0;
8
8
 
9
- beforeEach(async () => {
10
- db = new DeepBase({ name: 'test' });
11
- await db.connect();
12
- });
9
+ // Increase timeout for Redis operations
10
+ this.timeout(10000);
13
11
 
14
- afterEach(async () => {
15
- await db.del('foo');
12
+ beforeEach(async function() {
13
+ testCounter++;
14
+ db = new DeepBase(new RedisDriver({
15
+ url: 'redis://localhost:6379',
16
+ prefix: `test_${testCounter}`
17
+ }));
18
+
19
+ try {
20
+ await db.connect();
21
+ } catch (error) {
22
+ this.skip(); // Skip tests if Redis is not available
23
+ }
24
+ });
25
+
26
+ afterEach(async function() {
27
+ if (db) {
28
+ try {
29
+ await db.del(); // Clear all data
16
30
  await db.disconnect();
31
+ } catch (error) {
32
+ // Ignore cleanup errors
33
+ }
34
+ }
35
+ });
36
+
37
+ describe('Connection', function() {
38
+ it('should connect to Redis', async function() {
39
+ const driver = db.getDriver(0);
40
+ assert.ok(driver.client);
41
+ assert.ok(driver.client.isOpen);
17
42
  });
43
+ });
18
44
 
19
- describe('#set()', () => {
20
- it('should set a value at a given path', async () => {
21
- await db.set('foo', 'bar', 'baz');
22
- const baz = await db.get('foo', 'bar');
23
- assert.deepEqual(baz, 'baz');
24
- });
25
-
26
- it('should overwrite an existing value at the same path', async () => {
27
- await db.set('foo', 'bar', 'baz');
28
- await db.set('foo', 'bar', 'qux');
29
- const qux = await db.get('foo', 'bar')
30
- assert.deepEqual(qux, 'qux');
31
- });
45
+ describe('Basic Operations', function() {
46
+ it('should set and get a simple value', async function() {
47
+ await db.set('key1', 'value');
48
+ const result = await db.get('key1');
49
+ assert.strictEqual(result, 'value');
50
+ });
32
51
 
33
- it('should do nothing if no value is provided', async () => {
34
- await db.set('foo', 'bar');
35
- await db.set('foo', 'bar', undefined);
36
- const udf = await db.get('foo', 'bar');
37
- assert.deepEqual(udf, undefined);
38
- });
52
+ it('should set and get nested values', async function() {
53
+ await db.set('user', 'name', 'Alice');
54
+ await db.set('user', 'age', 30);
55
+
56
+ const name = await db.get('user', 'name');
57
+ const age = await db.get('user', 'age');
58
+
59
+ assert.strictEqual(name, 'Alice');
60
+ assert.strictEqual(age, 30);
61
+ });
39
62
 
40
- it('should save changes to Redis', async () => {
41
- await db.set('foo', 'bar', 'baz');
42
- db = new DeepBase({ name: 'test' }); // create a new instance to reload the saved data
43
- await db.connect();
44
- const baz = await db.get('foo', 'bar');
45
- assert.deepEqual(baz, 'baz');
46
- });
63
+ it('should get entire nested object', async function() {
64
+ await db.set('user', 'name', 'Bob');
65
+ await db.set('user', 'age', 25);
66
+
67
+ const user = await db.get('user');
68
+ assert.strictEqual(user.name, 'Bob');
69
+ assert.strictEqual(user.age, 25);
47
70
  });
48
71
 
49
- describe('#get()', () => {
50
- it('should retrieve a value at a given path', async () => {
51
- await db.set('foo', 'bar', 'baz');
52
- assert.deepEqual(await db.get('foo', 'bar'), 'baz');
53
- });
72
+ it('should return null for non-existent keys', async function() {
73
+ const result = await db.get('nonexistent');
74
+ assert.strictEqual(result, null);
75
+ });
54
76
 
55
- it('should return null if the value does not exist', async () => {
56
- assert.deepEqual(await db.get('foo', 'bar'), null);
57
- });
77
+ it('should get all keys', async function() {
78
+ await db.set('key1', 'value1');
79
+ await db.set('key2', 'value2');
80
+
81
+ const all = await db.get();
82
+ assert.ok(all.key1);
83
+ assert.ok(all.key2);
84
+ assert.strictEqual(all.key1, 'value1');
85
+ assert.strictEqual(all.key2, 'value2');
86
+ });
87
+ });
58
88
 
59
- it('should return the entire database if no keys are provided', async () => {
60
- await db.set('foo', 'bar', 'baz');
61
- await db.set('qux', 'quux', 'corge');
62
- assert.deepEqual(await db.get(), { foo: { bar: 'baz' }, qux: { quux: 'corge' } });
63
- });
89
+ describe('Delete Operations', function() {
90
+ it('should delete a key', async function() {
91
+ await db.set('temp', 'value');
92
+ await db.set('keep', 'value');
93
+ await db.del('temp');
94
+
95
+ const temp = await db.get('temp');
96
+ const keep = await db.get('keep');
97
+
98
+ assert.strictEqual(temp, null);
99
+ assert.strictEqual(keep, 'value');
64
100
  });
65
101
 
66
- describe('#del()', () => {
67
- it('should delete a value at a given path', async () => {
68
- await db.set('foo', 'bar', 'baz');
69
- await db.del('foo', 'bar');
70
- assert.deepEqual(await db.get('foo', 'bar'), null);
71
- });
102
+ it('should delete nested field', async function() {
103
+ await db.set('user', 'name', 'Alice');
104
+ await db.set('user', 'age', 30);
105
+ await db.del('user', 'age');
106
+
107
+ const user = await db.get('user');
108
+ assert.strictEqual(user.name, 'Alice');
109
+ assert.strictEqual(user.age, undefined);
110
+ });
72
111
 
73
- it('should do nothing if the value does not exist', async () => {
74
- await db.del('foo', 'bar');
75
- assert.deepEqual(await db.get('foo', 'bar'), null);
76
- });
112
+ it('should clear all data', async function() {
113
+ await db.set('key1', 'value1');
114
+ await db.set('key2', 'value2');
115
+ await db.del();
116
+
117
+ const all = await db.get();
118
+ assert.deepStrictEqual(all, {});
119
+ });
120
+ });
77
121
 
78
- it('should save changes to Redis', async () => {
79
- await db.set('foo', 'bar', 'baz');
80
- await db.del('foo', 'bar');
81
- db = new DeepBase({ name: 'test' }); // create a new instance to reload the saved data
82
- await db.connect();
83
- assert.deepEqual(await db.get('foo', 'bar'), null);
84
- });
122
+ describe('Add Operation', function() {
123
+ it('should add item with auto-generated ID', async function() {
124
+ const path = await db.add('items', 'Charlie');
125
+
126
+ assert.strictEqual(path.length, 2);
127
+ assert.strictEqual(path[0], 'items');
128
+ assert.strictEqual(typeof path[1], 'string');
129
+ assert.strictEqual(path[1].length, 10);
130
+
131
+ const item = await db.get(...path);
132
+ assert.strictEqual(item, 'Charlie');
85
133
  });
86
134
 
87
- describe('#add()', () => {
135
+ it('should add multiple items with unique IDs', async function() {
136
+ const path1 = await db.add('items', 'item1');
137
+ const path2 = await db.add('items', 'item2');
138
+
139
+ assert.notStrictEqual(path1[1], path2[1]);
140
+ });
88
141
 
89
- it('should save changes to Redis', async () => {
90
- const obj = { bar: 'baz' }
91
- const path = await db.add('foo', obj);
92
- assert.deepEqual(await db.get(...path), obj);
93
- });
142
+ it('should add complex objects', async function() {
143
+ const path = await db.add('users', { name: 'Alice', age: 30 });
144
+ const user = await db.get(...path);
145
+
146
+ assert.deepStrictEqual(user, { name: 'Alice', age: 30 });
94
147
  });
148
+ });
95
149
 
96
- describe('#inc()', () => {
150
+ describe('Increment/Decrement', function() {
151
+ it('should increment a value', async function() {
152
+ await db.set('key', 'counter', 10);
153
+ await db.inc('key', 'counter', 5);
154
+
155
+ const result = await db.get('key', 'counter');
156
+ assert.strictEqual(result, 15);
157
+ });
97
158
 
98
- it('should increment a value at a given path', async () => {
99
- await db.set('foo', 'bar', 1);
100
- await db.inc('foo', 'bar', 2);
101
- assert.deepEqual(await db.get('foo', 'bar'), 3);
102
- });
159
+ it('should decrement a value', async function() {
160
+ await db.set('key', 'counter', 20);
161
+ await db.dec('key', 'counter', 8);
162
+
163
+ const result = await db.get('key', 'counter');
164
+ assert.strictEqual(result, 12);
103
165
  });
104
166
 
105
- describe('#dec()', () => {
167
+ it('should increment nested value', async function() {
168
+ await db.set('user', 'balance', 100);
169
+ await db.inc('user', 'balance', 50);
170
+
171
+ const balance = await db.get('user', 'balance');
172
+ assert.strictEqual(balance, 150);
173
+ });
106
174
 
107
- it('should save changes to Redis', async () => {
108
- await db.set('foo', 'bar', 3);
109
- await db.dec('foo', 'bar', 2);
110
- db = new DeepBase({ name: 'test' }); // create a new instance to reload the saved data
111
- await db.connect();
112
- assert.deepEqual(await db.get('foo', 'bar'), 1);
113
- });
175
+ it('should handle multiple increments', async function() {
176
+ await db.set('key', 'views', 0);
177
+ await db.inc('key', 'views', 1);
178
+ await db.inc('key', 'views', 1);
179
+ await db.inc('key', 'views', 1);
180
+
181
+ const views = await db.get('key', 'views');
182
+ assert.strictEqual(views, 3);
183
+ });
114
184
 
115
- it('should set the value to -1 if it does not exist', async () => {
116
- await db.dec('foo', 'bar', 2);
117
- assert.deepEqual(await db.get('foo', 'bar'), -2);
118
- });
185
+ it('should increment non-existent value from zero', async function() {
186
+ await db.inc('key', 'new_counter', 10);
187
+
188
+ const result = await db.get('key', 'new_counter');
189
+ assert.strictEqual(result, 10);
119
190
  });
191
+ });
120
192
 
121
- describe('#keys()', () => {
193
+ describe('Update Operation', function() {
194
+ it('should update value with function', async function() {
195
+ await db.set('key', 'name', 'alice');
196
+ await db.upd('key', 'name', name => name.toUpperCase());
197
+
198
+ const result = await db.get('key', 'name');
199
+ assert.strictEqual(result, 'ALICE');
200
+ });
122
201
 
123
- it('should return keys', async () => {
124
- await db.set('foo', 'bar', 1);
125
- await db.set('foo', 'quux', 1);
126
- assert.deepEqual(await db.keys('foo'), ['bar', 'quux']);
127
- });
202
+ it('should update nested value with function', async function() {
203
+ await db.set('user', 'age', 25);
204
+ await db.upd('user', 'age', age => age + 1);
205
+
206
+ const age = await db.get('user', 'age');
207
+ assert.strictEqual(age, 26);
128
208
  });
129
209
 
130
- describe('#values()', () => {
210
+ it('should update complex objects', async function() {
211
+ await db.set('config', 'settings', { theme: 'dark', lang: 'en' });
212
+ await db.upd('config', 'settings', settings => ({
213
+ ...settings,
214
+ lang: 'es'
215
+ }));
216
+
217
+ const settings = await db.get('config', 'settings');
218
+ assert.strictEqual(settings.theme, 'dark');
219
+ assert.strictEqual(settings.lang, 'es');
220
+ });
221
+ });
222
+
223
+ describe('Keys, Values, Entries', function() {
224
+ beforeEach(async function() {
225
+ await db.set('users', 'alice', { age: 30 });
226
+ await db.set('users', 'bob', { age: 25 });
227
+ });
228
+
229
+ it('should get keys', async function() {
230
+ const keys = await db.keys('users');
231
+ assert.strictEqual(keys.length, 2);
232
+ assert.ok(keys.includes('alice'));
233
+ assert.ok(keys.includes('bob'));
234
+ });
235
+
236
+ it('should get values', async function() {
237
+ const values = await db.values('users');
238
+ assert.strictEqual(values.length, 2);
239
+ assert.ok(values.some(v => v.age === 30));
240
+ assert.ok(values.some(v => v.age === 25));
241
+ });
131
242
 
132
- it('should return values', async () => {
133
- await db.set('foo', 'bar', 1);
134
- await db.set('foo', 'quux', 1);
135
- assert.deepEqual(await db.values('foo'), [1, 1]);
136
- });
243
+ it('should get entries', async function() {
244
+ const entries = await db.entries('users');
245
+ assert.strictEqual(entries.length, 2);
246
+ assert.ok(entries.some(([k, v]) => k === 'alice' && v.age === 30));
247
+ assert.ok(entries.some(([k, v]) => k === 'bob' && v.age === 25));
137
248
  });
249
+ });
138
250
 
139
- describe('#entries()', () => {
251
+ describe('Complex Nested Operations', function() {
252
+ it('should handle deeply nested objects', async function() {
253
+ await db.set('app', 'config', 'database', 'host', 'localhost');
254
+ await db.set('app', 'config', 'database', 'port', 6379);
255
+
256
+ const host = await db.get('app', 'config', 'database', 'host');
257
+ const port = await db.get('app', 'config', 'database', 'port');
258
+
259
+ assert.strictEqual(host, 'localhost');
260
+ assert.strictEqual(port, 6379);
261
+ });
262
+
263
+ it('should handle arrays', async function() {
264
+ await db.set('key', 'tags', ['redis', 'cache', 'database']);
265
+
266
+ const tags = await db.get('key', 'tags');
267
+ assert.deepStrictEqual(tags, ['redis', 'cache', 'database']);
268
+ });
140
269
 
141
- it('should return entries', async () => {
142
- await db.set('foo', 'bar', 1);
143
- await db.set('foo', 'quux', 2);
144
- assert.deepEqual(await db.entries('foo'), [['bar', 1], ['quux', 2]]);
145
- });
270
+ it('should handle mixed types', async function() {
271
+ await db.set('key', 'string', 'text');
272
+ await db.set('key', 'number', 42);
273
+ await db.set('key', 'boolean', true);
274
+ await db.set('key', 'null', null);
275
+ await db.set('key', 'array', [1, 2, 3]);
276
+
277
+ const doc = await db.get('key');
278
+ assert.strictEqual(doc.string, 'text');
279
+ assert.strictEqual(doc.number, 42);
280
+ assert.strictEqual(doc.boolean, true);
281
+ assert.strictEqual(doc.null, null);
282
+ assert.deepStrictEqual(doc.array, [1, 2, 3]);
146
283
  });
284
+ });
147
285
 
148
- describe('#upd()', async () => {
286
+ describe('JSON Path Operations', function() {
287
+ it('should create intermediate objects automatically', async function() {
288
+ await db.set('key', 'level1', 'level2', 'value', 'deep');
289
+
290
+ const result = await db.get('key', 'level1', 'level2', 'value');
291
+ assert.strictEqual(result, 'deep');
292
+ });
149
293
 
150
- it('should update field keys', async () => {
151
- await db.set('foo', 'bar', 2);
152
- await db.upd('foo', 'bar', n => n * 3);
153
- assert.deepEqual(await db.get('foo', 'bar'), 6);
154
- });
294
+ it('should handle object replacement', async function() {
295
+ await db.set('key', 'obj', { old: 'value' });
296
+ await db.set('key', 'obj', { new: 'value' });
297
+
298
+ const obj = await db.get('key', 'obj');
299
+ assert.deepStrictEqual(obj, { new: 'value' });
155
300
  });
301
+ });
156
302
 
303
+ describe('Performance', function() {
304
+ it('should handle rapid sequential operations', async function() {
305
+ const operations = 50;
306
+
307
+ for (let i = 0; i < operations; i++) {
308
+ await db.set('perf', `key${i}`, i);
309
+ }
310
+
311
+ for (let i = 0; i < operations; i++) {
312
+ const value = await db.get('perf', `key${i}`);
313
+ assert.strictEqual(value, i);
314
+ }
315
+ });
316
+
317
+ it('should handle parallel operations', async function() {
318
+ const operations = 20;
319
+ const promises = [];
320
+
321
+ for (let i = 0; i < operations; i++) {
322
+ promises.push(db.set('parallel', `key${i}`, i));
323
+ }
324
+
325
+ await Promise.all(promises);
326
+
327
+ const keys = await db.keys('parallel');
328
+ assert.strictEqual(keys.length, operations);
329
+ });
330
+ });
331
+ });
157
332
 
158
- });
package/demo/demo.js DELETED
@@ -1,59 +0,0 @@
1
- import DeepBase from "../index.js";
2
-
3
- const mem = new DeepBase({ name: "demo" });
4
-
5
- await mem.connect();
6
-
7
- // Reset
8
- await mem.del();
9
-
10
- // SET
11
- await mem.set("config", "lang", "en");
12
-
13
- const configLang = await mem.get("config", "lang");
14
- console.log(configLang); // "en"
15
-
16
- // ADD
17
- const path = await mem.add("user", { name: "martin" });
18
- console.log(path) // [ 'user', 'CqtOILTDUg' ] / CqtOILTDUg is a random string
19
-
20
- const userName = await mem.get(...path, "name");
21
- console.log(userName); // "martin"
22
-
23
- // INC
24
- await mem.inc(...path, "count", 1);
25
- await mem.inc(...path, "count", 1);
26
-
27
- const userBalance = await mem.get(...path, "count");
28
- console.log(userBalance); // 2
29
-
30
- await mem.add("user", { name: "anya" });
31
-
32
- const userIds = await mem.keys("user")
33
- console.log(userIds) // [ 'CqtOILTDUg', 'MXOlTBSmEf' ]
34
-
35
- const userValues = await mem.values("user")
36
- console.log(userValues)
37
- // [ { name: 'martin', count: 2 }, { name: 'anya' }]
38
-
39
- // UPDATE
40
- await mem.upd("config", "lang", v => v.toUpperCase());
41
- const lang = await mem.get("config", "lang"); // EN
42
-
43
- console.log(await mem.get())
44
-
45
- await mem.disconnect();
46
- // {
47
- // "config": {
48
- // "lang": "EN"
49
- // },
50
- // "user": {
51
- // "CqtOILTDUg": {
52
- // "name": "martin",
53
- // "count": 2
54
- // },
55
- // "MXOlTBSmEf": {
56
- // "name": "anya"
57
- // }
58
- // }
59
- // }
package/index.cjs DELETED
@@ -1 +0,0 @@
1
- module.exports = require('./index.js').default;
package/index.js DELETED
@@ -1,169 +0,0 @@
1
- import { createClient } from "redis";
2
- import { customAlphabet } from "nanoid";
3
-
4
- class DeepBaseRedis {
5
-
6
- constructor(opts = {}) {
7
- this.nidAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
8
- this.nidLength = 10;
9
- this.name = "db";
10
- this.url = "redis://localhost:6379";
11
- this.client = createClient({ url: this.url });
12
- this.nanoid = customAlphabet(this.nidAlphabet, this.nidLength);
13
- Object.assign(this, opts);
14
- }
15
-
16
- async disconnect() {
17
- await this.client.disconnect();
18
- }
19
-
20
- async connect() {
21
- await this.client.connect();
22
- }
23
-
24
- async set(...args) {
25
- if (args.length < 2) return;
26
- const keys = args.slice(0, -1);
27
- const value = args[args.length - 1];
28
-
29
- const key = keys.shift();
30
- const keyPath = keys.length == 0 ? "." : keys.join(".");
31
- await this._set(key, keyPath, value);
32
- return args.slice(0, -1);
33
- }
34
-
35
- async _set(key, path, value) {
36
-
37
- if (value === undefined) {
38
- await this.client.json.del(this.name + ":" + key, path);
39
- return;
40
- }
41
-
42
- let r = null;
43
- try {
44
- r = await this.client.json.set(this.name + ":" + key, path, value);
45
- } catch (error) {
46
-
47
- }
48
-
49
- if (r === null) {
50
- const keys = path.split('.');
51
- keys.pop();
52
- const keyPath = keys.length == 0 ? "." : keys.join(".");
53
- await this._set(key, keyPath, {});
54
- await this._set(key, path, value);
55
- }
56
- }
57
-
58
- async get(...args) {
59
-
60
- if (args.length === 0) {
61
-
62
- const dic = {};
63
- for (let key of await this._zeroKeys()) {
64
- dic[key] = await this.get(key);
65
- }
66
- return dic;
67
- }
68
-
69
- const key = args.shift();
70
- const path = args.length == 0 ? "." : args.join(".");
71
-
72
- let r = null;
73
- try {
74
- r = await this.client.json.get(this.name + ":" + key, { path });
75
- } catch (error) {
76
-
77
- }
78
- return r;
79
- }
80
-
81
- async keys(...args) {
82
- const r = await this.get(...args);
83
- return (r !== null && typeof r === "object") ? Object.keys(r) : [];
84
- }
85
-
86
- async values(...args) {
87
- const r = await this.get(...args);
88
- return (r !== null && typeof r === "object") ? Object.values(r) : [];
89
- }
90
-
91
- async entries(...args) {
92
- const r = await this.get(...args);
93
- return (r !== null && typeof r === "object") ? Object.entries(r) : [];
94
- }
95
-
96
- async upd(...args) {
97
- const func = args.pop();
98
- return this.set(...args, func(await this.get(...args)));
99
- }
100
-
101
- async inc(...args) {
102
-
103
- const i = args.pop();
104
- const key = args.shift();
105
- const path = args.length == 0 ? "." : args.join(".");
106
-
107
- try {
108
- await this.client.json.numIncrBy(this.name + ":" + key, path, i);
109
- return args.slice(0, -1);
110
- } catch (error) {
111
- args.unshift(key);
112
- return this.upd(...args, n => n + i);
113
- }
114
- }
115
-
116
- async dec(...args) {
117
- const i = args.pop();
118
- args.push(-i);
119
- return this.inc(...args);
120
- }
121
-
122
- async _zeroKeys() {
123
- const scan = {
124
- TYPE: "ReJSON-RL",
125
- MATCH: this.name + ":*",
126
- COUNT: 1000,
127
- }
128
-
129
- const keys = [];
130
- for await (let key of this.client.scanIterator(scan)) {
131
- keys.push(key.substring(this.name.length + 1));
132
- }
133
-
134
- return keys;
135
- }
136
-
137
- async del(...args) {
138
-
139
- if (args.length === 0) {
140
-
141
- for (let key of await this._zeroKeys()) {
142
- await this.del(key);
143
- }
144
- }
145
-
146
- const key = args.shift();
147
- const path = args.length == 0 ? "." : args.join(".");
148
- await this.client.json.del(this.name + ":" + key, path);
149
- return [key, ...args];
150
- }
151
-
152
- async add(...keys) {
153
- const value = keys.pop();
154
- const id = this.nanoid();
155
- await this.set(...[...keys, id, value]);
156
- return [...keys, id];
157
- }
158
-
159
- use(plugin) {
160
- const prototype = Object.getPrototypeOf(plugin);
161
- Object.getOwnPropertyNames(prototype).forEach(method => {
162
- if (method !== "constructor") {
163
- this[method] = plugin[method].bind(this);
164
- }
165
- });
166
- }
167
- }
168
-
169
- export default DeepBaseRedis;