corebasic 1.0.196 → 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 +374 -61
- 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
|
|
|
@@ -48,9 +51,26 @@ export function start(dbName) {
|
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
function deepFreeze(obj) {
|
|
55
|
+
if (obj && typeof obj === "object" && !Object.isFrozen(obj)) {
|
|
56
|
+
Object.freeze(obj);
|
|
57
|
+
|
|
58
|
+
for (const key of Object.keys(obj)) {
|
|
59
|
+
deepFreeze(obj[key]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return obj;
|
|
63
|
+
}
|
|
64
|
+
class DipMeta {
|
|
65
|
+
constructor(obj) {
|
|
66
|
+
Object.assign(this, deepFreeze(obj));
|
|
67
|
+
Object.freeze(this);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
|
|
52
72
|
export const globalMeta = args => {
|
|
53
|
-
return { ...args, company: "GLOBAL", outlet: "GLOBAL" }
|
|
73
|
+
return new DipMeta({ ...args, company: "GLOBAL", outlet: "GLOBAL" })
|
|
54
74
|
}
|
|
55
75
|
export const companyMeta = (company, args) => {
|
|
56
76
|
if (typeof company !== "string" && !Array.isArray(company))
|
|
@@ -58,7 +78,7 @@ export const companyMeta = (company, args) => {
|
|
|
58
78
|
if (!company || (typeof company === "string" && company.trim() === "") || company === "GLOBAL" || (Array.isArray(company) && (!company.length || company.some(item => typeof item !== "string" || item.trim() === "" || item === "GLOBAL")) ))
|
|
59
79
|
throw {message: "Error: Invalid company provided in Dip.companyMeta()"}
|
|
60
80
|
|
|
61
|
-
return {...args, outlet: "GLOBAL", company}
|
|
81
|
+
return new DipMeta({...args, outlet: "GLOBAL", company})
|
|
62
82
|
}
|
|
63
83
|
export const outletMeta = (outlet, args) => {
|
|
64
84
|
if (typeof outlet !== "string" && !Array.isArray(outlet))
|
|
@@ -66,7 +86,7 @@ export const outletMeta = (outlet, args) => {
|
|
|
66
86
|
if (!outlet || (typeof outlet === "string" && outlet.trim() === "") || outlet === "GLOBAL" || (Array.isArray(outlet) && (!outlet.length || outlet.some(item => typeof item !== "string" || item.trim() === "" || item === "GLOBAL")) ))
|
|
67
87
|
throw {message: "Error: Invalid outlet provided in Dip.outletMeta()"}
|
|
68
88
|
|
|
69
|
-
return {...args, company: "GLOBAL", outlet}
|
|
89
|
+
return new DipMeta({...args, company: "GLOBAL", outlet})
|
|
70
90
|
}
|
|
71
91
|
|
|
72
92
|
export const customMeta = (company, outlet, args) => {
|
|
@@ -81,7 +101,7 @@ export const customMeta = (company, outlet, args) => {
|
|
|
81
101
|
if (!outlet || (typeof outlet === "string" && outlet.trim() === "") || outlet === "GLOBAL" || (Array.isArray(outlet) && (!outlet.length || outlet.some(item => typeof item !== "string" || item.trim() === "" || item === "GLOBAL")) ))
|
|
82
102
|
throw {message: "Error: Invalid outlet provided in Dip.customMeta()"}
|
|
83
103
|
|
|
84
|
-
return {...args, company, outlet}
|
|
104
|
+
return new DipMeta({...args, company, outlet})
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
function validate_collection(collection, fn_name) {
|
|
@@ -142,19 +162,23 @@ function generate_suffix(meta) {
|
|
|
142
162
|
return suffixes
|
|
143
163
|
}
|
|
144
164
|
|
|
165
|
+
// Ported
|
|
145
166
|
let BATCH_COUNTER = 0n;
|
|
146
167
|
let batches = {}
|
|
147
168
|
|
|
169
|
+
// Ported
|
|
148
170
|
export function batchBegin() { // Manual Lifecycle. If you forget to batchSubmit() or batchAbort(), then memory leak due to dangling batch
|
|
149
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)
|
|
150
172
|
batches[uid] = []
|
|
151
173
|
return uid
|
|
152
174
|
}
|
|
153
|
-
|
|
154
|
-
|
|
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]
|
|
155
178
|
}
|
|
156
179
|
|
|
157
|
-
|
|
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.
|
|
158
182
|
/**
|
|
159
183
|
* Usage:
|
|
160
184
|
* const result = await Dip.batch(async batch => {
|
|
@@ -168,23 +192,29 @@ export const batch = async (callback) => { // Convenience function with auto lif
|
|
|
168
192
|
|
|
169
193
|
try {
|
|
170
194
|
await callback(batch)
|
|
171
|
-
return await batchSubmit(batch)
|
|
195
|
+
return await batchSubmit(batch, arg)
|
|
172
196
|
} catch (err) {
|
|
173
197
|
batchAbort(batch)
|
|
174
198
|
throw err
|
|
175
199
|
}
|
|
176
200
|
}
|
|
177
201
|
|
|
178
|
-
|
|
179
|
-
|
|
202
|
+
// Ported
|
|
203
|
+
export function batchSubmit(batch_id, arg) {
|
|
204
|
+
if (!batch_id || !batches[batch_id] || !batches[batch_id].length)
|
|
180
205
|
throw {message: `Error: Invalid batch in Dip.batchSubmit()`}
|
|
181
|
-
let txns = batches[
|
|
182
|
-
|
|
183
|
-
|
|
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]
|
|
184
214
|
return new Promise((resolve, reject) => resolve())
|
|
185
|
-
.then(() => execute({ "batch": txns,
|
|
215
|
+
.then(() => execute({ "batch": txns, version: arg?.version, raw, engine, compression: arg?.compression, executor, syncMode, port, timeout }))
|
|
186
216
|
.then (result => {
|
|
187
|
-
Events.send("
|
|
217
|
+
Events.send("Dip.batchSubmit", { response: result })
|
|
188
218
|
return result
|
|
189
219
|
})
|
|
190
220
|
}
|
|
@@ -244,69 +274,74 @@ function validate_topology(meta, collection, fn_name) {
|
|
|
244
274
|
return new_meta
|
|
245
275
|
}
|
|
246
276
|
|
|
277
|
+
// Ported
|
|
247
278
|
export const operation = async (name, extras) => {
|
|
248
279
|
return execute({ operation: name, ...extras })
|
|
249
280
|
}
|
|
250
281
|
|
|
251
282
|
|
|
252
|
-
|
|
283
|
+
// Ported
|
|
284
|
+
export const insert = async (meta, collection, value, _id, options, extras) => {
|
|
285
|
+
|
|
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
|
|
253
287
|
|
|
254
288
|
validate_collection(collection, 'in Dip.insert()')
|
|
255
289
|
|
|
256
290
|
validate_meta(meta, 'in Dip.insert()')
|
|
257
291
|
|
|
258
|
-
const topology_meta = validate_topology(meta, collection, 'in Dip.insert()')
|
|
292
|
+
const topology_meta = validate_topology(meta, collection, 'in Dip.insert()') // TODO: loop for arrays
|
|
259
293
|
|
|
260
|
-
|
|
294
|
+
let arg = {
|
|
261
295
|
collection: collection,
|
|
262
|
-
suffix: generate_suffix(meta),
|
|
263
296
|
insert: value,
|
|
297
|
+
options: options ? options : {},
|
|
298
|
+
syncMode: meta.syncMode,
|
|
264
299
|
...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
|
|
265
300
|
...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
|
|
266
301
|
}
|
|
267
302
|
arg = Object.assign(arg, extras)
|
|
303
|
+
arg.suffix = generate_suffix(meta)
|
|
304
|
+
arg.executor = meta.executor ?? "native"
|
|
268
305
|
arg = Object.assign({}, topology_meta, arg)
|
|
269
306
|
if (_id)
|
|
270
307
|
arg._id = _id
|
|
271
308
|
|
|
272
|
-
if (!(["string", "number"].includes(typeof value?._id)))
|
|
273
|
-
throw {message: "Error: invalid _id in Dip.insert()"}
|
|
274
|
-
if (typeof value?._id === "string" && value._id.trim().length === 0)
|
|
275
|
-
throw {message: "Error: invalid _id in Dip.insert()"}
|
|
276
|
-
|
|
277
|
-
|
|
278
309
|
if (meta.batch) {
|
|
279
310
|
if (!batches[meta.batch])
|
|
280
311
|
throw {message: 'Error: Invalid batch in Dip.insert()'}
|
|
281
312
|
batches[meta.batch].push(arg)
|
|
282
313
|
return
|
|
283
314
|
}
|
|
284
|
-
arg.mode = "command"
|
|
285
315
|
return new Promise((resolve, reject) => resolve())
|
|
286
316
|
.then(() => execute(arg))
|
|
287
317
|
.then(result => {
|
|
288
|
-
Events.send("
|
|
318
|
+
Events.send("Dip.insert", { collection: collectionArray(collection), response: result })
|
|
289
319
|
return result
|
|
290
320
|
})
|
|
291
321
|
}
|
|
292
322
|
|
|
323
|
+
// Ported
|
|
293
324
|
export const query = async (meta, collection, query, options, extras) => {
|
|
294
325
|
|
|
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
|
|
327
|
+
|
|
295
328
|
validate_collection(collection, 'in Dip.query()')
|
|
296
329
|
|
|
297
330
|
validate_meta(meta, 'in Dip.query()')
|
|
298
331
|
|
|
299
|
-
const topology_meta = validate_topology(meta, collection, 'in Dip.query()')
|
|
332
|
+
const topology_meta = validate_topology(meta, collection, 'in Dip.query()') // TODO: loop for arrays
|
|
300
333
|
|
|
301
|
-
|
|
334
|
+
let arg = {
|
|
302
335
|
collection: collection,
|
|
303
|
-
suffix: generate_suffix(meta),
|
|
304
336
|
query: query,
|
|
305
337
|
options: options ? options : {},
|
|
338
|
+
syncMode: meta.syncMode,
|
|
306
339
|
...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
|
|
307
340
|
...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
|
|
308
341
|
}
|
|
309
342
|
arg = Object.assign(arg, extras)
|
|
343
|
+
arg.suffix = generate_suffix(meta)
|
|
344
|
+
arg.executor = meta.executor ?? "native"
|
|
310
345
|
arg = Object.assign({}, topology_meta, arg)
|
|
311
346
|
if (meta.batch) {
|
|
312
347
|
if (!batches[meta.batch])
|
|
@@ -314,17 +349,16 @@ export const query = async (meta, collection, query, options, extras) => {
|
|
|
314
349
|
batches[meta.batch].push(arg)
|
|
315
350
|
return
|
|
316
351
|
}
|
|
317
|
-
arg.mode = "query"
|
|
318
352
|
return new Promise((resolve, reject) => resolve())
|
|
319
353
|
.then(() => execute(arg))
|
|
320
354
|
}
|
|
321
355
|
|
|
322
356
|
|
|
323
357
|
function collectionArray(col) {
|
|
324
|
-
|
|
358
|
+
let type = Object.prototype.toString.call(col)
|
|
325
359
|
if (type === '[object Array]') {
|
|
326
|
-
|
|
327
|
-
for (
|
|
360
|
+
let newcol = []
|
|
361
|
+
for (let i in col) {
|
|
328
362
|
if (!Utils.isEmpty(col[i]))
|
|
329
363
|
newcol.push(col[i])
|
|
330
364
|
}
|
|
@@ -336,25 +370,29 @@ function collectionArray(col) {
|
|
|
336
370
|
return []
|
|
337
371
|
}
|
|
338
372
|
|
|
339
|
-
|
|
373
|
+
// Ported
|
|
340
374
|
export const update = async (meta, collection, query, update, options, extras) => {
|
|
341
375
|
|
|
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
|
|
377
|
+
|
|
342
378
|
validate_collection(collection, 'in Dip.update()')
|
|
343
379
|
|
|
344
380
|
validate_meta(meta, 'in Dip.update()')
|
|
345
381
|
|
|
346
|
-
const topology_meta = validate_topology(meta, collection, 'in Dip.update()')
|
|
382
|
+
const topology_meta = validate_topology(meta, collection, 'in Dip.update()') // TODO: loop for arrays
|
|
347
383
|
|
|
348
|
-
|
|
384
|
+
let arg = {
|
|
349
385
|
collection: collection,
|
|
350
|
-
suffix: generate_suffix(meta),
|
|
351
386
|
query: query,
|
|
352
387
|
update: update,
|
|
353
388
|
options: options ? options : {},
|
|
389
|
+
syncMode: meta.syncMode,
|
|
354
390
|
...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
|
|
355
391
|
...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
|
|
356
392
|
}
|
|
357
393
|
arg = Object.assign(arg, extras)
|
|
394
|
+
arg.suffix = generate_suffix(meta)
|
|
395
|
+
arg.executor = meta.executor ?? "native"
|
|
358
396
|
arg = Object.assign({}, topology_meta, arg)
|
|
359
397
|
if (meta.batch) {
|
|
360
398
|
if (!batches[meta.batch])
|
|
@@ -362,17 +400,19 @@ export const update = async (meta, collection, query, update, options, extras) =
|
|
|
362
400
|
batches[meta.batch].push(arg)
|
|
363
401
|
return
|
|
364
402
|
}
|
|
365
|
-
arg.mode = "command"
|
|
366
403
|
return new Promise((resolve, reject) => resolve())
|
|
367
404
|
.then(() => execute(arg))
|
|
368
405
|
.then (result => {
|
|
369
|
-
Events.send("
|
|
406
|
+
Events.send("Dip.update", { collection: collectionArray(collection), response: result })
|
|
370
407
|
return result
|
|
371
408
|
})
|
|
372
409
|
}
|
|
373
410
|
|
|
411
|
+
// Ported
|
|
374
412
|
export const remove = async (meta, collection, query, options, extras) => {
|
|
375
413
|
|
|
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
|
|
415
|
+
|
|
376
416
|
validate_collection(collection, 'in Dip.remove()')
|
|
377
417
|
|
|
378
418
|
if (Utils.isEmpty(query._id) && !meta.allowDangerousRemove)
|
|
@@ -380,18 +420,20 @@ export const remove = async (meta, collection, query, options, extras) => {
|
|
|
380
420
|
|
|
381
421
|
validate_meta(meta, 'in Dip.remove()')
|
|
382
422
|
|
|
383
|
-
const topology_meta = validate_topology(meta, collection, 'in Dip.remove()')
|
|
423
|
+
const topology_meta = validate_topology(meta, collection, 'in Dip.remove()') // TODO: loop for arrays
|
|
384
424
|
|
|
385
|
-
|
|
425
|
+
let arg = {
|
|
386
426
|
collection: collection,
|
|
387
|
-
suffix: generate_suffix(meta),
|
|
388
427
|
query: query,
|
|
389
|
-
delete: true,
|
|
390
428
|
options: options ? options : {},
|
|
429
|
+
delete: true,
|
|
430
|
+
syncMode: meta.syncMode,
|
|
391
431
|
...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
|
|
392
432
|
...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
|
|
393
433
|
}
|
|
394
434
|
arg = Object.assign(arg, extras)
|
|
435
|
+
arg.suffix = generate_suffix(meta)
|
|
436
|
+
arg.executor = meta.executor ?? "native"
|
|
395
437
|
arg = Object.assign({}, topology_meta, arg)
|
|
396
438
|
if (meta.batch) {
|
|
397
439
|
if (!batches[meta.batch])
|
|
@@ -399,18 +441,14 @@ export const remove = async (meta, collection, query, options, extras) => {
|
|
|
399
441
|
batches[meta.batch].push(arg)
|
|
400
442
|
return
|
|
401
443
|
}
|
|
402
|
-
arg.mode = "command"
|
|
403
444
|
return new Promise((resolve, reject) => resolve())
|
|
404
445
|
.then(() => execute(arg))
|
|
405
446
|
.then(result => {
|
|
406
|
-
Events.send("
|
|
447
|
+
Events.send("Dip.remove", { collection: collectionArray(collection), response: result })
|
|
407
448
|
return result
|
|
408
449
|
})
|
|
409
450
|
}
|
|
410
451
|
|
|
411
|
-
export const close = async (meta, db) => {
|
|
412
|
-
return query(meta, "-", {}, {}, {db: db ?? root.db, close: true})
|
|
413
|
-
}
|
|
414
452
|
|
|
415
453
|
|
|
416
454
|
export let config = {
|
|
@@ -430,29 +468,304 @@ function execute(arg) {
|
|
|
430
468
|
// _id: 1 // Optional. Used with insert
|
|
431
469
|
// }
|
|
432
470
|
arg.db = arg.db ?? db
|
|
471
|
+
arg.executor = arg.executor ?? "native"
|
|
433
472
|
arg.engine = arg.engine ?? internal.engineName
|
|
434
|
-
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
|
|
435
481
|
|
|
436
482
|
|
|
437
483
|
return config.useDipper ? dipper(arg)
|
|
438
484
|
: axios
|
|
439
|
-
.post(arg.DIP_URL ?? fullurl, {...arg, DIP_URL: undefined})
|
|
440
|
-
// .timeout(5000)
|
|
485
|
+
.post(arg.DIP_URL ?? fullurl, buildHybridRequest({...arg, DIP_URL: undefined}), { responseType: 'arraybuffer', timeout: timeout ?? 5000, headers: {"Content-Type": "application/dip"} })
|
|
441
486
|
.then(result => {
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
+
})
|
|
450
504
|
// .catch(err => { console.log(err.message); console.log(err.response); return err});
|
|
451
505
|
}
|
|
452
506
|
|
|
453
507
|
|
|
454
508
|
|
|
455
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
|
+
|
|
456
769
|
|
|
457
770
|
|
|
458
771
|
|