snowbase 6.0.2 → 6.0.5

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.
@@ -1,69 +1,69 @@
1
- import {
2
- randomBytes,
3
- createCipheriv,
4
- createDecipheriv,
5
- scryptSync,
6
- } from "crypto";
7
- import { stableStringify } from "./utils.js";
8
-
9
- /**
10
- * @param {string} password
11
- * @param {Buffer} salt
12
- * @returns {Buffer}
13
- */
14
- export function getKey(password, salt) {
15
- return scryptSync(password, salt, 32);
16
- }
17
-
18
- /**
19
- * @param {any} data
20
- * @param {string} secret
21
- * @returns {string}
22
- */
23
- export function encrypt(data, secret) {
24
- const iv = randomBytes(12);
25
- const salt = randomBytes(16);
26
- const key = getKey(secret, salt);
27
- const cipher = createCipheriv("aes-256-gcm", key, iv);
28
-
29
- const encrypted = Buffer.concat([
30
- cipher.update(stableStringify(data), "utf8"),
31
- cipher.final(),
32
- ]);
33
-
34
- const tag = cipher.getAuthTag();
35
-
36
- return JSON.stringify({
37
- salt: salt.toString("base64"),
38
- iv: iv.toString("base64"),
39
- tag: tag.toString("base64"),
40
- data: encrypted.toString("base64"),
41
- });
42
- }
43
-
44
- /**
45
- * @param {string} raw
46
- * @param {string} secret
47
- * @returns {any}
48
- */
49
- export function decrypt(raw, secret) {
50
- const payload = JSON.parse(raw);
51
-
52
- const salt = Buffer.from(payload.salt, "base64");
53
- const key = getKey(secret, salt);
54
-
55
- const decipher = createDecipheriv(
56
- "aes-256-gcm",
57
- key,
58
- Buffer.from(payload.iv, "base64"),
59
- );
60
-
61
- decipher.setAuthTag(Buffer.from(payload.tag, "base64"));
62
-
63
- const decrypted = Buffer.concat([
64
- decipher.update(Buffer.from(payload.data, "base64")),
65
- decipher.final(),
66
- ]);
67
-
68
- return JSON.parse(decrypted.toString("utf8"));
69
- }
1
+ import {
2
+ randomBytes,
3
+ createCipheriv,
4
+ createDecipheriv,
5
+ scryptSync,
6
+ } from "crypto";
7
+ import { stableStringify } from "./utils.js";
8
+
9
+ /**
10
+ * @param {string} password
11
+ * @param {Buffer} salt
12
+ * @returns {Buffer}
13
+ */
14
+ export function getKey(password, salt) {
15
+ return scryptSync(password, salt, 32);
16
+ }
17
+
18
+ /**
19
+ * @param {any} data
20
+ * @param {string} secret
21
+ * @returns {string}
22
+ */
23
+ export function encrypt(data, secret) {
24
+ const iv = randomBytes(12);
25
+ const salt = randomBytes(16);
26
+ const key = getKey(secret, salt);
27
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
28
+
29
+ const encrypted = Buffer.concat([
30
+ cipher.update(stableStringify(data), "utf8"),
31
+ cipher.final(),
32
+ ]);
33
+
34
+ const tag = cipher.getAuthTag();
35
+
36
+ return JSON.stringify({
37
+ salt: salt.toString("base64"),
38
+ iv: iv.toString("base64"),
39
+ tag: tag.toString("base64"),
40
+ data: encrypted.toString("base64"),
41
+ });
42
+ }
43
+
44
+ /**
45
+ * @param {string} raw
46
+ * @param {string} secret
47
+ * @returns {any}
48
+ */
49
+ export function decrypt(raw, secret) {
50
+ const payload = JSON.parse(raw);
51
+
52
+ const salt = Buffer.from(payload.salt, "base64");
53
+ const key = getKey(secret, salt);
54
+
55
+ const decipher = createDecipheriv(
56
+ "aes-256-gcm",
57
+ key,
58
+ Buffer.from(payload.iv, "base64"),
59
+ );
60
+
61
+ decipher.setAuthTag(Buffer.from(payload.tag, "base64"));
62
+
63
+ const decrypted = Buffer.concat([
64
+ decipher.update(Buffer.from(payload.data, "base64")),
65
+ decipher.final(),
66
+ ]);
67
+
68
+ return JSON.parse(decrypted.toString("utf8"));
69
+ }
@@ -1,229 +1,229 @@
1
- import { decrypt, encrypt } from "./crypto.js";
2
- import { createHash } from "crypto";
3
- import { readFile, writeFile, appendFile, copyFile } from "node:fs/promises";
4
- import { join } from "path";
5
-
6
- /**
7
- * @param {string} path
8
- * @returns {Promise<string>}
9
- */
10
- export async function hash(path) {
11
- const fileBuffer = await readFile(path);
12
- return createHash("sha256").update(fileBuffer).digest("hex");
13
- }
14
-
15
- /**
16
- * Converts a value to a stable JSON string
17
- * @param {any} value - Value to convert
18
- * @returns {Promise<string>} - Stable JSON string
19
- */
20
- export async function stableStringify(value) {
21
- if (value === null || typeof value !== "object") {
22
- return JSON.stringify(value);
23
- }
24
-
25
- if (Array.isArray(value)) {
26
- return `[${(await Promise.all(value.map((v) => stableStringify(v)))).join(",")}]`;
27
- }
28
-
29
- const keys = Object.keys(value).sort();
30
-
31
- return `{${Promise.all(
32
- keys
33
- .map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`)
34
- .join(","),
35
- )}}`;
36
- }
37
- /**
38
- * Normalizes a chunk of data
39
- * @param {string|Object} chunk - Chunk to normalize
40
- * @returns {Promise<{raw: Object, stable: string}>} - Normalized chunk
41
- */
42
- export async function normalize(chunk) {
43
- const obj = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
44
-
45
- return {
46
- raw: obj,
47
- stable: await stableStringify(obj),
48
- };
49
- }
50
- /**
51
- * Splits a path like "user/name/first" into ["user", "name", "first"] for nested access
52
- * @param {string|undefined|Function} path - Path to parse
53
- * @param {function} error - Error callback
54
- * @returns {Promise<string[]>} - Array of path parts
55
- */
56
- export async function parsePath(path, error) {
57
- if (path == null || path === "") {
58
- error("[INVALID_PATH] Invalid path");
59
- return [];
60
- }
61
- if (typeof path !== "string") {
62
- error("[INVALID_PATH] Path must be a string");
63
- return [];
64
- }
65
- const parts = path.split("/").filter(Boolean);
66
- const forbidden = new Set(["prototype", "__proto__", "constructor"]); //protecting against prototype pollution
67
- if (parts.some((part) => forbidden.has(part)))
68
- error("[FORBIDDEN_KEY] Path contains forbidden key");
69
- if (parts.some((part) => part.includes(".")))
70
- error("[INVALID_PATH] Path parts cannot contain dots");
71
- if (parts.length === 0) error("[INVALID_PATH] Path is empty (just /)");
72
- return parts;
73
- }
74
-
75
- /**
76
- * Reads the database file
77
- * @param {string} path - Database file path
78
- * @param {function} error - Error callback
79
- * @param {boolean} encryption - Is encryption enabled
80
- * @param {string} [secret] - Encryption secret
81
- * @returns {Promise<Record<string, any>>} - Parsed database data
82
- */
83
- export async function read(path, error, encryption, secret) {
84
- const raw = await readFile(path, "utf8");
85
- let data;
86
- try {
87
- data = encryption ? decrypt(raw, secret || "") : JSON.parse(raw);
88
- } catch {
89
- error(
90
- "[INVALID_DATA] Database file contains invalid JSON or invalid encryption",
91
- );
92
- }
93
-
94
- if (data == null || typeof data !== "object" || Array.isArray(data)) {
95
- error("[INVALID_DATA] Database root must be an object");
96
- }
97
-
98
- //validate dangerous keys
99
- const forbidden = new Set(["prototype", "__proto__", "constructor"]);
100
- /** @param {Record<string, any>} obj */
101
- const hasForbiddenKey = (obj) => {
102
- for (const key of Object.keys(obj)) {
103
- if (forbidden.has(key)) {
104
- error("[FORBIDDEN_KEY] Database contains forbidden key: " + key);
105
- }
106
- if (typeof obj[key] === "object" && obj[key] !== null) {
107
- hasForbiddenKey(obj[key]);
108
- }
109
- }
110
- };
111
-
112
- hasForbiddenKey(data);
113
- return data;
114
- }
115
-
116
- /**
117
- * Logs an operation
118
- * @param {string} operation - Operation to log
119
- * @param {Record<string, any>|undefined} details - Details of the operation
120
- * @param {string|undefined} logsPath - Path to log file
121
- * @param {function} emit - Emit event
122
- * @param {boolean} logging - Enable logging
123
- * @returns {Promise<string>} - Timestamp
124
- */
125
- export async function log(operation, details, logsPath, emit, logging) {
126
- let timestamp = new Date().toISOString();
127
- if (!logging) {
128
- await emit(operation, details ? details : undefined, timestamp);
129
- return timestamp;
130
- }
131
- if (!logsPath) {
132
- await emit(operation, details ? details : undefined, timestamp);
133
- return timestamp;
134
- }
135
- if (!details)
136
- await appendFile(logsPath, `[${timestamp}] ${operation}\n`, "utf8");
137
- if (operation) {
138
- switch (operation) {
139
- case "get":
140
- await appendFile(
141
- logsPath,
142
- `[${timestamp}] ${operation} - Path: ${details?.path || "/"}\n`,
143
- "utf8",
144
- );
145
- break;
146
- case "remove":
147
- await appendFile(
148
- logsPath,
149
- `[${timestamp}] ${operation} - Path: ${details?.path || "/"}\n`,
150
- "utf8",
151
- );
152
- break;
153
- case "set":
154
- await appendFile(
155
- logsPath,
156
- `[${timestamp}] ${operation} - Path: ${details?.path || "/"}\n`,
157
- "utf8",
158
- );
159
- break;
160
- case "clear":
161
- await appendFile(
162
- logsPath,
163
- `[${timestamp}] ${operation} - Cleared all data\n`,
164
- "utf8",
165
- );
166
- break;
167
- }
168
- }
169
- emit(operation, details ? details : undefined, timestamp);
170
- return timestamp;
171
- }
172
-
173
- /**
174
- * Gets a target value from the data by path
175
- * @param {Record<string, any>} data - Data to search in
176
- * @param {string} path - Path to get target from
177
- * @param {function} error - Error callback
178
- * @returns {Promise<any>} - Found value or undefined
179
- */
180
- export async function getTargetFromData(data, path, error) {
181
- const parts = await parsePath(path, error);
182
- let current = data;
183
-
184
- for (const part of parts) {
185
- if (
186
- current == null ||
187
- typeof current !== "object" ||
188
- !Object.hasOwn(current, part)
189
- ) {
190
- return undefined;
191
- }
192
- current = current[part];
193
- }
194
- return current;
195
- }
196
-
197
- /**
198
- * Writes data to the database file
199
- * @param {string} path - Database file path
200
- * @param {Record<string, any>} data - Data to write
201
- * @param {function} error - Error callback
202
- * @param {function} emit - Emit event
203
- * @param {boolean} encryption - Is encryption enabled
204
- * @param {string|undefined} secret - Encryption secret
205
- */
206
- export async function write(path, data, error, emit, encryption, secret) {
207
- let content = "";
208
- try {
209
- content = encryption
210
- ? encrypt(data, secret || "")
211
- : await stableStringify(data);
212
- } catch {
213
- await error("[INVALID_DATA] Data must be JSON serializable or encryptable");
214
- }
215
-
216
- await writeFile(path, content, "utf8");
217
- emit("_write", data, new Date().toISOString());
218
- }
219
-
220
- export function randomId5() {
221
- const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
222
- let id = "";
223
-
224
- for (let i = 0; i < 5; i++) {
225
- id += chars[Math.floor(Math.random() * chars.length)];
226
- }
227
-
228
- return id;
1
+ import { decrypt, encrypt } from "./crypto.js";
2
+ import { createHash } from "crypto";
3
+ import { readFile, writeFile, appendFile, copyFile } from "node:fs/promises";
4
+ import { join } from "path";
5
+
6
+ /**
7
+ * @param {string} path
8
+ * @returns {Promise<string>}
9
+ */
10
+ export async function hash(path) {
11
+ const fileBuffer = await readFile(path);
12
+ return createHash("sha256").update(fileBuffer).digest("hex");
13
+ }
14
+
15
+ /**
16
+ * Converts a value to a stable JSON string
17
+ * @param {any} value - Value to convert
18
+ * @returns {Promise<string>} - Stable JSON string
19
+ */
20
+ export async function stableStringify(value) {
21
+ if (value === null || typeof value !== "object") {
22
+ return JSON.stringify(value);
23
+ }
24
+
25
+ if (Array.isArray(value)) {
26
+ return `[${(await Promise.all(value.map((v) => stableStringify(v)))).join(",")}]`;
27
+ }
28
+
29
+ const keys = Object.keys(value).sort();
30
+
31
+ return `{${Promise.all(
32
+ keys
33
+ .map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`)
34
+ .join(","),
35
+ )}}`;
36
+ }
37
+ /**
38
+ * Normalizes a chunk of data
39
+ * @param {string|Object} chunk - Chunk to normalize
40
+ * @returns {Promise<{raw: Object, stable: string}>} - Normalized chunk
41
+ */
42
+ export async function normalize(chunk) {
43
+ const obj = typeof chunk === "string" ? JSON.parse(chunk) : chunk;
44
+
45
+ return {
46
+ raw: obj,
47
+ stable: await stableStringify(obj),
48
+ };
49
+ }
50
+ /**
51
+ * Splits a path like "user/name/first" into ["user", "name", "first"] for nested access
52
+ * @param {string|undefined|Function} path - Path to parse
53
+ * @param {function} error - Error callback
54
+ * @returns {Promise<string[]>} - Array of path parts
55
+ */
56
+ export async function parsePath(path, error) {
57
+ if (path == null || path === "") {
58
+ error("[INVALID_PATH] Invalid path");
59
+ return [];
60
+ }
61
+ if (typeof path !== "string") {
62
+ error("[INVALID_PATH] Path must be a string");
63
+ return [];
64
+ }
65
+ const parts = path.split("/").filter(Boolean);
66
+ const forbidden = new Set(["prototype", "__proto__", "constructor"]); //protecting against prototype pollution
67
+ if (parts.some((part) => forbidden.has(part)))
68
+ error("[FORBIDDEN_KEY] Path contains forbidden key");
69
+ if (parts.some((part) => part.includes(".")))
70
+ error("[INVALID_PATH] Path parts cannot contain dots");
71
+ if (parts.length === 0) error("[INVALID_PATH] Path is empty (just /)");
72
+ return parts;
73
+ }
74
+
75
+ /**
76
+ * Reads the database file
77
+ * @param {string} path - Database file path
78
+ * @param {function} error - Error callback
79
+ * @param {boolean} encryption - Is encryption enabled
80
+ * @param {string} [secret] - Encryption secret
81
+ * @returns {Promise<Record<string, any>>} - Parsed database data
82
+ */
83
+ export async function read(path, error, encryption, secret) {
84
+ const raw = await readFile(path, "utf8");
85
+ let data;
86
+ try {
87
+ data = encryption ? decrypt(raw, secret || "") : JSON.parse(raw);
88
+ } catch {
89
+ error(
90
+ "[INVALID_DATA] Database file contains invalid JSON or invalid encryption",
91
+ );
92
+ }
93
+
94
+ if (data == null || typeof data !== "object" || Array.isArray(data)) {
95
+ error("[INVALID_DATA] Database root must be an object");
96
+ }
97
+
98
+ //validate dangerous keys
99
+ const forbidden = new Set(["prototype", "__proto__", "constructor"]);
100
+ /** @param {Record<string, any>} obj */
101
+ const hasForbiddenKey = (obj) => {
102
+ for (const key of Object.keys(obj)) {
103
+ if (forbidden.has(key)) {
104
+ error("[FORBIDDEN_KEY] Database contains forbidden key: " + key);
105
+ }
106
+ if (typeof obj[key] === "object" && obj[key] !== null) {
107
+ hasForbiddenKey(obj[key]);
108
+ }
109
+ }
110
+ };
111
+
112
+ hasForbiddenKey(data);
113
+ return data;
114
+ }
115
+
116
+ /**
117
+ * Logs an operation
118
+ * @param {string} operation - Operation to log
119
+ * @param {Record<string, any>|undefined} details - Details of the operation
120
+ * @param {string|undefined} logsPath - Path to log file
121
+ * @param {function} emit - Emit event
122
+ * @param {boolean} logging - Enable logging
123
+ * @returns {Promise<string>} - Timestamp
124
+ */
125
+ export async function log(operation, details, logsPath, emit, logging) {
126
+ let timestamp = new Date().toISOString();
127
+ if (!logging) {
128
+ await emit(operation, details ? details : undefined, timestamp);
129
+ return timestamp;
130
+ }
131
+ if (!logsPath) {
132
+ await emit(operation, details ? details : undefined, timestamp);
133
+ return timestamp;
134
+ }
135
+ if (!details)
136
+ await appendFile(logsPath, `[${timestamp}] ${operation}\n`, "utf8");
137
+ if (operation) {
138
+ switch (operation) {
139
+ case "get":
140
+ await appendFile(
141
+ logsPath,
142
+ `[${timestamp}] ${operation} - Path: ${details?.path || "/"}\n`,
143
+ "utf8",
144
+ );
145
+ break;
146
+ case "remove":
147
+ await appendFile(
148
+ logsPath,
149
+ `[${timestamp}] ${operation} - Path: ${details?.path || "/"}\n`,
150
+ "utf8",
151
+ );
152
+ break;
153
+ case "set":
154
+ await appendFile(
155
+ logsPath,
156
+ `[${timestamp}] ${operation} - Path: ${details?.path || "/"}\n`,
157
+ "utf8",
158
+ );
159
+ break;
160
+ case "clear":
161
+ await appendFile(
162
+ logsPath,
163
+ `[${timestamp}] ${operation} - Cleared all data\n`,
164
+ "utf8",
165
+ );
166
+ break;
167
+ }
168
+ }
169
+ emit(operation, details ? details : undefined, timestamp);
170
+ return timestamp;
171
+ }
172
+
173
+ /**
174
+ * Gets a target value from the data by path
175
+ * @param {Record<string, any>} data - Data to search in
176
+ * @param {string} path - Path to get target from
177
+ * @param {function} error - Error callback
178
+ * @returns {Promise<any>} - Found value or undefined
179
+ */
180
+ export async function getTargetFromData(data, path, error) {
181
+ const parts = await parsePath(path, error);
182
+ let current = data;
183
+
184
+ for (const part of parts) {
185
+ if (
186
+ current == null ||
187
+ typeof current !== "object" ||
188
+ !Object.hasOwn(current, part)
189
+ ) {
190
+ return undefined;
191
+ }
192
+ current = current[part];
193
+ }
194
+ return current;
195
+ }
196
+
197
+ /**
198
+ * Writes data to the database file
199
+ * @param {string} path - Database file path
200
+ * @param {Record<string, any>} data - Data to write
201
+ * @param {function} error - Error callback
202
+ * @param {function} emit - Emit event
203
+ * @param {boolean} encryption - Is encryption enabled
204
+ * @param {string|undefined} secret - Encryption secret
205
+ */
206
+ export async function write(path, data, error, emit, encryption, secret) {
207
+ let content = "";
208
+ try {
209
+ content = encryption
210
+ ? encrypt(data, secret || "")
211
+ : await stableStringify(data);
212
+ } catch {
213
+ await error("[INVALID_DATA] Data must be JSON serializable or encryptable");
214
+ }
215
+
216
+ await writeFile(path, content, "utf8");
217
+ emit("_write", data, new Date().toISOString());
218
+ }
219
+
220
+ export function randomId5() {
221
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
222
+ let id = "";
223
+
224
+ for (let i = 0; i < 5; i++) {
225
+ id += chars[Math.floor(Math.random() * chars.length)];
226
+ }
227
+
228
+ return id;
229
229
  }