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 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
- export function batchAbort(batch) { // Mandatory to prevent meory leak due to dangling batch if batchBegin() is called without batchSubmit()
154
- delete batches[batch]
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
- export const batch = async (callback) => { // Convenience function with auto lifecyle management and auto submit when callback returns. abort is called appropriately.
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
- export function batchSubmit(batch, arg) {
179
- if (!batch || !batches[batch] || !batches[batch].length)
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[batch]
182
- let mode = batches[batch].some(txn => txn.insert || txn.update || txn.delete) ? "command" : "query"
183
- delete batches[batch]
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, mode, version: arg?.version, compression: arg?.compression }))
215
+ .then(() => execute({ "batch": txns, version: arg?.version, raw, engine, compression: arg?.compression, executor, syncMode, port, timeout }))
186
216
  .then (result => {
187
- Events.send("Elabase.batchSubmit", { response: result })
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
- export const insert = async (meta, collection, value, _id, extras) => {
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
- var arg = {
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("Elabase.insert", { collection: collectionArray(collection), response: result })
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
- var arg = {
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
- var type = Object.prototype.toString.call(col)
358
+ let type = Object.prototype.toString.call(col)
325
359
  if (type === '[object Array]') {
326
- var newcol = []
327
- for (var i in col) {
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
- var arg = {
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("Elabase.update", { collection: collectionArray(collection), response: result })
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
- var arg = {
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("Elabase.remove", { collection: collectionArray(collection), response: result })
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 = true // Compress all for now
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
- // console.log(res.status);
443
- // console.log(JSON.stringify(res.header, null, 4));
444
- // console.log(JSON.stringify(res.body, null, 4));
445
-
446
- let res = result.data.mode === "query" ? result.data.items : result.data
447
- res.stats = result.data.mode === "query" ? _ => result.data.stats : res.stats
448
- return res
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
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "corebasic",
3
3
  "type": "module",
4
- "version": "1.0.196",
4
+ "version": "1.0.199",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "scripts": {