jexidb 2.1.7 → 2.1.8

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/dist/Database.cjs CHANGED
@@ -4126,6 +4126,37 @@ class FileHandler {
4126
4126
  // Global I/O limiter to prevent file descriptor exhaustion in concurrent operations
4127
4127
  this.readLimiter = pLimit(opts.maxConcurrentReads || 4);
4128
4128
  }
4129
+ _getIoTimeoutMs(override) {
4130
+ if (typeof override === 'number') return override;
4131
+ if (typeof this.opts.ioTimeoutMs === 'number') return this.opts.ioTimeoutMs;
4132
+ return 0;
4133
+ }
4134
+ async _withIoTimeout(fn, timeoutMs, onTimeout) {
4135
+ if (!timeoutMs || timeoutMs <= 0) {
4136
+ return fn();
4137
+ }
4138
+ let timeoutId;
4139
+ const timeoutPromise = new Promise((_, reject) => {
4140
+ timeoutId = setTimeout(() => {
4141
+ if (onTimeout) {
4142
+ try {
4143
+ onTimeout();
4144
+ } catch {}
4145
+ }
4146
+ const err = new Error(`I/O timeout after ${timeoutMs}ms`);
4147
+ err.code = 'ETIMEDOUT';
4148
+ reject(err);
4149
+ }, timeoutMs);
4150
+ });
4151
+ try {
4152
+ return await Promise.race([fn(), timeoutPromise]);
4153
+ } finally {
4154
+ if (timeoutId) clearTimeout(timeoutId);
4155
+ }
4156
+ }
4157
+ async _readWithTimeout(fd, buffer, offset, length, position, timeoutMs) {
4158
+ return this._withIoTimeout(() => fd.read(buffer, offset, length, position), timeoutMs, () => fd.close().catch(() => {}));
4159
+ }
4129
4160
  async truncate(offset) {
4130
4161
  try {
4131
4162
  await fs.promises.access(this.file, fs.constants.F_OK);
@@ -4230,6 +4261,7 @@ class FileHandler {
4230
4261
  if (!(await this.exists())) {
4231
4262
  return Buffer.alloc(0); // Return empty buffer if file doesn't exist
4232
4263
  }
4264
+ const timeoutMs = this._getIoTimeoutMs();
4233
4265
  let fd = await fs.promises.open(this.file, 'r');
4234
4266
  try {
4235
4267
  // CRITICAL FIX: Check file size before attempting to read
@@ -4254,7 +4286,7 @@ class FileHandler {
4254
4286
  let buffer = Buffer.alloc(length);
4255
4287
  const {
4256
4288
  bytesRead
4257
- } = await fd.read(buffer, 0, length, start);
4289
+ } = await this._readWithTimeout(fd, buffer, 0, length, start, timeoutMs);
4258
4290
  await fd.close();
4259
4291
 
4260
4292
  // CRITICAL FIX: Ensure we read the expected amount of data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jexidb",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "type": "module",
5
5
  "description": "JexiDB is a pure JS NPM library for managing data on disk efficiently, without the need for a server.",
6
6
  "main": "./dist/Database.cjs",
@@ -15,6 +15,42 @@ export default class FileHandler {
15
15
  this.readLimiter = pLimit(opts.maxConcurrentReads || 4)
16
16
  }
17
17
 
18
+ _getIoTimeoutMs(override) {
19
+ if (typeof override === 'number') return override
20
+ if (typeof this.opts.ioTimeoutMs === 'number') return this.opts.ioTimeoutMs
21
+ return 0
22
+ }
23
+
24
+ async _withIoTimeout(fn, timeoutMs, onTimeout) {
25
+ if (!timeoutMs || timeoutMs <= 0) {
26
+ return fn()
27
+ }
28
+ let timeoutId
29
+ const timeoutPromise = new Promise((_, reject) => {
30
+ timeoutId = setTimeout(() => {
31
+ if (onTimeout) {
32
+ try { onTimeout() } catch {}
33
+ }
34
+ const err = new Error(`I/O timeout after ${timeoutMs}ms`)
35
+ err.code = 'ETIMEDOUT'
36
+ reject(err)
37
+ }, timeoutMs)
38
+ })
39
+ try {
40
+ return await Promise.race([fn(), timeoutPromise])
41
+ } finally {
42
+ if (timeoutId) clearTimeout(timeoutId)
43
+ }
44
+ }
45
+
46
+ async _readWithTimeout(fd, buffer, offset, length, position, timeoutMs) {
47
+ return this._withIoTimeout(
48
+ () => fd.read(buffer, offset, length, position),
49
+ timeoutMs,
50
+ () => fd.close().catch(() => {})
51
+ )
52
+ }
53
+
18
54
  async truncate(offset) {
19
55
  try {
20
56
  await fs.promises.access(this.file, fs.constants.F_OK)
@@ -128,6 +164,7 @@ export default class FileHandler {
128
164
  return Buffer.alloc(0) // Return empty buffer if file doesn't exist
129
165
  }
130
166
 
167
+ const timeoutMs = this._getIoTimeoutMs()
131
168
  let fd = await fs.promises.open(this.file, 'r')
132
169
  try {
133
170
  // CRITICAL FIX: Check file size before attempting to read
@@ -151,7 +188,7 @@ export default class FileHandler {
151
188
  }
152
189
 
153
190
  let buffer = Buffer.alloc(length)
154
- const { bytesRead } = await fd.read(buffer, 0, length, start)
191
+ const { bytesRead } = await this._readWithTimeout(fd, buffer, 0, length, start, timeoutMs)
155
192
  await fd.close()
156
193
 
157
194
  // CRITICAL FIX: Ensure we read the expected amount of data