corebasic 1.0.223 → 1.0.225

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.
@@ -79,8 +79,11 @@ export async function suffix(doc, policies, insertMode, arg) {
79
79
  if (!keys.length && !insertMode) {
80
80
  ls = true;
81
81
  for (const suffix of suffixes) {
82
- const dirs = (await Dip.operation("ls", { db: arg.db, collection: arg.collection, suffix })).filter(dir => !dir.startsWith('chunk-'));
83
- suffixAssociatedKeys[suffix] = dirs;
82
+ for (const collection of arg.collection) {
83
+ const dirs = (await Dip.operation("ls", { db: arg.db, collection: collection, suffix })).filter(dir => !dir.startsWith('chunk-'));
84
+ suffixAssociatedKeys[suffix] = suffixAssociatedKeys[suffix] ?? [];
85
+ suffixAssociatedKeys[suffix].push(...dirs);
86
+ }
84
87
  }
85
88
  console.warn(`Warn: Query omitted suffix policy key ${policy.key} in Dip. Falling back to Dip.operation(ls), incurring additional performance and network round-trip overhead.`);
86
89
  }
@@ -1,9 +1,10 @@
1
1
  import { describe, test } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { suffix } from "../suffix.js";
4
+ const arg = { db: "test-db", collection: ["test-collection"] };
4
5
  describe("suffix()", () => {
5
6
  test("returns empty suffixes when no policies exist", async () => {
6
- const result = await suffix({}, [], true, {});
7
+ const result = await suffix({}, [], true, arg);
7
8
  assert.deepEqual(result, []);
8
9
  });
9
10
  test("generates string suffix from fixed value", async () => {
@@ -12,7 +13,7 @@ describe("suffix()", () => {
12
13
  type: "string",
13
14
  value: "hello"
14
15
  }
15
- ], true, {});
16
+ ], true, arg);
16
17
  assert.deepEqual(result, [
17
18
  "/hello"
18
19
  ]);
@@ -23,7 +24,7 @@ describe("suffix()", () => {
23
24
  type: "string",
24
25
  value: " hello "
25
26
  }
26
- ], true, {});
27
+ ], true, arg);
27
28
  assert.deepEqual(result, [
28
29
  "/hello"
29
30
  ]);
@@ -34,7 +35,7 @@ describe("suffix()", () => {
34
35
  type: "string",
35
36
  value: " "
36
37
  }
37
- ], true, {}));
38
+ ], true, arg));
38
39
  });
39
40
  test("uses document key for string policy", async () => {
40
41
  const result = await suffix({
@@ -44,7 +45,7 @@ describe("suffix()", () => {
44
45
  type: "string",
45
46
  key: "name"
46
47
  }
47
- ], true, {});
48
+ ], true, arg);
48
49
  assert.deepEqual(result, [
49
50
  "/alice"
50
51
  ]);
@@ -62,7 +63,7 @@ describe("suffix()", () => {
62
63
  type: "number",
63
64
  key: "year"
64
65
  }
65
- ], true, {});
66
+ ], true, arg);
66
67
  assert.deepEqual(result, [
67
68
  "/books/2026"
68
69
  ]);
@@ -79,7 +80,7 @@ describe("suffix()", () => {
79
80
  type: "string",
80
81
  key: "tags"
81
82
  }
82
- ], true, {});
83
+ ], true, arg);
83
84
  assert.deepEqual(result.sort(), [
84
85
  "/a",
85
86
  "/b",
@@ -94,7 +95,7 @@ describe("suffix()", () => {
94
95
  type: "number",
95
96
  key: "age"
96
97
  }
97
- ], true, {});
98
+ ], true, arg);
98
99
  assert.deepEqual(result, [
99
100
  "/42"
100
101
  ]);
@@ -105,7 +106,7 @@ describe("suffix()", () => {
105
106
  type: "boolean",
106
107
  value: true
107
108
  }
108
- ], true, {}));
109
+ ], true, arg));
109
110
  });
110
111
  test("defaults type to string", async () => {
111
112
  const result = await suffix({
@@ -114,7 +115,7 @@ describe("suffix()", () => {
114
115
  {
115
116
  key: "id"
116
117
  }
117
- ], true, {});
118
+ ], true, arg);
118
119
  assert.deepEqual(result, [
119
120
  "/abc"
120
121
  ]);
@@ -127,7 +128,7 @@ describe("suffix()", () => {
127
128
  type: "date",
128
129
  key: "created"
129
130
  }
130
- ], true, {}));
131
+ ], true, arg));
131
132
  });
132
133
  test("rejects date format containing slash", async () => {
133
134
  await assert.rejects(() => suffix({
@@ -137,7 +138,7 @@ describe("suffix()", () => {
137
138
  type: "date:YYYY/MM/DD",
138
139
  key: "created"
139
140
  }
140
- ], true, {}));
141
+ ], true, arg));
141
142
  });
142
143
  test("formats date suffix", async () => {
143
144
  const result = await suffix({
@@ -147,7 +148,7 @@ describe("suffix()", () => {
147
148
  type: "date:YYYY-MM-DD",
148
149
  key: "created"
149
150
  }
150
- ], true, {});
151
+ ], true, arg);
151
152
  assert.deepEqual(result, [
152
153
  "/2026-07-09"
153
154
  ]);
@@ -164,7 +165,7 @@ describe("suffix()", () => {
164
165
  type: "string",
165
166
  key: "tags"
166
167
  }
167
- ], true, {});
168
+ ], true, arg);
168
169
  assert.deepEqual(result.sort(), [
169
170
  "/a",
170
171
  "/b"
@@ -176,7 +177,7 @@ describe("suffix()", () => {
176
177
  type: "string",
177
178
  key: "missing"
178
179
  }
179
- ], true, {}));
180
+ ], true, arg));
180
181
  });
181
182
  test("number range expands inclusive bounds", async () => {
182
183
  const result = await suffix({
@@ -189,7 +190,7 @@ describe("suffix()", () => {
189
190
  type: "number",
190
191
  key: "score"
191
192
  }
192
- ], true, {});
193
+ ], true, arg);
193
194
  assert.deepEqual(result.sort(), [
194
195
  "/1",
195
196
  "/2",
@@ -209,7 +210,7 @@ describe("suffix()", () => {
209
210
  min: 5,
210
211
  max: 7
211
212
  }
212
- ], true, {});
213
+ ], true, arg);
213
214
  assert.deepEqual(result.sort(), ["/1", "/2"]);
214
215
  });
215
216
  test("number range uses policy max fallback", async () => {
@@ -223,7 +224,7 @@ describe("suffix()", () => {
223
224
  key: "score",
224
225
  max: 7
225
226
  }
226
- ], true, {});
227
+ ], true, arg);
227
228
  assert.deepEqual(result.sort(), ["/5", "/6", "/7"]);
228
229
  });
229
230
  test("number range uses policy min fallback", async () => {
@@ -237,7 +238,7 @@ describe("suffix()", () => {
237
238
  key: "score",
238
239
  min: 5
239
240
  }
240
- ], true, {});
241
+ ], true, arg);
241
242
  assert.deepEqual(result.sort(), ["/5", "/6", "/7"]);
242
243
  });
243
244
  test("date range expands dates", async () => {
@@ -251,7 +252,7 @@ describe("suffix()", () => {
251
252
  type: "date:YYYY-MM-DD",
252
253
  key: "created"
253
254
  }
254
- ], true, {});
255
+ ], true, arg);
255
256
  assert.deepEqual(result.sort(), [
256
257
  "/2026-01-01",
257
258
  "/2026-01-02",
@@ -270,7 +271,7 @@ describe("suffix()", () => {
270
271
  key: "value",
271
272
  ls_threshold: 5
272
273
  }
273
- ], true, {}));
274
+ ], true, arg));
274
275
  });
275
276
  test("throws when ls threshold exceeded for date range", async () => {
276
277
  await assert.rejects(() => suffix({
@@ -284,7 +285,7 @@ describe("suffix()", () => {
284
285
  key: "created",
285
286
  ls_threshold: 2
286
287
  }
287
- ], true, {}));
288
+ ], true, arg));
288
289
  });
289
290
  test("creates cartesian product for multiple suffix values", async () => {
290
291
  const result = await suffix({
@@ -305,7 +306,7 @@ describe("suffix()", () => {
305
306
  type: "number",
306
307
  key: "b"
307
308
  }
308
- ], true, {});
309
+ ], true, arg);
309
310
  assert.deepEqual(result.sort(), [
310
311
  "/x/1",
311
312
  "/x/2",
@@ -133,7 +133,7 @@ let batches = {};
133
133
  // Ported
134
134
  export function batchBegin() {
135
135
  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)
136
- batches[uid] = [];
136
+ batches[uid.toString()] = [];
137
137
  return uid;
138
138
  }
139
139
  // Ported
@@ -264,15 +264,18 @@ export const insert = async (meta, collection, value, options, extras) => {
264
264
  validate_meta(meta, 'in Dip.insert()');
265
265
  const topology_meta = validate_topology(meta, collection, 'in Dip.insert()'); // TODO: loop for arrays
266
266
  let arg = {
267
- collection: collection,
267
+ collection: typeof collection === "string" ? [collection] : collection,
268
268
  insert: value,
269
269
  options: options ? options : {},
270
270
  syncMode: meta.syncMode,
271
+ db: extras?.db ?? meta?.db ?? db,
271
272
  ...(meta.DIP_URL ? { DIP_URL: meta.DIP_URL } : {}),
272
273
  ...(meta.DIP_DB ? { db: meta.DIP_DB } : {}),
274
+ suffix: [],
275
+ executor: "native",
273
276
  };
274
277
  arg = Object.assign(arg, extras);
275
- let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.insert, true, arg);
278
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.insert, true, { db: arg.db, collection: arg.collection });
276
279
  let suffix_from_meta = generate_suffix(meta);
277
280
  arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy));
278
281
  arg.executor = meta.executor ?? "native";
@@ -302,15 +305,18 @@ export const query = async (meta, collection, query, options, extras) => {
302
305
  validate_meta(meta, 'in Dip.query()');
303
306
  const topology_meta = validate_topology(meta, collection, 'in Dip.query()'); // TODO: loop for arrays
304
307
  let arg = {
305
- collection: collection,
308
+ collection: typeof collection === "string" ? [collection] : collection,
306
309
  query: query,
307
310
  options: options ? options : {},
308
311
  syncMode: meta.syncMode,
312
+ db: extras?.db ?? meta?.db ?? db,
309
313
  ...(meta.DIP_URL ? { DIP_URL: meta.DIP_URL } : {}),
310
314
  ...(meta.DIP_DB ? { db: meta.DIP_DB } : {}),
315
+ suffix: [],
316
+ executor: "native",
311
317
  };
312
318
  arg = Object.assign(arg, extras);
313
- let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, arg);
319
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, { db: arg.db, collection: arg.collection });
314
320
  let suffix_from_meta = generate_suffix(meta);
315
321
  arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy));
316
322
  arg.executor = meta.executor ?? "native";
@@ -348,16 +354,19 @@ export const update = async (meta, collection, query, update, options, extras) =
348
354
  validate_meta(meta, 'in Dip.update()');
349
355
  const topology_meta = validate_topology(meta, collection, 'in Dip.update()'); // TODO: loop for arrays
350
356
  let arg = {
351
- collection: collection,
357
+ collection: typeof collection === "string" ? [collection] : collection,
352
358
  query: query,
353
359
  update: update,
354
360
  options: options ? options : {},
355
361
  syncMode: meta.syncMode,
362
+ db: extras?.db ?? meta?.db ?? db,
356
363
  ...(meta.DIP_URL ? { DIP_URL: meta.DIP_URL } : {}),
357
364
  ...(meta.DIP_DB ? { db: meta.DIP_DB } : {}),
365
+ suffix: [],
366
+ executor: "native",
358
367
  };
359
368
  arg = Object.assign(arg, extras);
360
- let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, arg);
369
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, { db: arg.db, collection: arg.collection });
361
370
  let suffix_from_meta = generate_suffix(meta);
362
371
  arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy));
363
372
  arg.executor = meta.executor ?? "native";
@@ -385,16 +394,19 @@ export const remove = async (meta, collection, query, options, extras) => {
385
394
  validate_meta(meta, 'in Dip.remove()');
386
395
  const topology_meta = validate_topology(meta, collection, 'in Dip.remove()'); // TODO: loop for arrays
387
396
  let arg = {
388
- collection: collection,
397
+ collection: typeof collection === "string" ? [collection] : collection,
389
398
  query: query,
390
399
  options: options ? options : {},
391
400
  delete: true,
392
401
  syncMode: meta.syncMode,
402
+ db: extras?.db ?? meta?.db ?? db,
393
403
  ...(meta.DIP_URL ? { DIP_URL: meta.DIP_URL } : {}),
394
404
  ...(meta.DIP_DB ? { db: meta.DIP_DB } : {}),
405
+ suffix: [],
406
+ executor: "native",
395
407
  };
396
408
  arg = Object.assign(arg, extras);
397
- let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, arg);
409
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, { db: arg.db, collection: arg.collection });
398
410
  let suffix_from_meta = generate_suffix(meta);
399
411
  arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy));
400
412
  arg.executor = meta.executor ?? "native";
@@ -541,13 +553,13 @@ function convert_to_array_buffer(value) {
541
553
  return Essentials.stringToUtf8ArrayBuffer(JSON.stringify(value));
542
554
  }
543
555
  export function newRaw() {
544
- let obj = { buffers: [], size: 0, hashes: {} };
545
- obj.add = value => {
556
+ let obj = { buffers: [], size: 0, hashes: new Map() };
557
+ obj.add = (value) => {
546
558
  if (value === undefined)
547
559
  return undefined;
548
560
  const buf = (value instanceof ArrayBuffer) ? value : convert_to_array_buffer(value);
549
561
  const hash = Essentials.defaultHash(buf);
550
- for (let item of obj.hashes[hash] ?? []) { // Conflict. Fallback to verify
562
+ for (const item of obj.hashes.get(hash) ?? []) { // Conflict. Fallback to verify
551
563
  if (Essentials.memcmpEqual(buf, item.buf))
552
564
  return item.offset;
553
565
  }
@@ -555,8 +567,9 @@ export function newRaw() {
555
567
  obj.buffers.push({ buf, vlq });
556
568
  let offset = obj.size;
557
569
  obj.size += vlq.byteLength + buf.byteLength;
558
- obj.hashes[hash] = obj.hashes[hash] ?? [];
559
- obj.hashes[hash].push({ offset, buf });
570
+ if (!obj.hashes.get(hash))
571
+ obj.hashes.set(hash, []);
572
+ obj.hashes.get(hash).push({ offset, buf });
560
573
  return offset;
561
574
  };
562
575
  return obj;
@@ -331,13 +331,22 @@ export function parseMob(mob, code) {
331
331
  return mob;
332
332
  mob = mob.trim().replace(/(,| |-|\+)/g, ''); // remove comma, space, hyphen and plus
333
333
  mob = mob.startsWith("0") ? mob.replace(/^0*/, '') : mob; // remove starting n zeroes
334
- let prefix = Mobile.codes[code].phone;
334
+ const entry = Mobile.codes[code];
335
+ if (!entry)
336
+ throw new Error('Error: Invalid code in Utils.parseMob()');
337
+ let prefix = entry.phone;
335
338
  if (!mob.startsWith(prefix)) {
336
339
  mob = `${prefix}${mob}`;
337
340
  }
338
341
  else { // mob.startsWith(prefix)
339
- if (mob.length < Mobile.codes[code].phoneLength + prefix.length) // mob just happens to start with prefix
340
- mob = `${prefix}${mob}`;
342
+ const phoneLengthItems = typeof entry.phoneLength === "number" ? [entry.phoneLength] : entry.phoneLength;
343
+ for (const phoneLength of phoneLengthItems) {
344
+ if (mob.length < phoneLength + prefix.length) // mob just happens to start with prefix
345
+ return `${prefix}${mob}`;
346
+ else if (mob.length === phoneLength + prefix.length)
347
+ return mob;
348
+ }
349
+ throw new Error('Error: Mismatched phoneLength in Utils.parseMob()');
341
350
  }
342
351
  return mob;
343
352
  }
package/libs/cpp.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  import crypto from 'node:crypto';
3
3
 
4
4
 
5
- function readContent(buffer, valueRef, bytesRef) {
5
+ function readContent(buffer: Uint8Array, valueRef, bytesRef) { // Buffer is assignable to Uint8Array so no need for Buffer | Uint8Array
6
6
  const MASK = 0x7F;
7
7
  const MSB = 0x80;
8
8
  const MAX_VLQ_BYTES = 10;
@@ -135,7 +135,7 @@ export function matchPolicy(query, when, g_obj?: Record<string, any>) {
135
135
 
136
136
 
137
137
 
138
- type Arg = { db?: string; collection?: string; [key: string]: any }
138
+ type Arg = { db: string; collection: string[]; }
139
139
 
140
140
  export async function applySuffixPolicy(COLLECTIONS_JSON, collections, query, insertMode?: boolean, arg?: Arg) {
141
141
  collections = Array.isArray(collections) ? collections : [collections]
@@ -2,7 +2,7 @@ import {entries, reduceRanges, extractBounds, getNormalizedBounds, type Bounds}
2
2
  import {formatDate, fillDates} from './date.ts'
3
3
  import * as Dip from '../../elabase.ts'
4
4
 
5
- type Arg = { db?: string; collection?: string; [key: string]: any }
5
+ type Arg = { db: string; collection: string[]; }
6
6
 
7
7
  const EXPLICIT_SUFFIX_POLICY_TYPES = new Set([ "string", "date", "number" ]);
8
8
  export async function suffix(doc, policies, insertMode?: boolean, arg?: Arg) {
@@ -87,8 +87,11 @@ export async function suffix(doc, policies, insertMode?: boolean, arg?: Arg) {
87
87
  if (!keys.length && !insertMode) {
88
88
  ls = true
89
89
  for (const suffix of suffixes) {
90
- const dirs = (await Dip.operation("ls", { db: arg.db, collection: arg.collection, suffix })).filter(dir => !dir.startsWith('chunk-'))
91
- suffixAssociatedKeys[suffix] = dirs
90
+ for (const collection of arg.collection) {
91
+ const dirs = (await Dip.operation("ls", { db: arg.db, collection: collection, suffix })).filter(dir => !dir.startsWith('chunk-'))
92
+ suffixAssociatedKeys[suffix] = suffixAssociatedKeys[suffix] ?? []
93
+ suffixAssociatedKeys[suffix].push(...dirs)
94
+ }
92
95
  }
93
96
  console.warn(`Warn: Query omitted suffix policy key ${policy.key} in Dip. Falling back to Dip.operation(ls), incurring additional performance and network round-trip overhead.`)
94
97
  }
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
3
3
 
4
4
  import { suffix } from "../suffix.ts";
5
5
 
6
+ const arg = {db: "test-db", collection: ["test-collection"]}
6
7
 
7
8
  describe("suffix()", () => {
8
9
 
@@ -13,7 +14,7 @@ describe("suffix()", () => {
13
14
  {},
14
15
  [],
15
16
  true,
16
- {}
17
+ arg
17
18
  );
18
19
 
19
20
  assert.deepEqual(result, []);
@@ -33,7 +34,7 @@ describe("suffix()", () => {
33
34
  }
34
35
  ],
35
36
  true,
36
- {}
37
+ arg
37
38
  );
38
39
 
39
40
 
@@ -59,7 +60,7 @@ describe("suffix()", () => {
59
60
  }
60
61
  ],
61
62
  true,
62
- {}
63
+ arg
63
64
  );
64
65
 
65
66
 
@@ -87,7 +88,7 @@ describe("suffix()", () => {
87
88
  }
88
89
  ],
89
90
  true,
90
- {}
91
+ arg
91
92
  )
92
93
  );
93
94
 
@@ -108,7 +109,7 @@ describe("suffix()", () => {
108
109
  }
109
110
  ],
110
111
  true,
111
- {}
112
+ arg
112
113
  );
113
114
 
114
115
 
@@ -141,7 +142,7 @@ describe("suffix()", () => {
141
142
  }
142
143
  ],
143
144
  true,
144
- {}
145
+ arg
145
146
  );
146
147
 
147
148
 
@@ -173,7 +174,7 @@ describe("suffix()", () => {
173
174
  }
174
175
  ],
175
176
  true,
176
- {}
177
+ arg
177
178
  );
178
179
 
179
180
 
@@ -203,7 +204,7 @@ describe("suffix()", () => {
203
204
  }
204
205
  ],
205
206
  true,
206
- {}
207
+ arg
207
208
  );
208
209
 
209
210
 
@@ -231,7 +232,7 @@ describe("suffix()", () => {
231
232
  }
232
233
  ],
233
234
  true,
234
- {}
235
+ arg
235
236
  )
236
237
  );
237
238
 
@@ -251,7 +252,7 @@ describe("suffix()", () => {
251
252
  }
252
253
  ],
253
254
  true,
254
- {}
255
+ arg
255
256
  );
256
257
 
257
258
 
@@ -281,7 +282,7 @@ describe("suffix()", () => {
281
282
  }
282
283
  ],
283
284
  true,
284
- {}
285
+ arg
285
286
  )
286
287
  );
287
288
 
@@ -304,7 +305,7 @@ describe("suffix()", () => {
304
305
  }
305
306
  ],
306
307
  true,
307
- {}
308
+ arg
308
309
  )
309
310
  );
310
311
 
@@ -325,7 +326,7 @@ describe("suffix()", () => {
325
326
  }
326
327
  ],
327
328
  true,
328
- {}
329
+ arg
329
330
  );
330
331
 
331
332
 
@@ -357,7 +358,7 @@ describe("suffix()", () => {
357
358
  }
358
359
  ],
359
360
  true,
360
- {}
361
+ arg
361
362
  );
362
363
 
363
364
 
@@ -386,7 +387,7 @@ describe("suffix()", () => {
386
387
  }
387
388
  ],
388
389
  true,
389
- {}
390
+ arg
390
391
  )
391
392
  );
392
393
 
@@ -410,7 +411,7 @@ describe("suffix()", () => {
410
411
  }
411
412
  ],
412
413
  true,
413
- {}
414
+ arg
414
415
  );
415
416
 
416
417
 
@@ -445,7 +446,7 @@ describe("suffix()", () => {
445
446
  }
446
447
  ],
447
448
  true,
448
- {}
449
+ arg
449
450
  );
450
451
  assert.deepEqual(result.sort(), ["/1", "/2"]);
451
452
  });
@@ -466,7 +467,7 @@ describe("suffix()", () => {
466
467
  }
467
468
  ],
468
469
  true,
469
- {}
470
+ arg
470
471
  );
471
472
  assert.deepEqual(result.sort(), ["/5", "/6", "/7"]);
472
473
  });
@@ -486,7 +487,7 @@ describe("suffix()", () => {
486
487
  }
487
488
  ],
488
489
  true,
489
- {}
490
+ arg
490
491
  );
491
492
  assert.deepEqual(result.sort(), ["/5", "/6", "/7"]);
492
493
  });
@@ -507,7 +508,7 @@ describe("suffix()", () => {
507
508
  }
508
509
  ],
509
510
  true,
510
- {}
511
+ arg
511
512
  );
512
513
 
513
514
 
@@ -543,7 +544,7 @@ describe("suffix()", () => {
543
544
  }
544
545
  ],
545
546
  true,
546
- {}
547
+ arg
547
548
  )
548
549
  );
549
550
 
@@ -570,7 +571,7 @@ describe("suffix()", () => {
570
571
  }
571
572
  ],
572
573
  true,
573
- {}
574
+ arg
574
575
  )
575
576
  );
576
577
 
@@ -602,7 +603,7 @@ describe("suffix()", () => {
602
603
  }
603
604
  ],
604
605
  true,
605
- {}
606
+ arg
606
607
  );
607
608
 
608
609
 
package/libs/elabase.ts CHANGED
@@ -173,7 +173,7 @@ let batches = {}
173
173
  // Ported
174
174
  export function batchBegin() { // Manual Lifecycle. If you forget to batchSubmit() or batchAbort(), then memory leak due to dangling batch
175
175
  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)
176
- batches[uid] = []
176
+ batches[uid.toString()] = []
177
177
  return uid
178
178
  }
179
179
  // Ported
@@ -310,6 +310,30 @@ export const operation = async (name, extras) => {
310
310
  return execute({ operation: name, ...extras })
311
311
  }
312
312
 
313
+ type Options = {
314
+ upsert: boolean;
315
+ monotonic: boolean;
316
+ idempotent: boolean;
317
+ $orderby: any;
318
+ $fields: any;
319
+ _id?: string | number;
320
+ }
321
+ type InsertRequest = {
322
+ db: string;
323
+ executor: "native" | "raw";
324
+ collection: string[];
325
+ suffix: string | string[];
326
+ _id?: string | number;
327
+ insert: number | any;
328
+ options: Options;
329
+ syncMode: boolean;
330
+ DIP_URL?: string;
331
+ DIP_DB?: string;
332
+ }
333
+
334
+ type QueryRequest = Omit<InsertRequest, "_id" | "insert"> & { query: any }
335
+ type UpdateRequest = Omit<InsertRequest, "insert"> & { query: any, update: any }
336
+ type RemoveRequest = QueryRequest & { delete: true }
313
337
 
314
338
  // Ported
315
339
  export const insert = async (meta, collection, value, options?: any, extras?: any) => {
@@ -322,16 +346,19 @@ export const insert = async (meta, collection, value, options?: any, extras?: an
322
346
 
323
347
  const topology_meta = validate_topology(meta, collection, 'in Dip.insert()') // TODO: loop for arrays
324
348
 
325
- let arg = {
326
- collection: collection,
349
+ let arg: InsertRequest = {
350
+ collection: typeof collection === "string" ? [collection] : collection,
327
351
  insert: value,
328
352
  options: options ? options : {},
329
353
  syncMode: meta.syncMode,
354
+ db: extras?.db ?? meta?.db ?? db,
330
355
  ...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
331
356
  ...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
357
+ suffix: [],
358
+ executor: "native",
332
359
  }
333
360
  arg = Object.assign(arg, extras)
334
- let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.insert, true, arg)
361
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.insert, true, {db: arg.db, collection: arg.collection})
335
362
  let suffix_from_meta = generate_suffix(meta)
336
363
  arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy))
337
364
  arg.executor = meta.executor ?? "native"
@@ -366,16 +393,19 @@ export const query = async (meta, collection, query, options?: any, extras?: any
366
393
 
367
394
  const topology_meta = validate_topology(meta, collection, 'in Dip.query()') // TODO: loop for arrays
368
395
 
369
- let arg = {
370
- collection: collection,
396
+ let arg: QueryRequest = {
397
+ collection: typeof collection === "string" ? [collection] : collection,
371
398
  query: query,
372
399
  options: options ? options : {},
373
400
  syncMode: meta.syncMode,
401
+ db: extras?.db ?? meta?.db ?? db,
374
402
  ...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
375
403
  ...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
404
+ suffix: [],
405
+ executor: "native",
376
406
  }
377
407
  arg = Object.assign(arg, extras)
378
- let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, arg)
408
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, {db: arg.db, collection: arg.collection})
379
409
  let suffix_from_meta = generate_suffix(meta)
380
410
  arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy))
381
411
  arg.executor = meta.executor ?? "native"
@@ -418,17 +448,20 @@ export const update = async (meta, collection, query, update, options?: any, ext
418
448
 
419
449
  const topology_meta = validate_topology(meta, collection, 'in Dip.update()') // TODO: loop for arrays
420
450
 
421
- let arg = {
422
- collection: collection,
451
+ let arg: UpdateRequest = {
452
+ collection: typeof collection === "string" ? [collection] : collection,
423
453
  query: query,
424
454
  update: update,
425
455
  options: options ? options : {},
426
456
  syncMode: meta.syncMode,
457
+ db: extras?.db ?? meta?.db ?? db,
427
458
  ...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
428
459
  ...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
460
+ suffix: [],
461
+ executor: "native",
429
462
  }
430
463
  arg = Object.assign(arg, extras)
431
- let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, arg)
464
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, {db: arg.db, collection: arg.collection})
432
465
  let suffix_from_meta = generate_suffix(meta)
433
466
  arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy))
434
467
  arg.executor = meta.executor ?? "native"
@@ -461,17 +494,20 @@ export const remove = async (meta, collection, query, options?: any, extras?: an
461
494
 
462
495
  const topology_meta = validate_topology(meta, collection, 'in Dip.remove()') // TODO: loop for arrays
463
496
 
464
- let arg = {
465
- collection: collection,
497
+ let arg: RemoveRequest = {
498
+ collection: typeof collection === "string" ? [collection] : collection,
466
499
  query: query,
467
500
  options: options ? options : {},
468
501
  delete: true,
469
502
  syncMode: meta.syncMode,
503
+ db: extras?.db ?? meta?.db ?? db,
470
504
  ...(meta.DIP_URL ? {DIP_URL: meta.DIP_URL} : {}),
471
505
  ...(meta.DIP_DB ? {db: meta.DIP_DB} : {}),
506
+ suffix: [],
507
+ executor: "native",
472
508
  }
473
509
  arg = Object.assign(arg, extras)
474
- let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, arg)
510
+ let suffix_from_policy = await applySuffixPolicy(COLLECTIONS_JSON, collection, arg.query, false, {db: arg.db, collection: arg.collection})
475
511
  let suffix_from_meta = generate_suffix(meta)
476
512
  arg.suffix = (suffix_from_meta.length ? suffix_from_meta : [""]).flatMap(meta => (suffix_from_policy.length ? suffix_from_policy : [""]).map(policy => meta + policy))
477
513
  arg.executor = meta.executor ?? "native"
@@ -657,14 +693,22 @@ function convert_to_array_buffer(value) {
657
693
  }
658
694
 
659
695
  export function newRaw() {
660
- let obj = {buffers: [], size: 0, hashes:{}}
661
- obj.add = value => {
696
+ type BufferItem = {buf: ArrayBuffer | Uint8Array; vlq: Uint8Array}
697
+ type HashItem = {buf: ArrayBuffer | Uint8Array; offset: number}
698
+ type RawManager = {
699
+ buffers: BufferItem[];
700
+ size: number;
701
+ hashes: Map<bigint, HashItem[]>;
702
+ add: (value: ArrayBuffer | string | number | any) => number;
703
+ }
704
+ let obj: RawManager = {buffers: [], size: 0, hashes: new Map()} as RawManager
705
+ obj.add = (value: ArrayBuffer | string | number | any): number => {
662
706
  if (value === undefined)
663
707
  return undefined
664
708
  const buf = (value instanceof ArrayBuffer) ? value : convert_to_array_buffer(value);
665
709
 
666
710
  const hash = Essentials.defaultHash(buf)
667
- for (let item of obj.hashes[hash] ?? []) { // Conflict. Fallback to verify
711
+ for (const item of obj.hashes.get(hash) ?? []) { // Conflict. Fallback to verify
668
712
  if (Essentials.memcmpEqual(buf, item.buf))
669
713
  return item.offset
670
714
  }
@@ -674,8 +718,10 @@ export function newRaw() {
674
718
  let offset = obj.size
675
719
  obj.size += vlq.byteLength + buf.byteLength;
676
720
 
677
- obj.hashes[hash] = obj.hashes[hash] ?? []
678
- obj.hashes[hash].push({offset, buf})
721
+
722
+ if (!obj.hashes.get(hash))
723
+ obj.hashes.set(hash, [])
724
+ obj.hashes.get(hash).push({offset, buf})
679
725
 
680
726
  return offset
681
727
  }
@@ -695,11 +741,11 @@ function buildHybridRequest(request) {
695
741
  if (item.update) {
696
742
  if ( item._id === undefined && item.options?.upsert && item.query?._id !== undefined)
697
743
  item._id = item.query._id
698
- item.update = raw.add(item.update)
744
+ item.update = raw.add(item.update)
699
745
  } else if (item.insert) {
700
746
  if (item._id === undefined && item.insert?._id !== undefined)
701
747
  item._id = item.insert._id
702
- item.insert = raw.add(item.insert)
748
+ item.insert = raw.add(item.insert)
703
749
  }
704
750
  item._id = raw.add(item._id)
705
751
 
@@ -1,7 +1,10 @@
1
1
 
2
2
  // Found at https://gist.github.com/ally-commits/9073ff23fc7f96fab1290fdec22775bc
3
3
 
4
- export const codes = {
4
+ type CountryItem = { code: string; label: string; phone: string; phoneLength: number | number[], min?: number, max?: number, suggested?: boolean }
5
+
6
+
7
+ export const codes: Record<string, CountryItem> = {
5
8
  AD: { code: 'AD', label: 'Andorra', phone: '376', phoneLength: 6 },
6
9
  AE: {
7
10
  code: 'AE',
package/libs/utils.ts CHANGED
@@ -280,72 +280,126 @@ export async function fileToJson(path, file) {
280
280
  let data = (await readFile(new URL(file, path).toString().replace('file://',''), 'utf8'))
281
281
  // return JSON.parse((JSON as Record<string, any>).minify(data))
282
282
 
283
- const json_minify = function (json: string): string {
284
-
285
- var tokenizer = /"|(\/\*)|(\*\/)|(\/\/)|\n|\r|\[|]/g,
286
- in_string = false,
287
- in_multiline_comment = false,
288
- in_singleline_comment = false,
289
- tmp, tmp2, new_str = [], ns = 0, from = 0, lc, rc,
290
- prevFrom
291
- ;
292
-
293
- tokenizer.lastIndex = 0;
294
-
295
- while ( tmp = tokenizer.exec(json) ) {
296
- lc = RegExp.leftContext;
297
- rc = RegExp.rightContext;
298
- if (!in_multiline_comment && !in_singleline_comment) {
299
- tmp2 = lc.substring(from);
300
- if (!in_string) {
301
- tmp2 = tmp2.replace(/(\n|\r|\s)*/g,"");
302
- }
303
- new_str[ns++] = tmp2;
304
- }
305
- prevFrom = from;
306
- from = tokenizer.lastIndex;
307
-
308
- // found a " character, and we're not currently in
309
- // a comment? check for previous `\` escaping immediately
310
- // leftward adjacent to this match
311
- if (tmp[0] === "\"" && !in_multiline_comment && !in_singleline_comment) {
312
- // limit left-context matching to only go back
313
- // to the position of the last token match
314
- //
315
- // see: https://github.com/getify/JSON.minify/issues/64
316
- lc.lastIndex = prevFrom;
317
-
318
- // perform leftward adjacent escaping match
319
- tmp2 = lc.match(/(\\)*$/);
320
- // start of string with ", or unescaped " character found to end string?
321
- if (!in_string || !tmp2 || (tmp2[0].length % 2) === 0) {
322
- in_string = !in_string;
283
+ // import stripJsonComments from "strip-json-comments";
284
+ // ====================================================
285
+ const singleComment = Symbol('singleComment');
286
+ const multiComment = Symbol('multiComment');
287
+
288
+ const stripWithoutWhitespace = () => '';
289
+
290
+ // Replace all characters except ASCII spaces, tabs and line endings with regular spaces to ensure valid JSON output.
291
+ const stripWithWhitespace = (string, start, end?: number) => string.slice(start, end).replace(/[^ \t\r\n]/g, ' ');
292
+
293
+ const isEscaped = (jsonString, quotePosition) => {
294
+ let index = quotePosition - 1;
295
+ let backslashCount = 0;
296
+
297
+ while (jsonString[index] === '\\') {
298
+ index -= 1;
299
+ backslashCount += 1;
300
+ }
301
+
302
+ return Boolean(backslashCount % 2);
303
+ };
304
+
305
+ function stripJsonComments(jsonString, {whitespace = true, trailingCommas = false} = {}) {
306
+ if (typeof jsonString !== 'string') {
307
+ throw new TypeError(`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``);
308
+ }
309
+
310
+ const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
311
+
312
+ let isInsideString = false;
313
+ let isInsideComment: false | typeof singleComment | typeof multiComment = false;
314
+ // let isInsideComment = false;
315
+ let offset = 0;
316
+ let buffer = '';
317
+ let result = '';
318
+ let commaIndex = -1;
319
+
320
+ for (let index = 0; index < jsonString.length; index++) {
321
+ const currentCharacter = jsonString[index];
322
+ const nextCharacter = jsonString[index + 1];
323
+
324
+ if (!isInsideComment && currentCharacter === '"') {
325
+ // Enter or exit string
326
+ const escaped = isEscaped(jsonString, index);
327
+ if (!escaped) {
328
+ isInsideString = !isInsideString;
323
329
  }
324
- from--; // include " character in next catch
325
- rc = json.substring(from);
326
330
  }
327
- else if (tmp[0] === "/*" && !in_string && !in_multiline_comment && !in_singleline_comment) {
328
- in_multiline_comment = true;
329
- }
330
- else if (tmp[0] === "*/" && !in_string && in_multiline_comment && !in_singleline_comment) {
331
- in_multiline_comment = false;
332
- }
333
- else if (tmp[0] === "//" && !in_string && !in_multiline_comment && !in_singleline_comment) {
334
- in_singleline_comment = true;
335
- }
336
- else if ((tmp[0] === "\n" || tmp[0] === "\r") && !in_string && !in_multiline_comment && in_singleline_comment) {
337
- in_singleline_comment = false;
331
+
332
+ if (isInsideString) {
333
+ continue;
338
334
  }
339
- else if (!in_multiline_comment && !in_singleline_comment && !(/\n|\r|\s/.test(tmp[0]))) {
340
- new_str[ns++] = tmp[0];
335
+
336
+ if (!isInsideComment && currentCharacter + nextCharacter === '//') {
337
+ // Enter single-line comment
338
+ buffer += jsonString.slice(offset, index);
339
+ offset = index;
340
+ isInsideComment = singleComment;
341
+ index++;
342
+ } else if (isInsideComment === singleComment && currentCharacter + nextCharacter === '\r\n') {
343
+ // Exit single-line comment via \r\n
344
+ index++;
345
+ isInsideComment = false;
346
+ buffer += strip(jsonString, offset, index);
347
+ offset = index;
348
+ continue;
349
+ } else if (isInsideComment === singleComment && currentCharacter === '\n') {
350
+ // Exit single-line comment via \n
351
+ isInsideComment = false;
352
+ buffer += strip(jsonString, offset, index);
353
+ offset = index;
354
+ } else if (!isInsideComment && currentCharacter + nextCharacter === '/*') {
355
+ // Enter multiline comment
356
+ buffer += jsonString.slice(offset, index);
357
+ offset = index;
358
+ isInsideComment = multiComment;
359
+ index++;
360
+ continue;
361
+ } else if (isInsideComment === multiComment && currentCharacter + nextCharacter === '*/') {
362
+ // Exit multiline comment
363
+ index++;
364
+ isInsideComment = false;
365
+ buffer += strip(jsonString, offset, index + 1);
366
+ offset = index + 1;
367
+ continue;
368
+ } else if (trailingCommas && !isInsideComment) {
369
+ if (commaIndex !== -1) {
370
+ if (currentCharacter === '}' || currentCharacter === ']') {
371
+ // Strip trailing comma
372
+ buffer += jsonString.slice(offset, index);
373
+ result += strip(buffer, 0, 1) + buffer.slice(1);
374
+ buffer = '';
375
+ offset = index;
376
+ commaIndex = -1;
377
+ } else if (currentCharacter !== ' ' && currentCharacter !== '\t' && currentCharacter !== '\r' && currentCharacter !== '\n') {
378
+ // Hit non-whitespace following a comma; comma is not trailing
379
+ buffer += jsonString.slice(offset, index);
380
+ offset = index;
381
+ commaIndex = -1;
382
+ }
383
+ } else if (currentCharacter === ',') {
384
+ // Flush buffer prior to this point, and save new comma index
385
+ result += buffer + jsonString.slice(offset, index);
386
+ buffer = '';
387
+ offset = index;
388
+ commaIndex = index;
389
+ }
341
390
  }
342
391
  }
343
- new_str[ns++] = rc;
344
- return new_str.join("");
392
+
393
+ const remaining = (isInsideComment === singleComment)
394
+ ? strip(jsonString, offset)
395
+ : jsonString.slice(offset);
396
+
397
+ return result + buffer + remaining;
345
398
  }
399
+ // ====================================================
346
400
 
347
401
 
348
- return JSON.parse(json_minify(data))
402
+ return JSON.parse(stripJsonComments(data))
349
403
  }
350
404
 
351
405
  // ----------------
@@ -406,13 +460,23 @@ export function parseMob(mob: string, code: string): string {
406
460
  mob = mob.trim().replace(/(,| |-|\+)/g, '') // remove comma, space, hyphen and plus
407
461
  mob = mob.startsWith("0") ? mob.replace(/^0*/,'') : mob // remove starting n zeroes
408
462
 
409
- let prefix = Mobile.codes[code].phone
463
+ const entry = Mobile.codes[code]
464
+ if (!entry)
465
+ throw new Error('Error: Invalid code in Utils.parseMob()')
466
+
467
+ let prefix = entry.phone
410
468
 
411
469
  if (!mob.startsWith(prefix)) {
412
470
  mob = `${prefix}${mob}`
413
471
  } else { // mob.startsWith(prefix)
414
- if (mob.length < Mobile.codes[code].phoneLength + prefix.length) // mob just happens to start with prefix
415
- mob = `${prefix}${mob}`
472
+ const phoneLengthItems = typeof entry.phoneLength === "number" ? [entry.phoneLength] : entry.phoneLength
473
+ for (const phoneLength of phoneLengthItems) {
474
+ if (mob.length < phoneLength + prefix.length) // mob just happens to start with prefix
475
+ return `${prefix}${mob}`
476
+ else if (mob.length === phoneLength + prefix.length)
477
+ return mob
478
+ }
479
+ throw new Error('Error: Mismatched phoneLength in Utils.parseMob()')
416
480
  }
417
481
  return mob
418
482
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "corebasic",
3
3
  "type": "module",
4
- "version": "1.0.223",
4
+ "version": "1.0.225",
5
5
  "description": "",
6
6
  "main": "dist/index.js",
7
7
  "types": "./index.ts",