corebasic 1.0.212 → 1.0.214

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.
@@ -0,0 +1,656 @@
1
+ import axios from 'axios';
2
+ import { default as dipper, shard_stats as ShardStats } from './dipper.js';
3
+ import * as Utils from './utils.js';
4
+ import * as Cpp from './cpp.js';
5
+ import { applySuffixPolicy } from './dip/suffix/policy.js';
6
+ export const shard_stats = ShardStats;
7
+ let Events = {
8
+ send: function () { }
9
+ };
10
+ const url = process.env.DIP_URL || "http://127.0.0.1"; // Set DIP_URL to "http://dip" (Prod) or http://dip-dev (Dev) in CI/CD
11
+ let port = process.env.DIP_PORT || 9401;
12
+ let api = 'execute';
13
+ let fullurl = `${url}:${port}/${api}`;
14
+ let Elabase = {
15
+ Engine: {
16
+ Lmdb: 1,
17
+ Sled: 2,
18
+ Sanakirja: 3,
19
+ Singularity: 4,
20
+ Memory: 5,
21
+ Analytics: 6,
22
+ }
23
+ };
24
+ let engine = Elabase.Engine.Lmdb;
25
+ let internal = {
26
+ db: undefined,
27
+ engineName: engine === Elabase.Engine.Lmdb ? "lmdb" : (engine === Elabase.Engine.Sled ? "sled" : engine === Elabase.Engine.Sanakirja ? "sanakirja" : "lmdb")
28
+ };
29
+ let db = internal.db; // default database
30
+ export function start(dbName) {
31
+ if (!Utils.isEmpty(dbName) && Utils.isEmpty(db)) {
32
+ internal.db = "data/" + dbName;
33
+ db = "data/" + dbName;
34
+ }
35
+ }
36
+ function deepFreeze(obj) {
37
+ if (obj && typeof obj === "object" && !Object.isFrozen(obj)) {
38
+ Object.freeze(obj);
39
+ for (const key of Object.keys(obj)) {
40
+ deepFreeze(obj[key]);
41
+ }
42
+ }
43
+ return obj;
44
+ }
45
+ class DipMeta {
46
+ constructor(obj) {
47
+ Object.assign(this, deepFreeze(obj));
48
+ Object.freeze(this);
49
+ }
50
+ }
51
+ export const isDipMeta = meta => meta instanceof DipMeta;
52
+ export const globalMeta = args => {
53
+ return new DipMeta({ ...args, company: "GLOBAL", outlet: "GLOBAL" });
54
+ };
55
+ export const companyMeta = (company, args) => {
56
+ if (typeof company !== "string" && !Array.isArray(company))
57
+ throw new Error("Error: Invalid company provided in Dip.companyMeta()");
58
+ 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
+ throw new Error("Error: Invalid company provided in Dip.companyMeta()");
60
+ return new DipMeta({ ...args, outlet: "GLOBAL", company });
61
+ };
62
+ export const outletMeta = (outlet, args) => {
63
+ if (typeof outlet !== "string" && !Array.isArray(outlet))
64
+ throw new Error("Error: Invalid outlet provided in Dip.outletMeta()");
65
+ if (!outlet || (typeof outlet === "string" && outlet.trim() === "") || outlet === "GLOBAL" || (Array.isArray(outlet) && (!outlet.length || outlet.some(item => typeof item !== "string" || item.trim() === "" || item === "GLOBAL"))))
66
+ throw new Error("Error: Invalid outlet provided in Dip.outletMeta()");
67
+ return new DipMeta({ ...args, company: "GLOBAL", outlet });
68
+ };
69
+ export const customMeta = (company, outlet, args) => {
70
+ if (typeof company !== "string" && !Array.isArray(company))
71
+ throw new Error("Error: Invalid company provided in Dip.customMeta()");
72
+ if (typeof outlet !== "string" && !Array.isArray(outlet))
73
+ throw new Error("Error: Invalid outlet provided in Dip.customMeta()");
74
+ if (!company || (typeof company === "string" && company.trim() === "") || company === "GLOBAL" || (Array.isArray(company) && (!company.length || company.some(item => typeof item !== "string" || item.trim() === "" || item === "GLOBAL"))))
75
+ throw new Error("Error: Invalid company provided in Dip.customMeta()");
76
+ if (!outlet || (typeof outlet === "string" && outlet.trim() === "") || outlet === "GLOBAL" || (Array.isArray(outlet) && (!outlet.length || outlet.some(item => typeof item !== "string" || item.trim() === "" || item === "GLOBAL"))))
77
+ throw new Error("Error: Invalid outlet provided in Dip.customMeta()");
78
+ return new DipMeta({ ...args, company, outlet });
79
+ };
80
+ function validate_collection(collection, fn_name) {
81
+ if ((typeof collection === 'string' && collection.trim() === "") || (typeof collection !== 'string' && !Array.isArray(collection)) || (Array.isArray(collection) && (!collection.length || collection.some(item => typeof item !== "string" || item.trim() === "")))) {
82
+ throw new Error(`Error: Invalid collection ${fn_name}`);
83
+ }
84
+ }
85
+ function validate_meta(meta, fn_name) {
86
+ if (!meta || typeof meta !== 'object') {
87
+ throw new Error(`Error: Invalid meta ${fn_name}`);
88
+ }
89
+ if ((typeof meta.company === 'string' && meta.company.trim() === "") || (typeof meta.company !== 'string' && !Array.isArray(meta.company)) || (Array.isArray(meta.company) && (!meta.company.length || meta.company.some(item => typeof item !== "string" || item.trim() === "")))) {
90
+ throw new Error(`Error: Invalid meta.company ${fn_name}`);
91
+ }
92
+ if ((typeof meta.outlet === 'string' && meta.outlet.trim() === "") || (typeof meta.outlet !== 'string' && !Array.isArray(meta.outlet)) || (Array.isArray(meta.outlet) && (!meta.outlet.length || meta.outlet.some(item => typeof item !== "string" || item.trim() === "")))) {
93
+ throw new Error(`Error: Invalid meta.outlet ${fn_name}`);
94
+ }
95
+ if ((meta.suffix !== undefined && typeof meta.suffix !== 'string' && !Array.isArray(meta.suffix)) || (Array.isArray(meta.suffix) && meta.suffix.some(item => typeof item !== "string" || item.trim() === ""))) {
96
+ throw new Error(`Error: Invalid meta.suffix ${fn_name}`);
97
+ }
98
+ else if (typeof meta.suffix === 'string' && meta.suffix.trim() === "")
99
+ throw new Error(`Error: Invalid meta.suffix ${fn_name}`);
100
+ }
101
+ function generate_suffix(meta) {
102
+ // Assuming validate_meta() is called before reaching here. If not then this is intentional validation bypass
103
+ let company = typeof meta.company == 'string' ? [meta.company] : (meta.company?.length ? meta.company : []);
104
+ let outlet = typeof meta.outlet == 'string' ? [meta.outlet] : (meta.outlet?.length ? meta.outlet : []);
105
+ let suffix = typeof meta.suffix == 'string' ? [meta.suffix] : (meta.suffix?.length ? meta.suffix : []);
106
+ company = company.filter(item => item.trim() !== "");
107
+ outlet = outlet.filter(item => item.trim() !== "");
108
+ suffix = suffix.filter(item => item.trim() !== "");
109
+ company = company.length ? company : [""];
110
+ outlet = outlet.length ? outlet : [""];
111
+ suffix = suffix.length ? suffix : [""];
112
+ company = new Set(company);
113
+ outlet = new Set(outlet);
114
+ suffix = new Set(suffix);
115
+ // console.log(company, outlet, suffix)
116
+ let suffixes = [];
117
+ for (let c of company) {
118
+ for (let o of outlet) {
119
+ for (let s of suffix) {
120
+ let pathSegments = [c, o, s].filter(segment => segment !== "");
121
+ if (pathSegments.length > 0) {
122
+ suffixes.push(pathSegments.join('/'));
123
+ }
124
+ }
125
+ }
126
+ }
127
+ return suffixes;
128
+ }
129
+ // Ported
130
+ let BATCH_COUNTER = 0n;
131
+ let batches = {};
132
+ // Ported
133
+ export function batchBegin() {
134
+ 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)
135
+ batches[uid] = [];
136
+ return uid;
137
+ }
138
+ // Ported
139
+ export function batchAbort(batch_id) {
140
+ delete batches[batch_id];
141
+ }
142
+ // Ported
143
+ export const batch = async (callback, arg) => {
144
+ /**
145
+ * Usage:
146
+ * const result = await Dip.batch(async batch => {
147
+ * for (let i = 0; i < 45000; i++)
148
+ * Dip.insert({...meta, batch}, collection, {_id: i})
149
+ * })
150
+ *
151
+ * Note: Nested batch is supported as they are just independant batches.
152
+ */
153
+ const batch = batchBegin();
154
+ try {
155
+ await callback(batch);
156
+ return await batchSubmit(batch, arg);
157
+ }
158
+ catch (err) {
159
+ batchAbort(batch);
160
+ throw err;
161
+ }
162
+ };
163
+ // Ported
164
+ export function batchSubmit(batch_id, arg) {
165
+ if (!batch_id || !batches[batch_id] || !batches[batch_id].length)
166
+ throw new Error(`Error: Invalid batch in Dip.batchSubmit()`);
167
+ let txns = batches[batch_id];
168
+ const executor = arg?.executor ?? batches[batch_id].find(txn => txn.executor)?.executor;
169
+ const syncMode = arg?.syncMode ?? batches[batch_id].find(txn => txn.syncMode)?.syncMode;
170
+ const raw = arg?.raw;
171
+ const engine = arg?.engine;
172
+ const port = arg?.port;
173
+ const timeout = arg?.timeout;
174
+ delete batches[batch_id];
175
+ return new Promise((resolve, reject) => resolve())
176
+ .then(() => execute({ "batch": txns, version: arg?.version, raw, engine, compression: arg?.compression, executor, syncMode, port, timeout }))
177
+ .then(result => {
178
+ Events.send("Dip.batchSubmit", { response: result });
179
+ return result;
180
+ });
181
+ }
182
+ const SCHEMAS_JSON_PROMISES = {};
183
+ export const schema = async (name) => {
184
+ // Cache the loading promise right away so parallel requests share it
185
+ if (!SCHEMAS_JSON_PROMISES[name])
186
+ SCHEMAS_JSON_PROMISES[name] = Utils.fileToJson(new URL('../../..', import.meta.url), `schemas/${name}.json`);
187
+ try {
188
+ const schema = await SCHEMAS_JSON_PROMISES[name];
189
+ return structuredClone(schema);
190
+ }
191
+ catch (e) {
192
+ // Delete the broken promise from cache so a retry can occur later
193
+ delete SCHEMAS_JSON_PROMISES[name];
194
+ throw new Error(`Error: Failed to load schema ${name} in Dip`);
195
+ }
196
+ };
197
+ const COLLECTIONS_JSON = await (async () => {
198
+ try {
199
+ return await Utils.fileToJson(new URL('../../..', import.meta.url), 'collections.json');
200
+ }
201
+ catch (e) {
202
+ console.error(e);
203
+ console.warn("Warn: Dip Failed to load collections.json. Semantics, topology and legality enforcement is not active");
204
+ }
205
+ })();
206
+ function init_collections_config() {
207
+ for (let collection in COLLECTIONS_JSON) {
208
+ let config = COLLECTIONS_JSON[collection];
209
+ if (config.suffix !== undefined && (typeof config.suffix !== "object" || Array.isArray(config.suffix))) {
210
+ throw new Error(`Error: Initiating collection configs failed. Invalid suffix in collection: ${collection}`);
211
+ }
212
+ }
213
+ }
214
+ if (COLLECTIONS_JSON)
215
+ init_collections_config();
216
+ const EXCLUDED_COLLECTIONS_CONFIG = {};
217
+ export function excludeCollectionConfig(collection) {
218
+ EXCLUDED_COLLECTIONS_CONFIG[collection] = true;
219
+ }
220
+ export function includeCollectionConfig(collection) {
221
+ delete EXCLUDED_COLLECTIONS_CONFIG[collection];
222
+ }
223
+ function validate_topology(meta, collection, fn_name) {
224
+ let new_meta = meta;
225
+ let config = COLLECTIONS_JSON?.[collection];
226
+ if (!config) {
227
+ if (!EXCLUDED_COLLECTIONS_CONFIG[collection])
228
+ console.warn(`Warn: Missing config for collection: ${collection}${fn_name ? ` ${fn_name}` : ''}`);
229
+ return new_meta;
230
+ }
231
+ function check_core_meta(config, meta, suffix) {
232
+ if (config.company !== meta.company && typeof config.company !== "object")
233
+ throw new Error(`Error: Incompatible meta.company config for collection: ${collection}${suffix ? ' suffix: ' + suffix : ''}${fn_name ? ` ${fn_name}` : ''}`);
234
+ if (config.outlet !== meta.outlet && typeof config.outlet !== "object")
235
+ throw new Error(`Error: Incompatible meta.outlet config for collection: ${collection}${suffix ? ' suffix: ' + suffix : ''}${fn_name ? ` ${fn_name}` : ''}`);
236
+ }
237
+ check_core_meta(config, meta);
238
+ if (config.suffix && meta.suffix) {
239
+ if (!config.suffix[meta.suffix]) {
240
+ console.warn(`Warn: Missing suffix config for collection: ${collection} suffix:${meta.suffix}${fn_name ? ` ${fn_name}` : ''}`);
241
+ return new_meta;
242
+ }
243
+ check_core_meta(config.suffix[meta.suffix], meta, meta.suffix);
244
+ new_meta = Object.assign({}, { ...config, suffix: undefined, company: undefined, outlet: undefined }, { ...config.suffix[meta.suffix], company: undefined, outlet: undefined }, meta);
245
+ }
246
+ else {
247
+ new_meta = Object.assign({}, { ...config, suffix: undefined, company: undefined, outlet: undefined }, { company: undefined, outlet: undefined }, meta);
248
+ }
249
+ return new_meta;
250
+ }
251
+ // Ported
252
+ export const operation = async (name, extras) => {
253
+ return execute({ operation: name, ...extras });
254
+ };
255
+ // Ported
256
+ export const insert = async (meta, collection, value, options, extras) => {
257
+ if (!(meta instanceof DipMeta))
258
+ console.warn('Warn: Provided meta is not an instance of DipMeta in Dip.insert()'); // TODO: throw error once review completes
259
+ validate_collection(collection, 'in Dip.insert()');
260
+ validate_meta(meta, 'in Dip.insert()');
261
+ const topology_meta = validate_topology(meta, collection, 'in Dip.insert()'); // TODO: loop for arrays
262
+ let arg = {
263
+ collection: collection,
264
+ insert: value,
265
+ options: options ? options : {},
266
+ syncMode: meta.syncMode,
267
+ ...(meta.DIP_URL ? { DIP_URL: meta.DIP_URL } : {}),
268
+ ...(meta.DIP_DB ? { db: meta.DIP_DB } : {}),
269
+ };
270
+ arg = Object.assign(arg, extras);
271
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.insert, true, arg);
272
+ let suffix_from_meta = generate_suffix(meta);
273
+ arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy));
274
+ arg.executor = meta.executor ?? "native";
275
+ arg = Object.assign({}, topology_meta, arg);
276
+ if (options?._id) {
277
+ arg._id = options._id;
278
+ arg.options = { ...options, _id: undefined };
279
+ }
280
+ if (meta.batch) {
281
+ if (!batches[meta.batch])
282
+ throw new Error('Error: Invalid batch in Dip.insert()');
283
+ batches[meta.batch].push(arg);
284
+ return;
285
+ }
286
+ return new Promise((resolve, reject) => resolve())
287
+ .then(() => execute(arg))
288
+ .then(result => {
289
+ Events.send("Dip.insert", { collection: collectionArray(collection), response: result });
290
+ return result;
291
+ });
292
+ };
293
+ // Ported
294
+ export const query = async (meta, collection, query, options, extras) => {
295
+ if (!(meta instanceof DipMeta))
296
+ console.warn('Warn: Provided meta is not an instance of DipMeta in Dip.query()'); // TODO: throw error once review completes
297
+ validate_collection(collection, 'in Dip.query()');
298
+ validate_meta(meta, 'in Dip.query()');
299
+ const topology_meta = validate_topology(meta, collection, 'in Dip.query()'); // TODO: loop for arrays
300
+ let arg = {
301
+ collection: collection,
302
+ query: query,
303
+ options: options ? options : {},
304
+ syncMode: meta.syncMode,
305
+ ...(meta.DIP_URL ? { DIP_URL: meta.DIP_URL } : {}),
306
+ ...(meta.DIP_DB ? { db: meta.DIP_DB } : {}),
307
+ };
308
+ arg = Object.assign(arg, extras);
309
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, arg);
310
+ let suffix_from_meta = generate_suffix(meta);
311
+ arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy));
312
+ arg.executor = meta.executor ?? "native";
313
+ arg = Object.assign({}, topology_meta, arg);
314
+ if (meta.batch) {
315
+ if (!batches[meta.batch])
316
+ throw new Error('Error: Invalid batch in Dip.query()');
317
+ batches[meta.batch].push(arg);
318
+ return;
319
+ }
320
+ return new Promise((resolve, reject) => resolve())
321
+ .then(() => execute(arg));
322
+ };
323
+ function collectionArray(col) {
324
+ let type = Object.prototype.toString.call(col);
325
+ if (type === '[object Array]') {
326
+ let newcol = [];
327
+ for (let i in col) {
328
+ if (!Utils.isEmpty(col[i]))
329
+ newcol.push(col[i]);
330
+ }
331
+ return newcol;
332
+ }
333
+ else if (type === '[object String]') {
334
+ if (!Utils.isEmpty(col))
335
+ return [col];
336
+ }
337
+ return [];
338
+ }
339
+ // Ported
340
+ export const update = async (meta, collection, query, update, options, extras) => {
341
+ if (!(meta instanceof DipMeta))
342
+ console.warn('Warn: Provided meta is not an instance of DipMeta in Dip.update()'); // TODO: throw error once review completes
343
+ validate_collection(collection, 'in Dip.update()');
344
+ validate_meta(meta, 'in Dip.update()');
345
+ const topology_meta = validate_topology(meta, collection, 'in Dip.update()'); // TODO: loop for arrays
346
+ let arg = {
347
+ collection: collection,
348
+ query: query,
349
+ update: update,
350
+ options: options ? options : {},
351
+ syncMode: meta.syncMode,
352
+ ...(meta.DIP_URL ? { DIP_URL: meta.DIP_URL } : {}),
353
+ ...(meta.DIP_DB ? { db: meta.DIP_DB } : {}),
354
+ };
355
+ arg = Object.assign(arg, extras);
356
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, arg);
357
+ let suffix_from_meta = generate_suffix(meta);
358
+ arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy));
359
+ arg.executor = meta.executor ?? "native";
360
+ arg = Object.assign({}, topology_meta, arg);
361
+ if (meta.batch) {
362
+ if (!batches[meta.batch])
363
+ throw new Error('Error: Invalid batch in Dip.update()');
364
+ batches[meta.batch].push(arg);
365
+ return;
366
+ }
367
+ return new Promise((resolve, reject) => resolve())
368
+ .then(() => execute(arg))
369
+ .then(result => {
370
+ Events.send("Dip.update", { collection: collectionArray(collection), response: result });
371
+ return result;
372
+ });
373
+ };
374
+ // Ported
375
+ export const remove = async (meta, collection, query, options, extras) => {
376
+ if (!(meta instanceof DipMeta))
377
+ console.warn('Warn: Provided meta is not an instance of DipMeta in Dip.remove()'); // TODO: throw error once review completes
378
+ validate_collection(collection, 'in Dip.remove()');
379
+ if (Utils.isEmpty(query._id) && !meta.allowDangerousRemove)
380
+ throw new Error("Error: Dip.remove() without _id blocked by default (Dangerous Operation). Set meta.allowDangerousRemove if intentional.");
381
+ validate_meta(meta, 'in Dip.remove()');
382
+ const topology_meta = validate_topology(meta, collection, 'in Dip.remove()'); // TODO: loop for arrays
383
+ let arg = {
384
+ collection: collection,
385
+ query: query,
386
+ options: options ? options : {},
387
+ delete: true,
388
+ syncMode: meta.syncMode,
389
+ ...(meta.DIP_URL ? { DIP_URL: meta.DIP_URL } : {}),
390
+ ...(meta.DIP_DB ? { db: meta.DIP_DB } : {}),
391
+ };
392
+ arg = Object.assign(arg, extras);
393
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, arg);
394
+ let suffix_from_meta = generate_suffix(meta);
395
+ arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy));
396
+ arg.executor = meta.executor ?? "native";
397
+ arg = Object.assign({}, topology_meta, arg);
398
+ if (meta.batch) {
399
+ if (!batches[meta.batch])
400
+ throw new Error('Error: Invalid batch in Dip.query()');
401
+ batches[meta.batch].push(arg);
402
+ return;
403
+ }
404
+ return new Promise((resolve, reject) => resolve())
405
+ .then(() => execute(arg))
406
+ .then(result => {
407
+ Events.send("Dip.remove", { collection: collectionArray(collection), response: result });
408
+ return result;
409
+ });
410
+ };
411
+ export let config = {
412
+ useDipper: false
413
+ };
414
+ function execute(arg) {
415
+ // arg example
416
+ // {
417
+ // db: "middle-earth",
418
+ // collection: "staff",
419
+ // query: "[@][?name == 'Bilbo'] | [0]",
420
+ // insert: { "name": "Bilbo" },
421
+ // query: { "name": "Frodo"},
422
+ // update: { "$set": { "age": 21 } }
423
+ // _id: 1 // Optional. Used with insert
424
+ // }
425
+ arg.db = arg.db ?? db;
426
+ arg.executor = arg.executor ?? "native";
427
+ arg.engine = arg.engine ?? internal.engineName;
428
+ if (arg.compression === undefined)
429
+ arg.compression = true; // Compress all for now by default
430
+ let fullurl = `${url}:${arg?.port ?? port}/${api}`;
431
+ delete arg.port;
432
+ const timeout = arg.timeout;
433
+ delete arg.timeout;
434
+ return config.useDipper ? dipper(arg)
435
+ : axios
436
+ .post(arg.DIP_URL ?? fullurl, buildHybridRequest({ ...arg, DIP_URL: undefined }), { responseType: 'arraybuffer', timeout: timeout ?? 5000, headers: { "Content-Type": "application/dip" } })
437
+ .then(result => {
438
+ // console.log(res.status);
439
+ // console.log(JSON.stringify(res.header, null, 4));
440
+ // console.log(JSON.stringify(res.body, null, 4));
441
+ result.getSliceAsText = (offset, length) => Cpp.getSliceAsText(result.data, offset, length);
442
+ result.getSliceAsArrayBuffer = (offset, length) => Cpp.getSliceAsArrayBuffer(result.data, offset, length);
443
+ result["Content-Type"] = result.headers["content-type"];
444
+ result.body = result.data;
445
+ if (result.headers["content-type"].includes("application/json"))
446
+ result.body = JSON.parse(result.data);
447
+ let parsed = parseBinaryResponse(result);
448
+ let response = prepareResult(parsed);
449
+ return response;
450
+ });
451
+ // .catch(err => { console.log(err.message); console.log(err.response); return err});
452
+ }
453
+ let Essentials = {
454
+ vlqToUint64: Cpp.vlqToUint64,
455
+ sliceArrayBuffer: Cpp.sliceArrayBuffer,
456
+ stringToUtf8ArrayBuffer: Cpp.stringToUtf8ArrayBuffer,
457
+ numberToSortableBytes: Cpp.numberToSortableBytes,
458
+ defaultHash: Cpp.defaultHash,
459
+ memcmpEqual: Cpp.memcmpEqual,
460
+ uint64ToVlq: Cpp.uint64ToVlq,
461
+ arrayBufferToString: Cpp.arrayBufferToString,
462
+ };
463
+ function prepareResult(result) {
464
+ let res = result.content?.length === 1 ? result.content[0].items : result.content; // For raw, this resolves to result.content which is ArrayBuffer
465
+ res.metadata = _ => result.metadata;
466
+ if (result.metadata.executor === "raw")
467
+ setRawBatchIterator(res);
468
+ return res;
469
+ }
470
+ function parseBinaryResponse(result) {
471
+ const contentType = (result["Content-Type"] || "").toLowerCase();
472
+ if (contentType.includes("application/json")) {
473
+ return { metadata: result.body, content: result.body.items };
474
+ }
475
+ if (!contentType.includes("application/dip")) {
476
+ throw new Error("Error: Dip unsupported media. Invalid content-type.");
477
+ }
478
+ const arrayBuffer = result.body;
479
+ // let wire = parseWireFormat(arrayBuffer)
480
+ // let meta = wireFormat.metadata
481
+ // let content = wireFormat.content
482
+ // try {
483
+ // meta = JSON.parse(meta);
484
+ // } catch (_) {
485
+ // throw {contentType, meta, content, message: "Error: Dip failed to parse metadata. Unsupported response format. Response format does not match content-type."}
486
+ // }
487
+ // parseWireFormat() equivalent
488
+ // =============================
489
+ let dataView = new DataView(arrayBuffer.buffer, arrayBuffer.byteOffset, arrayBuffer.byteLength);
490
+ let offset = 0;
491
+ // 1. Read the metadata size header
492
+ let vlq = Essentials.vlqToUint64(Essentials.sliceArrayBuffer(arrayBuffer, offset, 10));
493
+ let metaSize = vlq.value;
494
+ offset += vlq.bytes;
495
+ // 2. Extract and parse metadata string via C++ instantly
496
+ let metaText = result.getSliceAsText(offset, metaSize);
497
+ let nativeResponse = JSON.parse(metaText);
498
+ offset += metaSize;
499
+ // Extract everything remaining automatically by passing -1
500
+ let content = result.getSliceAsArrayBuffer(offset, -1); // content is already ArrayBuffer but QByteArray to ArrayBuffer cannot be Uint8Array() wrapped
501
+ // content = new Uint8Array(content ?? new ArrayBuffer(0))
502
+ return { metadata: nativeResponse, content: content };
503
+ }
504
+ function setRawBatchIterator(arrayBuffer) {
505
+ arrayBuffer.metadata().items.forEach(batch => {
506
+ batch.kv = _ => {
507
+ const batchItem = {
508
+ count: batch.count,
509
+ slice: Essentials.sliceArrayBuffer(arrayBuffer, batch.offset, batch.size),
510
+ [Symbol.iterator]() {
511
+ let index = 0;
512
+ let offset = 0;
513
+ return {
514
+ next: () => {
515
+ if (index < this.count) {
516
+ let kv = getRawKeyValue(this.slice, offset);
517
+ offset = kv.next;
518
+ index++;
519
+ return { value: kv, done: false };
520
+ }
521
+ return { done: true };
522
+ }
523
+ };
524
+ }
525
+ };
526
+ return batchItem;
527
+ };
528
+ });
529
+ arrayBuffer.items = arrayBuffer.metadata().items;
530
+ }
531
+ // ===============
532
+ function convert_to_array_buffer(value) {
533
+ if (typeof value === "string")
534
+ return Essentials.stringToUtf8ArrayBuffer(value);
535
+ else if (typeof value === "number")
536
+ return Essentials.numberToSortableBytes(value);
537
+ return Essentials.stringToUtf8ArrayBuffer(JSON.stringify(value));
538
+ }
539
+ export function newRaw() {
540
+ let obj = { buffers: [], size: 0, hashes: {} };
541
+ obj.add = value => {
542
+ if (value === undefined)
543
+ return undefined;
544
+ const buf = (value instanceof ArrayBuffer) ? value : convert_to_array_buffer(value);
545
+ const hash = Essentials.defaultHash(buf);
546
+ for (let item of obj.hashes[hash] ?? []) { // Conflict. Fallback to verify
547
+ if (Essentials.memcmpEqual(buf, item.buf))
548
+ return item.offset;
549
+ }
550
+ const vlq = new Uint8Array(Essentials.uint64ToVlq(buf.byteLength));
551
+ obj.buffers.push({ buf, vlq });
552
+ let offset = obj.size;
553
+ obj.size += vlq.byteLength + buf.byteLength;
554
+ obj.hashes[hash] = obj.hashes[hash] ?? [];
555
+ obj.hashes[hash].push({ offset, buf });
556
+ return offset;
557
+ };
558
+ return obj;
559
+ }
560
+ function buildHybridRequest(request) {
561
+ const batch = request.batch ?? [request];
562
+ let raw = request.raw ?? newRaw();
563
+ request.raw = undefined;
564
+ // Pass 1: Direct offset assignment & exact size calculation (Zero allocation)
565
+ for (let i = 0; i < batch.length; i++) {
566
+ const item = batch[i];
567
+ delete item.batch;
568
+ if (item.update) {
569
+ if (item._id === undefined && item.options?.upsert && item.query?._id !== undefined)
570
+ item._id = item.query._id;
571
+ item.update = raw.add(item.update);
572
+ }
573
+ else if (item.insert) {
574
+ if (item._id === undefined && item.insert?._id !== undefined)
575
+ item._id = item.insert._id;
576
+ item.insert = raw.add(item.insert);
577
+ }
578
+ item._id = raw.add(item._id);
579
+ if (item.options?.$rangeFrom !== undefined)
580
+ item.options.$rangeFrom = raw.add(item.options.$rangeFrom);
581
+ if (item.options?.$rangeTo !== undefined)
582
+ item.options.$rangeTo = raw.add(item.options.$rangeTo);
583
+ }
584
+ // Pass 2: Generate the final JSON payload string
585
+ const jsonBuffer = convert_to_array_buffer(request);
586
+ const jsonByteLen = jsonBuffer.byteLength;
587
+ // Pass 3: Allocate the exact Master Buffer ONCE
588
+ let vlq = new Uint8Array(Essentials.uint64ToVlq(jsonByteLen));
589
+ const totalSize = vlq.byteLength + jsonByteLen + raw.size;
590
+ const masterBuffer = new ArrayBuffer(totalSize);
591
+ const masterView = new Uint8Array(masterBuffer);
592
+ // Write the JSON size prefix
593
+ masterView.set(vlq, 0);
594
+ // Direct copy of JSON string bytes into their final destination
595
+ masterView.set(new Uint8Array(jsonBuffer), vlq.byteLength);
596
+ // Copy binary payloads directly into their final destination window
597
+ let writeOffset = vlq.byteLength + jsonByteLen;
598
+ for (const buffer of raw.buffers) {
599
+ const buf = buffer.buf;
600
+ const vlq = buffer.vlq;
601
+ // Write length prefix directly into final master buffer
602
+ masterView.set(vlq, writeOffset);
603
+ writeOffset += vlq.byteLength;
604
+ // Blit bytes directly from source into final master buffer destination
605
+ masterView.set(new Uint8Array(buf), writeOffset);
606
+ writeOffset += buf.byteLength;
607
+ }
608
+ return masterBuffer;
609
+ }
610
+ // Usage getRawKeyValue()
611
+ // Dip.query()
612
+ // .then(r => {
613
+ // let count = r.metadata().count
614
+ // let offset = 0
615
+ // for (let i = 0; i < count; i++) {
616
+ // let item = getKeyValue(r, offset)
617
+ // offset = item.next
618
+ // let res = Utils.arrayBufferToFile(item.data, '/home/khayaali/Downloads/zdip.png')
619
+ // console.log(item._id,res)
620
+ // }
621
+ // })
622
+ function bufferToString(arrayBuffer) {
623
+ return Essentials.arrayBufferToString(arrayBuffer);
624
+ }
625
+ function getRawKeyValue(arrayBuffer, offset) {
626
+ let dataView = new DataView(arrayBuffer.buffer, arrayBuffer.byteOffset, arrayBuffer.byteLength);
627
+ let masterView = new Uint8Array(arrayBuffer);
628
+ // 1. Extract Key Size and Key
629
+ let vlq = Essentials.vlqToUint64(Essentials.sliceArrayBuffer(arrayBuffer, offset, 10));
630
+ let keySize = vlq.value;
631
+ offset += vlq.bytes;
632
+ // Zero-copy view window bounded strictly to the key bytes
633
+ let _id = masterView.subarray(offset, offset + keySize);
634
+ let _id_string;
635
+ _id.toText = _ => {
636
+ // Fix: Pass the typed array view itself, NOT the underlying root buffer
637
+ _id_string = _id_string === undefined ? _id.buffer.slice(_id.byteOffset, _id.byteOffset + _id.byteLength) : _id_string;
638
+ return bufferToString(_id_string);
639
+ };
640
+ offset += keySize;
641
+ // 2. Extract Data Size and Data Binary Payload
642
+ vlq = Essentials.vlqToUint64(Essentials.sliceArrayBuffer(arrayBuffer, offset, 10));
643
+ let dataSize = vlq.value;
644
+ offset += vlq.bytes;
645
+ // Optimization: Use subarray() instead of .slice() to make the data payload 100% zero-copy too
646
+ let _val = masterView.subarray(offset, offset + dataSize);
647
+ offset += dataSize;
648
+ _val = Essentials.sliceArrayBuffer(arrayBuffer, _val.byteOffset, _val.byteLength);
649
+ // _val = _val.buffer.slice(_val.byteOffset, _val.byteOffset + _val.byteLength);
650
+ _val.toText = _ => bufferToString(_val);
651
+ return { _id, _val, next: offset };
652
+ }
653
+ // Axios usage
654
+ //--------------
655
+ // const axios = require('axios')
656
+ // axios.get('http://www.floatrates.com/daily/usd.json').then(data => console.log(data)).catch(error=> console.log(error))