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 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
- export function batchAbort(batch) { // Mandatory to prevent meory leak due to dangling batch if batchBegin() is called without batchSubmit()
171
- 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]
172
178
  }
173
179
 
174
- 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.
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
- export function batchSubmit(batch, arg) {
196
- 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)
197
205
  throw {message: `Error: Invalid batch in Dip.batchSubmit()`}
198
- let txns = batches[batch]
199
- let mode = batches[batch].some(txn => txn.insert || txn.update || txn.delete) ? "command" : "query"
200
- 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]
201
214
  return new Promise((resolve, reject) => resolve())
202
- .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 }))
203
216
  .then (result => {
204
- Events.send("Elabase.batchSubmit", { response: result })
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
- export const insert = async (meta, collection, value, _id, extras) => {
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
- var arg = {
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("Elabase.insert", { collection: collectionArray(collection), response: result })
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
- var arg = {
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
- var type = Object.prototype.toString.call(col)
358
+ let type = Object.prototype.toString.call(col)
346
359
  if (type === '[object Array]') {
347
- var newcol = []
348
- for (var i in col) {
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
- var arg = {
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("Elabase.update", { collection: collectionArray(collection), response: result })
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
- var arg = {
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("Elabase.remove", { collection: collectionArray(collection), response: result })
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 = 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
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
- // console.log(res.status);
468
- // console.log(JSON.stringify(res.header, null, 4));
469
- // console.log(JSON.stringify(res.body, null, 4));
470
-
471
- let res = result.data.mode === "query" ? result.data.items : result.data
472
- res.stats = result.data.mode === "query" ? _ => result.data.stats : res.stats
473
- return res
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
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "corebasic",
3
3
  "type": "module",
4
- "version": "1.0.198",
4
+ "version": "1.0.199",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "scripts": {