jsql-neo 3.5.2 → 4.0.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/LICENSE +202 -0
- package/bin/jsql +97 -0
- package/index.js +20 -0
- package/lib/database.js +484 -28
- package/lib/mod.js +316 -0
- package/lib/mysql_compat.js +232 -0
- package/lib/mysql_server.js +514 -0
- package/lib/native_client.js +470 -0
- package/lib/nedb_compat.js +506 -0
- package/lib/plugin.js +35 -0
- package/lib/sql.js +1160 -0
- package/lib/table.js +42 -21
- package/lib/wasm_client.js +176 -8
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.toml +24 -0
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +449 -0
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +147 -0
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +41 -0
- package/nativesrc/jsql-neo-core/src/engine/table.rs +664 -0
- package/nativesrc/jsql-neo-core/src/lib.rs +3 -0
- package/nativesrc/jsql-neo-core/src/storage/mod.rs +1 -0
- package/nativesrc/jsql-neo-core/src/storage/persistent.rs +2 -0
- package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -0
- package/nativesrc/jsql-neo-core/src/types.rs +94 -0
- package/nativesrc/jsql-neo-native/Cargo.lock +606 -0
- package/nativesrc/jsql-neo-native/Cargo.toml +16 -0
- package/nativesrc/jsql-neo-native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-native/package.json +7 -0
- package/nativesrc/jsql-neo-native/src/lib.rs +281 -0
- package/package.json +9 -1
- package/wasm/jsql_neo_wasm.d.ts +8 -0
- package/wasm/jsql_neo_wasm.js +455 -6
- package/wasm/jsql_neo_wasm_bg.js +22 -0
- package/wasm/jsql_neo_wasm_bg.wasm +0 -0
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +4 -0
- package/wasm/package.json +1 -8
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
// © Vexify 2026 All Rights Reserved.
|
|
2
|
+
/**
|
|
3
|
+
* NeDB 兼容层 — Datastore
|
|
4
|
+
* 提供 NeDB 风格的 insert/find/findOne/update/remove/count 等 API,
|
|
5
|
+
* 底层复用 JSQL 引擎(WASM / Native / Pure JS)。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const Database = require('./database');
|
|
9
|
+
|
|
10
|
+
let uidCounter = 0;
|
|
11
|
+
function genId() {
|
|
12
|
+
uidCounter++;
|
|
13
|
+
return (Date.now() + uidCounter * 37).toString(36) + Math.random().toString(36).slice(2, 8);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getPath(obj, path) {
|
|
17
|
+
if (!path) return obj;
|
|
18
|
+
const parts = String(path).split('.');
|
|
19
|
+
let cur = obj;
|
|
20
|
+
for (const p of parts) {
|
|
21
|
+
if (cur === null || cur === undefined || typeof cur !== 'object') return undefined;
|
|
22
|
+
cur = cur[p];
|
|
23
|
+
}
|
|
24
|
+
return cur;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function setPath(obj, path, value) {
|
|
28
|
+
const parts = String(path).split('.');
|
|
29
|
+
let cur = obj;
|
|
30
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
31
|
+
if (cur[parts[i]] === null || typeof cur[parts[i]] !== 'object') cur[parts[i]] = {};
|
|
32
|
+
cur = cur[parts[i]];
|
|
33
|
+
}
|
|
34
|
+
cur[parts[parts.length - 1]] = value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function unsetPath(obj, path) {
|
|
38
|
+
const parts = String(path).split('.');
|
|
39
|
+
let cur = obj;
|
|
40
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
41
|
+
if (cur === null || typeof cur !== 'object') return;
|
|
42
|
+
cur = cur[parts[i]];
|
|
43
|
+
}
|
|
44
|
+
if (cur && typeof cur === 'object') delete cur[parts[parts.length - 1]];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function arraysEqual(a, b) {
|
|
48
|
+
if (a.length !== b.length) return false;
|
|
49
|
+
for (let i = 0; i < a.length; i++) if (!looseEqual(a[i], b[i])) return false;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function looseEqual(a, b) {
|
|
54
|
+
if (a === b) return true;
|
|
55
|
+
if (a === null || b === null || a === undefined || b === undefined) return false;
|
|
56
|
+
if (typeof a === 'number' || typeof b === 'number') {
|
|
57
|
+
const na = Number(a), nb = Number(b);
|
|
58
|
+
if (!Number.isNaN(na) && !Number.isNaN(nb)) return na === nb;
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(a) && Array.isArray(b)) return arraysEqual(a, b);
|
|
61
|
+
if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {
|
|
62
|
+
const ka = Object.keys(a), kb = Object.keys(b);
|
|
63
|
+
if (ka.length !== kb.length) return false;
|
|
64
|
+
return ka.every(k => looseEqual(a[k], b[k]));
|
|
65
|
+
}
|
|
66
|
+
return String(a) === String(b);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function matchOperator(value, op, expected) {
|
|
70
|
+
switch (op) {
|
|
71
|
+
case '$eq': return looseEqual(value, expected);
|
|
72
|
+
case '$ne': return !looseEqual(value, expected);
|
|
73
|
+
case '$lt': return value !== null && value !== undefined && value < expected;
|
|
74
|
+
case '$lte': return value !== null && value !== undefined && value <= expected;
|
|
75
|
+
case '$gt': return value !== null && value !== undefined && value > expected;
|
|
76
|
+
case '$gte': return value !== null && value !== undefined && value >= expected;
|
|
77
|
+
case '$in': return Array.isArray(expected) && expected.some(e => looseEqual(value, e));
|
|
78
|
+
case '$nin': return Array.isArray(expected) && !expected.some(e => looseEqual(value, e));
|
|
79
|
+
case '$exists': return expected ? value !== undefined : value === undefined;
|
|
80
|
+
case '$regex':
|
|
81
|
+
return typeof value === 'string' && new RegExp(expected.source, expected.flags || 'i').test(value);
|
|
82
|
+
case '$size': return Array.isArray(value) && value.length === expected;
|
|
83
|
+
default: return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function matchFieldValue(value, condition) {
|
|
88
|
+
if (condition === null || condition === undefined) {
|
|
89
|
+
return value === null || value === undefined;
|
|
90
|
+
}
|
|
91
|
+
if (condition instanceof RegExp) {
|
|
92
|
+
return typeof value === 'string' && condition.test(value);
|
|
93
|
+
}
|
|
94
|
+
if (typeof condition === 'object' && !Array.isArray(condition) && Object.keys(condition).some(k => k.startsWith('$'))) {
|
|
95
|
+
for (const [op, expected] of Object.entries(condition)) {
|
|
96
|
+
if (op === '$not') {
|
|
97
|
+
if (matchFieldValue(value, expected)) return false;
|
|
98
|
+
} else if (op === '$and') {
|
|
99
|
+
if (!Array.isArray(expected) || !expected.every(c => matchFieldValue(value, c))) return false;
|
|
100
|
+
} else if (op === '$or') {
|
|
101
|
+
if (!Array.isArray(expected) || !expected.some(c => matchFieldValue(value, c))) return false;
|
|
102
|
+
} else if (op === '$elemMatch') {
|
|
103
|
+
if (!Array.isArray(value) || !value.some(el => match(el, expected))) return false;
|
|
104
|
+
} else {
|
|
105
|
+
if (!matchOperator(value, op, expected)) return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return looseEqual(value, condition);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function match(doc, query) {
|
|
114
|
+
if (!query) return true;
|
|
115
|
+
if (typeof query === 'function') return !!query(doc);
|
|
116
|
+
if (query instanceof RegExp) return matchFieldValue(doc, query);
|
|
117
|
+
|
|
118
|
+
if (query.$and) return query.$and.every(q => match(doc, q));
|
|
119
|
+
if (query.$or) return query.$or.some(q => match(doc, q));
|
|
120
|
+
if (query.$not) return !match(doc, query.$not);
|
|
121
|
+
if (query.$where && typeof query.$where === 'function') {
|
|
122
|
+
if (!query.$where.call(doc, doc)) return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const [key, condition] of Object.entries(query)) {
|
|
126
|
+
if (key.startsWith('$')) continue;
|
|
127
|
+
const value = getPath(doc, key);
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
if (Array.isArray(condition)) {
|
|
130
|
+
if (!arraysEqual(value, condition)) return false;
|
|
131
|
+
} else if (condition && typeof condition === 'object' && !(condition instanceof RegExp) && Object.keys(condition).some(k => k.startsWith('$'))) {
|
|
132
|
+
if (!matchFieldValue(value, condition)) return false;
|
|
133
|
+
} else {
|
|
134
|
+
if (!value.some(el => matchFieldValue(el, condition))) return false;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
if (!matchFieldValue(value, condition)) return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function applyModifier(doc, update) {
|
|
144
|
+
for (const [op, payload] of Object.entries(update)) {
|
|
145
|
+
if (op === '$set') {
|
|
146
|
+
for (const [k, v] of Object.entries(payload)) setPath(doc, k, v);
|
|
147
|
+
} else if (op === '$unset') {
|
|
148
|
+
for (const k of Object.keys(payload)) unsetPath(doc, k);
|
|
149
|
+
} else if (op === '$inc') {
|
|
150
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
151
|
+
const cur = getPath(doc, k);
|
|
152
|
+
setPath(doc, k, (Number(cur) || 0) + v);
|
|
153
|
+
}
|
|
154
|
+
} else if (op === '$mul') {
|
|
155
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
156
|
+
const cur = getPath(doc, k);
|
|
157
|
+
setPath(doc, k, cur === undefined ? 0 : (Number(cur) || 0) * v);
|
|
158
|
+
}
|
|
159
|
+
} else if (op === '$min') {
|
|
160
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
161
|
+
const cur = getPath(doc, k);
|
|
162
|
+
if (cur === undefined || v < cur) setPath(doc, k, v);
|
|
163
|
+
}
|
|
164
|
+
} else if (op === '$max') {
|
|
165
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
166
|
+
const cur = getPath(doc, k);
|
|
167
|
+
if (cur === undefined || v > cur) setPath(doc, k, v);
|
|
168
|
+
}
|
|
169
|
+
} else if (op === '$push') {
|
|
170
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
171
|
+
const cur = getPath(doc, k);
|
|
172
|
+
const arr = Array.isArray(cur) ? cur : [];
|
|
173
|
+
if (v && typeof v === 'object' && v.$each) arr.push(...v.$each);
|
|
174
|
+
else arr.push(v);
|
|
175
|
+
setPath(doc, k, arr);
|
|
176
|
+
}
|
|
177
|
+
} else if (op === '$addToSet') {
|
|
178
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
179
|
+
const cur = getPath(doc, k);
|
|
180
|
+
const arr = Array.isArray(cur) ? cur : [];
|
|
181
|
+
if (v && typeof v === 'object' && v.$each) {
|
|
182
|
+
for (const e of v.$each) if (!arr.some(x => looseEqual(x, e))) arr.push(e);
|
|
183
|
+
} else {
|
|
184
|
+
if (!arr.some(x => looseEqual(x, v))) arr.push(v);
|
|
185
|
+
}
|
|
186
|
+
setPath(doc, k, arr);
|
|
187
|
+
}
|
|
188
|
+
} else if (op === '$pop') {
|
|
189
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
190
|
+
const arr = getPath(doc, k);
|
|
191
|
+
if (Array.isArray(arr)) setPath(doc, k, v < 0 ? arr.slice(1) : arr.slice(0, -1));
|
|
192
|
+
}
|
|
193
|
+
} else if (op === '$pull') {
|
|
194
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
195
|
+
const arr = getPath(doc, k);
|
|
196
|
+
if (Array.isArray(arr)) setPath(doc, k, arr.filter(el => !match(el, { k: v })));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return doc;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function isModifier(update) {
|
|
204
|
+
return Object.keys(update || {}).some(k => k.startsWith('$'));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
class Cursor {
|
|
208
|
+
constructor(datastore, query, projection) {
|
|
209
|
+
this._store = datastore;
|
|
210
|
+
this._query = query || {};
|
|
211
|
+
this._projection = projection || null;
|
|
212
|
+
this._sort = null;
|
|
213
|
+
this._limit = null;
|
|
214
|
+
this._skip = 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
sort(spec) {
|
|
218
|
+
this._sort = spec;
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
limit(n) {
|
|
223
|
+
this._limit = n;
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
skip(n) {
|
|
228
|
+
this._skip = n;
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
projection(p) {
|
|
233
|
+
this._projection = p;
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async _run() {
|
|
238
|
+
return this._store._findDocs(this._query, {
|
|
239
|
+
sort: this._sort, limit: this._limit, skip: this._skip, projection: this._projection,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
exec(cb) {
|
|
244
|
+
const p = this._run();
|
|
245
|
+
if (cb) p.then(docs => cb(null, docs), err => cb(err)).catch(() => {});
|
|
246
|
+
return p;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
toArray(cb) {
|
|
250
|
+
return this.exec(cb);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async count(cb) {
|
|
254
|
+
const docs = await this._store._findDocs(this._query, {});
|
|
255
|
+
if (cb) cb(null, docs.length);
|
|
256
|
+
return docs.length;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
then(resolve, reject) {
|
|
260
|
+
return this._run().then(resolve, reject);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
class Datastore {
|
|
265
|
+
constructor(options = {}) {
|
|
266
|
+
this.filename = options.filename || null;
|
|
267
|
+
this.database = options.database || options.engine || null;
|
|
268
|
+
this._ownEngine = false;
|
|
269
|
+
this._started = false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async _ensureEngine() {
|
|
273
|
+
if (this.database) {
|
|
274
|
+
if (!this._started) {
|
|
275
|
+
if (typeof this.database.start === 'function') await this.database.start();
|
|
276
|
+
this._started = true;
|
|
277
|
+
}
|
|
278
|
+
return this.database;
|
|
279
|
+
}
|
|
280
|
+
this.database = new Database(this.filename || ':memory:');
|
|
281
|
+
if (typeof this.database.start === 'function') await this.database.start();
|
|
282
|
+
this._ownEngine = true;
|
|
283
|
+
this._started = true;
|
|
284
|
+
return this.database;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async _tableName() {
|
|
288
|
+
return this._table || (this._table = 'nedb_' + (this.filename ? require('path').basename(this.filename).replace(/\.db$/, '').replace(/[^a-zA-Z0-9_]/g, '_') : 'data'));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async _ensureTable() {
|
|
292
|
+
const db = await this._ensureEngine();
|
|
293
|
+
const name = await this._tableName();
|
|
294
|
+
if (typeof db.hasTable === 'function' && await db.hasTable(name)) {
|
|
295
|
+
return name;
|
|
296
|
+
}
|
|
297
|
+
await db.createTable(name, { _doc: { type: 'string' } });
|
|
298
|
+
return name;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
loadDatabase(cb) {
|
|
302
|
+
const p = this._ensureEngine().then(() => this._ensureTable()).then(() => undefined);
|
|
303
|
+
if (cb) p.then(() => cb(null), err => cb(err));
|
|
304
|
+
return p;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
insert(docs, cb) {
|
|
308
|
+
const single = !Array.isArray(docs);
|
|
309
|
+
const arr = single ? [docs] : docs;
|
|
310
|
+
if (arr.length === 0) {
|
|
311
|
+
const p = Promise.resolve(single ? null : []);
|
|
312
|
+
if (cb) p.then(r => cb(null, r), err => cb(err));
|
|
313
|
+
return p;
|
|
314
|
+
}
|
|
315
|
+
const p = (async () => {
|
|
316
|
+
const db = await this._ensureEngine();
|
|
317
|
+
const name = await this._ensureTable();
|
|
318
|
+
const rows = arr.map(d => {
|
|
319
|
+
const doc = { ...d };
|
|
320
|
+
if (doc._id === undefined) doc._id = genId();
|
|
321
|
+
this._lastInserted = doc;
|
|
322
|
+
return { _doc: JSON.stringify(doc) };
|
|
323
|
+
});
|
|
324
|
+
await db.insert(name, rows);
|
|
325
|
+
await db.flush();
|
|
326
|
+
const inserted = arr.map((d, i) => JSON.parse(rows[i]._doc));
|
|
327
|
+
return single ? inserted[0] : inserted;
|
|
328
|
+
})();
|
|
329
|
+
if (cb) p.then(r => cb(null, r), err => cb(err));
|
|
330
|
+
return p;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async _readDocs() {
|
|
334
|
+
const db = await this._ensureEngine();
|
|
335
|
+
const name = await this._ensureTable();
|
|
336
|
+
const all = await db.find(name, {}, { limit: 1e9, offset: 0 });
|
|
337
|
+
const rows = [];
|
|
338
|
+
for (let i = 0; i < all.length; i++) {
|
|
339
|
+
const row = all[i];
|
|
340
|
+
const raw = row.fields ? row.fields._doc : row._doc;
|
|
341
|
+
if (typeof raw !== 'string') continue;
|
|
342
|
+
try {
|
|
343
|
+
rows.push({ row, id: rowId(row, all), doc: JSON.parse(raw) });
|
|
344
|
+
} catch (e) {
|
|
345
|
+
// skip corrupted row
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return rows;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async _findDocs(query, opts = {}) {
|
|
352
|
+
const entries = await this._readDocs();
|
|
353
|
+
let docs = entries.filter(({ doc }) => match(doc, query)).map(({ doc }) => doc);
|
|
354
|
+
if (opts.sort) {
|
|
355
|
+
const keys = Object.entries(opts.sort);
|
|
356
|
+
docs.sort((a, b) => {
|
|
357
|
+
for (const [k, dir] of keys) {
|
|
358
|
+
const av = getPath(a, k), bv = getPath(b, k);
|
|
359
|
+
if (av === bv) continue;
|
|
360
|
+
const cmp = av === undefined || av === null ? -1 : (bv === undefined || bv === null ? 1 : (av < bv ? -1 : 1));
|
|
361
|
+
if (cmp !== 0) return dir >= 0 ? cmp : -cmp;
|
|
362
|
+
}
|
|
363
|
+
return 0;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
if (opts.skip) docs = docs.slice(opts.skip);
|
|
367
|
+
if (opts.limit !== null && opts.limit !== undefined) docs = docs.slice(0, opts.limit);
|
|
368
|
+
if (opts.projection) {
|
|
369
|
+
docs = docs.map(d => {
|
|
370
|
+
const out = {};
|
|
371
|
+
for (const [k, include] of Object.entries(opts.projection)) {
|
|
372
|
+
if (include) out[k] = getPath(d, k);
|
|
373
|
+
}
|
|
374
|
+
if (d._id !== undefined && opts.projection._id !== 0) out._id = d._id;
|
|
375
|
+
return out;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return docs;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
find(query, projection, cb) {
|
|
382
|
+
if (typeof projection === 'function') { cb = projection; projection = null; }
|
|
383
|
+
const cursor = new Cursor(this, query, projection);
|
|
384
|
+
if (cb) cursor.exec(cb);
|
|
385
|
+
return cursor;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
findOne(query, cb) {
|
|
389
|
+
const p = (async () => {
|
|
390
|
+
const docs = await this._findDocs(query, { limit: 1 });
|
|
391
|
+
return docs.length > 0 ? docs[0] : null;
|
|
392
|
+
})();
|
|
393
|
+
if (cb) p.then(d => cb(null, d), err => cb(err));
|
|
394
|
+
return p;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
update(query, update, options = {}, cb) {
|
|
398
|
+
if (typeof options === 'function') { cb = options; options = {}; }
|
|
399
|
+
const opts = { multi: options.multi === true, upsert: options.upsert === true, returnUpdatedDocs: options.returnUpdatedDocs === true };
|
|
400
|
+
const p = (async () => {
|
|
401
|
+
const db = await this._ensureEngine();
|
|
402
|
+
const name = await this._ensureTable();
|
|
403
|
+
const entries = await this._readDocs();
|
|
404
|
+
const matched = entries.filter(({ doc }) => match(doc, query));
|
|
405
|
+
let affected = 0;
|
|
406
|
+
let upserted = null;
|
|
407
|
+
if (matched.length === 0 && opts.upsert) {
|
|
408
|
+
const base = {};
|
|
409
|
+
for (const [k, v] of Object.entries(query)) {
|
|
410
|
+
if (!k.startsWith('$')) base[k] = v;
|
|
411
|
+
}
|
|
412
|
+
const newDoc = isModifier(update)
|
|
413
|
+
? { ...base, ...(update.$set || {}) }
|
|
414
|
+
: { ...update };
|
|
415
|
+
if (newDoc._id === undefined) newDoc._id = genId();
|
|
416
|
+
await db.insert(name, { _doc: JSON.stringify(newDoc) });
|
|
417
|
+
await db.flush();
|
|
418
|
+
upserted = { ...newDoc };
|
|
419
|
+
} else {
|
|
420
|
+
const targets = opts.multi ? matched : matched.slice(0, 1);
|
|
421
|
+
const updates = [];
|
|
422
|
+
for (const { row, id, doc } of targets) {
|
|
423
|
+
if (id === undefined) continue;
|
|
424
|
+
const next = { ...doc };
|
|
425
|
+
if (isModifier(update)) {
|
|
426
|
+
applyModifier(next, update);
|
|
427
|
+
} else {
|
|
428
|
+
for (const k of Object.keys(next)) delete next[k];
|
|
429
|
+
Object.assign(next, update);
|
|
430
|
+
next._id = doc._id;
|
|
431
|
+
}
|
|
432
|
+
if (JSON.stringify(next) !== JSON.stringify(doc)) {
|
|
433
|
+
updates.push([id, { _doc: JSON.stringify(next) }]);
|
|
434
|
+
affected++;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (updates.length > 0) {
|
|
438
|
+
if (typeof db.updateByIds === 'function') {
|
|
439
|
+
await db.updateByIds(name, updates);
|
|
440
|
+
} else {
|
|
441
|
+
for (const [id, data] of updates) await db.updateById(name, id, data);
|
|
442
|
+
}
|
|
443
|
+
await db.flush();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (opts.returnUpdatedDocs) {
|
|
447
|
+
const docs = await this._findDocs(query, { limit: opts.multi ? 1e9 : 1 });
|
|
448
|
+
return { numAffected: affected, affectedDocuments: docs, upsert: upserted };
|
|
449
|
+
}
|
|
450
|
+
return affected;
|
|
451
|
+
})();
|
|
452
|
+
if (cb) p.then(r => cb(null, r), err => cb(err));
|
|
453
|
+
return p;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
remove(query, options = {}, cb) {
|
|
457
|
+
if (typeof options === 'function') { cb = options; options = {}; }
|
|
458
|
+
const multi = options.multi === true;
|
|
459
|
+
const p = (async () => {
|
|
460
|
+
const db = await this._ensureEngine();
|
|
461
|
+
const name = await this._ensureTable();
|
|
462
|
+
const entries = await this._readDocs();
|
|
463
|
+
const toRemove = entries.filter(({ doc }) => match(doc, query));
|
|
464
|
+
const targets = multi ? toRemove : toRemove.slice(0, 1);
|
|
465
|
+
const ids = targets.map(t => t.id).filter(id => id !== undefined);
|
|
466
|
+
if (ids.length > 0) {
|
|
467
|
+
await db.removeByIds(name, ids);
|
|
468
|
+
await db.flush();
|
|
469
|
+
}
|
|
470
|
+
return targets.length;
|
|
471
|
+
})();
|
|
472
|
+
if (cb) p.then(r => cb(null, r), err => cb(err));
|
|
473
|
+
return p;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
count(query, cb) {
|
|
477
|
+
const p = this._findDocs(query, {}).then(docs => docs.length);
|
|
478
|
+
if (cb) p.then(n => cb(null, n), err => cb(err));
|
|
479
|
+
return p;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async ensureIndex(options = {}) {
|
|
483
|
+
await this._ensureEngine();
|
|
484
|
+
return { fieldName: options.fieldName, unique: options.unique === true };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async compaction() {
|
|
488
|
+
const db = await this._ensureEngine();
|
|
489
|
+
if (typeof db.compact === 'function') return db.compact();
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async stop() {
|
|
494
|
+
if (this.database && this._ownEngine && typeof this.database.stop === 'function') {
|
|
495
|
+
await this.database.stop();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function rowId(row, all) {
|
|
501
|
+
if (row && typeof row === 'object' && 'id' in row && row.fields) return row.id;
|
|
502
|
+
const idx = all.indexOf(row);
|
|
503
|
+
return idx === -1 ? undefined : idx + 1;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
module.exports = { Datastore, Cursor };
|
package/lib/plugin.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class Plugin {
|
|
2
|
+
constructor(name, opts = {}) {
|
|
3
|
+
this.name = name;
|
|
4
|
+
this.hooks = opts.hooks || {};
|
|
5
|
+
this._onEvent = opts.onEvent || null;
|
|
6
|
+
this._install = opts.install || null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
onEvent(fn) {
|
|
10
|
+
this._onEvent = fn;
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
on(hook, fn) {
|
|
15
|
+
this.hooks[hook] = fn;
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
install(fn) {
|
|
20
|
+
this._install = fn;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
build() {
|
|
25
|
+
const inst = {
|
|
26
|
+
name: this.name,
|
|
27
|
+
hooks: this.hooks
|
|
28
|
+
};
|
|
29
|
+
if (this._onEvent) inst.onEvent = this._onEvent;
|
|
30
|
+
if (this._install) inst.install = this._install;
|
|
31
|
+
return inst;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { Plugin };
|