feat(init): 初始化项目结构和基础代码
添加了异步锁库的基础实现,包括内存锁和文件锁功能。 - 新增 `AsyncLock` 类用于进程内异步加锁 - 新增 `FileLock` 类用于跨进程的文件锁机制 - 添加单元测试覆盖核心逻辑 - 配置 Jest 测试环境并启用覆盖率收集 - 创建 README 文档说明安装、使用方法与 API 详情 - 添加 .gitignore 忽略构建产物及敏感文件 - 添加 MIT 许可证声明 该提交涵盖了项目的初始设置以及基本功能的完整实现。
This commit is contained in:
commit
d3637a21c5
|
|
@ -0,0 +1,17 @@
|
||||||
|
node_modules
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnp.*
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
</import>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 nlocks contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
# nlocks
|
||||||
|
|
||||||
|
A Node.js library providing asynchronous locking mechanisms including in-memory locks and file-based locks for coordinating access to resources across concurrent processes.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **AsyncLock**: In-memory asynchronous lock for coordinating access to resources within a single process
|
||||||
|
- **FileLock**: File-based lock for coordinating access to resources across multiple processes
|
||||||
|
- Lightweight and easy to use
|
||||||
|
- Promise-based API
|
||||||
|
- Comprehensive test coverage
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install nlocks
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Via index.js (recommended)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { AsyncLock, FileLock } = require('nlocks');
|
||||||
|
|
||||||
|
const asyncLock = new AsyncLock();
|
||||||
|
const fileLock = new FileLock();
|
||||||
|
```
|
||||||
|
|
||||||
|
### AsyncLock
|
||||||
|
|
||||||
|
An in-memory lock for synchronizing async operations within a single Node.js process:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const AsyncLock = require('nlocks/async-lock');
|
||||||
|
|
||||||
|
const lock = new AsyncLock();
|
||||||
|
|
||||||
|
async function protectedOperation() {
|
||||||
|
await lock.acquire();
|
||||||
|
try {
|
||||||
|
// Your critical section code here
|
||||||
|
console.log('Performing protected operation');
|
||||||
|
await someAsyncWork();
|
||||||
|
} finally {
|
||||||
|
lock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### FileLock
|
||||||
|
|
||||||
|
A file-based lock for synchronizing operations across multiple Node.js processes:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const FileLock = require('nlocks/file-lock');
|
||||||
|
|
||||||
|
const lock = new FileLock();
|
||||||
|
|
||||||
|
async function fileProtectedOperation() {
|
||||||
|
await lock.acquire('/path/to/your/file.txt');
|
||||||
|
try {
|
||||||
|
// Your file operation code here
|
||||||
|
console.log('Performing file operation');
|
||||||
|
await someFileAsyncWork();
|
||||||
|
} finally {
|
||||||
|
lock.release('/path/to/your/file.txt');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### AsyncLock
|
||||||
|
|
||||||
|
- `new AsyncLock()` - Creates a new AsyncLock instance
|
||||||
|
- `acquire(): Promise<void>` - Acquires the lock
|
||||||
|
- `release(): void` - Releases the lock
|
||||||
|
|
||||||
|
### FileLock
|
||||||
|
|
||||||
|
- `new FileLock()` - Creates a new FileLock instance
|
||||||
|
- `acquire(filePath): Promise<void>` - Acquires a lock for the specified file path
|
||||||
|
- `release(filePath): void` - Releases the lock for the specified file path
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run the test suite with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
const AsyncLock = require('../async-lock');
|
||||||
|
|
||||||
|
describe('AsyncLock', () => {
|
||||||
|
let lock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
lock = new AsyncLock();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('acquire and release', () => {
|
||||||
|
test('should acquire lock without waiting when not locked', async () => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
await lock.acquire();
|
||||||
|
const endTime = Date.now();
|
||||||
|
|
||||||
|
// Should resolve almost immediately (no waiting)
|
||||||
|
expect(endTime - startTime).toBeLessThan(10);
|
||||||
|
|
||||||
|
lock.release();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should protect async function without waiting when not locked', async () => {
|
||||||
|
let protectedFunctionCalled = false;
|
||||||
|
|
||||||
|
const protectedFunction = async () => {
|
||||||
|
await lock.acquire();
|
||||||
|
protectedFunctionCalled = true;
|
||||||
|
// Simulate some async work
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
lock.release();
|
||||||
|
return 'done';
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await protectedFunction();
|
||||||
|
|
||||||
|
expect(protectedFunctionCalled).toBe(true);
|
||||||
|
expect(result).toBe('done');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should allow consecutive acquisitions and releases without queueing', async () => {
|
||||||
|
// First acquisition
|
||||||
|
await lock.acquire();
|
||||||
|
expect(lock.locked).toBe(true);
|
||||||
|
lock.release();
|
||||||
|
|
||||||
|
// Second acquisition
|
||||||
|
await lock.acquire();
|
||||||
|
expect(lock.locked).toBe(true);
|
||||||
|
lock.release();
|
||||||
|
|
||||||
|
// Third acquisition
|
||||||
|
await lock.acquire();
|
||||||
|
expect(lock.locked).toBe(true);
|
||||||
|
lock.release();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle multiple concurrent acquisitions correctly', async () => {
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
const incrementCounter = async () => {
|
||||||
|
await lock.acquire();
|
||||||
|
const temp = counter;
|
||||||
|
// Simulate some async work that could cause race condition
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1));
|
||||||
|
counter = temp + 1;
|
||||||
|
lock.release();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run multiple concurrent operations
|
||||||
|
await Promise.all([
|
||||||
|
incrementCounter(),
|
||||||
|
incrementCounter(),
|
||||||
|
incrementCounter()
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(counter).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle case where release is called without any pending acquirers', () => {
|
||||||
|
// Initially unlocked
|
||||||
|
expect(lock.locked).toBe(false);
|
||||||
|
|
||||||
|
// Calling release on unlocked lock should not error
|
||||||
|
lock.release();
|
||||||
|
expect(lock.locked).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should properly queue multiple concurrent requests', async () => {
|
||||||
|
let executionOrder = [];
|
||||||
|
|
||||||
|
const task = async (id) => {
|
||||||
|
await lock.acquire();
|
||||||
|
executionOrder.push(id);
|
||||||
|
// Simulate work
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
lock.release();
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start multiple concurrent tasks
|
||||||
|
const tasks = [
|
||||||
|
task(1),
|
||||||
|
task(2),
|
||||||
|
task(3)
|
||||||
|
];
|
||||||
|
|
||||||
|
const r = await Promise.all(tasks);
|
||||||
|
|
||||||
|
// All tasks should execute in order
|
||||||
|
expect(executionOrder).toEqual([1, 2, 3]);
|
||||||
|
expect(r).toEqual([1, 2, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no await for async function',async()=>{
|
||||||
|
const results= []
|
||||||
|
async function task(prefix, count) {
|
||||||
|
await lock.acquire()
|
||||||
|
let i = 0
|
||||||
|
const h = setInterval(() => {
|
||||||
|
results.push(`${prefix}${i}`)
|
||||||
|
i++
|
||||||
|
if (i >= count) {
|
||||||
|
lock.release()
|
||||||
|
clearInterval(h)
|
||||||
|
}
|
||||||
|
},10)
|
||||||
|
}
|
||||||
|
|
||||||
|
task('a', 5)
|
||||||
|
task('b', 5)
|
||||||
|
task('c', 5)
|
||||||
|
await lock.acquire()
|
||||||
|
const r = results.join('')
|
||||||
|
expect(r.includes('a0a1a2a3a4')).toBe(true)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
const FileLock = require('../file-lock');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
describe('FileLock', () => {
|
||||||
|
let lock;
|
||||||
|
const testFilePath = path.join(__dirname, 'test-file.txt');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
lock = new FileLock();
|
||||||
|
// Clean up any leftover lock files
|
||||||
|
const lockFilePath = `${testFilePath}.lock`;
|
||||||
|
if (fs.existsSync(lockFilePath)) {
|
||||||
|
fs.unlinkSync(lockFilePath);
|
||||||
|
}
|
||||||
|
// Create test file
|
||||||
|
fs.writeFileSync(testFilePath, 'test content');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Clean up files
|
||||||
|
const lockFilePath = `${testFilePath}.lock`;
|
||||||
|
if (fs.existsSync(lockFilePath)) {
|
||||||
|
fs.unlinkSync(lockFilePath);
|
||||||
|
}
|
||||||
|
if (fs.existsSync(testFilePath)) {
|
||||||
|
fs.unlinkSync(testFilePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('acquire and release', () => {
|
||||||
|
test('should acquire lock without waiting when not locked', async () => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
await lock.acquire(testFilePath);
|
||||||
|
const endTime = Date.now();
|
||||||
|
|
||||||
|
// Should resolve almost immediately (no waiting)
|
||||||
|
expect(endTime - startTime).toBeLessThan(100);
|
||||||
|
|
||||||
|
lock.release(testFilePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should protect file access without waiting when not locked', async () => {
|
||||||
|
let protectedFunctionCalled = false;
|
||||||
|
|
||||||
|
const protectedFunction = async () => {
|
||||||
|
await lock.acquire(testFilePath);
|
||||||
|
protectedFunctionCalled = true;
|
||||||
|
// Simulate some async work
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
lock.release(testFilePath);
|
||||||
|
return 'done';
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await protectedFunction();
|
||||||
|
|
||||||
|
expect(protectedFunctionCalled).toBe(true);
|
||||||
|
expect(result).toBe('done');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should allow consecutive acquisitions and releases without queueing', async () => {
|
||||||
|
// First acquisition
|
||||||
|
await lock.acquire(testFilePath);
|
||||||
|
lock.release(testFilePath);
|
||||||
|
|
||||||
|
// Second acquisition
|
||||||
|
await lock.acquire(testFilePath);
|
||||||
|
lock.release(testFilePath);
|
||||||
|
|
||||||
|
// Third acquisition
|
||||||
|
await lock.acquire(testFilePath);
|
||||||
|
lock.release(testFilePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle multiple concurrent acquisitions correctly', async () => {
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
const incrementCounter = async () => {
|
||||||
|
await lock.acquire(testFilePath);
|
||||||
|
const temp = counter;
|
||||||
|
// Simulate some async work that could cause race condition
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1));
|
||||||
|
counter = temp + 1;
|
||||||
|
lock.release(testFilePath);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run multiple concurrent operations
|
||||||
|
await Promise.all([
|
||||||
|
incrementCounter(),
|
||||||
|
incrementCounter(),
|
||||||
|
incrementCounter()
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(counter).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle case where release is called without any pending acquirers', () => {
|
||||||
|
// Calling release on unlocked lock should not error
|
||||||
|
expect(() => lock.release(testFilePath)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should properly queue multiple concurrent requests', async () => {
|
||||||
|
let executionOrder = [];
|
||||||
|
|
||||||
|
const task = async (id) => {
|
||||||
|
await lock.acquire(testFilePath);
|
||||||
|
executionOrder.push(id);
|
||||||
|
// Simulate work
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
lock.release(testFilePath);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start multiple concurrent tasks
|
||||||
|
const tasks = [
|
||||||
|
task(1),
|
||||||
|
task(2),
|
||||||
|
task(3)
|
||||||
|
];
|
||||||
|
|
||||||
|
await Promise.all(tasks);
|
||||||
|
|
||||||
|
// All tasks should execute in order
|
||||||
|
expect(executionOrder).toEqual([1, 2, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create lock file when acquiring lock', async () => {
|
||||||
|
await lock.acquire(testFilePath);
|
||||||
|
const lockFilePath = `${testFilePath}.lock`;
|
||||||
|
|
||||||
|
expect(fs.existsSync(lockFilePath)).toBe(true);
|
||||||
|
expect(fs.readFileSync(lockFilePath, 'utf8')).toBe(process.pid.toString());
|
||||||
|
|
||||||
|
lock.release(testFilePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should remove lock file when releasing lock', async () => {
|
||||||
|
await lock.acquire(testFilePath);
|
||||||
|
const lockFilePath = `${testFilePath}.lock`;
|
||||||
|
|
||||||
|
expect(fs.existsSync(lockFilePath)).toBe(true);
|
||||||
|
|
||||||
|
lock.release(testFilePath);
|
||||||
|
expect(fs.existsSync(lockFilePath)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
class AsyncLock {
|
||||||
|
constructor() {
|
||||||
|
this.queue = []
|
||||||
|
this.locked = false
|
||||||
|
}
|
||||||
|
|
||||||
|
acquire(){
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!this.locked) {
|
||||||
|
// No contention, acquire lock immediately
|
||||||
|
this.locked = true
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
// Add to queue when lock is busy
|
||||||
|
this.queue.push(resolve)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
release(){
|
||||||
|
if (this.queue.length > 0) {
|
||||||
|
// Resolve the next queued promise
|
||||||
|
const nextResolve = this.queue.shift()
|
||||||
|
nextResolve()
|
||||||
|
// Keep locked status as true since we're passing the lock to the next waiter
|
||||||
|
} else {
|
||||||
|
// No waiters, unlock
|
||||||
|
this.locked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AsyncLock
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
class FileLock {
|
||||||
|
constructor() {
|
||||||
|
this.lockedFiles = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquire a file-based lock
|
||||||
|
* @param {string} filePath - The path of the file to lock
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
acquire(filePath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const lockFilePath = this._getLockFilePath(filePath)
|
||||||
|
|
||||||
|
// Try to acquire lock immediately if not locked
|
||||||
|
if (!this.lockedFiles.has(filePath)) {
|
||||||
|
this._createLockFile(lockFilePath, filePath, resolve, reject)
|
||||||
|
} else {
|
||||||
|
// Add to queue if already locked
|
||||||
|
const queue = this.lockedFiles.get(filePath).queue
|
||||||
|
queue.push({ resolve, reject })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release a file-based lock
|
||||||
|
* @param {string} filePath - The path of the file to unlock
|
||||||
|
*/
|
||||||
|
release(filePath) {
|
||||||
|
if (!this.lockedFiles.has(filePath)) {
|
||||||
|
// Releasing an unlocked file is a no-op
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const lockInfo = this.lockedFiles.get(filePath)
|
||||||
|
const lockFilePath = this._getLockFilePath(filePath)
|
||||||
|
|
||||||
|
// Remove lock file
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(lockFilePath)) {
|
||||||
|
fs.unlinkSync(lockFilePath)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Ignore errors during unlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are queued requests
|
||||||
|
if (lockInfo.queue.length > 0) {
|
||||||
|
// Process the next queued request
|
||||||
|
const nextRequest = lockInfo.queue.shift()
|
||||||
|
this._createLockFile(lockFilePath, filePath, nextRequest.resolve, nextRequest.reject)
|
||||||
|
} else {
|
||||||
|
// No more queued requests, remove from locked files
|
||||||
|
this.lockedFiles.delete(filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a lock file
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_createLockFile(lockFilePath, filePath, resolve, reject) {
|
||||||
|
try {
|
||||||
|
// Use wx flag to atomically create the lock file
|
||||||
|
// If the file already exists, this will throw an error
|
||||||
|
fs.writeFileSync(lockFilePath, process.pid.toString(), { flag: 'wx' })
|
||||||
|
|
||||||
|
// Initialize queue for this file if not exists
|
||||||
|
if (!this.lockedFiles.has(filePath)) {
|
||||||
|
this.lockedFiles.set(filePath, {
|
||||||
|
lockFilePath: lockFilePath,
|
||||||
|
queue: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve()
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'EEXIST') {
|
||||||
|
// Lock file already exists, add to queue
|
||||||
|
if (!this.lockedFiles.has(filePath)) {
|
||||||
|
this.lockedFiles.set(filePath, {
|
||||||
|
lockFilePath: lockFilePath,
|
||||||
|
queue: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.lockedFiles.get(filePath).queue.push({ resolve, reject })
|
||||||
|
} else {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get lock file path from original file path
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getLockFilePath(filePath) {
|
||||||
|
const resolvedPath = path.resolve(filePath)
|
||||||
|
return `${resolvedPath}.lock`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FileLock
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const AsyncLock = require('./async-lock')
|
||||||
|
const FileLock = require('./file-lock')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
AsyncLock,
|
||||||
|
FileLock
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
/**
|
||||||
|
* For a detailed explanation regarding each configuration property, visit:
|
||||||
|
* https://jestjs.io/docs/configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {import('jest').Config} */
|
||||||
|
const config = {
|
||||||
|
// All imported modules in your tests should be mocked automatically
|
||||||
|
// automock: false,
|
||||||
|
|
||||||
|
// Stop running tests after `n` failures
|
||||||
|
// bail: 0,
|
||||||
|
|
||||||
|
// The directory where Jest should store its cached dependency information
|
||||||
|
// cacheDirectory: "/tmp/jest_rs",
|
||||||
|
|
||||||
|
// Automatically clear mock calls, instances, contexts and results before every test
|
||||||
|
clearMocks: true,
|
||||||
|
|
||||||
|
// Indicates whether the coverage information should be collected while executing the test
|
||||||
|
collectCoverage: true,
|
||||||
|
|
||||||
|
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||||
|
// collectCoverageFrom: undefined,
|
||||||
|
|
||||||
|
// The directory where Jest should output its coverage files
|
||||||
|
coverageDirectory: "coverage",
|
||||||
|
|
||||||
|
// An array of regexp pattern strings used to skip coverage collection
|
||||||
|
// coveragePathIgnorePatterns: [
|
||||||
|
// "/node_modules/"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// Indicates which provider should be used to instrument code for coverage
|
||||||
|
coverageProvider: "v8",
|
||||||
|
|
||||||
|
// A list of reporter names that Jest uses when writing coverage reports
|
||||||
|
// coverageReporters: [
|
||||||
|
// "json",
|
||||||
|
// "text",
|
||||||
|
// "lcov",
|
||||||
|
// "clover"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An object that configures minimum threshold enforcement for coverage results
|
||||||
|
// coverageThreshold: undefined,
|
||||||
|
|
||||||
|
// A path to a custom dependency extractor
|
||||||
|
// dependencyExtractor: undefined,
|
||||||
|
|
||||||
|
// Make calling deprecated APIs throw helpful error messages
|
||||||
|
// errorOnDeprecated: false,
|
||||||
|
|
||||||
|
// The default configuration for fake timers
|
||||||
|
// fakeTimers: {
|
||||||
|
// "enableGlobally": false
|
||||||
|
// },
|
||||||
|
|
||||||
|
// Force coverage collection from ignored files using an array of glob patterns
|
||||||
|
// forceCoverageMatch: [],
|
||||||
|
|
||||||
|
// A path to a module which exports an async function that is triggered once before all test suites
|
||||||
|
// globalSetup: undefined,
|
||||||
|
|
||||||
|
// A path to a module which exports an async function that is triggered once after all test suites
|
||||||
|
// globalTeardown: undefined,
|
||||||
|
|
||||||
|
// A set of global variables that need to be available in all test environments
|
||||||
|
// globals: {},
|
||||||
|
|
||||||
|
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||||
|
// maxWorkers: "50%",
|
||||||
|
|
||||||
|
// An array of directory names to be searched recursively up from the requiring module's location
|
||||||
|
// moduleDirectories: [
|
||||||
|
// "node_modules"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of file extensions your modules use
|
||||||
|
// moduleFileExtensions: [
|
||||||
|
// "js",
|
||||||
|
// "mjs",
|
||||||
|
// "cjs",
|
||||||
|
// "jsx",
|
||||||
|
// "ts",
|
||||||
|
// "mts",
|
||||||
|
// "cts",
|
||||||
|
// "tsx",
|
||||||
|
// "json",
|
||||||
|
// "node"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||||
|
// moduleNameMapper: {},
|
||||||
|
|
||||||
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||||
|
// modulePathIgnorePatterns: [],
|
||||||
|
|
||||||
|
// Activates notifications for test results
|
||||||
|
// notify: false,
|
||||||
|
|
||||||
|
// An enum that specifies notification mode. Requires { notify: true }
|
||||||
|
// notifyMode: "failure-change",
|
||||||
|
|
||||||
|
// A preset that is used as a base for Jest's configuration
|
||||||
|
// preset: undefined,
|
||||||
|
|
||||||
|
// Run tests from one or more projects
|
||||||
|
// projects: undefined,
|
||||||
|
|
||||||
|
// Use this configuration option to add custom reporters to Jest
|
||||||
|
// reporters: undefined,
|
||||||
|
|
||||||
|
// Automatically reset mock state before every test
|
||||||
|
// resetMocks: false,
|
||||||
|
|
||||||
|
// Reset the module registry before running each individual test
|
||||||
|
// resetModules: false,
|
||||||
|
|
||||||
|
// A path to a custom resolver
|
||||||
|
// resolver: undefined,
|
||||||
|
|
||||||
|
// Automatically restore mock state and implementation before every test
|
||||||
|
// restoreMocks: false,
|
||||||
|
|
||||||
|
// The root directory that Jest should scan for tests and modules within
|
||||||
|
// rootDir: undefined,
|
||||||
|
|
||||||
|
// A list of paths to directories that Jest should use to search for files in
|
||||||
|
// roots: [
|
||||||
|
// "<rootDir>"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// Allows you to use a custom runner instead of Jest's default test runner
|
||||||
|
// runner: "jest-runner",
|
||||||
|
|
||||||
|
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||||
|
// setupFiles: [],
|
||||||
|
|
||||||
|
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||||
|
// setupFilesAfterEnv: [],
|
||||||
|
|
||||||
|
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||||
|
// slowTestThreshold: 5,
|
||||||
|
|
||||||
|
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||||
|
// snapshotSerializers: [],
|
||||||
|
|
||||||
|
// The test environment that will be used for testing
|
||||||
|
// testEnvironment: "jest-environment-node",
|
||||||
|
|
||||||
|
// Options that will be passed to the testEnvironment
|
||||||
|
// testEnvironmentOptions: {},
|
||||||
|
|
||||||
|
// Adds a location field to test results
|
||||||
|
// testLocationInResults: false,
|
||||||
|
|
||||||
|
// The glob patterns Jest uses to detect test files
|
||||||
|
// testMatch: [
|
||||||
|
// "**/__tests__/**/*.?([mc])[jt]s?(x)",
|
||||||
|
// "**/?(*.)+(spec|test).?([mc])[jt]s?(x)"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||||
|
// testPathIgnorePatterns: [
|
||||||
|
// "/node_modules/"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||||
|
// testRegex: [],
|
||||||
|
|
||||||
|
// This option allows the use of a custom results processor
|
||||||
|
// testResultsProcessor: undefined,
|
||||||
|
|
||||||
|
// This option allows use of a custom test runner
|
||||||
|
// testRunner: "jest-circus/runner",
|
||||||
|
|
||||||
|
// A map from regular expressions to paths to transformers
|
||||||
|
// transform: undefined,
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||||
|
// transformIgnorePatterns: [
|
||||||
|
// "/node_modules/",
|
||||||
|
// "\\.pnp\\.[^\\/]+$"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||||
|
// unmockedModulePathPatterns: undefined,
|
||||||
|
|
||||||
|
// Indicates whether each individual test should be reported during the run
|
||||||
|
// verbose: undefined,
|
||||||
|
|
||||||
|
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||||
|
// watchPathIgnorePatterns: [],
|
||||||
|
|
||||||
|
// Whether to use watchman for file crawling
|
||||||
|
// watchman: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "nlocks",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A Node.js library providing asynchronous locking mechanisms including in-memory locks and file-based locks",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"keywords": ["lock", "async", "file", "mutex", "synchronization", "concurrency"],
|
||||||
|
"author": "nlocks contributors",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"jest": "^30.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue