jexidb 2.1.9 → 2.2.0

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.
@@ -1,297 +1,297 @@
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
- // DISABLED: Schema migration detection was causing field mapping corruption
167
- // The logic was incorrectly assuming ID was in first position when it's appended at the end
168
- // This caused fields to be shifted incorrectly during object-to-array-to-object conversion
169
- let arrayOffset = 0
170
-
171
- // Map array values to object properties
172
- // Only include fields that are in the schema
173
- for (let i = 0; i < Math.min(arr.length - arrayOffset, this.schema.length); i++) {
174
- const fieldName = this.schema[i]
175
- const arrayIndex = i + arrayOffset
176
- // Only include non-undefined values to avoid cluttering the object
177
- if (arr[arrayIndex] !== undefined) {
178
- obj[fieldName] = arr[arrayIndex]
179
- }
180
- }
181
-
182
- // CRITICAL FIX: Always preserve 'id' field if it exists in the original object
183
- // The 'id' field may not be in the schema but must be preserved
184
- if (idIndex !== -1 && arr[idIndex] !== undefined) {
185
- // 'id' is in schema and has a value
186
- obj.id = arr[idIndex]
187
- } else if (!obj.id && arr.length > this.schema.length + arrayOffset) {
188
- // 'id' is not in schema but array has extra element(s) - check if last element could be ID
189
- // This handles cases where ID was added after schema initialization
190
- for (let i = this.schema.length + arrayOffset; i < arr.length; i++) {
191
- // Try to infer if this is an ID (string that looks like an ID)
192
- const potentialId = arr[i]
193
- if (potentialId !== undefined && potentialId !== null && typeof potentialId === 'string' && potentialId.length > 0 && potentialId.length < 100) {
194
- obj.id = potentialId
195
- break // Use first potential ID found
196
- }
197
- }
198
- }
199
-
200
- return obj
201
- }
202
-
203
- /**
204
- * Get field index by name
205
- */
206
- getFieldIndex(fieldName) {
207
- return this.fieldToIndex.get(fieldName)
208
- }
209
-
210
- /**
211
- * Get field name by index
212
- */
213
- getFieldName(index) {
214
- return this.indexToField.get(index)
215
- }
216
-
217
- /**
218
- * Check if field exists in schema
219
- */
220
- hasField(fieldName) {
221
- return this.fieldToIndex.has(fieldName)
222
- }
223
-
224
- /**
225
- * Get schema as array of field names
226
- */
227
- getSchema() {
228
- return [...this.schema] // Return copy
229
- }
230
-
231
- /**
232
- * Get schema size
233
- */
234
- getSchemaSize() {
235
- return this.schema.length
236
- }
237
-
238
- /**
239
- * Validate that object conforms to schema
240
- */
241
- validateObject(obj) {
242
- if (!this.isInitialized || !this.opts.strictSchema) {
243
- return true
244
- }
245
-
246
- if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
247
- return false
248
- }
249
-
250
- // Check if object has all required fields
251
- for (const field of this.schema) {
252
- if (!(field in obj)) {
253
- if (this.opts.debugMode) {
254
- console.warn('SchemaManager: Missing required field:', field)
255
- }
256
- return false
257
- }
258
- }
259
-
260
- return true
261
- }
262
-
263
- /**
264
- * Get schema metadata for serialization
265
- */
266
- getSchemaMetadata() {
267
- return {
268
- version: this.schemaVersion,
269
- fields: [...this.schema],
270
- fieldCount: this.schema.length,
271
- isInitialized: this.isInitialized
272
- }
273
- }
274
-
275
- /**
276
- * Reset schema
277
- */
278
- reset() {
279
- this.schema = []
280
- this.fieldToIndex.clear()
281
- this.indexToField.clear()
282
- this.isInitialized = false
283
- this.schemaVersion++
284
- }
285
-
286
- /**
287
- * Get performance statistics
288
- */
289
- getStats() {
290
- return {
291
- schemaSize: this.schema.length,
292
- isInitialized: this.isInitialized,
293
- version: this.schemaVersion,
294
- enableArraySerialization: this.opts.enableArraySerialization
295
- }
296
- }
297
- }
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
+ // DISABLED: Schema migration detection was causing field mapping corruption
167
+ // The logic was incorrectly assuming ID was in first position when it's appended at the end
168
+ // This caused fields to be shifted incorrectly during object-to-array-to-object conversion
169
+ let arrayOffset = 0
170
+
171
+ // Map array values to object properties
172
+ // Only include fields that are in the schema
173
+ for (let i = 0; i < Math.min(arr.length - arrayOffset, this.schema.length); i++) {
174
+ const fieldName = this.schema[i]
175
+ const arrayIndex = i + arrayOffset
176
+ // Only include non-undefined values to avoid cluttering the object
177
+ if (arr[arrayIndex] !== undefined) {
178
+ obj[fieldName] = arr[arrayIndex]
179
+ }
180
+ }
181
+
182
+ // CRITICAL FIX: Always preserve 'id' field if it exists in the original object
183
+ // The 'id' field may not be in the schema but must be preserved
184
+ if (idIndex !== -1 && arr[idIndex] !== undefined) {
185
+ // 'id' is in schema and has a value
186
+ obj.id = arr[idIndex]
187
+ } else if (!obj.id && arr.length > this.schema.length + arrayOffset) {
188
+ // 'id' is not in schema but array has extra element(s) - check if last element could be ID
189
+ // This handles cases where ID was added after schema initialization
190
+ for (let i = this.schema.length + arrayOffset; i < arr.length; i++) {
191
+ // Try to infer if this is an ID (string that looks like an ID)
192
+ const potentialId = arr[i]
193
+ if (potentialId !== undefined && potentialId !== null && typeof potentialId === 'string' && potentialId.length > 0 && potentialId.length < 100) {
194
+ obj.id = potentialId
195
+ break // Use first potential ID found
196
+ }
197
+ }
198
+ }
199
+
200
+ return obj
201
+ }
202
+
203
+ /**
204
+ * Get field index by name
205
+ */
206
+ getFieldIndex(fieldName) {
207
+ return this.fieldToIndex.get(fieldName)
208
+ }
209
+
210
+ /**
211
+ * Get field name by index
212
+ */
213
+ getFieldName(index) {
214
+ return this.indexToField.get(index)
215
+ }
216
+
217
+ /**
218
+ * Check if field exists in schema
219
+ */
220
+ hasField(fieldName) {
221
+ return this.fieldToIndex.has(fieldName)
222
+ }
223
+
224
+ /**
225
+ * Get schema as array of field names
226
+ */
227
+ getSchema() {
228
+ return [...this.schema] // Return copy
229
+ }
230
+
231
+ /**
232
+ * Get schema size
233
+ */
234
+ getSchemaSize() {
235
+ return this.schema.length
236
+ }
237
+
238
+ /**
239
+ * Validate that object conforms to schema
240
+ */
241
+ validateObject(obj) {
242
+ if (!this.isInitialized || !this.opts.strictSchema) {
243
+ return true
244
+ }
245
+
246
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
247
+ return false
248
+ }
249
+
250
+ // Check if object has all required fields
251
+ for (const field of this.schema) {
252
+ if (!(field in obj)) {
253
+ if (this.opts.debugMode) {
254
+ console.warn('SchemaManager: Missing required field:', field)
255
+ }
256
+ return false
257
+ }
258
+ }
259
+
260
+ return true
261
+ }
262
+
263
+ /**
264
+ * Get schema metadata for serialization
265
+ */
266
+ getSchemaMetadata() {
267
+ return {
268
+ version: this.schemaVersion,
269
+ fields: [...this.schema],
270
+ fieldCount: this.schema.length,
271
+ isInitialized: this.isInitialized
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Reset schema
277
+ */
278
+ reset() {
279
+ this.schema = []
280
+ this.fieldToIndex.clear()
281
+ this.indexToField.clear()
282
+ this.isInitialized = false
283
+ this.schemaVersion++
284
+ }
285
+
286
+ /**
287
+ * Get performance statistics
288
+ */
289
+ getStats() {
290
+ return {
291
+ schemaSize: this.schema.length,
292
+ isInitialized: this.isInitialized,
293
+ version: this.schemaVersion,
294
+ enableArraySerialization: this.opts.enableArraySerialization
295
+ }
296
+ }
297
+ }