corebasic 1.0.198 → 1.0.199
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/libs/cpp.js +216 -0
- package/libs/dip.js +1 -0
- package/libs/elabase.js +341 -53
- package/package.json +1 -1
package/libs/cpp.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function readContent(buffer, valueRef, bytesRef) {
|
|
6
|
+
const MASK = 0x7F;
|
|
7
|
+
const MSB = 0x80;
|
|
8
|
+
const MAX_VLQ_BYTES = 10;
|
|
9
|
+
|
|
10
|
+
// Ensure we have a valid Uint8Array view
|
|
11
|
+
if (!buffer) return false;
|
|
12
|
+
const view = ArrayBuffer.isView(buffer) ? buffer : new Uint8Array(buffer);
|
|
13
|
+
const bufferSize = view.length;
|
|
14
|
+
|
|
15
|
+
let length = 0n;
|
|
16
|
+
let shift = 0n;
|
|
17
|
+
let lenBytes = 0;
|
|
18
|
+
|
|
19
|
+
while (true) {
|
|
20
|
+
if (lenBytes >= MAX_VLQ_BYTES || lenBytes >= bufferSize) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const byte = view[lenBytes];
|
|
25
|
+
lenBytes++;
|
|
26
|
+
|
|
27
|
+
length |= BigInt(byte & MASK) << shift;
|
|
28
|
+
shift += 7n;
|
|
29
|
+
|
|
30
|
+
if ((byte & MSB) === 0) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Emulate C++ pointer assignment by modifying the wrapper objects
|
|
36
|
+
valueRef.value = length;
|
|
37
|
+
bytesRef.value = lenBytes;
|
|
38
|
+
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
export function vlqToUint64(arrayBuffer) {
|
|
45
|
+
// Objects act as containers to simulate C++ pointers/references
|
|
46
|
+
const valueRef = { value: 0n };
|
|
47
|
+
const bytesRef = { value: 0 };
|
|
48
|
+
|
|
49
|
+
const success = readContent(arrayBuffer, valueRef, bytesRef);
|
|
50
|
+
|
|
51
|
+
// If read_content returned false, handle or return empty/default values
|
|
52
|
+
if (!success) {
|
|
53
|
+
return { value: Number(0n), bytes: 0 };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Returns a plain object, the equivalent of QVariantMap
|
|
57
|
+
return {
|
|
58
|
+
value: Number(valueRef.value), // BigInt to Number
|
|
59
|
+
bytes: bytesRef.value // Number
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
function writeVlqcontent(buffer, value) {
|
|
67
|
+
const MASK = 0x7Fn;
|
|
68
|
+
const MSB = 0x80;
|
|
69
|
+
let bufferPtr = 0;
|
|
70
|
+
let tempValue = BigInt(value);
|
|
71
|
+
|
|
72
|
+
do {
|
|
73
|
+
let byte = Number(tempValue & MASK);
|
|
74
|
+
tempValue >>= 7n;
|
|
75
|
+
if (tempValue > 0n) {
|
|
76
|
+
byte |= MSB;
|
|
77
|
+
}
|
|
78
|
+
buffer[bufferPtr++] = byte;
|
|
79
|
+
} while (tempValue > 0n);
|
|
80
|
+
|
|
81
|
+
return bufferPtr;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function uint64ToVlq(value) {
|
|
85
|
+
// 10 bytes is the maximum VLQ size for a uint64
|
|
86
|
+
const out = Buffer.allocUnsafe(10);
|
|
87
|
+
const size = writeVlqcontent(out, value);
|
|
88
|
+
|
|
89
|
+
// Returns a fresh, correctly sized buffer copy
|
|
90
|
+
return Buffer.from(out.subarray(0, size));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
export function sliceArrayBuffer(buffer, offset, length) {
|
|
97
|
+
// Ensure we are working with a Node.js Buffer
|
|
98
|
+
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
99
|
+
const totalSize = buf.length;
|
|
100
|
+
|
|
101
|
+
// Boundary check matching your C++ logic
|
|
102
|
+
if (offset < 0 || offset >= totalSize) {
|
|
103
|
+
return Buffer.alloc(0); // Returns an empty Buffer (like QByteArray())
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// FIX: If length is -1, automatically calculate the remaining size to the end
|
|
107
|
+
if (length <= 0 || offset + length > totalSize) {
|
|
108
|
+
length = totalSize - offset;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// subarray() creates a zero-copy/shallow view over the original memory.
|
|
112
|
+
// Note: second argument is end index instead of cpp length
|
|
113
|
+
return buf.subarray(offset, offset + length); // COW/Shallow copy I hope or else what is the use of this function
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function stringToUtf8ArrayBuffer(arg) {
|
|
117
|
+
// Converts the string into a Node.js Buffer encoded in UTF-8
|
|
118
|
+
return Buffer.from(arg, 'utf8');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
export function numberToSortableBytes(value) {
|
|
123
|
+
// 1. Create an 8-byte buffer and a data view to read/write raw bits
|
|
124
|
+
const buffer = Buffer.alloc(8);
|
|
125
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
126
|
+
|
|
127
|
+
// 2. Write the double value into the buffer to extract its raw 64-bit bits
|
|
128
|
+
view.setFloat64(0, value, true); // Use little-endian for direct memory mapping
|
|
129
|
+
let bits = view.getBigUint64(0, true);
|
|
130
|
+
|
|
131
|
+
const SIGN_MASK = 0x8000000000000000n; // Use BigInt literal for 64-bit mask
|
|
132
|
+
|
|
133
|
+
// 3. Apply the sorting transformation logic
|
|
134
|
+
if ((bits & SIGN_MASK) !== 0n) {
|
|
135
|
+
// Negative: invert all bits
|
|
136
|
+
bits = ~bits;
|
|
137
|
+
} else {
|
|
138
|
+
// Positive: flip the sign bit
|
|
139
|
+
bits ^= SIGN_MASK;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 4. Write the transformed bits back as Big Endian (equivalent to qToBigEndian)
|
|
143
|
+
view.setBigUint64(0, bits, false); // false = Big Endian
|
|
144
|
+
|
|
145
|
+
return buffer;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
export function defaultHash(buffer) {
|
|
152
|
+
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
153
|
+
|
|
154
|
+
// Uses the high-speed, non-cryptographic xxhash64 algorithm
|
|
155
|
+
// supported natively in both Node.js and Bun's crypto module layers.
|
|
156
|
+
const hashBytes = crypto.createHash('md5').update(buf).digest(); // Earlier used xxhash64 but not available in my openssl. so md5
|
|
157
|
+
|
|
158
|
+
// Reads the 8-byte hash output into a 64-bit BigInt (quint64)
|
|
159
|
+
return hashBytes.readBigUInt64BE(0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
export function memcmpEqual(buffer1, buffer2) {
|
|
164
|
+
const b1 = Buffer.isBuffer(buffer1) ? buffer1 : Buffer.from(buffer1);
|
|
165
|
+
const b2 = Buffer.isBuffer(buffer2) ? buffer2 : Buffer.from(buffer2);
|
|
166
|
+
|
|
167
|
+
return b1.equals(b2);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
export function arrayBufferToString(buffer) {
|
|
172
|
+
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
173
|
+
|
|
174
|
+
// Decodes the buffer bytes as a UTF-8 JavaScript string
|
|
175
|
+
return buf.toString('utf8');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
// Matches QString Response::getSliceAsText
|
|
183
|
+
export function getSliceAsText(rawTextBuffer, offset, length) {
|
|
184
|
+
// Reuses the identical slice boundary logic from above
|
|
185
|
+
try {
|
|
186
|
+
const slice = getSliceAsArrayBuffer(rawTextBuffer, offset, length);
|
|
187
|
+
|
|
188
|
+
// Returns an empty string if slice was invalid, or the decoded UTF-8 string
|
|
189
|
+
return slice.length === 0 ? "" : slice.toString('utf8');
|
|
190
|
+
} catch (e) { console.log(e); throw e}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Matches QByteArray Response::getSliceAsArrayBuffer
|
|
194
|
+
export function getSliceAsArrayBuffer(rawTextBuffer, offset, length) {
|
|
195
|
+
const totalSize = rawTextBuffer.length;
|
|
196
|
+
|
|
197
|
+
if (offset < 0 || offset >= totalSize) {
|
|
198
|
+
return Buffer.alloc(0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (length === -1) {
|
|
202
|
+
length = totalSize - offset;
|
|
203
|
+
} else if (length <= 0 || (offset + length) > totalSize) {
|
|
204
|
+
return Buffer.alloc(0); // Strict failure rule matching your new C++ code
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Creates a zero-copy view of the sliced buffer
|
|
208
|
+
return rawTextBuffer.subarray(offset, offset + length);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
package/libs/dip.js
CHANGED
|
@@ -8,6 +8,7 @@ export const query = Elabase.query
|
|
|
8
8
|
export const update = Elabase.update
|
|
9
9
|
export const remove = Elabase.remove
|
|
10
10
|
|
|
11
|
+
export const newRaw = Elabase.newRaw
|
|
11
12
|
export const batchBegin = Elabase.batchBegin
|
|
12
13
|
export const batchAbort = Elabase.batchAbort
|
|
13
14
|
export const batchSubmit = Elabase.batchSubmit
|
package/libs/elabase.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
2
|
import {default as dipper, shard_stats as ShardStats} from './dipper.js'
|
|
3
3
|
import * as Utils from './utils.js'
|
|
4
|
-
|
|
4
|
+
import * as Cpp from './cpp.js'
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
@@ -29,6 +29,9 @@ let Elabase = {
|
|
|
29
29
|
Lmdb: 1,
|
|
30
30
|
Sled: 2,
|
|
31
31
|
Sanakirja: 3,
|
|
32
|
+
Singularity: 4,
|
|
33
|
+
Memory: 5,
|
|
34
|
+
Analytics: 6,
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -159,19 +162,23 @@ function generate_suffix(meta) {
|
|
|
159
162
|
return suffixes
|
|
160
163
|
}
|
|
161
164
|
|
|
165
|
+
// Ported
|
|
162
166
|
let BATCH_COUNTER = 0n;
|
|
163
167
|
let batches = {}
|
|
164
168
|
|
|
169
|
+
// Ported
|
|
165
170
|
export function batchBegin() { // Manual Lifecycle. If you forget to batchSubmit() or batchAbort(), then memory leak due to dangling batch
|
|
166
171
|
const uid = ++BATCH_COUNTER; // Safe. No skew in async ++BATCH_COUNTER as single thread and ++COUNTER is atomic in event loop sense. ⚠️ Only breaks on worker_threads or cluster (multi-process)
|
|
167
172
|
batches[uid] = []
|
|
168
173
|
return uid
|
|
169
174
|
}
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
// Ported
|
|
176
|
+
export function batchAbort(batch_id) { // Mandatory to prevent meory leak due to dangling batch if batchBegin() is called without batchSubmit()
|
|
177
|
+
delete batches[batch_id]
|
|
172
178
|
}
|
|
173
179
|
|
|
174
|
-
|
|
180
|
+
// Ported
|
|
181
|
+
export const batch = async (callback, arg) => { // Convenience function with auto lifecyle management and auto submit when callback returns. abort is called appropriately.
|
|
175
182
|
/**
|
|
176
183
|
* Usage:
|
|
177
184
|
* const result = await Dip.batch(async batch => {
|
|
@@ -185,23 +192,29 @@ export const batch = async (callback) => { // Convenience function with auto lif
|
|
|
185
192
|
|
|
186
193
|
try {
|
|
187
194
|
await callback(batch)
|
|
188
|
-
return await batchSubmit(batch)
|
|
195
|
+
return await batchSubmit(batch, arg)
|
|
189
196
|
} catch (err) {
|
|
190
197
|
batchAbort(batch)
|
|
191
198
|
throw err
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
201
|
|
|
195
|
-
|
|
196
|
-
|
|
202
|
+
// Ported
|
|
203
|
+
export function batchSubmit(batch_id, arg) {
|
|
204
|
+
if (!batch_id || !batches[batch_id] || !batches[batch_id].length)
|
|
197
205
|
throw {message: `Error: Invalid batch in Dip.batchSubmit()`}
|
|
198
|
-
let txns = batches[
|
|
199
|
-
|
|
200
|
-
|
|
206
|
+
let txns = batches[batch_id]
|
|
207
|
+
const executor = arg?.executor ?? batches[batch_id].find(txn => txn.executor)?.executor
|
|
208
|
+
const syncMode = arg?.syncMode ?? batches[batch_id].find(txn => txn.syncMode)?.syncMode
|
|
209
|
+
const raw = arg?.raw
|
|
210
|
+
const engine = arg?.engine
|
|
211
|
+
const port = arg?.port
|
|
212
|
+
const timeout = arg?.timeout
|
|
213
|
+
delete batches[batch_id]
|
|
201
214
|
return new Promise((resolve, reject) => resolve())
|
|
202
|
-
.then(() => execute({ "batch": txns,
|
|
215
|
+
.then(() => execute({ "batch": txns, version: arg?.version, raw, engine, compression: arg?.compression, executor, syncMode, port, timeout }))
|
|
203
216
|
.then (result => {
|
|
204
|
-
Events.send("
|
|
217
|
+
Events.send("Dip.batchSubmit", { response: result })
|
|
205
218
|
return result
|
|
206
219
|
})
|
|
207
220
|
}
|
|
@@ -261,12 +274,14 @@ function validate_topology(meta, collection, fn_name) {
|
|
|
261
274
|
return new_meta
|
|
262
275
|
}
|
|
263
276
|
|
|
277
|
+
// Ported
|
|
264
278
|
export const operation = async (name, extras) => {
|
|
265
279
|
return execute({ operation: name, ...extras })
|
|
266
280
|
}
|
|
267
281
|
|
|
268
282
|
|
|
269
|
-
|
|
283
|
+
// Ported
|
|
284
|
+
export const insert = async (meta, collection, value, _id, options, extras) => {
|
|
270
285
|
|
|
271
286
|
if (!(meta instanceof DipMeta)) console.warn('Warn: Provided meta is not an instance of DipMeta in Dip.insert()') // TODO: throw error once review completes
|
|
272
287
|
|
|
@@ -276,39 +291,36 @@ export const insert = async (meta, collection, value, _id, extras) => {
|
|
|
276
291
|
|
|
277
292
|
const topology_meta = validate_topology(meta, collection, 'in Dip.insert()') // TODO: loop for arrays
|
|
278
293
|
|
|
279
|
-
|
|
294
|
+
let arg = {
|
|
280
295
|
collection: collection,
|
|
281
|
-
suffix: generate_suffix(meta),
|
|
282
296
|
insert: value,
|
|
297
|
+
options: options ? options : {},
|
|
298
|
+
syncMode: meta.syncMode,
|
|
283
299
|
...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
|
|
284
300
|
...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
|
|
285
301
|
}
|
|
286
302
|
arg = Object.assign(arg, extras)
|
|
303
|
+
arg.suffix = generate_suffix(meta)
|
|
304
|
+
arg.executor = meta.executor ?? "native"
|
|
287
305
|
arg = Object.assign({}, topology_meta, arg)
|
|
288
306
|
if (_id)
|
|
289
307
|
arg._id = _id
|
|
290
308
|
|
|
291
|
-
if (!(["string", "number"].includes(typeof value?._id)))
|
|
292
|
-
throw {message: "Error: invalid _id in Dip.insert()"}
|
|
293
|
-
if (typeof value?._id === "string" && value._id.trim().length === 0)
|
|
294
|
-
throw {message: "Error: invalid _id in Dip.insert()"}
|
|
295
|
-
|
|
296
|
-
|
|
297
309
|
if (meta.batch) {
|
|
298
310
|
if (!batches[meta.batch])
|
|
299
311
|
throw {message: 'Error: Invalid batch in Dip.insert()'}
|
|
300
312
|
batches[meta.batch].push(arg)
|
|
301
313
|
return
|
|
302
314
|
}
|
|
303
|
-
arg.mode = "command"
|
|
304
315
|
return new Promise((resolve, reject) => resolve())
|
|
305
316
|
.then(() => execute(arg))
|
|
306
317
|
.then(result => {
|
|
307
|
-
Events.send("
|
|
318
|
+
Events.send("Dip.insert", { collection: collectionArray(collection), response: result })
|
|
308
319
|
return result
|
|
309
320
|
})
|
|
310
321
|
}
|
|
311
322
|
|
|
323
|
+
// Ported
|
|
312
324
|
export const query = async (meta, collection, query, options, extras) => {
|
|
313
325
|
|
|
314
326
|
if (!(meta instanceof DipMeta)) console.warn('Warn: Provided meta is not an instance of DipMeta in Dip.query()') // TODO: throw error once review completes
|
|
@@ -319,15 +331,17 @@ export const query = async (meta, collection, query, options, extras) => {
|
|
|
319
331
|
|
|
320
332
|
const topology_meta = validate_topology(meta, collection, 'in Dip.query()') // TODO: loop for arrays
|
|
321
333
|
|
|
322
|
-
|
|
334
|
+
let arg = {
|
|
323
335
|
collection: collection,
|
|
324
|
-
suffix: generate_suffix(meta),
|
|
325
336
|
query: query,
|
|
326
337
|
options: options ? options : {},
|
|
338
|
+
syncMode: meta.syncMode,
|
|
327
339
|
...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
|
|
328
340
|
...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
|
|
329
341
|
}
|
|
330
342
|
arg = Object.assign(arg, extras)
|
|
343
|
+
arg.suffix = generate_suffix(meta)
|
|
344
|
+
arg.executor = meta.executor ?? "native"
|
|
331
345
|
arg = Object.assign({}, topology_meta, arg)
|
|
332
346
|
if (meta.batch) {
|
|
333
347
|
if (!batches[meta.batch])
|
|
@@ -335,17 +349,16 @@ export const query = async (meta, collection, query, options, extras) => {
|
|
|
335
349
|
batches[meta.batch].push(arg)
|
|
336
350
|
return
|
|
337
351
|
}
|
|
338
|
-
arg.mode = "query"
|
|
339
352
|
return new Promise((resolve, reject) => resolve())
|
|
340
353
|
.then(() => execute(arg))
|
|
341
354
|
}
|
|
342
355
|
|
|
343
356
|
|
|
344
357
|
function collectionArray(col) {
|
|
345
|
-
|
|
358
|
+
let type = Object.prototype.toString.call(col)
|
|
346
359
|
if (type === '[object Array]') {
|
|
347
|
-
|
|
348
|
-
for (
|
|
360
|
+
let newcol = []
|
|
361
|
+
for (let i in col) {
|
|
349
362
|
if (!Utils.isEmpty(col[i]))
|
|
350
363
|
newcol.push(col[i])
|
|
351
364
|
}
|
|
@@ -357,7 +370,7 @@ function collectionArray(col) {
|
|
|
357
370
|
return []
|
|
358
371
|
}
|
|
359
372
|
|
|
360
|
-
|
|
373
|
+
// Ported
|
|
361
374
|
export const update = async (meta, collection, query, update, options, extras) => {
|
|
362
375
|
|
|
363
376
|
if (!(meta instanceof DipMeta)) console.warn('Warn: Provided meta is not an instance of DipMeta in Dip.update()') // TODO: throw error once review completes
|
|
@@ -368,16 +381,18 @@ export const update = async (meta, collection, query, update, options, extras) =
|
|
|
368
381
|
|
|
369
382
|
const topology_meta = validate_topology(meta, collection, 'in Dip.update()') // TODO: loop for arrays
|
|
370
383
|
|
|
371
|
-
|
|
384
|
+
let arg = {
|
|
372
385
|
collection: collection,
|
|
373
|
-
suffix: generate_suffix(meta),
|
|
374
386
|
query: query,
|
|
375
387
|
update: update,
|
|
376
388
|
options: options ? options : {},
|
|
389
|
+
syncMode: meta.syncMode,
|
|
377
390
|
...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
|
|
378
391
|
...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
|
|
379
392
|
}
|
|
380
393
|
arg = Object.assign(arg, extras)
|
|
394
|
+
arg.suffix = generate_suffix(meta)
|
|
395
|
+
arg.executor = meta.executor ?? "native"
|
|
381
396
|
arg = Object.assign({}, topology_meta, arg)
|
|
382
397
|
if (meta.batch) {
|
|
383
398
|
if (!batches[meta.batch])
|
|
@@ -385,15 +400,15 @@ export const update = async (meta, collection, query, update, options, extras) =
|
|
|
385
400
|
batches[meta.batch].push(arg)
|
|
386
401
|
return
|
|
387
402
|
}
|
|
388
|
-
arg.mode = "command"
|
|
389
403
|
return new Promise((resolve, reject) => resolve())
|
|
390
404
|
.then(() => execute(arg))
|
|
391
405
|
.then (result => {
|
|
392
|
-
Events.send("
|
|
406
|
+
Events.send("Dip.update", { collection: collectionArray(collection), response: result })
|
|
393
407
|
return result
|
|
394
408
|
})
|
|
395
409
|
}
|
|
396
410
|
|
|
411
|
+
// Ported
|
|
397
412
|
export const remove = async (meta, collection, query, options, extras) => {
|
|
398
413
|
|
|
399
414
|
if (!(meta instanceof DipMeta)) console.warn('Warn: Provided meta is not an instance of DipMeta in Dip.remove()') // TODO: throw error once review completes
|
|
@@ -407,16 +422,18 @@ export const remove = async (meta, collection, query, options, extras) => {
|
|
|
407
422
|
|
|
408
423
|
const topology_meta = validate_topology(meta, collection, 'in Dip.remove()') // TODO: loop for arrays
|
|
409
424
|
|
|
410
|
-
|
|
425
|
+
let arg = {
|
|
411
426
|
collection: collection,
|
|
412
|
-
suffix: generate_suffix(meta),
|
|
413
427
|
query: query,
|
|
414
|
-
delete: true,
|
|
415
428
|
options: options ? options : {},
|
|
429
|
+
delete: true,
|
|
430
|
+
syncMode: meta.syncMode,
|
|
416
431
|
...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
|
|
417
432
|
...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
|
|
418
433
|
}
|
|
419
434
|
arg = Object.assign(arg, extras)
|
|
435
|
+
arg.suffix = generate_suffix(meta)
|
|
436
|
+
arg.executor = meta.executor ?? "native"
|
|
420
437
|
arg = Object.assign({}, topology_meta, arg)
|
|
421
438
|
if (meta.batch) {
|
|
422
439
|
if (!batches[meta.batch])
|
|
@@ -424,18 +441,14 @@ export const remove = async (meta, collection, query, options, extras) => {
|
|
|
424
441
|
batches[meta.batch].push(arg)
|
|
425
442
|
return
|
|
426
443
|
}
|
|
427
|
-
arg.mode = "command"
|
|
428
444
|
return new Promise((resolve, reject) => resolve())
|
|
429
445
|
.then(() => execute(arg))
|
|
430
446
|
.then(result => {
|
|
431
|
-
Events.send("
|
|
447
|
+
Events.send("Dip.remove", { collection: collectionArray(collection), response: result })
|
|
432
448
|
return result
|
|
433
449
|
})
|
|
434
450
|
}
|
|
435
451
|
|
|
436
|
-
export const close = async (meta, db) => {
|
|
437
|
-
return query(meta, "-", {}, {}, {db: db ?? root.db, close: true})
|
|
438
|
-
}
|
|
439
452
|
|
|
440
453
|
|
|
441
454
|
export let config = {
|
|
@@ -455,29 +468,304 @@ function execute(arg) {
|
|
|
455
468
|
// _id: 1 // Optional. Used with insert
|
|
456
469
|
// }
|
|
457
470
|
arg.db = arg.db ?? db
|
|
471
|
+
arg.executor = arg.executor ?? "native"
|
|
458
472
|
arg.engine = arg.engine ?? internal.engineName
|
|
459
|
-
arg.compression
|
|
473
|
+
if (arg.compression === undefined)
|
|
474
|
+
arg.compression = true // Compress all for now by default
|
|
475
|
+
|
|
476
|
+
let fullurl = `${url}:${arg?.port ?? port}/${api}`
|
|
477
|
+
|
|
478
|
+
delete arg.port
|
|
479
|
+
const timeout = arg.timeout
|
|
480
|
+
delete arg.timeout
|
|
460
481
|
|
|
461
482
|
|
|
462
483
|
return config.useDipper ? dipper(arg)
|
|
463
484
|
: axios
|
|
464
|
-
.post(arg.DIP_URL ?? fullurl, {...arg, DIP_URL: undefined})
|
|
465
|
-
// .timeout(5000)
|
|
485
|
+
.post(arg.DIP_URL ?? fullurl, buildHybridRequest({...arg, DIP_URL: undefined}), { responseType: 'arraybuffer', timeout: timeout ?? 5000, headers: {"Content-Type": "application/dip"} })
|
|
466
486
|
.then(result => {
|
|
467
|
-
//
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
487
|
+
// console.log(res.status);
|
|
488
|
+
// console.log(JSON.stringify(res.header, null, 4));
|
|
489
|
+
// console.log(JSON.stringify(res.body, null, 4));
|
|
490
|
+
|
|
491
|
+
result.getSliceAsText = (offset, length) => Cpp.getSliceAsText(result.data, offset, length)
|
|
492
|
+
result.getSliceAsArrayBuffer = (offset, length) => Cpp.getSliceAsArrayBuffer(result.data, offset, length)
|
|
493
|
+
result["Content-Type"] = result.headers["content-type"]
|
|
494
|
+
result.body = result.data
|
|
495
|
+
if (result.headers["content-type"].includes("application/json"))
|
|
496
|
+
result.body = JSON.parse(result.data)
|
|
497
|
+
|
|
498
|
+
let parsed = parseBinaryResponse(result)
|
|
499
|
+
let response = prepareResult(parsed)
|
|
500
|
+
|
|
501
|
+
return response
|
|
502
|
+
|
|
503
|
+
})
|
|
475
504
|
// .catch(err => { console.log(err.message); console.log(err.response); return err});
|
|
476
505
|
}
|
|
477
506
|
|
|
478
507
|
|
|
479
508
|
|
|
480
509
|
|
|
510
|
+
let Essentials = {
|
|
511
|
+
vlqToUint64: Cpp.vlqToUint64,
|
|
512
|
+
sliceArrayBuffer: Cpp.sliceArrayBuffer,
|
|
513
|
+
stringToUtf8ArrayBuffer: Cpp.stringToUtf8ArrayBuffer,
|
|
514
|
+
numberToSortableBytes: Cpp.numberToSortableBytes,
|
|
515
|
+
defaultHash: Cpp.defaultHash,
|
|
516
|
+
memcmpEqual: Cpp.memcmpEqual,
|
|
517
|
+
uint64ToVlq: Cpp.uint64ToVlq,
|
|
518
|
+
arrayBufferToString: Cpp.arrayBufferToString,
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
function prepareResult(result) {
|
|
524
|
+
let res = result.content?.length === 1 ? result.content[0].items : result.content // For raw, this resolves to result.content which is ArrayBuffer
|
|
525
|
+
res.metadata = _ => result.metadata
|
|
526
|
+
if (result.metadata.executor === "raw")
|
|
527
|
+
setRawBatchIterator(res)
|
|
528
|
+
return res
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function parseBinaryResponse(result) {
|
|
532
|
+
const contentType = (result["Content-Type"] || "").toLowerCase()
|
|
533
|
+
if (contentType.includes("application/json")) {
|
|
534
|
+
return {metadata: result.body, content: result.body.items}
|
|
535
|
+
}
|
|
536
|
+
if (!contentType.includes("application/dip")) {
|
|
537
|
+
throw {message: "Error: Dip unsupported media. Invalid content-type."}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const arrayBuffer = result.body
|
|
541
|
+
|
|
542
|
+
// let wire = parseWireFormat(arrayBuffer)
|
|
543
|
+
// let meta = wireFormat.metadata
|
|
544
|
+
// let content = wireFormat.content
|
|
545
|
+
|
|
546
|
+
// try {
|
|
547
|
+
// meta = JSON.parse(meta);
|
|
548
|
+
// } catch (_) {
|
|
549
|
+
// throw {contentType, meta, content, message: "Error: Dip failed to parse metadata. Unsupported response format. Response format does not match content-type."}
|
|
550
|
+
// }
|
|
551
|
+
|
|
552
|
+
// parseWireFormat() equivalent
|
|
553
|
+
// =============================
|
|
554
|
+
let dataView = new DataView(arrayBuffer.buffer, arrayBuffer.byteOffset, arrayBuffer.byteLength);
|
|
555
|
+
let offset = 0;
|
|
556
|
+
|
|
557
|
+
// 1. Read the metadata size header
|
|
558
|
+
let vlq = Essentials.vlqToUint64(Essentials.sliceArrayBuffer(arrayBuffer, offset, 10));
|
|
559
|
+
let metaSize = vlq.value;
|
|
560
|
+
offset += vlq.bytes;
|
|
561
|
+
|
|
562
|
+
// 2. Extract and parse metadata string via C++ instantly
|
|
563
|
+
let metaText = result.getSliceAsText(offset, metaSize);
|
|
564
|
+
let nativeResponse = JSON.parse(metaText);
|
|
565
|
+
offset += metaSize;
|
|
566
|
+
|
|
567
|
+
// Extract everything remaining automatically by passing -1
|
|
568
|
+
let content = result.getSliceAsArrayBuffer(offset, -1); // content is already ArrayBuffer but QByteArray to ArrayBuffer cannot be Uint8Array() wrapped
|
|
569
|
+
// content = new Uint8Array(content ?? new ArrayBuffer(0))
|
|
570
|
+
|
|
571
|
+
return { metadata: nativeResponse, content: content }
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
function setRawBatchIterator(arrayBuffer) {
|
|
576
|
+
arrayBuffer.metadata().items.forEach(batch => {
|
|
577
|
+
batch.kv = _ => {
|
|
578
|
+
const batchItem = {
|
|
579
|
+
count: batch.count,
|
|
580
|
+
slice: Essentials.sliceArrayBuffer(arrayBuffer, batch.offset, batch.size),
|
|
581
|
+
[Symbol.iterator]() {
|
|
582
|
+
let index = 0;
|
|
583
|
+
let offset = 0;
|
|
584
|
+
return {
|
|
585
|
+
next: () => {
|
|
586
|
+
if (index < this.count) {
|
|
587
|
+
let kv = getRawKeyValue(this.slice, offset)
|
|
588
|
+
offset = kv.next
|
|
589
|
+
index++
|
|
590
|
+
return { value: kv, done: false };
|
|
591
|
+
}
|
|
592
|
+
return { done: true };
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return batchItem
|
|
598
|
+
}
|
|
599
|
+
})
|
|
600
|
+
arrayBuffer.items = arrayBuffer.metadata().items
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
// ===============
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
function convert_to_array_buffer(value) {
|
|
611
|
+
if (typeof value === "string")
|
|
612
|
+
return Essentials.stringToUtf8ArrayBuffer(value)
|
|
613
|
+
else if (typeof value === "number")
|
|
614
|
+
return Essentials.numberToSortableBytes(value)
|
|
615
|
+
return Essentials.stringToUtf8ArrayBuffer(JSON.stringify(value))
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export function newRaw() {
|
|
619
|
+
let obj = {buffers: [], size: 0, hashes:{}}
|
|
620
|
+
obj.add = value => {
|
|
621
|
+
if (value === undefined)
|
|
622
|
+
return undefined
|
|
623
|
+
const buf = (value instanceof ArrayBuffer) ? value : convert_to_array_buffer(value);
|
|
624
|
+
|
|
625
|
+
const hash = Essentials.defaultHash(buf)
|
|
626
|
+
for (let item of obj.hashes[hash] ?? []) { // Conflict. Fallback to verify
|
|
627
|
+
if (Essentials.memcmpEqual(buf, item.buf))
|
|
628
|
+
return item.offset
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const vlq = new Uint8Array(Essentials.uint64ToVlq(buf.byteLength))
|
|
632
|
+
obj.buffers.push({buf, vlq})
|
|
633
|
+
let offset = obj.size
|
|
634
|
+
obj.size += vlq.byteLength + buf.byteLength;
|
|
635
|
+
|
|
636
|
+
obj.hashes[hash] = obj.hashes[hash] ?? []
|
|
637
|
+
obj.hashes[hash].push({offset, buf})
|
|
638
|
+
|
|
639
|
+
return offset
|
|
640
|
+
}
|
|
641
|
+
return obj
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function buildHybridRequest(request) {
|
|
645
|
+
const batch = request.batch ?? [request];
|
|
646
|
+
|
|
647
|
+
let raw = request.raw ?? newRaw()
|
|
648
|
+
request.raw = undefined
|
|
649
|
+
|
|
650
|
+
// Pass 1: Direct offset assignment & exact size calculation (Zero allocation)
|
|
651
|
+
for (let i = 0; i < batch.length; i++) {
|
|
652
|
+
const item = batch[i];
|
|
653
|
+
delete item.batch
|
|
654
|
+
if (item.update) {
|
|
655
|
+
if ( item._id === undefined && item.options?.upsert && item.query?._id !== undefined)
|
|
656
|
+
item._id = item.query._id
|
|
657
|
+
item.update = raw.add(item.update)
|
|
658
|
+
} else if (item.insert) {
|
|
659
|
+
if (item._id === undefined && item.insert?._id !== undefined)
|
|
660
|
+
item._id = item.insert._id
|
|
661
|
+
item.insert = raw.add(item.insert)
|
|
662
|
+
}
|
|
663
|
+
item._id = raw.add(item._id)
|
|
664
|
+
|
|
665
|
+
if (item.options?.$rangeFrom !== undefined)
|
|
666
|
+
item.options.$rangeFrom = raw.add(item.options.$rangeFrom)
|
|
667
|
+
if (item.options?.$rangeTo !== undefined)
|
|
668
|
+
item.options.$rangeTo = raw.add(item.options.$rangeTo)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Pass 2: Generate the final JSON payload string
|
|
672
|
+
const jsonBuffer = convert_to_array_buffer(request)
|
|
673
|
+
const jsonByteLen = jsonBuffer.byteLength;
|
|
674
|
+
|
|
675
|
+
// Pass 3: Allocate the exact Master Buffer ONCE
|
|
676
|
+
let vlq = new Uint8Array(Essentials.uint64ToVlq(jsonByteLen))
|
|
677
|
+
const totalSize = vlq.byteLength + jsonByteLen + raw.size;
|
|
678
|
+
const masterBuffer = new ArrayBuffer(totalSize);
|
|
679
|
+
const masterView = new Uint8Array(masterBuffer);
|
|
680
|
+
|
|
681
|
+
// Write the JSON size prefix
|
|
682
|
+
masterView.set(vlq, 0);
|
|
683
|
+
|
|
684
|
+
// Direct copy of JSON string bytes into their final destination
|
|
685
|
+
masterView.set(new Uint8Array(jsonBuffer), vlq.byteLength);
|
|
686
|
+
|
|
687
|
+
// Copy binary payloads directly into their final destination window
|
|
688
|
+
let writeOffset = vlq.byteLength + jsonByteLen;
|
|
689
|
+
for (const buffer of raw.buffers) {
|
|
690
|
+
const buf = buffer.buf
|
|
691
|
+
const vlq = buffer.vlq
|
|
692
|
+
|
|
693
|
+
// Write length prefix directly into final master buffer
|
|
694
|
+
masterView.set(vlq, writeOffset);
|
|
695
|
+
writeOffset += vlq.byteLength
|
|
696
|
+
|
|
697
|
+
// Blit bytes directly from source into final master buffer destination
|
|
698
|
+
masterView.set(new Uint8Array(buf), writeOffset);
|
|
699
|
+
writeOffset += buf.byteLength;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return masterBuffer;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Usage getRawKeyValue()
|
|
706
|
+
// Dip.query()
|
|
707
|
+
// .then(r => {
|
|
708
|
+
// let count = r.metadata().count
|
|
709
|
+
// let offset = 0
|
|
710
|
+
// for (let i = 0; i < count; i++) {
|
|
711
|
+
// let item = getKeyValue(r, offset)
|
|
712
|
+
// offset = item.next
|
|
713
|
+
// let res = Utils.arrayBufferToFile(item.data, '/home/khayaali/Downloads/zdip.png')
|
|
714
|
+
// console.log(item._id,res)
|
|
715
|
+
// }
|
|
716
|
+
// })
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
function bufferToString(arrayBuffer) {
|
|
721
|
+
return Essentials.arrayBufferToString(arrayBuffer)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function getRawKeyValue(arrayBuffer, offset) {
|
|
725
|
+
let dataView = new DataView(arrayBuffer.buffer, arrayBuffer.byteOffset, arrayBuffer.byteLength);
|
|
726
|
+
let masterView = new Uint8Array(arrayBuffer);
|
|
727
|
+
|
|
728
|
+
// 1. Extract Key Size and Key
|
|
729
|
+
let vlq = Essentials.vlqToUint64(Essentials.sliceArrayBuffer(arrayBuffer, offset, 10));
|
|
730
|
+
let keySize = vlq.value;
|
|
731
|
+
offset += vlq.bytes;
|
|
732
|
+
|
|
733
|
+
// Zero-copy view window bounded strictly to the key bytes
|
|
734
|
+
let _id = masterView.subarray(offset, offset + keySize);
|
|
735
|
+
let _id_string
|
|
736
|
+
_id.toText = _ => {
|
|
737
|
+
// Fix: Pass the typed array view itself, NOT the underlying root buffer
|
|
738
|
+
_id_string = _id_string === undefined ? _id.buffer.slice(_id.byteOffset, _id.byteOffset + _id.byteLength) : _id_string
|
|
739
|
+
return bufferToString(_id_string)
|
|
740
|
+
}
|
|
741
|
+
offset += keySize;
|
|
742
|
+
|
|
743
|
+
// 2. Extract Data Size and Data Binary Payload
|
|
744
|
+
vlq = Essentials.vlqToUint64(Essentials.sliceArrayBuffer(arrayBuffer, offset, 10));
|
|
745
|
+
let dataSize = vlq.value;
|
|
746
|
+
offset += vlq.bytes;
|
|
747
|
+
|
|
748
|
+
// Optimization: Use subarray() instead of .slice() to make the data payload 100% zero-copy too
|
|
749
|
+
let _val = masterView.subarray(offset, offset + dataSize);
|
|
750
|
+
offset += dataSize;
|
|
751
|
+
|
|
752
|
+
_val = Essentials.sliceArrayBuffer(arrayBuffer, _val.byteOffset, _val.byteLength)
|
|
753
|
+
// _val = _val.buffer.slice(_val.byteOffset, _val.byteOffset + _val.byteLength);
|
|
754
|
+
_val.toText = _ => bufferToString(_val)
|
|
755
|
+
return { _id, _val, next: offset };
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
|
|
481
769
|
|
|
482
770
|
|
|
483
771
|
|