apiro-db 1.0.7 → 1.0.8

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 (2) hide show
  1. package/lib/store.js +83 -65
  2. package/package.json +9 -2
package/lib/store.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const crypto = require("crypto");
2
2
  const { readFile, writeFile } = require("./file");
3
3
  const { encrypt, decrypt } = require("./crypto");
4
- const { generateMasterKey, encryptMasterKey, decryptMasterKey } = require("./masterKey");
4
+ const { encryptMasterKey, decryptMasterKey } = require("./masterKey");
5
5
  const {
6
6
  getByPath,
7
7
  setByPath,
@@ -11,118 +11,136 @@ const {
11
11
  class SecureStore {
12
12
  constructor(options = {}) {
13
13
  this.file = options.file || "./secure.db";
14
+
14
15
  this.data = {};
15
16
  this.masterKey = null;
17
+
18
+ this._dirty = false;
19
+ this._writeTimer = null;
20
+ this._writeInProgress = false;
21
+
16
22
  this.ready = this._init();
17
23
  }
18
24
 
25
+ /* -----------------------------
26
+ INIT
27
+ ------------------------------ */
28
+
19
29
  async _init() {
20
30
  const existing = await readFile(this.file);
21
31
 
22
32
  if (!existing) {
23
33
  this.masterKey = crypto.randomBytes(32);
24
- const encryptedKey = encryptMasterKey(this.masterKey);
25
-
26
- const payload = encrypt(JSON.stringify({}), this.masterKey);
27
-
28
- await writeFile(this.file, JSON.stringify({
29
- _meta: { key: encryptedKey },
30
- payload
31
- }));
32
-
34
+ await this._flush(true);
33
35
  return;
34
36
  }
35
37
 
36
38
  const parsed = JSON.parse(existing);
37
39
  this.masterKey = decryptMasterKey(parsed._meta.key);
38
- const decrypted = decrypt(parsed.payload, this.masterKey);
39
- this.data = JSON.parse(decrypted);
40
+ this.data = JSON.parse(decrypt(parsed.payload, this.masterKey));
40
41
  }
41
42
 
42
- async _save() {
43
- const payload = encrypt(JSON.stringify(this.data), this.masterKey);
43
+ /* -----------------------------
44
+ WRITE-BEHIND ENGINE
45
+ ------------------------------ */
44
46
 
45
- await writeFile(this.file, JSON.stringify({
46
- _meta: { key: encryptMasterKey(this.masterKey) },
47
- payload
48
- }));
49
- }
47
+ _scheduleFlush() {
48
+ this._dirty = true;
50
49
 
51
- async get(key) {
52
- await this.ready;
53
- return getByPath(this.data, key);
54
- }
50
+ if (this._writeTimer) return;
55
51
 
56
- async set(key, value) {
57
- await this.ready;
58
- setByPath(this.data, key, value);
59
- await this._save();
60
- return value;
52
+ this._writeTimer = setTimeout(() => {
53
+ this._writeTimer = null;
54
+ this._flush();
55
+ }, 50);
61
56
  }
62
57
 
63
- async delete(key) {
64
- await this.ready;
65
- const existed = deleteByPath(this.data, key);
66
- await this._save();
67
- return existed;
68
- }
58
+ async _flush(force = false) {
59
+ if (this._writeInProgress) return;
60
+ if (!this._dirty && !force) return;
69
61
 
70
- async add(key, amount) {
71
- await this.ready;
62
+ this._writeInProgress = true;
63
+ this._dirty = false;
72
64
 
73
- let current = getByPath(this.data, key);
74
- if (typeof current !== "number") current = 0;
65
+ const payload = encrypt(
66
+ JSON.stringify(this.data),
67
+ this.masterKey
68
+ );
75
69
 
76
- const next = current + amount;
77
- setByPath(this.data, key, next);
70
+ await writeFile(
71
+ this.file,
72
+ JSON.stringify({
73
+ _meta: { key: encryptMasterKey(this.masterKey) },
74
+ payload
75
+ })
76
+ );
78
77
 
79
- await this._save();
80
- return next;
78
+ this._writeInProgress = false;
81
79
  }
82
80
 
83
- async subtract(key, amount) {
81
+ /* -----------------------------
82
+ PUBLIC API (PATH-FIRST)
83
+ ------------------------------ */
84
+
85
+ async get(path) {
84
86
  await this.ready;
87
+ return getByPath(this.data, path);
88
+ }
85
89
 
86
- let current = getByPath(this.data, key);
87
- if (typeof current !== "number") current = 0;
90
+ async has(path) {
91
+ await this.ready;
92
+ return getByPath(this.data, path) !== undefined;
93
+ }
88
94
 
89
- const next = current - amount;
90
- setByPath(this.data, key, next);
95
+ async set(path, value) {
96
+ await this.ready;
97
+ setByPath(this.data, path, value);
98
+ this._scheduleFlush();
99
+ return value;
100
+ }
91
101
 
92
- await this._save();
93
- return next;
102
+ async delete(path) {
103
+ await this.ready;
104
+ const existed = deleteByPath(this.data, path);
105
+ if (existed) this._scheduleFlush();
106
+ return existed;
94
107
  }
95
108
 
96
- async push(key, value) {
109
+ async add(path, amount) {
97
110
  await this.ready;
98
111
 
99
- let arr = getByPath(this.data, key);
100
- if (!Array.isArray(arr)) arr = [];
112
+ const current = getByPath(this.data, path);
113
+ const next = (typeof current === "number" ? current : 0) + amount;
101
114
 
102
- arr.push(value);
103
- setByPath(this.data, key, arr);
115
+ setByPath(this.data, path, next);
116
+ this._scheduleFlush();
117
+ return next;
118
+ }
104
119
 
105
- await this._save();
106
- return arr;
120
+ async subtract(path, amount) {
121
+ return this.add(path, -amount);
107
122
  }
108
123
 
109
- async has(path, value) {
124
+ async push(path, value) {
110
125
  await this.ready;
111
126
 
112
127
  const current = getByPath(this.data, path);
128
+ const next = Array.isArray(current) ? current : [];
113
129
 
114
- if (current === undefined) return false;
130
+ next.push(value);
131
+ setByPath(this.data, path, next);
115
132
 
116
- if (value === undefined) return true;
133
+ this._scheduleFlush();
134
+ return next;
135
+ }
117
136
 
118
- // If current is an array, check includes
119
- if (Array.isArray(current)) {
120
- return current.includes(value);
121
- }
137
+ /* -----------------------------
138
+ SAFETY
139
+ ------------------------------ */
122
140
 
123
- return current === value;
141
+ async close() {
142
+ await this._flush(true);
124
143
  }
125
-
126
144
  }
127
145
 
128
146
  module.exports = { SecureStore };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiro-db",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "A lightweight, zero-dependency, encrypted data store for Node.js.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -8,7 +8,14 @@
8
8
  },
9
9
  "keywords": ["database", "storage", "encryption", "secure"],
10
10
  "author": "krispowers",
11
- "license": "ISC",
11
+ "license": "MIT",
12
+ "contributors": [
13
+ {
14
+ "name": "Kris Powers",
15
+ "githubUsername": "krispowers",
16
+ "url": "https://github.com/KrisPowers"
17
+ }
18
+ ],
12
19
  "files": [
13
20
  "Readme.md",
14
21
  "index.js",