jexidb 2.1.0 → 2.1.2

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.
Files changed (41) hide show
  1. package/dist/Database.cjs +9253 -437
  2. package/package.json +9 -2
  3. package/src/Database.mjs +1572 -212
  4. package/src/FileHandler.mjs +83 -44
  5. package/src/OperationQueue.mjs +23 -23
  6. package/src/SchemaManager.mjs +325 -268
  7. package/src/Serializer.mjs +234 -24
  8. package/src/managers/IndexManager.mjs +778 -87
  9. package/src/managers/QueryManager.mjs +340 -67
  10. package/src/managers/TermManager.mjs +7 -7
  11. package/src/utils/operatorNormalizer.mjs +116 -0
  12. package/.babelrc +0 -13
  13. package/.gitattributes +0 -2
  14. package/CHANGELOG.md +0 -140
  15. package/babel.config.json +0 -5
  16. package/docs/API.md +0 -1051
  17. package/docs/EXAMPLES.md +0 -701
  18. package/docs/README.md +0 -194
  19. package/examples/iterate-usage-example.js +0 -157
  20. package/examples/simple-iterate-example.js +0 -115
  21. package/jest.config.js +0 -24
  22. package/scripts/README.md +0 -47
  23. package/scripts/clean-test-files.js +0 -75
  24. package/scripts/prepare.js +0 -31
  25. package/scripts/run-tests.js +0 -80
  26. package/test/$not-operator-with-and.test.js +0 -282
  27. package/test/README.md +0 -8
  28. package/test/close-init-cycle.test.js +0 -256
  29. package/test/critical-bugs-fixes.test.js +0 -1069
  30. package/test/index-persistence.test.js +0 -306
  31. package/test/index-serialization.test.js +0 -314
  32. package/test/indexed-query-mode.test.js +0 -360
  33. package/test/iterate-method.test.js +0 -272
  34. package/test/query-operators.test.js +0 -238
  35. package/test/regex-array-fields.test.js +0 -129
  36. package/test/score-method.test.js +0 -238
  37. package/test/setup.js +0 -17
  38. package/test/term-mapping-minimal.test.js +0 -154
  39. package/test/term-mapping-simple.test.js +0 -257
  40. package/test/term-mapping.test.js +0 -514
  41. package/test/writebuffer-flush-resilience.test.js +0 -204
@@ -64,7 +64,26 @@ export default class Serializer {
64
64
  if (!this.opts.enableArraySerialization) {
65
65
  return arr
66
66
  }
67
- return this.schemaManager.arrayToObject(arr)
67
+ const obj = this.schemaManager.arrayToObject(arr)
68
+
69
+ // CRITICAL FIX: Always preserve 'id' field if it exists in the original array
70
+ // The 'id' field may not be in the schema but must be preserved
71
+ // Check if array has more elements than schema fields - the extra element(s) might be the ID
72
+ if (!obj.id && Array.isArray(arr) && this.schemaManager.isInitialized) {
73
+ const schemaLength = this.schemaManager.schema ? this.schemaManager.schema.length : 0
74
+ if (arr.length > schemaLength) {
75
+ // Check if any extra element looks like an ID (string)
76
+ for (let i = schemaLength; i < arr.length; i++) {
77
+ const potentialId = arr[i]
78
+ if (potentialId !== undefined && potentialId !== null && typeof potentialId === 'string' && potentialId.length > 0) {
79
+ obj.id = potentialId
80
+ break
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ return obj
68
87
  }
69
88
 
70
89
  /**
@@ -259,42 +278,69 @@ export default class Serializer {
259
278
  optimizedStringify(obj) {
260
279
  // CRITICAL: Normalize encoding for all string fields before stringify
261
280
  const normalizedObj = this.deepNormalizeEncoding(obj)
262
-
281
+ return this._stringifyNormalizedValue(normalizedObj)
282
+ }
283
+
284
+ _stringifyNormalizedValue(value) {
263
285
  // Fast path for null and undefined
264
- if (normalizedObj === null) return 'null'
265
- if (normalizedObj === undefined) return 'null'
286
+ if (value === null || value === undefined) {
287
+ return 'null'
288
+ }
289
+
290
+ const type = typeof value
266
291
 
267
292
  // Fast path for primitives
268
- if (typeof normalizedObj === 'boolean') return normalizedObj ? 'true' : 'false'
269
- if (typeof normalizedObj === 'number') return normalizedObj.toString()
270
- if (typeof normalizedObj === 'string') {
293
+ if (type === 'boolean') {
294
+ return value ? 'true' : 'false'
295
+ }
296
+
297
+ if (type === 'number') {
298
+ return Number.isFinite(value) ? value.toString() : 'null'
299
+ }
300
+
301
+ if (type === 'string') {
271
302
  // Fast path for simple strings (no escaping needed)
272
- if (!/[\\"\u0000-\u001f]/.test(normalizedObj)) {
273
- return '"' + normalizedObj + '"'
303
+ if (!/[\\"\u0000-\u001f]/.test(value)) {
304
+ return '"' + value + '"'
274
305
  }
275
306
  // Fall back to JSON.stringify for complex strings
276
- return JSON.stringify(normalizedObj)
307
+ return JSON.stringify(value)
277
308
  }
278
309
 
279
- // Fast path for arrays
280
- if (Array.isArray(normalizedObj)) {
281
- if (normalizedObj.length === 0) return '[]'
282
-
283
- // For arrays, always use JSON.stringify to avoid concatenation issues
284
- return JSON.stringify(normalizedObj)
310
+ if (Array.isArray(value)) {
311
+ return this._stringifyNormalizedArray(value)
285
312
  }
286
313
 
287
- // Fast path for objects
288
- if (typeof normalizedObj === 'object') {
289
- const keys = Object.keys(normalizedObj)
314
+ if (type === 'object') {
315
+ const keys = Object.keys(value)
290
316
  if (keys.length === 0) return '{}'
291
-
292
- // For objects, always use JSON.stringify to avoid concatenation issues
293
- return JSON.stringify(normalizedObj)
317
+ // Use native stringify for object to leverage stable handling of undefined, Dates, etc.
318
+ return JSON.stringify(value)
294
319
  }
295
320
 
296
- // Fallback to JSON.stringify for unknown types
297
- return JSON.stringify(normalizedObj)
321
+ // Fallback to JSON.stringify for unknown types (BigInt, symbols, etc.)
322
+ return JSON.stringify(value)
323
+ }
324
+
325
+ _stringifyNormalizedArray(arr) {
326
+ const length = arr.length
327
+ if (length === 0) return '[]'
328
+
329
+ let result = '['
330
+ for (let i = 0; i < length; i++) {
331
+ if (i > 0) result += ','
332
+ const element = arr[i]
333
+
334
+ // JSON spec: undefined, functions, and symbols are serialized as null within arrays
335
+ if (element === undefined || typeof element === 'function' || typeof element === 'symbol') {
336
+ result += 'null'
337
+ continue
338
+ }
339
+
340
+ result += this._stringifyNormalizedValue(element)
341
+ }
342
+ result += ']'
343
+ return result
298
344
  }
299
345
 
300
346
  /**
@@ -350,12 +396,176 @@ export default class Serializer {
350
396
  // Fast path for empty strings
351
397
  if (strLength === 0) return null
352
398
 
399
+ // CRITICAL FIX: Detect and handle multiple JSON objects in the same line
400
+ // This can happen if data was corrupted during concurrent writes or offset calculation errors
401
+ const firstBrace = str.indexOf('{')
402
+ const firstBracket = str.indexOf('[')
403
+
404
+ // Helper function to extract first complete JSON object/array from a string
405
+ // CRITICAL FIX: Must handle strings and escaped characters correctly
406
+ // to avoid counting braces/brackets that are inside string values
407
+ const extractFirstJson = (jsonStr, startChar) => {
408
+ if (startChar === '{') {
409
+ let braceCount = 0
410
+ let endPos = -1
411
+ let inString = false
412
+ let escapeNext = false
413
+
414
+ for (let i = 0; i < jsonStr.length; i++) {
415
+ const char = jsonStr[i]
416
+
417
+ if (escapeNext) {
418
+ escapeNext = false
419
+ continue
420
+ }
421
+
422
+ if (char === '\\') {
423
+ escapeNext = true
424
+ continue
425
+ }
426
+
427
+ if (char === '"' && !escapeNext) {
428
+ inString = !inString
429
+ continue
430
+ }
431
+
432
+ if (!inString) {
433
+ if (char === '{') braceCount++
434
+ if (char === '}') {
435
+ braceCount--
436
+ if (braceCount === 0) {
437
+ endPos = i + 1
438
+ break
439
+ }
440
+ }
441
+ }
442
+ }
443
+ return endPos > 0 ? jsonStr.substring(0, endPos) : null
444
+ } else if (startChar === '[') {
445
+ let bracketCount = 0
446
+ let endPos = -1
447
+ let inString = false
448
+ let escapeNext = false
449
+
450
+ for (let i = 0; i < jsonStr.length; i++) {
451
+ const char = jsonStr[i]
452
+
453
+ if (escapeNext) {
454
+ escapeNext = false
455
+ continue
456
+ }
457
+
458
+ if (char === '\\') {
459
+ escapeNext = true
460
+ continue
461
+ }
462
+
463
+ if (char === '"' && !escapeNext) {
464
+ inString = !inString
465
+ continue
466
+ }
467
+
468
+ if (!inString) {
469
+ if (char === '[') bracketCount++
470
+ if (char === ']') {
471
+ bracketCount--
472
+ if (bracketCount === 0) {
473
+ endPos = i + 1
474
+ break
475
+ }
476
+ }
477
+ }
478
+ }
479
+ return endPos > 0 ? jsonStr.substring(0, endPos) : null
480
+ }
481
+ return null
482
+ }
483
+
484
+ // Check if JSON starts at the beginning of the string
485
+ const jsonStartsAtZero = (firstBrace === 0) || (firstBracket === 0)
486
+ let hasValidJson = false
487
+
488
+ if (jsonStartsAtZero) {
489
+ // JSON starts at beginning - check for multiple JSON objects/arrays
490
+ if (firstBrace === 0) {
491
+ const secondBrace = str.indexOf('{', 1)
492
+ if (secondBrace !== -1) {
493
+ // Multiple objects detected - extract first
494
+ const extracted = extractFirstJson(str, '{')
495
+ if (extracted) {
496
+ str = extracted
497
+ hasValidJson = true
498
+ if (this.opts && this.opts.debugMode) {
499
+ console.warn(`⚠️ Deserialize: Multiple JSON objects detected, using first object only`)
500
+ }
501
+ }
502
+ } else {
503
+ hasValidJson = true // Single valid object starting at 0
504
+ }
505
+ } else if (firstBracket === 0) {
506
+ const secondBracket = str.indexOf('[', 1)
507
+ if (secondBracket !== -1) {
508
+ // Multiple arrays detected - extract first
509
+ const extracted = extractFirstJson(str, '[')
510
+ if (extracted) {
511
+ str = extracted
512
+ hasValidJson = true
513
+ if (this.opts && this.opts.debugMode) {
514
+ console.warn(`⚠️ Deserialize: Multiple JSON arrays detected, using first array only`)
515
+ }
516
+ }
517
+ } else {
518
+ hasValidJson = true // Single valid array starting at 0
519
+ }
520
+ }
521
+ } else {
522
+ // JSON doesn't start at beginning - try to find and extract first valid JSON
523
+ const jsonStart = firstBrace !== -1 ? (firstBracket !== -1 ? Math.min(firstBrace, firstBracket) : firstBrace) : firstBracket
524
+
525
+ if (jsonStart !== -1 && jsonStart > 0) {
526
+ // Found JSON but not at start - extract from that position
527
+ const jsonStr = str.substring(jsonStart)
528
+ const startChar = jsonStr[0]
529
+ const extracted = extractFirstJson(jsonStr, startChar)
530
+
531
+ if (extracted) {
532
+ str = extracted
533
+ hasValidJson = true
534
+ if (this.opts && this.opts.debugMode) {
535
+ console.warn(`⚠️ Deserialize: Found JSON after ${jsonStart} chars of invalid text, extracted first ${startChar === '{' ? 'object' : 'array'}`)
536
+ }
537
+ }
538
+ }
539
+ }
540
+
541
+ // CRITICAL FIX: If no valid JSON structure found, throw error before attempting parse
542
+ // This allows walk() and other callers to catch and skip invalid lines
543
+ if (!hasValidJson && firstBrace === -1 && firstBracket === -1) {
544
+ const errorStr = Buffer.isBuffer(data) ? data.toString('utf8').trim() : data.trim()
545
+ const error = new Error(`Failed to deserialize JSON data: No valid JSON structure found in "${errorStr.substring(0, 100)}..."`)
546
+ // Mark this as a "no valid JSON" error so it can be handled appropriately
547
+ error.noValidJson = true
548
+ throw error
549
+ }
550
+
551
+ // If we tried to extract but got nothing valid, also throw error
552
+ if (hasValidJson && (!str || str.trim().length === 0)) {
553
+ const error = new Error(`Failed to deserialize JSON data: Extracted JSON is empty`)
554
+ error.noValidJson = true
555
+ throw error
556
+ }
557
+
353
558
  // Parse JSON data
354
559
  const parsedData = JSON.parse(str)
355
560
 
356
561
  // Convert from array format back to object if needed
357
562
  return this.convertFromArrayFormat(parsedData)
358
563
  } catch (e) {
564
+ // If error was already formatted with noValidJson flag, re-throw as-is
565
+ if (e.noValidJson) {
566
+ throw e
567
+ }
568
+ // Otherwise, format the error message
359
569
  const str = Buffer.isBuffer(data) ? data.toString('utf8').trim() : data.trim()
360
570
  throw new Error(`Failed to deserialize JSON data: "${str.substring(0, 100)}..." - ${e.message}`)
361
571
  }