@eldoy/memdb 0.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.
Files changed (4) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +157 -0
  3. package/index.js +242 -0
  4. package/package.json +25 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2025 Vidar Eldøy
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ ## MemDB
2
+
3
+ A lightweight, **in-memory JSON database** for Node.js designed for speed and simplicity. Intended for **single-process environments only**.
4
+
5
+ `memdb` keeps all data strictly in memory. There is **no persistence**.
6
+
7
+ ---
8
+
9
+ ### Features
10
+
11
+ * **Mongo-style Queries:** `$gt`, `$lt`, `$gte`, `$lte`, `$ne`, `$in`, `$nin`, `$regex`
12
+ * **In-memory Only:** All reads and writes operate on live memory
13
+ * **Date Support:** Automatic normalization and comparison of `Date` values
14
+ * **Sorting:** Multi-key ascending / descending sort
15
+ * **Pagination:** Built-in `limit` and `skip`
16
+ * **Minimal API:** Only `get()` and `set()`
17
+ * **Synchronous Writes:** `set()` is fully synchronous
18
+
19
+ ---
20
+
21
+ ### Installation
22
+
23
+ ```sh
24
+ npm i memdb
25
+ ```
26
+
27
+ ---
28
+
29
+ ### Usage
30
+
31
+ ```js
32
+ var memdb = require('memdb')
33
+
34
+ // Single in-memory collection
35
+ var db = memdb()
36
+
37
+ db.set({ type: 'user', name: 'Alice' })
38
+ db.set({ type: 'user', name: 'Bob' })
39
+
40
+ var users = db.get({ type: 'user' })
41
+ ```
42
+
43
+ ---
44
+
45
+ ### Usage Examples
46
+
47
+ #### 1. Inserting Data
48
+
49
+ Passing a single object inserts a new document. A UUID `id` is generated automatically if missing.
50
+
51
+ ```js
52
+ db.set({
53
+ name: 'Project Alpha',
54
+ status: 'pending',
55
+ priority: 1,
56
+ createdAt: new Date()
57
+ })
58
+ ```
59
+
60
+ ---
61
+
62
+ #### 2. Querying with Operators
63
+
64
+ ```js
65
+ // Numeric and inequality operators
66
+ var tasks = db.get({
67
+ priority: { $gte: 5 },
68
+ status: { $ne: 'archived' }
69
+ })
70
+
71
+ // Regex and array operators
72
+ var results = db.get({
73
+ name: { $regex: /^Project/i },
74
+ tags: { $in: ['urgent', 'active'] }
75
+ })
76
+ ```
77
+
78
+ ---
79
+
80
+ #### 3. Updating Data
81
+
82
+ ```js
83
+ // Update all pending tasks
84
+ db.set({ status: 'pending' }, { status: 'active' })
85
+
86
+ // Update by ID
87
+ db.set({ id: 'some-uuid' }, { progress: 100 })
88
+ ```
89
+
90
+ ---
91
+
92
+ #### 4. Deleting Data
93
+
94
+ ```js
95
+ // Delete a single record
96
+ db.set({ id: 'some-uuid' }, null)
97
+
98
+ // Delete all completed tasks
99
+ db.set({ status: 'completed' }, null)
100
+
101
+ // Clear entire database
102
+ db.set({}, null)
103
+ ```
104
+
105
+ ---
106
+
107
+ #### 5. Pagination and Sorting
108
+
109
+ ```js
110
+ var page = db.get(
111
+ { type: 'log' },
112
+ { sort: { createdAt: -1 }, limit: 10, skip: 10 }
113
+ )
114
+ ```
115
+
116
+ ---
117
+
118
+ ### API Reference
119
+
120
+ | Method | Description |
121
+ | ----------------------- | ------------------------------------------------------------- |
122
+ | `get(query, [options])` | Returns matching documents. Supports `{ limit, skip, sort }`. |
123
+ | `set(query, [values])` | Insert, update, or delete documents. |
124
+
125
+ ---
126
+
127
+ ### Execution Model
128
+
129
+ * All data lives in memory
130
+ * No filesystem access
131
+ * No async behavior
132
+ * No background work
133
+ * Process exit discards all data
134
+
135
+ ---
136
+
137
+ ### Limitations
138
+
139
+ * Single process only
140
+ * No persistence
141
+ * No transactions
142
+ * No indexes
143
+ * Linear scan queries
144
+
145
+ Designed for embedded, ephemeral, test, cache, and control-plane use cases.
146
+
147
+ ---
148
+
149
+ ### License
150
+
151
+ ISC.
152
+
153
+ ---
154
+
155
+ ### Acknowledgements
156
+
157
+ Created by [Vidar Eldøy](https://eldoy.com)
package/index.js ADDED
@@ -0,0 +1,242 @@
1
+ var crypto = require('crypto')
2
+
3
+ module.exports = function memdb() {
4
+ var data = []
5
+
6
+ function genId() {
7
+ return crypto.randomUUID()
8
+ }
9
+
10
+ function isDate(v) {
11
+ return v instanceof Date
12
+ }
13
+
14
+ function valEq(a, b) {
15
+ if (isDate(a) && isDate(b)) return a.getTime() === b.getTime()
16
+ return a === b
17
+ }
18
+
19
+ function normalize(v) {
20
+ if (isDate(v)) return v.getTime()
21
+ return v
22
+ }
23
+
24
+ function cmp(a, b) {
25
+ if (a === undefined && b === undefined) return 0
26
+ if (a === undefined) return 1
27
+ if (b === undefined) return -1
28
+ a = normalize(a)
29
+ b = normalize(b)
30
+ if (typeof a !== typeof b) return 0
31
+ if (a < b) return -1
32
+ if (a > b) return 1
33
+ return 0
34
+ }
35
+
36
+ function matchPredicate(doc, key, pred) {
37
+ var has = Object.prototype.hasOwnProperty.call(doc, key)
38
+ var val = doc[key]
39
+
40
+ if (
41
+ pred &&
42
+ typeof pred === 'object' &&
43
+ !Array.isArray(pred) &&
44
+ !isDate(pred)
45
+ ) {
46
+ for (var op in pred) {
47
+ var cond = pred[op]
48
+
49
+ if (op === '$eq') {
50
+ if (!has || !valEq(val, cond)) return false
51
+ } else if (op === '$ne') {
52
+ if (has && valEq(val, cond)) return false
53
+ } else if (op === '$gt') {
54
+ if (!has || normalize(val) <= normalize(cond)) return false
55
+ } else if (op === '$gte') {
56
+ if (!has || normalize(val) < normalize(cond)) return false
57
+ } else if (op === '$lt') {
58
+ if (!has || normalize(val) >= normalize(cond)) return false
59
+ } else if (op === '$lte') {
60
+ if (!has || normalize(val) > normalize(cond)) return false
61
+ } else if (op === '$in') {
62
+ if (!has) return false
63
+ var ok = false
64
+ for (var i = 0; i < cond.length; i++) {
65
+ if (valEq(val, cond[i])) {
66
+ ok = true
67
+ break
68
+ }
69
+ }
70
+ if (!ok) return false
71
+ } else if (op === '$nin') {
72
+ if (!has) return true
73
+ for (var i2 = 0; i2 < cond.length; i2++) {
74
+ if (valEq(val, cond[i2])) return false
75
+ }
76
+ } else if (op === '$regex') {
77
+ if (typeof val !== 'string') return false
78
+ var re
79
+ try {
80
+ re = cond instanceof RegExp ? cond : new RegExp(cond)
81
+ } catch (e) {
82
+ return false
83
+ }
84
+ if (!re.test(val)) return false
85
+ } else if (op === '$exists') {
86
+ if (cond === true && !has) return false
87
+ if (cond === false && has) return false
88
+ }
89
+ }
90
+ return true
91
+ }
92
+
93
+ return has && valEq(val, pred)
94
+ }
95
+
96
+ function matchQuery(doc, query) {
97
+ for (var k in query) {
98
+ if (k === '__proto__') continue
99
+ if (k === '$and') {
100
+ for (var i = 0; i < query[k].length; i++) {
101
+ if (!matchQuery(doc, query[k][i])) return false
102
+ }
103
+ continue
104
+ }
105
+ if (k === '$or') {
106
+ var ok = false
107
+ for (var j = 0; j < query[k].length; j++) {
108
+ if (matchQuery(doc, query[k][j])) {
109
+ ok = true
110
+ break
111
+ }
112
+ }
113
+ if (!ok) return false
114
+ continue
115
+ }
116
+ if (k === '$not') {
117
+ if (matchQuery(doc, query[k])) return false
118
+ continue
119
+ }
120
+ if (!matchPredicate(doc, k, query[k])) return false
121
+ }
122
+ return true
123
+ }
124
+
125
+ function applyProjection(doc, fields) {
126
+ if (!fields) return doc
127
+ var out = {}
128
+ var include = null
129
+ for (var k in fields) {
130
+ include = fields[k]
131
+ break
132
+ }
133
+ if (include) {
134
+ for (var f in fields) {
135
+ if (fields[f] && doc[f] !== undefined) out[f] = doc[f]
136
+ }
137
+ if (!fields.id) out.id = doc.id
138
+ } else {
139
+ for (var d in doc) {
140
+ if (fields[d] !== false) out[d] = doc[d]
141
+ }
142
+ }
143
+ return out
144
+ }
145
+
146
+ function get(query, opts, onBatch) {
147
+ if (!query) query = {}
148
+ if (!opts) opts = {}
149
+ var limit = opts.limit == null ? 1000 : opts.limit
150
+ var skip = opts.skip || 0
151
+
152
+ if (opts.count) {
153
+ var c = 0
154
+ for (var i = 0; i < data.length; i++) {
155
+ if (matchQuery(data[i], query)) c++
156
+ }
157
+ return { count: c }
158
+ }
159
+
160
+ var res = []
161
+ for (var j = 0; j < data.length; j++) {
162
+ if (matchQuery(data[j], query)) res.push(data[j])
163
+ }
164
+
165
+ if (opts.sort) {
166
+ var keys = Object.keys(opts.sort)
167
+ res.sort(function (a, b) {
168
+ for (var i2 = 0; i2 < keys.length; i2++) {
169
+ var k = keys[i2]
170
+ var c2 = cmp(a[k], b[k])
171
+ if (c2 !== 0) return c2 * opts.sort[k]
172
+ }
173
+ return 0
174
+ })
175
+ }
176
+
177
+ res = res.slice(skip, skip + limit)
178
+
179
+ if (onBatch) {
180
+ var size = opts.batch || res.length
181
+ for (var x = 0; x < res.length; x += size) {
182
+ onBatch(res.slice(x, x + size))
183
+ }
184
+ return
185
+ }
186
+
187
+ if (opts.fields) {
188
+ var proj = []
189
+ for (var y = 0; y < res.length; y++) {
190
+ proj.push(applyProjection(res[y], opts.fields))
191
+ }
192
+ return proj
193
+ }
194
+
195
+ return res
196
+ }
197
+
198
+ function set(q, v) {
199
+ if (Array.isArray(q)) {
200
+ for (var i = 0; i < q.length; i++) {
201
+ if (!q[i].id) q[i].id = genId()
202
+ data.push(q[i])
203
+ }
204
+ return q
205
+ }
206
+
207
+ if (v === undefined) {
208
+ if (!q.id) q.id = genId()
209
+ data.push(q)
210
+ return q
211
+ }
212
+
213
+ var matched = []
214
+ for (var j = 0; j < data.length; j++) {
215
+ if (matchQuery(data[j], q)) matched.push(j)
216
+ }
217
+
218
+ if (v === null) {
219
+ var n = matched.length
220
+ if (n) {
221
+ var keep = []
222
+ for (var k = 0; k < data.length; k++) {
223
+ if (matched.indexOf(k) === -1) keep.push(data[k])
224
+ }
225
+ data = keep
226
+ }
227
+ return { n: n }
228
+ }
229
+
230
+ for (var m = 0; m < matched.length; m++) {
231
+ var d = data[matched[m]]
232
+ for (var key in v) {
233
+ if (v[key] === undefined) delete d[key]
234
+ else d[key] = v[key]
235
+ }
236
+ }
237
+
238
+ return { n: matched.length }
239
+ }
240
+
241
+ return { get: get, set: set }
242
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@eldoy/memdb",
3
+ "version": "0.1.0",
4
+ "description": "High performance in-memory JSON database.",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "author": "Vidar Eldøy <vidar@eldoy.com>",
9
+ "license": "ISC",
10
+ "scripts": {
11
+ "test": "spekk",
12
+ "test:watch": "nodemon --exec spekk"
13
+ },
14
+ "homepage": "https://github.com/eldoy/memdb#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/eldoy/memdb.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/eldoy/memdb/issues"
21
+ },
22
+ "devDependencies": {
23
+ "spekk": "^0.3.2"
24
+ }
25
+ }