jexidb 2.0.2 → 2.1.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.
- package/.babelrc +13 -0
- package/.gitattributes +2 -0
- package/CHANGELOG.md +140 -0
- package/LICENSE +21 -21
- package/README.md +301 -527
- package/babel.config.json +5 -0
- package/dist/Database.cjs +3896 -0
- package/docs/API.md +1051 -0
- package/docs/EXAMPLES.md +701 -0
- package/docs/README.md +194 -0
- package/examples/iterate-usage-example.js +157 -0
- package/examples/simple-iterate-example.js +115 -0
- package/jest.config.js +24 -0
- package/package.json +63 -51
- package/scripts/README.md +47 -0
- package/scripts/clean-test-files.js +75 -0
- package/scripts/prepare.js +31 -0
- package/scripts/run-tests.js +80 -0
- package/src/Database.mjs +4130 -0
- package/src/FileHandler.mjs +1101 -0
- package/src/OperationQueue.mjs +279 -0
- package/src/SchemaManager.mjs +268 -0
- package/src/Serializer.mjs +511 -0
- package/src/managers/ConcurrencyManager.mjs +257 -0
- package/src/managers/IndexManager.mjs +1403 -0
- package/src/managers/QueryManager.mjs +1273 -0
- package/src/managers/StatisticsManager.mjs +262 -0
- package/src/managers/StreamingProcessor.mjs +429 -0
- package/src/managers/TermManager.mjs +278 -0
- package/test/$not-operator-with-and.test.js +282 -0
- package/test/README.md +8 -0
- package/test/close-init-cycle.test.js +256 -0
- package/test/critical-bugs-fixes.test.js +1069 -0
- package/test/index-persistence.test.js +306 -0
- package/test/index-serialization.test.js +314 -0
- package/test/indexed-query-mode.test.js +360 -0
- package/test/iterate-method.test.js +272 -0
- package/test/query-operators.test.js +238 -0
- package/test/regex-array-fields.test.js +129 -0
- package/test/score-method.test.js +238 -0
- package/test/setup.js +17 -0
- package/test/term-mapping-minimal.test.js +154 -0
- package/test/term-mapping-simple.test.js +257 -0
- package/test/term-mapping.test.js +514 -0
- package/test/writebuffer-flush-resilience.test.js +204 -0
- package/dist/FileHandler.js +0 -688
- package/dist/IndexManager.js +0 -353
- package/dist/IntegrityChecker.js +0 -364
- package/dist/JSONLDatabase.js +0 -1194
- package/dist/index.js +0 -617
- package/src/FileHandler.js +0 -674
- package/src/IndexManager.js +0 -363
- package/src/IntegrityChecker.js +0 -379
- package/src/JSONLDatabase.js +0 -1248
- package/src/index.js +0 -608
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConcurrencyManager - Handles all concurrency control and synchronization
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - _acquireMutexWithTimeout()
|
|
6
|
+
* - Mutex and fileMutex management
|
|
7
|
+
* - Concurrent operations control
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export class ConcurrencyManager {
|
|
11
|
+
constructor(database) {
|
|
12
|
+
this.database = database
|
|
13
|
+
this.opts = database.opts
|
|
14
|
+
this.mutex = database.mutex
|
|
15
|
+
this.fileMutex = database.fileMutex
|
|
16
|
+
this.operationQueue = database.operationQueue
|
|
17
|
+
this.pendingOperations = database.pendingOperations || 0
|
|
18
|
+
this.pendingPromises = database.pendingPromises || new Set()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Acquire mutex with timeout
|
|
23
|
+
* @param {Mutex} mutex - Mutex to acquire
|
|
24
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
25
|
+
* @returns {Promise<Function>} - Release function
|
|
26
|
+
*/
|
|
27
|
+
async _acquireMutexWithTimeout(mutex, timeout = null) {
|
|
28
|
+
const timeoutMs = timeout || this.opts.mutexTimeout
|
|
29
|
+
const startTime = Date.now()
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const release = await Promise.race([
|
|
33
|
+
mutex.acquire(),
|
|
34
|
+
new Promise((_, reject) =>
|
|
35
|
+
setTimeout(() => reject(new Error(`Mutex acquisition timeout after ${timeoutMs}ms`)), timeoutMs)
|
|
36
|
+
)
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
if (this.opts.debugMode) {
|
|
40
|
+
const acquireTime = Date.now() - startTime
|
|
41
|
+
if (acquireTime > 1000) {
|
|
42
|
+
console.warn(`⚠️ Slow mutex acquisition: ${acquireTime}ms`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Wrap release function to track mutex usage
|
|
47
|
+
const originalRelease = release
|
|
48
|
+
return () => {
|
|
49
|
+
try {
|
|
50
|
+
originalRelease()
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`❌ Error releasing mutex: ${error.message}`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (this.opts.debugMode) {
|
|
57
|
+
console.error(`❌ Mutex acquisition failed: ${error.message}`)
|
|
58
|
+
}
|
|
59
|
+
throw error
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Execute operation with queue management
|
|
66
|
+
* @param {Function} operation - Operation to execute
|
|
67
|
+
* @returns {Promise} - Operation result
|
|
68
|
+
*/
|
|
69
|
+
async executeWithQueue(operation) {
|
|
70
|
+
if (!this.operationQueue) {
|
|
71
|
+
return operation()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return this.operationQueue.enqueue(operation)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Wait for all pending operations to complete
|
|
79
|
+
* @returns {Promise<void>}
|
|
80
|
+
*/
|
|
81
|
+
async waitForPendingOperations() {
|
|
82
|
+
if (this.pendingOperations === 0) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const pendingPromisesArray = Array.from(this.pendingPromises)
|
|
87
|
+
|
|
88
|
+
if (pendingPromisesArray.length === 0) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await Promise.allSettled(pendingPromisesArray)
|
|
94
|
+
this.pendingPromises.clear()
|
|
95
|
+
this.pendingOperations = 0
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.warn('Error waiting for pending operations:', error)
|
|
98
|
+
this.pendingPromises.clear()
|
|
99
|
+
this.pendingOperations = 0
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get concurrency statistics
|
|
105
|
+
* @returns {Object} - Concurrency statistics
|
|
106
|
+
*/
|
|
107
|
+
getConcurrencyStats() {
|
|
108
|
+
return {
|
|
109
|
+
pendingOperations: this.pendingOperations,
|
|
110
|
+
pendingPromises: this.pendingPromises.size,
|
|
111
|
+
mutexTimeout: this.opts.mutexTimeout,
|
|
112
|
+
hasOperationQueue: !!this.operationQueue
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if system is under high concurrency load
|
|
118
|
+
* @returns {boolean} - True if under high load
|
|
119
|
+
*/
|
|
120
|
+
isUnderHighLoad() {
|
|
121
|
+
const maxOperations = this.opts.maxConcurrentOperations || 10
|
|
122
|
+
return this.pendingOperations >= maxOperations * 0.8 // 80% of max capacity
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get recommended timeout based on current load
|
|
127
|
+
* @returns {number} - Recommended timeout in milliseconds
|
|
128
|
+
*/
|
|
129
|
+
getRecommendedTimeout() {
|
|
130
|
+
const baseTimeout = this.opts.mutexTimeout || 15000 // Reduced from 30000 to 15000
|
|
131
|
+
const loadFactor = this.pendingOperations / 10 // Use fixed limit of 10
|
|
132
|
+
|
|
133
|
+
// Increase timeout based on load
|
|
134
|
+
return Math.min(baseTimeout * (1 + loadFactor), baseTimeout * 3)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Acquire multiple mutexes in order to prevent deadlocks
|
|
139
|
+
* @param {Array<Mutex>} mutexes - Mutexes to acquire in order
|
|
140
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
141
|
+
* @returns {Promise<Array<Function>>} - Array of release functions
|
|
142
|
+
*/
|
|
143
|
+
async acquireMultipleMutexes(mutexes, timeout = null) {
|
|
144
|
+
const releases = []
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
for (const mutex of mutexes) {
|
|
148
|
+
const release = await this._acquireMutexWithTimeout(mutex, timeout)
|
|
149
|
+
releases.push(release)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return releases
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// Release already acquired mutexes on error
|
|
155
|
+
for (const release of releases) {
|
|
156
|
+
try {
|
|
157
|
+
release()
|
|
158
|
+
} catch (releaseError) {
|
|
159
|
+
console.warn('Error releasing mutex:', releaseError)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throw error
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Execute operation with automatic mutex management
|
|
168
|
+
* @param {Function} operation - Operation to execute
|
|
169
|
+
* @param {Object} options - Options for execution
|
|
170
|
+
* @returns {Promise} - Operation result
|
|
171
|
+
*/
|
|
172
|
+
async executeWithMutex(operation, options = {}) {
|
|
173
|
+
const {
|
|
174
|
+
mutex = this.mutex,
|
|
175
|
+
timeout = null,
|
|
176
|
+
retries = 0,
|
|
177
|
+
retryDelay = 100
|
|
178
|
+
} = options
|
|
179
|
+
|
|
180
|
+
let lastError = null
|
|
181
|
+
|
|
182
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
183
|
+
try {
|
|
184
|
+
const release = await this._acquireMutexWithTimeout(mutex, timeout)
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const result = await operation()
|
|
188
|
+
return result
|
|
189
|
+
} finally {
|
|
190
|
+
release()
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
lastError = error
|
|
194
|
+
|
|
195
|
+
if (attempt < retries) {
|
|
196
|
+
// Wait before retry with exponential backoff
|
|
197
|
+
const delay = retryDelay * Math.pow(2, attempt)
|
|
198
|
+
await new Promise(resolve => setTimeout(resolve, delay))
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
throw lastError
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create a semaphore for limiting concurrent operations
|
|
208
|
+
* @param {number} limit - Maximum concurrent operations
|
|
209
|
+
* @returns {Object} - Semaphore object
|
|
210
|
+
*/
|
|
211
|
+
createSemaphore(limit) {
|
|
212
|
+
let current = 0
|
|
213
|
+
const queue = []
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
async acquire() {
|
|
217
|
+
return new Promise((resolve) => {
|
|
218
|
+
if (current < limit) {
|
|
219
|
+
current++
|
|
220
|
+
resolve()
|
|
221
|
+
} else {
|
|
222
|
+
queue.push(resolve)
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
release() {
|
|
228
|
+
if (queue.length > 0) {
|
|
229
|
+
const next = queue.shift()
|
|
230
|
+
next()
|
|
231
|
+
} else {
|
|
232
|
+
current--
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
getCurrent() {
|
|
237
|
+
return current
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
getQueueLength() {
|
|
241
|
+
return queue.length
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Cleanup concurrency resources
|
|
248
|
+
*/
|
|
249
|
+
cleanup() {
|
|
250
|
+
this.pendingPromises.clear()
|
|
251
|
+
this.pendingOperations = 0
|
|
252
|
+
|
|
253
|
+
if (this.opts.debugMode) {
|
|
254
|
+
console.log('🧹 Concurrency manager cleaned up')
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|