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
@@ -1,268 +1,325 @@
1
- /**
2
- * SchemaManager - Manages field schemas for optimized array-based serialization
3
- * This replaces the need for repeating field names in JSON objects
4
- */
5
- export default class SchemaManager {
6
- constructor(opts = {}) {
7
- this.opts = Object.assign({
8
- enableArraySerialization: true,
9
- strictSchema: true,
10
- debugMode: false
11
- }, opts)
12
-
13
- // Schema definition: array of field names in order
14
- this.schema = []
15
- this.fieldToIndex = new Map() // field name -> index
16
- this.indexToField = new Map() // index -> field name
17
- this.schemaVersion = 1
18
- this.isInitialized = false
19
- }
20
-
21
- /**
22
- * Initialize schema from options or auto-detect from data
23
- */
24
- initializeSchema(schemaOrData, autoDetect = false) {
25
- if (this.isInitialized && this.opts.strictSchema) {
26
- if (this.opts.debugMode) {
27
- console.log('SchemaManager: Schema already initialized, skipping')
28
- }
29
- return
30
- }
31
-
32
- if (Array.isArray(schemaOrData)) {
33
- // Explicit schema provided
34
- this.setSchema(schemaOrData)
35
- } else if (autoDetect && typeof schemaOrData === 'object') {
36
- // Auto-detect schema from data
37
- this.autoDetectSchema(schemaOrData)
38
- } else if (schemaOrData && typeof schemaOrData === 'object') {
39
- // Initialize from database options
40
- this.initializeFromOptions(schemaOrData)
41
- }
42
-
43
- this.isInitialized = true
44
- if (this.opts.debugMode) {
45
- console.log('SchemaManager: Schema initialized:', this.schema)
46
- }
47
- }
48
-
49
- /**
50
- * Set explicit schema
51
- */
52
- setSchema(fieldNames) {
53
- this.schema = [...fieldNames] // Create copy
54
- this.fieldToIndex.clear()
55
- this.indexToField.clear()
56
-
57
- this.schema.forEach((field, index) => {
58
- this.fieldToIndex.set(field, index)
59
- this.indexToField.set(index, field)
60
- })
61
-
62
- if (this.opts.debugMode) {
63
- console.log('SchemaManager: Schema set:', this.schema)
64
- }
65
- }
66
-
67
- /**
68
- * Auto-detect schema from sample data
69
- */
70
- autoDetectSchema(sampleData) {
71
- if (Array.isArray(sampleData)) {
72
- // Use first record as template
73
- if (sampleData.length > 0) {
74
- this.autoDetectSchema(sampleData[0])
75
- }
76
- return
77
- }
78
-
79
- if (typeof sampleData === 'object' && sampleData !== null) {
80
- const fields = Object.keys(sampleData).sort() // Sort for consistency
81
-
82
- // CRITICAL FIX: Always include 'id' field in schema for proper array format
83
- if (!fields.includes('id')) {
84
- fields.push('id')
85
- }
86
-
87
- this.setSchema(fields)
88
- }
89
- }
90
-
91
- /**
92
- * Initialize schema from database options
93
- */
94
- initializeFromOptions(opts) {
95
- if (opts.schema && Array.isArray(opts.schema)) {
96
- this.setSchema(opts.schema)
97
- }
98
- // CRITICAL FIX: Don't auto-initialize schema from indexes
99
- // This was causing data loss because only indexed fields were preserved
100
- // Let schema be auto-detected from actual data instead
101
- }
102
-
103
- /**
104
- * Add new field to schema (for schema evolution)
105
- */
106
- addField(fieldName) {
107
- if (this.fieldToIndex.has(fieldName)) {
108
- return this.fieldToIndex.get(fieldName)
109
- }
110
-
111
- const newIndex = this.schema.length
112
- this.schema.push(fieldName)
113
- this.fieldToIndex.set(fieldName, newIndex)
114
- this.indexToField.set(newIndex, fieldName)
115
-
116
- if (this.opts.debugMode) {
117
- console.log('SchemaManager: Added field:', fieldName, 'at index:', newIndex)
118
- }
119
-
120
- return newIndex
121
- }
122
-
123
- /**
124
- * Convert object to array using schema with strict field enforcement
125
- */
126
- objectToArray(obj) {
127
- if (!this.isInitialized || !this.opts.enableArraySerialization) {
128
- return obj // Fallback to object format
129
- }
130
-
131
- if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
132
- return obj // Don't convert non-objects or arrays
133
- }
134
-
135
- const result = new Array(this.schema.length)
136
-
137
- // Fill array with values in schema order
138
- // Missing fields become undefined, extra fields are ignored
139
- for (let i = 0; i < this.schema.length; i++) {
140
- const fieldName = this.schema[i]
141
- result[i] = obj[fieldName] !== undefined ? obj[fieldName] : undefined
142
- }
143
-
144
- return result
145
- }
146
-
147
- /**
148
- * Convert array back to object using schema
149
- */
150
- arrayToObject(arr) {
151
- if (!this.isInitialized || !this.opts.enableArraySerialization) {
152
- return arr // Fallback to array format
153
- }
154
-
155
- if (!Array.isArray(arr)) {
156
- return arr // Don't convert non-arrays
157
- }
158
-
159
- const obj = {}
160
-
161
- // Map array values to object properties
162
- // Only include fields that are in the schema
163
- for (let i = 0; i < Math.min(arr.length, this.schema.length); i++) {
164
- const fieldName = this.schema[i]
165
- // Only include non-undefined values to avoid cluttering the object
166
- if (arr[i] !== undefined) {
167
- obj[fieldName] = arr[i]
168
- }
169
- }
170
-
171
- return obj
172
- }
173
-
174
- /**
175
- * Get field index by name
176
- */
177
- getFieldIndex(fieldName) {
178
- return this.fieldToIndex.get(fieldName)
179
- }
180
-
181
- /**
182
- * Get field name by index
183
- */
184
- getFieldName(index) {
185
- return this.indexToField.get(index)
186
- }
187
-
188
- /**
189
- * Check if field exists in schema
190
- */
191
- hasField(fieldName) {
192
- return this.fieldToIndex.has(fieldName)
193
- }
194
-
195
- /**
196
- * Get schema as array of field names
197
- */
198
- getSchema() {
199
- return [...this.schema] // Return copy
200
- }
201
-
202
- /**
203
- * Get schema size
204
- */
205
- getSchemaSize() {
206
- return this.schema.length
207
- }
208
-
209
- /**
210
- * Validate that object conforms to schema
211
- */
212
- validateObject(obj) {
213
- if (!this.isInitialized || !this.opts.strictSchema) {
214
- return true
215
- }
216
-
217
- if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
218
- return false
219
- }
220
-
221
- // Check if object has all required fields
222
- for (const field of this.schema) {
223
- if (!(field in obj)) {
224
- if (this.opts.debugMode) {
225
- console.warn('SchemaManager: Missing required field:', field)
226
- }
227
- return false
228
- }
229
- }
230
-
231
- return true
232
- }
233
-
234
- /**
235
- * Get schema metadata for serialization
236
- */
237
- getSchemaMetadata() {
238
- return {
239
- version: this.schemaVersion,
240
- fields: [...this.schema],
241
- fieldCount: this.schema.length,
242
- isInitialized: this.isInitialized
243
- }
244
- }
245
-
246
- /**
247
- * Reset schema
248
- */
249
- reset() {
250
- this.schema = []
251
- this.fieldToIndex.clear()
252
- this.indexToField.clear()
253
- this.isInitialized = false
254
- this.schemaVersion++
255
- }
256
-
257
- /**
258
- * Get performance statistics
259
- */
260
- getStats() {
261
- return {
262
- schemaSize: this.schema.length,
263
- isInitialized: this.isInitialized,
264
- version: this.schemaVersion,
265
- enableArraySerialization: this.opts.enableArraySerialization
266
- }
267
- }
268
- }
1
+ /**
2
+ * SchemaManager - Manages field schemas for optimized array-based serialization
3
+ * This replaces the need for repeating field names in JSON objects
4
+ */
5
+ export default class SchemaManager {
6
+ constructor(opts = {}) {
7
+ this.opts = Object.assign({
8
+ enableArraySerialization: true,
9
+ strictSchema: true,
10
+ debugMode: false
11
+ }, opts)
12
+
13
+ // Schema definition: array of field names in order
14
+ this.schema = []
15
+ this.fieldToIndex = new Map() // field name -> index
16
+ this.indexToField = new Map() // index -> field name
17
+ this.schemaVersion = 1
18
+ this.isInitialized = false
19
+ }
20
+
21
+ /**
22
+ * Initialize schema from options or auto-detect from data
23
+ */
24
+ initializeSchema(schemaOrData, autoDetect = false) {
25
+ if (this.isInitialized && this.opts.strictSchema) {
26
+ if (this.opts.debugMode) {
27
+ console.log('SchemaManager: Schema already initialized, skipping')
28
+ }
29
+ return
30
+ }
31
+
32
+ if (Array.isArray(schemaOrData)) {
33
+ // Explicit schema provided
34
+ this.setSchema(schemaOrData)
35
+ } else if (autoDetect && typeof schemaOrData === 'object') {
36
+ // Auto-detect schema from data
37
+ this.autoDetectSchema(schemaOrData)
38
+ } else if (schemaOrData && typeof schemaOrData === 'object') {
39
+ // Initialize from database options
40
+ this.initializeFromOptions(schemaOrData)
41
+ }
42
+
43
+ this.isInitialized = true
44
+ if (this.opts.debugMode) {
45
+ console.log('SchemaManager: Schema initialized:', this.schema)
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Set explicit schema
51
+ */
52
+ setSchema(fieldNames) {
53
+ this.schema = [...fieldNames] // Create copy
54
+ this.fieldToIndex.clear()
55
+ this.indexToField.clear()
56
+
57
+ this.schema.forEach((field, index) => {
58
+ this.fieldToIndex.set(field, index)
59
+ this.indexToField.set(index, field)
60
+ })
61
+
62
+ if (this.opts.debugMode) {
63
+ console.log('SchemaManager: Schema set:', this.schema)
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Auto-detect schema from sample data
69
+ */
70
+ autoDetectSchema(sampleData) {
71
+ if (Array.isArray(sampleData)) {
72
+ // Use first record as template
73
+ if (sampleData.length > 0) {
74
+ this.autoDetectSchema(sampleData[0])
75
+ }
76
+ return
77
+ }
78
+
79
+ if (typeof sampleData === 'object' && sampleData !== null) {
80
+ const fields = Object.keys(sampleData).sort() // Sort for consistency
81
+
82
+ // CRITICAL FIX: Always include 'id' field in schema for proper array format
83
+ if (!fields.includes('id')) {
84
+ fields.push('id')
85
+ }
86
+
87
+ this.setSchema(fields)
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Initialize schema from database options
93
+ * Note: schema option is no longer supported, use fields instead
94
+ */
95
+ initializeFromOptions(opts) {
96
+ // Schema option is no longer supported - fields should be used instead
97
+ // This method is kept for compatibility but does nothing
98
+ // Schema initialization is handled by Database.initializeSchema() using fields
99
+ }
100
+
101
+ /**
102
+ * Add new field to schema (for schema evolution)
103
+ */
104
+ addField(fieldName) {
105
+ if (this.fieldToIndex.has(fieldName)) {
106
+ return this.fieldToIndex.get(fieldName)
107
+ }
108
+
109
+ const newIndex = this.schema.length
110
+ this.schema.push(fieldName)
111
+ this.fieldToIndex.set(fieldName, newIndex)
112
+ this.indexToField.set(newIndex, fieldName)
113
+
114
+ if (this.opts.debugMode) {
115
+ console.log('SchemaManager: Added field:', fieldName, 'at index:', newIndex)
116
+ }
117
+
118
+ return newIndex
119
+ }
120
+
121
+ /**
122
+ * Convert object to array using schema with strict field enforcement
123
+ */
124
+ objectToArray(obj) {
125
+ if (!this.isInitialized || !this.opts.enableArraySerialization) {
126
+ return obj // Fallback to object format
127
+ }
128
+
129
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
130
+ return obj // Don't convert non-objects or arrays
131
+ }
132
+
133
+ const result = new Array(this.schema.length)
134
+
135
+ // Fill array with values in schema order
136
+ // Missing fields become undefined, extra fields are ignored
137
+ for (let i = 0; i < this.schema.length; i++) {
138
+ const fieldName = this.schema[i]
139
+ result[i] = obj[fieldName] !== undefined ? obj[fieldName] : undefined
140
+ }
141
+
142
+ // CRITICAL FIX: Always append 'id' field if it exists and is not in schema
143
+ // The 'id' field must be preserved even if not in the schema
144
+ if (obj.id !== undefined && obj.id !== null && this.schema.indexOf('id') === -1) {
145
+ result.push(obj.id)
146
+ }
147
+
148
+ return result
149
+ }
150
+
151
+ /**
152
+ * Convert array back to object using schema
153
+ */
154
+ arrayToObject(arr) {
155
+ if (!this.isInitialized || !this.opts.enableArraySerialization) {
156
+ return arr // Fallback to array format
157
+ }
158
+
159
+ if (!Array.isArray(arr)) {
160
+ return arr // Don't convert non-arrays
161
+ }
162
+
163
+ const obj = {}
164
+ const idIndex = this.schema.indexOf('id')
165
+
166
+ // CRITICAL FIX: Handle schema migration where 'id' was first field in old schema
167
+ // but is not in current schema. Check if first element looks like an ID.
168
+ // Only do this if:
169
+ // 1. 'id' is not in current schema
170
+ // 2. Array has significantly more elements than current schema (2+ extra elements)
171
+ // This suggests the old schema had more fields, and 'id' was likely the first
172
+ // 3. First element is a very short string (max 20 chars) that looks like a generated ID
173
+ // (typically alphanumeric, often starting with letters like 'mit...' or similar patterns)
174
+ // 4. First field in current schema is not 'id' (to avoid false positives)
175
+ // 5. First element is not an array (to avoid false positives with array fields)
176
+ let arrayOffset = 0
177
+ if (idIndex === -1 && arr.length >= this.schema.length + 2 && this.schema.length > 0) {
178
+ // Only apply if array has at least 2 extra elements (suggests old schema had more fields)
179
+ const firstElement = arr[0]
180
+ const firstFieldName = this.schema[0]
181
+
182
+ // Only apply shift if:
183
+ // - First field is not 'id'
184
+ // - First element is a very short string (max 20 chars) that looks like a generated ID
185
+ // - First element is not an array (to avoid false positives)
186
+ // - Array has at least 2 extra elements (strong indicator of schema migration)
187
+ if (firstFieldName !== 'id' &&
188
+ typeof firstElement === 'string' &&
189
+ !Array.isArray(firstElement) &&
190
+ firstElement.length > 0 &&
191
+ firstElement.length <= 20 && // Very conservative: max 20 chars (typical ID length)
192
+ /^[a-zA-Z0-9_-]+$/.test(firstElement)) {
193
+ // First element is likely the ID from old schema
194
+ obj.id = firstElement
195
+ arrayOffset = 1
196
+ }
197
+ }
198
+
199
+ // Map array values to object properties
200
+ // Only include fields that are in the schema
201
+ for (let i = 0; i < Math.min(arr.length - arrayOffset, this.schema.length); i++) {
202
+ const fieldName = this.schema[i]
203
+ const arrayIndex = i + arrayOffset
204
+ // Only include non-undefined values to avoid cluttering the object
205
+ if (arr[arrayIndex] !== undefined) {
206
+ obj[fieldName] = arr[arrayIndex]
207
+ }
208
+ }
209
+
210
+ // CRITICAL FIX: Always preserve 'id' field if it exists in the original object
211
+ // The 'id' field may not be in the schema but must be preserved
212
+ if (idIndex !== -1 && arr[idIndex] !== undefined) {
213
+ // 'id' is in schema and has a value
214
+ obj.id = arr[idIndex]
215
+ } else if (!obj.id && arr.length > this.schema.length + arrayOffset) {
216
+ // 'id' is not in schema but array has extra element(s) - check if last element could be ID
217
+ // This handles cases where ID was added after schema initialization
218
+ for (let i = this.schema.length + arrayOffset; i < arr.length; i++) {
219
+ // Try to infer if this is an ID (string that looks like an ID)
220
+ const potentialId = arr[i]
221
+ if (potentialId !== undefined && potentialId !== null && typeof potentialId === 'string' && potentialId.length > 0 && potentialId.length < 100) {
222
+ obj.id = potentialId
223
+ break // Use first potential ID found
224
+ }
225
+ }
226
+ }
227
+
228
+ return obj
229
+ }
230
+
231
+ /**
232
+ * Get field index by name
233
+ */
234
+ getFieldIndex(fieldName) {
235
+ return this.fieldToIndex.get(fieldName)
236
+ }
237
+
238
+ /**
239
+ * Get field name by index
240
+ */
241
+ getFieldName(index) {
242
+ return this.indexToField.get(index)
243
+ }
244
+
245
+ /**
246
+ * Check if field exists in schema
247
+ */
248
+ hasField(fieldName) {
249
+ return this.fieldToIndex.has(fieldName)
250
+ }
251
+
252
+ /**
253
+ * Get schema as array of field names
254
+ */
255
+ getSchema() {
256
+ return [...this.schema] // Return copy
257
+ }
258
+
259
+ /**
260
+ * Get schema size
261
+ */
262
+ getSchemaSize() {
263
+ return this.schema.length
264
+ }
265
+
266
+ /**
267
+ * Validate that object conforms to schema
268
+ */
269
+ validateObject(obj) {
270
+ if (!this.isInitialized || !this.opts.strictSchema) {
271
+ return true
272
+ }
273
+
274
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
275
+ return false
276
+ }
277
+
278
+ // Check if object has all required fields
279
+ for (const field of this.schema) {
280
+ if (!(field in obj)) {
281
+ if (this.opts.debugMode) {
282
+ console.warn('SchemaManager: Missing required field:', field)
283
+ }
284
+ return false
285
+ }
286
+ }
287
+
288
+ return true
289
+ }
290
+
291
+ /**
292
+ * Get schema metadata for serialization
293
+ */
294
+ getSchemaMetadata() {
295
+ return {
296
+ version: this.schemaVersion,
297
+ fields: [...this.schema],
298
+ fieldCount: this.schema.length,
299
+ isInitialized: this.isInitialized
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Reset schema
305
+ */
306
+ reset() {
307
+ this.schema = []
308
+ this.fieldToIndex.clear()
309
+ this.indexToField.clear()
310
+ this.isInitialized = false
311
+ this.schemaVersion++
312
+ }
313
+
314
+ /**
315
+ * Get performance statistics
316
+ */
317
+ getStats() {
318
+ return {
319
+ schemaSize: this.schema.length,
320
+ isInitialized: this.isInitialized,
321
+ version: this.schemaVersion,
322
+ enableArraySerialization: this.opts.enableArraySerialization
323
+ }
324
+ }
325
+ }