Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

atomically

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

atomically - npm Package Compare versions

Comparing version 1.7.0 to 2.0.0

dist/constants.d.ts

11

dist/index.d.ts
/// <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.

/* 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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc