atomically
Advanced tools
Comparing version 1.7.0 to 2.0.0
/// <reference types="node" /> | ||
import { Callback, Data, Path, ReadOptions, WriteOptions } from './types'; | ||
declare function readFile(filePath: Path, options: string | ReadOptions & { | ||
import type { Callback, Data, Encoding, Path, ReadOptions, WriteOptions } from './types'; | ||
declare function readFile(filePath: Path, options: Encoding | ReadOptions & { | ||
encoding: string; | ||
}): Promise<string>; | ||
declare function readFile(filePath: Path, options?: ReadOptions): Promise<Buffer>; | ||
declare function readFileSync(filePath: Path, options: string | ReadOptions & { | ||
declare function readFileSync(filePath: Path, options: Encoding | ReadOptions & { | ||
encoding: string; | ||
}): string; | ||
declare function readFileSync(filePath: Path, options?: ReadOptions): Buffer; | ||
declare const writeFile: (filePath: Path, data: Data, options?: string | WriteOptions | Callback | undefined, callback?: Callback | undefined) => Promise<void>; | ||
declare const writeFileSync: (filePath: Path, data: Data, options?: string | WriteOptions) => void; | ||
declare function writeFile(filePath: Path, data: Data, callback?: Callback): Promise<void>; | ||
declare function writeFile(filePath: Path, data: Data, options?: Encoding | WriteOptions, callback?: Callback): Promise<void>; | ||
declare const writeFileSync: (filePath: Path, data: Data, options?: Encoding | WriteOptions) => void; | ||
export { readFile, readFileSync, writeFile, writeFileSync }; |
@@ -1,32 +0,23 @@ | ||
"use strict"; | ||
/* IMPORT */ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.writeFileSync = exports.writeFile = exports.readFileSync = exports.readFile = void 0; | ||
const path = require("path"); | ||
const consts_1 = require("./consts"); | ||
const fs_1 = require("./utils/fs"); | ||
const lang_1 = require("./utils/lang"); | ||
const scheduler_1 = require("./utils/scheduler"); | ||
const temp_1 = require("./utils/temp"); | ||
function readFile(filePath, options = consts_1.DEFAULT_READ_OPTIONS) { | ||
var _a; | ||
if (lang_1.default.isString(options)) | ||
import path from 'node:path'; | ||
import fs from 'stubborn-fs'; | ||
import { DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_USER_UID, DEFAULT_USER_GID, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX } from './constants.js'; | ||
import { isException, isFunction, isString, isUndefined } from './utils/lang.js'; | ||
import Scheduler from './utils/scheduler.js'; | ||
import Temp from './utils/temp.js'; | ||
function readFile(filePath, options = DEFAULT_READ_OPTIONS) { | ||
if (isString(options)) | ||
return readFile(filePath, { encoding: options }); | ||
const timeout = Date.now() + ((_a = options.timeout) !== null && _a !== void 0 ? _a : consts_1.DEFAULT_TIMEOUT_ASYNC); | ||
return fs_1.default.readFileRetry(timeout)(filePath, options); | ||
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_ASYNC) || -1); | ||
return fs.retry.readFile(timeout)(filePath, options); | ||
} | ||
exports.readFile = readFile; | ||
; | ||
function readFileSync(filePath, options = consts_1.DEFAULT_READ_OPTIONS) { | ||
var _a; | ||
if (lang_1.default.isString(options)) | ||
function readFileSync(filePath, options = DEFAULT_READ_OPTIONS) { | ||
if (isString(options)) | ||
return readFileSync(filePath, { encoding: options }); | ||
const timeout = Date.now() + ((_a = options.timeout) !== null && _a !== void 0 ? _a : consts_1.DEFAULT_TIMEOUT_SYNC); | ||
return fs_1.default.readFileSyncRetry(timeout)(filePath, options); | ||
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_SYNC) || -1); | ||
return fs.retry.readFileSync(timeout)(filePath, options); | ||
} | ||
exports.readFileSync = readFileSync; | ||
; | ||
const writeFile = (filePath, data, options, callback) => { | ||
if (lang_1.default.isFunction(options)) | ||
return writeFile(filePath, data, consts_1.DEFAULT_WRITE_OPTIONS, options); | ||
function writeFile(filePath, data, options, callback) { | ||
if (isFunction(options)) | ||
return writeFile(filePath, data, DEFAULT_WRITE_OPTIONS, options); | ||
const promise = writeFileAsync(filePath, data, options); | ||
@@ -36,62 +27,76 @@ if (callback) | ||
return promise; | ||
}; | ||
exports.writeFile = writeFile; | ||
const writeFileAsync = async (filePath, data, options = consts_1.DEFAULT_WRITE_OPTIONS) => { | ||
var _a; | ||
if (lang_1.default.isString(options)) | ||
} | ||
async function writeFileAsync(filePath, data, options = DEFAULT_WRITE_OPTIONS) { | ||
if (isString(options)) | ||
return writeFileAsync(filePath, data, { encoding: options }); | ||
const timeout = Date.now() + ((_a = options.timeout) !== null && _a !== void 0 ? _a : consts_1.DEFAULT_TIMEOUT_ASYNC); | ||
let schedulerCustomDisposer = null, schedulerDisposer = null, tempDisposer = null, tempPath = null, fd = null; | ||
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_ASYNC) || -1); | ||
let schedulerCustomDisposer = null; | ||
let schedulerDisposer = null; | ||
let tempDisposer = null; | ||
let tempPath = null; | ||
let fd = null; | ||
try { | ||
if (options.schedule) | ||
schedulerCustomDisposer = await options.schedule(filePath); | ||
schedulerDisposer = await scheduler_1.default.schedule(filePath); | ||
filePath = await fs_1.default.realpathAttempt(filePath) || filePath; | ||
[tempPath, tempDisposer] = temp_1.default.get(filePath, options.tmpCreate || temp_1.default.create, !(options.tmpPurge === false)); | ||
const useStatChown = consts_1.IS_POSIX && lang_1.default.isUndefined(options.chown), useStatMode = lang_1.default.isUndefined(options.mode); | ||
if (useStatChown || useStatMode) { | ||
const stat = await fs_1.default.statAttempt(filePath); | ||
if (stat) { | ||
schedulerDisposer = await Scheduler.schedule(filePath); | ||
const filePathReal = await fs.attempt.realpath(filePath); | ||
const filePathExists = !!filePathReal; | ||
filePath = filePathReal || filePath; | ||
[tempPath, tempDisposer] = Temp.get(filePath, options.tmpCreate || Temp.create, !(options.tmpPurge === false)); | ||
const useStatChown = IS_POSIX && isUndefined(options.chown); | ||
const useStatMode = isUndefined(options.mode); | ||
if (filePathExists && (useStatChown || useStatMode)) { | ||
const stats = await fs.attempt.stat(filePath); | ||
if (stats) { | ||
options = { ...options }; | ||
if (useStatChown) | ||
options.chown = { uid: stat.uid, gid: stat.gid }; | ||
if (useStatMode) | ||
options.mode = stat.mode; | ||
if (useStatChown) { | ||
options.chown = { uid: stats.uid, gid: stats.gid }; | ||
} | ||
if (useStatMode) { | ||
options.mode = stats.mode; | ||
} | ||
} | ||
} | ||
const parentPath = path.dirname(filePath); | ||
await fs_1.default.mkdirAttempt(parentPath, { | ||
mode: consts_1.DEFAULT_FOLDER_MODE, | ||
recursive: true | ||
}); | ||
fd = await fs_1.default.openRetry(timeout)(tempPath, 'w', options.mode || consts_1.DEFAULT_FILE_MODE); | ||
if (options.tmpCreated) | ||
if (!filePathExists) { | ||
const parentPath = path.dirname(filePath); | ||
await fs.attempt.mkdir(parentPath, { | ||
mode: DEFAULT_FOLDER_MODE, | ||
recursive: true | ||
}); | ||
} | ||
fd = await fs.retry.open(timeout)(tempPath, 'w', options.mode || DEFAULT_FILE_MODE); | ||
if (options.tmpCreated) { | ||
options.tmpCreated(tempPath); | ||
if (lang_1.default.isString(data)) { | ||
await fs_1.default.writeRetry(timeout)(fd, data, 0, options.encoding || consts_1.DEFAULT_ENCODING); | ||
} | ||
else if (!lang_1.default.isUndefined(data)) { | ||
await fs_1.default.writeRetry(timeout)(fd, data, 0, data.length, 0); | ||
if (isString(data)) { | ||
await fs.retry.write(timeout)(fd, data, 0, options.encoding || DEFAULT_ENCODING); | ||
} | ||
else if (!isUndefined(data)) { | ||
await fs.retry.write(timeout)(fd, data, 0, data.length, 0); | ||
} | ||
if (options.fsync !== false) { | ||
if (options.fsyncWait !== false) { | ||
await fs_1.default.fsyncRetry(timeout)(fd); | ||
await fs.retry.fsync(timeout)(fd); | ||
} | ||
else { | ||
fs_1.default.fsyncAttempt(fd); | ||
fs.attempt.fsync(fd); | ||
} | ||
} | ||
await fs_1.default.closeRetry(timeout)(fd); | ||
await fs.retry.close(timeout)(fd); | ||
fd = null; | ||
if (options.chown) | ||
await fs_1.default.chownAttempt(tempPath, options.chown.uid, options.chown.gid); | ||
if (options.mode) | ||
await fs_1.default.chmodAttempt(tempPath, options.mode); | ||
if (options.chown && (options.chown.uid !== DEFAULT_USER_UID || options.chown.gid !== DEFAULT_USER_GID)) { | ||
await fs.attempt.chown(tempPath, options.chown.uid, options.chown.gid); | ||
} | ||
if (options.mode && options.mode !== DEFAULT_FILE_MODE) { | ||
await fs.attempt.chmod(tempPath, options.mode); | ||
} | ||
try { | ||
await fs_1.default.renameRetry(timeout)(tempPath, filePath); | ||
await fs.retry.rename(timeout)(tempPath, filePath); | ||
} | ||
catch (error) { | ||
if (!isException(error)) | ||
throw error; | ||
if (error.code !== 'ENAMETOOLONG') | ||
throw error; | ||
await fs_1.default.renameRetry(timeout)(tempPath, temp_1.default.truncate(filePath)); | ||
await fs.retry.rename(timeout)(tempPath, Temp.truncate(filePath)); | ||
} | ||
@@ -103,5 +108,5 @@ tempDisposer(); | ||
if (fd) | ||
await fs_1.default.closeAttempt(fd); | ||
await fs.attempt.close(fd); | ||
if (tempPath) | ||
temp_1.default.purge(tempPath); | ||
Temp.purge(tempPath); | ||
if (schedulerCustomDisposer) | ||
@@ -112,58 +117,71 @@ schedulerCustomDisposer(); | ||
} | ||
}; | ||
const writeFileSync = (filePath, data, options = consts_1.DEFAULT_WRITE_OPTIONS) => { | ||
var _a; | ||
if (lang_1.default.isString(options)) | ||
} | ||
const writeFileSync = (filePath, data, options = DEFAULT_WRITE_OPTIONS) => { | ||
if (isString(options)) | ||
return writeFileSync(filePath, data, { encoding: options }); | ||
const timeout = Date.now() + ((_a = options.timeout) !== null && _a !== void 0 ? _a : consts_1.DEFAULT_TIMEOUT_SYNC); | ||
let tempDisposer = null, tempPath = null, fd = null; | ||
const timeout = Date.now() + ((options.timeout ?? DEFAULT_TIMEOUT_SYNC) || -1); | ||
let tempDisposer = null; | ||
let tempPath = null; | ||
let fd = null; | ||
try { | ||
filePath = fs_1.default.realpathSyncAttempt(filePath) || filePath; | ||
[tempPath, tempDisposer] = temp_1.default.get(filePath, options.tmpCreate || temp_1.default.create, !(options.tmpPurge === false)); | ||
const useStatChown = consts_1.IS_POSIX && lang_1.default.isUndefined(options.chown), useStatMode = lang_1.default.isUndefined(options.mode); | ||
if (useStatChown || useStatMode) { | ||
const stat = fs_1.default.statSyncAttempt(filePath); | ||
if (stat) { | ||
const filePathReal = fs.attempt.realpathSync(filePath); | ||
const filePathExists = !!filePathReal; | ||
filePath = filePathReal || filePath; | ||
[tempPath, tempDisposer] = Temp.get(filePath, options.tmpCreate || Temp.create, !(options.tmpPurge === false)); | ||
const useStatChown = IS_POSIX && isUndefined(options.chown); | ||
const useStatMode = isUndefined(options.mode); | ||
if (filePathExists && (useStatChown || useStatMode)) { | ||
const stats = fs.attempt.statSync(filePath); | ||
if (stats) { | ||
options = { ...options }; | ||
if (useStatChown) | ||
options.chown = { uid: stat.uid, gid: stat.gid }; | ||
if (useStatMode) | ||
options.mode = stat.mode; | ||
if (useStatChown) { | ||
options.chown = { uid: stats.uid, gid: stats.gid }; | ||
} | ||
if (useStatMode) { | ||
options.mode = stats.mode; | ||
} | ||
} | ||
} | ||
const parentPath = path.dirname(filePath); | ||
fs_1.default.mkdirSyncAttempt(parentPath, { | ||
mode: consts_1.DEFAULT_FOLDER_MODE, | ||
recursive: true | ||
}); | ||
fd = fs_1.default.openSyncRetry(timeout)(tempPath, 'w', options.mode || consts_1.DEFAULT_FILE_MODE); | ||
if (options.tmpCreated) | ||
if (!filePathExists) { | ||
const parentPath = path.dirname(filePath); | ||
fs.attempt.mkdirSync(parentPath, { | ||
mode: DEFAULT_FOLDER_MODE, | ||
recursive: true | ||
}); | ||
} | ||
fd = fs.retry.openSync(timeout)(tempPath, 'w', options.mode || DEFAULT_FILE_MODE); | ||
if (options.tmpCreated) { | ||
options.tmpCreated(tempPath); | ||
if (lang_1.default.isString(data)) { | ||
fs_1.default.writeSyncRetry(timeout)(fd, data, 0, options.encoding || consts_1.DEFAULT_ENCODING); | ||
} | ||
else if (!lang_1.default.isUndefined(data)) { | ||
fs_1.default.writeSyncRetry(timeout)(fd, data, 0, data.length, 0); | ||
if (isString(data)) { | ||
fs.retry.writeSync(timeout)(fd, data, 0, options.encoding || DEFAULT_ENCODING); | ||
} | ||
else if (!isUndefined(data)) { | ||
fs.retry.writeSync(timeout)(fd, data, 0, data.length, 0); | ||
} | ||
if (options.fsync !== false) { | ||
if (options.fsyncWait !== false) { | ||
fs_1.default.fsyncSyncRetry(timeout)(fd); | ||
fs.retry.fsyncSync(timeout)(fd); | ||
} | ||
else { | ||
fs_1.default.fsyncAttempt(fd); | ||
fs.attempt.fsync(fd); | ||
} | ||
} | ||
fs_1.default.closeSyncRetry(timeout)(fd); | ||
fs.retry.closeSync(timeout)(fd); | ||
fd = null; | ||
if (options.chown) | ||
fs_1.default.chownSyncAttempt(tempPath, options.chown.uid, options.chown.gid); | ||
if (options.mode) | ||
fs_1.default.chmodSyncAttempt(tempPath, options.mode); | ||
if (options.chown && (options.chown.uid !== DEFAULT_USER_UID || options.chown.gid !== DEFAULT_USER_GID)) { | ||
fs.attempt.chownSync(tempPath, options.chown.uid, options.chown.gid); | ||
} | ||
if (options.mode && options.mode !== DEFAULT_FILE_MODE) { | ||
fs.attempt.chmodSync(tempPath, options.mode); | ||
} | ||
try { | ||
fs_1.default.renameSyncRetry(timeout)(tempPath, filePath); | ||
fs.retry.renameSync(timeout)(tempPath, filePath); | ||
} | ||
catch (error) { | ||
if (!isException(error)) | ||
throw error; | ||
if (error.code !== 'ENAMETOOLONG') | ||
throw error; | ||
fs_1.default.renameSyncRetry(timeout)(tempPath, temp_1.default.truncate(filePath)); | ||
fs.retry.renameSync(timeout)(tempPath, Temp.truncate(filePath)); | ||
} | ||
@@ -175,7 +193,8 @@ tempDisposer(); | ||
if (fd) | ||
fs_1.default.closeSyncAttempt(fd); | ||
fs.attempt.closeSync(fd); | ||
if (tempPath) | ||
temp_1.default.purge(tempPath); | ||
Temp.purge(tempPath); | ||
} | ||
}; | ||
exports.writeFileSync = writeFileSync; | ||
/* EXPORT */ | ||
export { readFile, readFileSync, writeFile, writeFileSync }; |
/// <reference types="node" /> | ||
declare type Callback = (error: Exception | void) => any; | ||
declare type Data = Buffer | string | undefined; | ||
declare type Callback = (error: Exception | void) => void; | ||
declare type Data = Uint8Array | string | undefined; | ||
declare type Disposer = () => void; | ||
declare type Encoding = 'ascii' | 'base64' | 'binary' | 'hex' | 'latin1' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2'; | ||
declare type Exception = NodeJS.ErrnoException; | ||
declare type FN<Arguments extends any[] = any[], Return = any> = (...args: Arguments) => Return; | ||
declare type Path = string; | ||
declare type ReadOptions = { | ||
encoding?: string | null; | ||
encoding?: Encoding | null; | ||
mode?: string | number | false; | ||
@@ -18,3 +18,3 @@ timeout?: number; | ||
} | false; | ||
encoding?: string | null; | ||
encoding?: Encoding | null; | ||
fsync?: boolean; | ||
@@ -26,5 +26,5 @@ fsyncWait?: boolean; | ||
tmpCreate?: (filePath: string) => string; | ||
tmpCreated?: (filePath: string) => any; | ||
tmpCreated?: (filePath: string) => void; | ||
tmpPurge?: boolean; | ||
}; | ||
export { Callback, Data, Disposer, Exception, FN, Path, ReadOptions, WriteOptions }; | ||
export type { Callback, Data, Disposer, Encoding, Exception, Path, ReadOptions, WriteOptions }; |
@@ -1,3 +0,2 @@ | ||
"use strict"; | ||
/* TYPES */ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* MAIN */ | ||
export {}; |
@@ -1,6 +0,6 @@ | ||
declare const Lang: { | ||
isFunction: (x: any) => x is Function; | ||
isString: (x: any) => x is string; | ||
isUndefined: (x: any) => x is undefined; | ||
}; | ||
export default Lang; | ||
/// <reference types="node" /> | ||
declare const isException: (value: unknown) => value is NodeJS.ErrnoException; | ||
declare const isFunction: (value: unknown) => value is Function; | ||
declare const isString: (value: unknown) => value is string; | ||
declare const isUndefined: (value: unknown) => value is undefined; | ||
export { isException, isFunction, isString, isUndefined }; |
@@ -1,16 +0,16 @@ | ||
"use strict"; | ||
/* LANG */ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Lang = { | ||
isFunction: (x) => { | ||
return typeof x === 'function'; | ||
}, | ||
isString: (x) => { | ||
return typeof x === 'string'; | ||
}, | ||
isUndefined: (x) => { | ||
return typeof x === 'undefined'; | ||
} | ||
/* IMPORT */ | ||
/* MAIN */ | ||
const isException = (value) => { | ||
return (value instanceof Error) && ('code' in value); | ||
}; | ||
const isFunction = (value) => { | ||
return (typeof value === 'function'); | ||
}; | ||
const isString = (value) => { | ||
return (typeof value === 'string'); | ||
}; | ||
const isUndefined = (value) => { | ||
return (value === undefined); | ||
}; | ||
/* EXPORT */ | ||
exports.default = Lang; | ||
export { isException, isFunction, isString, isUndefined }; |
@@ -1,2 +0,2 @@ | ||
import { Disposer } from '../types'; | ||
import type { Disposer } from '../types'; | ||
declare const Scheduler: { | ||
@@ -3,0 +3,0 @@ next: (id: string) => void; |
@@ -1,9 +0,8 @@ | ||
"use strict"; | ||
/* IMPORT */ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* VARIABLES */ | ||
/* HELPERS */ | ||
const Queues = {}; | ||
/* SCHEDULER */ | ||
/* MAIN */ | ||
//TODO: Maybe publish this as a standalone package | ||
const Scheduler = { | ||
/* API */ | ||
next: (id) => { | ||
@@ -35,2 +34,2 @@ const queue = Queues[id]; | ||
/* EXPORT */ | ||
exports.default = Scheduler; | ||
export default Scheduler; |
@@ -1,2 +0,2 @@ | ||
import { Disposer } from '../types'; | ||
import type { Disposer } from '../types'; | ||
declare const Temp: { | ||
@@ -3,0 +3,0 @@ store: Record<string, boolean>; |
@@ -1,15 +0,18 @@ | ||
"use strict"; | ||
/* IMPORT */ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const path = require("path"); | ||
const consts_1 = require("../consts"); | ||
const fs_1 = require("./fs"); | ||
/* TEMP */ | ||
import path from 'node:path'; | ||
import fs from 'stubborn-fs'; | ||
import whenExit from 'when-exit'; | ||
import { LIMIT_BASENAME_LENGTH } from '../constants.js'; | ||
/* MAIN */ | ||
//TODO: Maybe publish this as a standalone package | ||
const Temp = { | ||
/* VARIABLES */ | ||
store: {}, | ||
/* API */ | ||
create: (filePath) => { | ||
const randomness = `000000${Math.floor(Math.random() * 16777215).toString(16)}`.slice(-6), // 6 random-enough hex characters | ||
timestamp = Date.now().toString().slice(-10), // 10 precise timestamp digits | ||
prefix = 'tmp-', suffix = `.${prefix}${timestamp}${randomness}`, tempPath = `${filePath}${suffix}`; | ||
const randomness = `000000${Math.floor(Math.random() * 16777215).toString(16)}`.slice(-6); // 6 random-enough hex characters | ||
const timestamp = Date.now().toString().slice(-10); // 10 precise timestamp digits | ||
const prefix = 'tmp-'; | ||
const suffix = `.${prefix}${timestamp}${randomness}`; | ||
const tempPath = `${filePath}${suffix}`; | ||
return tempPath; | ||
@@ -29,3 +32,3 @@ }, | ||
delete Temp.store[filePath]; | ||
fs_1.default.unlinkAttempt(filePath); | ||
fs.attempt.unlink(filePath); | ||
}, | ||
@@ -36,3 +39,3 @@ purgeSync: (filePath) => { | ||
delete Temp.store[filePath]; | ||
fs_1.default.unlinkSyncAttempt(filePath); | ||
fs.attempt.unlinkSync(filePath); | ||
}, | ||
@@ -46,3 +49,3 @@ purgeSyncAll: () => { | ||
const basename = path.basename(filePath); | ||
if (basename.length <= consts_1.LIMIT_BASENAME_LENGTH) | ||
if (basename.length <= LIMIT_BASENAME_LENGTH) | ||
return filePath; //FIXME: Rough and quick attempt at detecting ok lengths | ||
@@ -52,3 +55,3 @@ const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename); | ||
return filePath; //FIXME: No truncable part detected, can't really do much without also changing the parent path, which is unsafe, hoping for the best here | ||
const truncationLength = basename.length - consts_1.LIMIT_BASENAME_LENGTH; | ||
const truncationLength = basename.length - LIMIT_BASENAME_LENGTH; | ||
return `${filePath.slice(0, -basename.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`; //FIXME: The truncable part might be shorter than needed here | ||
@@ -58,4 +61,4 @@ } | ||
/* INIT */ | ||
process.on('exit', Temp.purgeSyncAll); // Ensuring purgeable temp files are purged on exit | ||
whenExit(Temp.purgeSyncAll); // Ensuring purgeable temp files are purged on exit | ||
/* EXPORT */ | ||
exports.default = Temp; | ||
export default Temp; |
{ | ||
"name": "atomically", | ||
"repository": "github:fabiospampinato/atomically", | ||
"description": "Read and write files atomically and reliably.", | ||
"version": "1.7.0", | ||
"version": "2.0.0", | ||
"type": "module", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"exports": "./dist/index.js", | ||
"types": "./dist/index.d.ts", | ||
"scripts": { | ||
"benchmark": "node ./tasks/benchmark.js", | ||
"clean": "rimraf dist", | ||
"compile": "tsc --skipLibCheck && tstei", | ||
"compile:watch": "tsc --skipLibCheck --watch", | ||
"test": "tap --no-coverage-report", | ||
"test:watch": "tap --no-coverage-report --watch", | ||
"benchmark": "tsex benchmark", | ||
"benchmarkLwatch": "tsex benchmark --watch", | ||
"clean": "tsex clean", | ||
"compile": "tsex compile", | ||
"compile:watch": "tsex compile --watch", | ||
"test:init": "esbuild --bundle --target=es2020 --platform=node --format=cjs src/index.ts > test/atomically.cjs", | ||
"test": "npm run test:init && tap --no-check-coverage --no-coverage-report", | ||
"test:watch": "npm run test:init && tap --no-check-coverage --no-coverage-report --watch", | ||
"prepublishOnly": "npm run clean && npm run compile && npm run test" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/fabiospampinato/atomically/issues" | ||
}, | ||
"license": "MIT", | ||
"author": { | ||
"name": "Fabio Spampinato", | ||
"email": "spampinabio@gmail.com" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/fabiospampinato/atomically.git" | ||
}, | ||
"keywords": [ | ||
@@ -35,18 +28,15 @@ "atomic", | ||
], | ||
"engines": { | ||
"node": ">=10.12.0" | ||
"dependencies": { | ||
"stubborn-fs": "^1.2.1", | ||
"when-exit": "^2.0.0" | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"@types/node": "^12.7.2", | ||
"lodash": "^4.17.19", | ||
"mkdirp": "^1.0.4", | ||
"promise-resolve-timeout": "^1.2.1", | ||
"@types/node": "^18.11.9", | ||
"esbuild": "^0.15.13", | ||
"require-inject": "^1.4.4", | ||
"rimraf": "^3.0.2", | ||
"tap": "^14.10.7", | ||
"typescript": "^3.5.3", | ||
"typescript-transform-export-interop": "^1.0.2", | ||
"write-file-atomic": "^3.0.3" | ||
"tap": "^16.3.0", | ||
"tsex": "^1.1.2", | ||
"typescript": "^4.8.4", | ||
"write-file-atomic": "^5.0.0" | ||
} | ||
} |
@@ -11,3 +11,3 @@ # Atomically | ||
- This library is slightly faster than [`write-file-atomic`](https://github.com/npm/write-file-atomic), and it can be 10x faster, while being essentially just as safe, by using the `fsyncWait` option. | ||
- This library has 0 dependencies, so there's less code to vet and the entire thing is roughly 20% smaller than [`write-file-atomic`](https://github.com/npm/write-file-atomic). | ||
- This library has 0 third-party dependencies, so there's less code to vet and the entire thing is roughly 20% smaller than [`write-file-atomic`](https://github.com/npm/write-file-atomic). | ||
- This library tries harder to write files on disk than [`write-file-atomic`](https://github.com/npm/write-file-atomic) does, by default retrying some failed operations and handling some more errors. | ||
@@ -53,4 +53,4 @@ - Reliability: | ||
- `timeout`: it allows you to specify the amount of maximum milliseconds within which the library will retry some failed operations: | ||
- when writing asynchronously by default it will keep retrying for 5000 milliseconds. | ||
- when writing synchronously by default it will keep retrying for 100 milliseconds. | ||
- when writing asynchronously by default it will keep retrying for 7500 milliseconds. | ||
- when writing synchronously by default it will keep retrying for 1000 milliseconds. | ||
- if `0` or `-1` no failed operations will be retried. | ||
@@ -57,0 +57,0 @@ - if another number is provided that will be the timeout interval. |
246
src/index.ts
/* IMPORT */ | ||
import * as path from 'path'; | ||
import {DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX} from './consts'; | ||
import FS from './utils/fs'; | ||
import Lang from './utils/lang'; | ||
import path from 'node:path'; | ||
import fs from 'stubborn-fs'; | ||
import {DEFAULT_ENCODING, DEFAULT_FILE_MODE, DEFAULT_FOLDER_MODE, DEFAULT_READ_OPTIONS, DEFAULT_WRITE_OPTIONS, DEFAULT_USER_UID, DEFAULT_USER_GID, DEFAULT_TIMEOUT_ASYNC, DEFAULT_TIMEOUT_SYNC, IS_POSIX} from './constants'; | ||
import {isException, isFunction, isString, isUndefined} from './utils/lang'; | ||
import Scheduler from './utils/scheduler'; | ||
import Temp from './utils/temp'; | ||
import {Callback, Data, Disposer, Path, ReadOptions, WriteOptions} from './types'; | ||
import type {Callback, Data, Disposer, Encoding, Path, ReadOptions, WriteOptions} from './types'; | ||
/* ATOMICALLY */ | ||
/* MAIN */ | ||
function readFile ( filePath: Path, options: string | ReadOptions & { encoding: string } ): Promise<string>; | ||
function readFile ( filePath: Path, options: Encoding | ReadOptions & { encoding: string } ): Promise<string>; | ||
function readFile ( filePath: Path, options?: ReadOptions ): Promise<Buffer>; | ||
function readFile ( filePath: Path, options: string | ReadOptions = DEFAULT_READ_OPTIONS ): Promise<Buffer | string> { | ||
function readFile ( filePath: Path, options: Encoding | ReadOptions = DEFAULT_READ_OPTIONS ): Promise<Buffer | string> { | ||
if ( Lang.isString ( options ) ) return readFile ( filePath, { encoding: options } ); | ||
if ( isString ( options ) ) return readFile ( filePath, { encoding: options } ); | ||
const timeout = Date.now () + ( options.timeout ?? DEFAULT_TIMEOUT_ASYNC ); | ||
const timeout = Date.now () + ( ( options.timeout ?? DEFAULT_TIMEOUT_ASYNC ) || -1 ); | ||
return FS.readFileRetry ( timeout )( filePath, options ); | ||
return fs.retry.readFile ( timeout )( filePath, options ); | ||
}; | ||
} | ||
function readFileSync ( filePath: Path, options: string | ReadOptions & { encoding: string } ): string; | ||
function readFileSync ( filePath: Path, options: Encoding | ReadOptions & { encoding: string } ): string; | ||
function readFileSync ( filePath: Path, options?: ReadOptions ): Buffer; | ||
function readFileSync ( filePath: Path, options: string | ReadOptions = DEFAULT_READ_OPTIONS ): Buffer | string { | ||
function readFileSync ( filePath: Path, options: Encoding | ReadOptions = DEFAULT_READ_OPTIONS ): Buffer | string { | ||
if ( Lang.isString ( options ) ) return readFileSync ( filePath, { encoding: options } ); | ||
if ( isString ( options ) ) return readFileSync ( filePath, { encoding: options } ); | ||
const timeout = Date.now () + ( options.timeout ?? DEFAULT_TIMEOUT_SYNC ); | ||
const timeout = Date.now () + ( ( options.timeout ?? DEFAULT_TIMEOUT_SYNC ) || -1 ); | ||
return FS.readFileSyncRetry ( timeout )( filePath, options ); | ||
return fs.retry.readFileSync ( timeout )( filePath, options ); | ||
}; | ||
} | ||
const writeFile = ( filePath: Path, data: Data, options?: string | WriteOptions | Callback, callback?: Callback ): Promise<void> => { | ||
function writeFile ( filePath: Path, data: Data, callback?: Callback ): Promise<void>; | ||
function writeFile ( filePath: Path, data: Data, options?: Encoding | WriteOptions, callback?: Callback ): Promise<void>; | ||
function writeFile ( filePath: Path, data: Data, options?: Encoding | WriteOptions | Callback, callback?: Callback ): Promise<void> { | ||
if ( Lang.isFunction ( options ) ) return writeFile ( filePath, data, DEFAULT_WRITE_OPTIONS, options ); | ||
if ( isFunction ( options ) ) return writeFile ( filePath, data, DEFAULT_WRITE_OPTIONS, options ); | ||
@@ -48,15 +50,15 @@ const promise = writeFileAsync ( filePath, data, options ); | ||
}; | ||
} | ||
const writeFileAsync = async ( filePath: Path, data: Data, options: string | WriteOptions = DEFAULT_WRITE_OPTIONS ): Promise<void> => { | ||
async function writeFileAsync ( filePath: Path, data: Data, options: Encoding | WriteOptions = DEFAULT_WRITE_OPTIONS ): Promise<void> { | ||
if ( Lang.isString ( options ) ) return writeFileAsync ( filePath, data, { encoding: options } ); | ||
if ( isString ( options ) ) return writeFileAsync ( filePath, data, { encoding: options } ); | ||
const timeout = Date.now () + ( options.timeout ?? DEFAULT_TIMEOUT_ASYNC ); | ||
const timeout = Date.now () + ( ( options.timeout ?? DEFAULT_TIMEOUT_ASYNC ) || -1 ); | ||
let schedulerCustomDisposer: Disposer | null = null, | ||
schedulerDisposer: Disposer | null = null, | ||
tempDisposer: Disposer | null = null, | ||
tempPath: string | null = null, | ||
fd: number | null = null; | ||
let schedulerCustomDisposer: Disposer | null = null; | ||
let schedulerDisposer: Disposer | null = null; | ||
let tempDisposer: Disposer | null = null; | ||
let tempPath: string | null = null; | ||
let fd: number | null = null; | ||
@@ -69,21 +71,32 @@ try { | ||
filePath = await FS.realpathAttempt ( filePath ) || filePath; | ||
const filePathReal = await fs.attempt.realpath ( filePath ); | ||
const filePathExists = !!filePathReal; | ||
filePath = filePathReal || filePath; | ||
[tempPath, tempDisposer] = Temp.get ( filePath, options.tmpCreate || Temp.create, !( options.tmpPurge === false ) ); | ||
const useStatChown = IS_POSIX && Lang.isUndefined ( options.chown ), | ||
useStatMode = Lang.isUndefined ( options.mode ); | ||
const useStatChown = IS_POSIX && isUndefined ( options.chown ); | ||
const useStatMode = isUndefined ( options.mode ); | ||
if ( useStatChown || useStatMode ) { | ||
if ( filePathExists && ( useStatChown || useStatMode ) ) { | ||
const stat = await FS.statAttempt ( filePath ); | ||
const stats = await fs.attempt.stat ( filePath ); | ||
if ( stat ) { | ||
if ( stats ) { | ||
options = { ...options }; | ||
if ( useStatChown ) options.chown = { uid: stat.uid, gid: stat.gid }; | ||
if ( useStatChown ) { | ||
if ( useStatMode ) options.mode = stat.mode; | ||
options.chown = { uid: stats.uid, gid: stats.gid }; | ||
} | ||
if ( useStatMode ) { | ||
options.mode = stats.mode; | ||
} | ||
} | ||
@@ -93,21 +106,29 @@ | ||
const parentPath = path.dirname ( filePath ); | ||
if ( !filePathExists ) { | ||
await FS.mkdirAttempt ( parentPath, { | ||
mode: DEFAULT_FOLDER_MODE, | ||
recursive: true | ||
}); | ||
const parentPath = path.dirname ( filePath ); | ||
fd = await FS.openRetry ( timeout )( tempPath, 'w', options.mode || DEFAULT_FILE_MODE ); | ||
await fs.attempt.mkdir ( parentPath, { | ||
mode: DEFAULT_FOLDER_MODE, | ||
recursive: true | ||
}); | ||
if ( options.tmpCreated ) options.tmpCreated ( tempPath ); | ||
} | ||
if ( Lang.isString ( data ) ) { | ||
fd = await fs.retry.open ( timeout )( tempPath, 'w', options.mode || DEFAULT_FILE_MODE ); | ||
await FS.writeRetry ( timeout )( fd, data, 0, options.encoding || DEFAULT_ENCODING ); | ||
if ( options.tmpCreated ) { | ||
} else if ( !Lang.isUndefined ( data ) ) { | ||
options.tmpCreated ( tempPath ); | ||
await FS.writeRetry ( timeout )( fd, data, 0, data.length, 0 ); | ||
} | ||
if ( isString ( data ) ) { | ||
await fs.retry.write ( timeout )( fd, data, 0, options.encoding || DEFAULT_ENCODING ); | ||
} else if ( !isUndefined ( data ) ) { | ||
await fs.retry.write ( timeout )( fd, data, 0, data.length, 0 ); | ||
} | ||
@@ -119,7 +140,7 @@ | ||
await FS.fsyncRetry ( timeout )( fd ); | ||
await fs.retry.fsync ( timeout )( fd ); | ||
} else { | ||
FS.fsyncAttempt ( fd ); | ||
fs.attempt.fsync ( fd ); | ||
@@ -130,19 +151,29 @@ } | ||
await FS.closeRetry ( timeout )( fd ); | ||
await fs.retry.close ( timeout )( fd ); | ||
fd = null; | ||
if ( options.chown ) await FS.chownAttempt ( tempPath, options.chown.uid, options.chown.gid ); | ||
if ( options.chown && ( options.chown.uid !== DEFAULT_USER_UID || options.chown.gid !== DEFAULT_USER_GID ) ) { | ||
if ( options.mode ) await FS.chmodAttempt ( tempPath, options.mode ); | ||
await fs.attempt.chown ( tempPath, options.chown.uid, options.chown.gid ); | ||
} | ||
if ( options.mode && options.mode !== DEFAULT_FILE_MODE ) { | ||
await fs.attempt.chmod ( tempPath, options.mode ); | ||
} | ||
try { | ||
await FS.renameRetry ( timeout )( tempPath, filePath ); | ||
await fs.retry.rename ( timeout )( tempPath, filePath ); | ||
} catch ( error ) { | ||
} catch ( error: unknown ) { | ||
if ( !isException ( error ) ) throw error; | ||
if ( error.code !== 'ENAMETOOLONG' ) throw error; | ||
await FS.renameRetry ( timeout )( tempPath, Temp.truncate ( filePath ) ); | ||
await fs.retry.rename ( timeout )( tempPath, Temp.truncate ( filePath ) ); | ||
@@ -157,3 +188,3 @@ } | ||
if ( fd ) await FS.closeAttempt ( fd ); | ||
if ( fd ) await fs.attempt.close ( fd ); | ||
@@ -168,35 +199,46 @@ if ( tempPath ) Temp.purge ( tempPath ); | ||
}; | ||
} | ||
const writeFileSync = ( filePath: Path, data: Data, options: string | WriteOptions = DEFAULT_WRITE_OPTIONS ): void => { | ||
const writeFileSync = ( filePath: Path, data: Data, options: Encoding | WriteOptions = DEFAULT_WRITE_OPTIONS ): void => { | ||
if ( Lang.isString ( options ) ) return writeFileSync ( filePath, data, { encoding: options } ); | ||
if ( isString ( options ) ) return writeFileSync ( filePath, data, { encoding: options } ); | ||
const timeout = Date.now () + ( options.timeout ?? DEFAULT_TIMEOUT_SYNC ); | ||
const timeout = Date.now () + ( ( options.timeout ?? DEFAULT_TIMEOUT_SYNC ) || -1 ); | ||
let tempDisposer: Disposer | null = null, | ||
tempPath: string | null = null, | ||
fd: number | null = null; | ||
let tempDisposer: Disposer | null = null; | ||
let tempPath: string | null = null; | ||
let fd: number | null = null; | ||
try { | ||
filePath = FS.realpathSyncAttempt ( filePath ) || filePath; | ||
const filePathReal = fs.attempt.realpathSync ( filePath ); | ||
const filePathExists = !!filePathReal; | ||
filePath = filePathReal || filePath; | ||
[tempPath, tempDisposer] = Temp.get ( filePath, options.tmpCreate || Temp.create, !( options.tmpPurge === false ) ); | ||
const useStatChown = IS_POSIX && Lang.isUndefined ( options.chown ), | ||
useStatMode = Lang.isUndefined ( options.mode ); | ||
const useStatChown = IS_POSIX && isUndefined ( options.chown ); | ||
const useStatMode = isUndefined ( options.mode ); | ||
if ( useStatChown || useStatMode ) { | ||
if ( filePathExists && ( useStatChown || useStatMode ) ) { | ||
const stat = FS.statSyncAttempt ( filePath ); | ||
const stats = fs.attempt.statSync ( filePath ); | ||
if ( stat ) { | ||
if ( stats ) { | ||
options = { ...options }; | ||
if ( useStatChown ) options.chown = { uid: stat.uid, gid: stat.gid }; | ||
if ( useStatChown ) { | ||
if ( useStatMode ) options.mode = stat.mode; | ||
options.chown = { uid: stats.uid, gid: stats.gid }; | ||
} | ||
if ( useStatMode ) { | ||
options.mode = stats.mode; | ||
} | ||
} | ||
@@ -206,21 +248,29 @@ | ||
const parentPath = path.dirname ( filePath ); | ||
if ( !filePathExists ) { | ||
FS.mkdirSyncAttempt ( parentPath, { | ||
mode: DEFAULT_FOLDER_MODE, | ||
recursive: true | ||
}); | ||
const parentPath = path.dirname ( filePath ); | ||
fd = FS.openSyncRetry ( timeout )( tempPath, 'w', options.mode || DEFAULT_FILE_MODE ); | ||
fs.attempt.mkdirSync ( parentPath, { | ||
mode: DEFAULT_FOLDER_MODE, | ||
recursive: true | ||
}); | ||
if ( options.tmpCreated ) options.tmpCreated ( tempPath ); | ||
} | ||
if ( Lang.isString ( data ) ) { | ||
fd = fs.retry.openSync ( timeout )( tempPath, 'w', options.mode || DEFAULT_FILE_MODE ); | ||
FS.writeSyncRetry ( timeout )( fd, data, 0, options.encoding || DEFAULT_ENCODING ); | ||
if ( options.tmpCreated ) { | ||
} else if ( !Lang.isUndefined ( data ) ) { | ||
options.tmpCreated ( tempPath ); | ||
FS.writeSyncRetry ( timeout )( fd, data, 0, data.length, 0 ); | ||
} | ||
if ( isString ( data ) ) { | ||
fs.retry.writeSync ( timeout )( fd, data, 0, options.encoding || DEFAULT_ENCODING ); | ||
} else if ( !isUndefined ( data ) ) { | ||
fs.retry.writeSync ( timeout )( fd, data, 0, data.length, 0 ); | ||
} | ||
@@ -232,7 +282,7 @@ | ||
FS.fsyncSyncRetry ( timeout )( fd ); | ||
fs.retry.fsyncSync ( timeout )( fd ); | ||
} else { | ||
FS.fsyncAttempt ( fd ); | ||
fs.attempt.fsync ( fd ); | ||
@@ -243,19 +293,29 @@ } | ||
FS.closeSyncRetry ( timeout )( fd ); | ||
fs.retry.closeSync ( timeout )( fd ); | ||
fd = null; | ||
if ( options.chown ) FS.chownSyncAttempt ( tempPath, options.chown.uid, options.chown.gid ); | ||
if ( options.chown && ( options.chown.uid !== DEFAULT_USER_UID || options.chown.gid !== DEFAULT_USER_GID ) ) { | ||
if ( options.mode ) FS.chmodSyncAttempt ( tempPath, options.mode ); | ||
fs.attempt.chownSync ( tempPath, options.chown.uid, options.chown.gid ); | ||
} | ||
if ( options.mode && options.mode !== DEFAULT_FILE_MODE ) { | ||
fs.attempt.chmodSync ( tempPath, options.mode ); | ||
} | ||
try { | ||
FS.renameSyncRetry ( timeout )( tempPath, filePath ); | ||
fs.retry.renameSync ( timeout )( tempPath, filePath ); | ||
} catch ( error ) { | ||
} catch ( error: unknown ) { | ||
if ( !isException ( error ) ) throw error; | ||
if ( error.code !== 'ENAMETOOLONG' ) throw error; | ||
FS.renameSyncRetry ( timeout )( tempPath, Temp.truncate ( filePath ) ); | ||
fs.retry.renameSync ( timeout )( tempPath, Temp.truncate ( filePath ) ); | ||
@@ -270,3 +330,3 @@ } | ||
if ( fd ) FS.closeSyncAttempt ( fd ); | ||
if ( fd ) fs.attempt.closeSync ( fd ); | ||
@@ -277,3 +337,3 @@ if ( tempPath ) Temp.purge ( tempPath ); | ||
}; | ||
} | ||
@@ -280,0 +340,0 @@ /* EXPORT */ |
/* TYPES */ | ||
/* MAIN */ | ||
type Callback = ( error: Exception | void ) => any; | ||
type Callback = ( error: Exception | void ) => void; | ||
type Data = Buffer | string | undefined; | ||
type Data = Uint8Array | string | undefined; | ||
type Disposer = () => void; | ||
type Encoding = 'ascii' | 'base64' | 'binary' | 'hex' | 'latin1' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2'; | ||
type Exception = NodeJS.ErrnoException; | ||
type FN<Arguments extends any[] = any[], Return = any> = ( ...args: Arguments ) => Return; | ||
type Path = string; | ||
type ReadOptions = { | ||
encoding?: string | null, | ||
encoding?: Encoding | null, | ||
mode?: string | number | false, | ||
@@ -24,3 +24,3 @@ timeout?: number | ||
chown?: { gid: number, uid: number } | false, | ||
encoding?: string | null, | ||
encoding?: Encoding | null, | ||
fsync?: boolean, | ||
@@ -32,3 +32,3 @@ fsyncWait?: boolean, | ||
tmpCreate?: ( filePath: string ) => string, | ||
tmpCreated?: ( filePath: string ) => any, | ||
tmpCreated?: ( filePath: string ) => void, | ||
tmpPurge?: boolean | ||
@@ -39,2 +39,2 @@ }; | ||
export {Callback, Data, Disposer, Exception, FN, Path, ReadOptions, WriteOptions}; | ||
export type {Callback, Data, Disposer, Encoding, Exception, Path, ReadOptions, WriteOptions}; |
/* LANG */ | ||
/* IMPORT */ | ||
const Lang = { | ||
import type {Exception} from '../types'; | ||
isFunction: ( x: any ): x is Function => { | ||
/* MAIN */ | ||
return typeof x === 'function'; | ||
const isException = ( value: unknown ): value is Exception => { | ||
}, | ||
return ( value instanceof Error ) && ( 'code' in value ); | ||
isString: ( x: any ): x is string => { | ||
}; | ||
return typeof x === 'string'; | ||
const isFunction = ( value: unknown ): value is Function => { | ||
}, | ||
return ( typeof value === 'function' ); | ||
isUndefined: ( x: any ): x is undefined => { | ||
}; | ||
return typeof x === 'undefined'; | ||
const isString = ( value: unknown ): value is string => { | ||
} | ||
return ( typeof value === 'string' ); | ||
}; | ||
const isUndefined = ( value: unknown ): value is undefined => { | ||
return ( value === undefined ); | ||
}; | ||
/* EXPORT */ | ||
export default Lang; | ||
export {isException, isFunction, isString, isUndefined}; |
/* IMPORT */ | ||
import {Disposer} from '../types'; | ||
import type {Disposer} from '../types'; | ||
/* VARIABLES */ | ||
/* HELPERS */ | ||
const Queues: Record<string, Function[] | undefined> = {}; | ||
/* SCHEDULER */ | ||
/* MAIN */ | ||
@@ -16,2 +16,4 @@ //TODO: Maybe publish this as a standalone package | ||
/* API */ | ||
next: ( id: string ): void => { | ||
@@ -18,0 +20,0 @@ |
/* IMPORT */ | ||
import * as path from 'path'; | ||
import {LIMIT_BASENAME_LENGTH} from '../consts'; | ||
import {Disposer} from '../types'; | ||
import FS from './fs'; | ||
import path from 'node:path'; | ||
import fs from 'stubborn-fs'; | ||
import whenExit from 'when-exit'; | ||
import {LIMIT_BASENAME_LENGTH} from '../constants'; | ||
import type {Disposer} from '../types'; | ||
/* TEMP */ | ||
/* MAIN */ | ||
@@ -15,11 +16,15 @@ //TODO: Maybe publish this as a standalone package | ||
/* VARIABLES */ | ||
store: <Record<string, boolean>> {}, // filePath => purge | ||
/* API */ | ||
create: ( filePath: string ): string => { | ||
const randomness = `000000${Math.floor ( Math.random () * 16777215 ).toString ( 16 )}`.slice ( -6 ), // 6 random-enough hex characters | ||
timestamp = Date.now ().toString ().slice ( -10 ), // 10 precise timestamp digits | ||
prefix = 'tmp-', | ||
suffix = `.${prefix}${timestamp}${randomness}`, | ||
tempPath = `${filePath}${suffix}`; | ||
const randomness = `000000${Math.floor ( Math.random () * 16777215 ).toString ( 16 )}`.slice ( -6 ); // 6 random-enough hex characters | ||
const timestamp = Date.now ().toString ().slice ( -10 ); // 10 precise timestamp digits | ||
const prefix = 'tmp-'; | ||
const suffix = `.${prefix}${timestamp}${randomness}`; | ||
const tempPath = `${filePath}${suffix}`; | ||
@@ -50,3 +55,3 @@ return tempPath; | ||
FS.unlinkAttempt ( filePath ); | ||
fs.attempt.unlink ( filePath ); | ||
@@ -61,3 +66,3 @@ }, | ||
FS.unlinkSyncAttempt ( filePath ); | ||
fs.attempt.unlinkSync ( filePath ); | ||
@@ -96,3 +101,3 @@ }, | ||
process.on ( 'exit', Temp.purgeSyncAll ); // Ensuring purgeable temp files are purged on exit | ||
whenExit ( Temp.purgeSyncAll ); // Ensuring purgeable temp files are purged on exit | ||
@@ -99,0 +104,0 @@ /* EXPORT */ |
/* IMPORT */ | ||
const fs = require ( 'fs' ), | ||
os = require ( 'os' ), | ||
path = require ( 'path' ), | ||
delay = require ( 'promise-resolve-timeout' ), | ||
writeFileAtomic = require ( 'write-file-atomic' ), | ||
{writeFile, writeFileSync} = require ( '../dist' ); | ||
import {randomUUID} from 'node:crypto'; | ||
import fs from 'node:fs'; | ||
import os from 'node:os'; | ||
import path from 'node:path'; | ||
import {setTimeout as delay} from 'node:timers/promises'; | ||
import writeFileAtomic from 'write-file-atomic'; | ||
import {writeFile, writeFileSync} from '../dist/index.js'; | ||
/* BENCHMARK */ | ||
/* MAIN */ | ||
const TEMP = os.tmpdir (), | ||
DST = i => path.join ( TEMP, `atomically-temp-${i}.txt` ), | ||
ITERATIONS = 250; | ||
const TEMP = os.tmpdir (); | ||
const UUID = randomUUID (); | ||
const DST = i => path.join ( TEMP, `atomically-${UUID}-temp-${i}.txt` ); | ||
const ITERATIONS = 250; | ||
@@ -40,3 +42,4 @@ const runSingleAsync = async ( name, fn, buffer, options ) => { | ||
const runAllAsync = async ( name, buffer ) => { | ||
await runSingleAsync ( `${name} -> async -> write-file-atomic`, writeFileAtomic, buffer ); | ||
await runSingleAsync ( `${name} -> async -> write-file-atomic`, writeFileAtomic, buffer, { mode: 0o666 } ); | ||
await runSingleAsync ( `${name} -> async -> write-file-atomic (faster)`, writeFileAtomic, buffer ); | ||
await runSingleAsync ( `${name} -> async -> write-file-atomic (fastest)`, writeFileAtomic, buffer, { fsync: false } ); | ||
@@ -49,3 +52,4 @@ await runSingleAsync ( `${name} -> async -> atomically`, writeFile, buffer ); | ||
const runAllSync = ( name, buffer ) => { | ||
runSingleSync ( `${name} -> sync -> write-file-atomic`, writeFileAtomic.sync, buffer ); | ||
runSingleSync ( `${name} -> sync -> write-file-atomic`, writeFileAtomic.sync, buffer, { mode: 0o666 } ); | ||
runSingleSync ( `${name} -> sync -> write-file-atomic (faster)`, writeFileAtomic.sync, buffer ); | ||
runSingleSync ( `${name} -> sync -> write-file-atomic (fastest)`, writeFileAtomic.sync, buffer, { fsync: false } ); | ||
@@ -52,0 +56,0 @@ runSingleSync ( `${name} -> sync -> atomically`, writeFileSync, buffer ); |
{ | ||
"compilerOptions": { | ||
"alwaysStrict": true, | ||
"declaration": true, | ||
"emitDecoratorMetadata": true, | ||
"experimentalDecorators": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"inlineSourceMap": false, | ||
"jsx": "react", | ||
"lib": ["dom", "scripthost", "es2015", "es2016", "es2017", "es2018", "es2019", "es2020"], | ||
"module": "commonjs", | ||
"moduleResolution": "node", | ||
"newLine": "LF", | ||
"noFallthroughCasesInSwitch": true, | ||
"noUnusedLocals": true, | ||
"noUnusedParameters": false, | ||
"outDir": "dist", | ||
"pretty": true, | ||
"strictNullChecks": true, | ||
"target": "es2018" | ||
}, | ||
"include": [ | ||
"src" | ||
], | ||
"exclude": [ | ||
"node_modules" | ||
] | ||
"extends": "tsex/tsconfig.json" | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
7
4
Yes
76191
2
27
1676
2
1
+ Addedstubborn-fs@^1.2.1
+ Addedwhen-exit@^2.0.0
+ Addedstubborn-fs@1.2.5(transitive)
+ Addedwhen-exit@2.1.3(transitive)