@deepseek-ai/dsh-atomic-write 0.2.1-alpha.2 → 0.2.1-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +8 -8
- package/README.md +4 -4
- package/README.zh.md +4 -4
- package/lib/index.js +4 -1
- package/lib/types/index.d.ts +3 -1
- package/package.json +3 -3
package/README.i18n.yaml
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
en: ed491d62812ad8cc
|
|
10
10
|
zh: 653e184dd035ec71
|
|
11
11
|
/deepseek-ai-dsh-atomic-write/summary:
|
|
12
|
-
en:
|
|
13
|
-
zh:
|
|
12
|
+
en: d2d40e759179f047
|
|
13
|
+
zh: c53f56de74b26e5a
|
|
14
14
|
/deepseek-ai-dsh-atomic-write/table-of-contents:
|
|
15
15
|
en: d152484eb41ac6b4
|
|
16
16
|
zh: 09388d293f9be9cb
|
|
17
17
|
/deepseek-ai-dsh-atomic-write/use-this-package:
|
|
18
|
-
en:
|
|
19
|
-
zh:
|
|
18
|
+
en: 30fbc2c61904195d
|
|
19
|
+
zh: 09c2ac3f44aa3f08
|
|
20
20
|
/deepseek-ai-dsh-atomic-write/use-this-package/writing-a-file-atomically:
|
|
21
21
|
en: 7b757dc3c3b27335
|
|
22
22
|
zh: a097d90a2561475d
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
en: 77c865c1f78d2a0c
|
|
25
25
|
zh: 5a91874c352c6f18
|
|
26
26
|
/deepseek-ai-dsh-atomic-write/use-this-package/failures-to-plan-for:
|
|
27
|
-
en:
|
|
28
|
-
zh:
|
|
27
|
+
en: a8292aa69d7f909c
|
|
28
|
+
zh: 60d8403fa6e04d17
|
|
29
29
|
/deepseek-ai-dsh-atomic-write/understand-the-implementation:
|
|
30
30
|
en: 93b537308f7c88c7
|
|
31
31
|
zh: 282e0d2042aeb1be
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
en: 815e93723a98919c
|
|
49
49
|
zh: 921da870f67e4033
|
|
50
50
|
/deepseek-ai-dsh-atomic-write/known-limitations-and-deferred-work:
|
|
51
|
-
en:
|
|
52
|
-
zh:
|
|
51
|
+
en: fa413fe1ee23e01f
|
|
52
|
+
zh: 8563b57f91ea194b
|
|
53
53
|
/deepseek-ai-dsh-atomic-write/known-limitations-and-deferred-work/dev-note:
|
|
54
54
|
en: d3461ceeaf041974
|
|
55
55
|
zh: f59df2adbf52fed9
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
Use `dsh-atomic-write` to replace a file without exposing partial content or following a symlinked temporary path. Its writer lock serializes read-modify-write cycles across processes so concurrent writers cannot overwrite one another with stale state. Each replacement uses caller-selected permission bits on a fresh inode, which safely narrows an existing file's permissions. This zero-dependency library accepts strings; it does not provide a `cordis.yml` plugin or crash durability because it does not call `fsync`.
|
|
12
|
+
Use `dsh-atomic-write` to replace a file without exposing partial content or following a symlinked temporary path. Its writer lock serializes read-modify-write cycles across processes so concurrent writers cannot overwrite one another with stale state. Each replacement uses caller-selected permission bits on a fresh inode, which safely narrows an existing file's permissions. This zero-dependency library accepts strings and binary byte arrays; it does not provide a `cordis.yml` plugin or crash durability because it does not call `fsync`.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -25,7 +25,7 @@ Use `dsh-atomic-write` to replace a file without exposing partial content or fol
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## Use this package
|
|
27
27
|
|
|
28
|
-
Use `writeFileAtomic` when a file-backed store must replace
|
|
28
|
+
Use `writeFileAtomic` when a file-backed store must replace complete text or binary content without ever exposing a partial, symlink-hijacked, or wider-permission state, and `withFileLock` when several processes read-modify-write the same file. The smallest path is one call with the final content and the replacement's permission bits.
|
|
29
29
|
|
|
30
30
|
### Writing a file atomically
|
|
31
31
|
|
|
@@ -60,7 +60,7 @@ Only writers contend — readers never take the lock — and a contender backs o
|
|
|
60
60
|
|
|
61
61
|
Windows retries one `EPERM` when the lock cannot be observed, because its holder can release between exclusive creation and the existence check. A repeated unconfirmed `EPERM` is rethrown without running the operation.
|
|
62
62
|
|
|
63
|
-
The lock's parent directory must already exist, so `withFileLock` rejects an invalid parent hierarchy before running the operation. A process that exits while holding the lock leaves the lock sibling behind, and the next writer takes it over. A lock whose record is empty or incomplete, or names a PID that a live process reused, is not taken over; later writers time out, and an operator removes it only after verifying that no writer still owns it.
|
|
63
|
+
The lock's parent directory must already exist, so `withFileLock` rejects an invalid parent hierarchy before running the operation. A process that exits while holding the lock leaves the lock sibling behind, and the next writer takes it over. A lock whose record is empty or incomplete, or names a PID that a live process reused, is not taken over; later writers time out, and an operator removes it only after verifying that no writer still owns it. An optional `signal` cancels acquisition and contention waits; the operation owns cancellation after it starts.
|
|
64
64
|
|
|
65
65
|
-----
|
|
66
66
|
|
|
@@ -122,7 +122,7 @@ Nothing here enters a request prefix, so provider cache reuse is unaffected.
|
|
|
122
122
|
These limits define where the package is not the right tool. They are current package constraints, not a task backlog.
|
|
123
123
|
|
|
124
124
|
- **Atomic, not durable** — no `fsync` of the file or its directory, so after a crash the rename may be observed unwound. The file-backed stores here re-read and republish on boot, keeping durability the caller's policy.
|
|
125
|
-
- **
|
|
125
|
+
- **Callers supply complete content** — strings and `Uint8Array` (including `Buffer`) are accepted; streaming writes are unsupported.
|
|
126
126
|
- **Some orphaned locks require operator recovery** — a lock whose record is empty or incomplete, or names a PID a live process reused, stays in place; later writers time out without deleting it. A contender that ends between creating a claim and removing the lock leaves both `<filename>.lock` and `<filename>.lock.takeover-<record hash>`, and the operator removes both.
|
|
127
127
|
- **One host and one PID namespace** — the probe runs on the contender's host. Writers on several hosts sharing a network filesystem, or containers sharing a volume, can see a live holder as exited, take over its lock, and write at the same time as it.
|
|
128
128
|
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@ kind: "package-library"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
使用 `dsh-atomic-write` 替换文件时,不会暴露部分内容,也不会跟随临时路径上的符号链接。它的写锁会跨进程串行化读-修改-写入循环,因此并发写入方不会用陈旧状态相互覆盖。每次替换都会在全新 inode
|
|
12
|
+
使用 `dsh-atomic-write` 替换文件时,不会暴露部分内容,也不会跟随临时路径上的符号链接。它的写锁会跨进程串行化读-修改-写入循环,因此并发写入方不会用陈旧状态相互覆盖。每次替换都会在全新 inode 上使用调用方选择的权限位,从而安全地收窄现有文件的权限。这个零依赖库接受字符串和二进制字节数组;它不提供 `cordis.yml` 插件,也不保证崩溃持久性,因为它不调用 `fsync`。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -25,7 +25,7 @@ kind: "package-library"
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## 使用本包
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
当文件型存储必须替换完整文本或二进制内容、且绝不允许暴露部分写入、符号链接劫持或权限过宽状态时,使用 `writeFileAtomic`;当多个进程对同一文件执行读-修改-写入循环时,使用 `withFileLock`。最小路径是一次调用,传入最终内容与替换 inode 的权限位。
|
|
29
29
|
|
|
30
30
|
### 原子写入文件
|
|
31
31
|
|
|
@@ -60,7 +60,7 @@ await withFileLock('/home/u/.dsh/cordis.patch.yml', async () => {
|
|
|
60
60
|
|
|
61
61
|
Windows 在无法观察到锁时会对 `EPERM` 重试一次,因为持锁方可能在独占创建与存在性检查之间释放锁。再次出现无法确认锁存在的 `EPERM` 时,会重新抛出错误且不运行操作。
|
|
62
62
|
|
|
63
|
-
锁的父目录必须已经存在,因此 `withFileLock` 会在运行操作之前拒绝无效的父目录层级。持锁进程退出时会把锁文件留在原地,下一个写入方会接管它。锁记录为空或不完整,或其 PID 已被存活进程复用时,锁不会被接管;后续写入方超时失败,操作者只有在确认没有写入方仍持有该锁后才会移除它。
|
|
63
|
+
锁的父目录必须已经存在,因此 `withFileLock` 会在运行操作之前拒绝无效的父目录层级。持锁进程退出时会把锁文件留在原地,下一个写入方会接管它。锁记录为空或不完整,或其 PID 已被存活进程复用时,锁不会被接管;后续写入方超时失败,操作者只有在确认没有写入方仍持有该锁后才会移除它。 `signal` 可取消获取锁和等待竞争;操作开始后由操作自身处理取消。
|
|
64
64
|
|
|
65
65
|
-----
|
|
66
66
|
|
|
@@ -122,7 +122,7 @@ Windows 在无法观察到锁时会对 `EPERM` 重试一次,因为持锁方可
|
|
|
122
122
|
这些限制说明本包何时不是合适的工具。它们是当前包约束,不是任务积压。
|
|
123
123
|
|
|
124
124
|
- **原子但不保证持久**——不对文件或其所在目录做 `fsync`,因此崩溃后可能观察到 rename 被回退。此处的文件型存储在启动时重新读取并重新发布,把持久性留作调用方的策略。
|
|
125
|
-
-
|
|
125
|
+
- **调用方提供完整内容**——接受字符串或 `Uint8Array`(包括 `Buffer`);不支持流式写入。
|
|
126
126
|
- **部分遗留锁需要操作者恢复**——锁记录为空或不完整,或其 PID 已被存活进程复用时,锁会留在原地;后续写入方超时也不会删除它。竞争者若在创建认领文件与移除锁之间结束,会同时留下 `<filename>.lock` 和 `<filename>.lock.takeover-<记录哈希>`,由操作者删除两者。
|
|
127
127
|
- **单一主机与单一 PID 命名空间**——探测在竞争者所在主机上进行。多台主机通过网络文件系统共享、或多个容器共享同一卷时,写入方可能把存活持有者视为已退出,接管其锁并与之同时写入。
|
|
128
128
|
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
4
5
|
//#region lib/types/index.js
|
|
5
6
|
/**
|
|
6
7
|
* Zero-dependency atomic file replacement and writer coordination.
|
|
@@ -191,6 +192,7 @@ async function withFileLock(filename, operation, options) {
|
|
|
191
192
|
let delay = LOCK_RETRY_INITIAL_MS;
|
|
192
193
|
let retriedUnconfirmedPermissionError = false;
|
|
193
194
|
for (;;) {
|
|
195
|
+
options?.signal?.throwIfAborted();
|
|
194
196
|
try {
|
|
195
197
|
await writeFile(lockPath, `${process.pid}\n`, {
|
|
196
198
|
mode: 384,
|
|
@@ -204,10 +206,11 @@ async function withFileLock(filename, operation, options) {
|
|
|
204
206
|
} else if (await takeOverExitedLock(lockPath)) continue;
|
|
205
207
|
}
|
|
206
208
|
if (Date.now() >= deadline) throw new Error(`atomic-write: timed out waiting for the writer lock at ${lockPath}`);
|
|
207
|
-
await
|
|
209
|
+
await setTimeout$1(delay, void 0, { signal: options?.signal });
|
|
208
210
|
delay = Math.min(delay * 2, LOCK_RETRY_MAX_MS);
|
|
209
211
|
}
|
|
210
212
|
try {
|
|
213
|
+
options?.signal?.throwIfAborted();
|
|
211
214
|
return await operation();
|
|
212
215
|
} finally {
|
|
213
216
|
await rm(lockPath, { force: true });
|
package/lib/types/index.d.ts
CHANGED
|
@@ -43,9 +43,11 @@ export interface WriteFileAtomicOptions {
|
|
|
43
43
|
* @param content - complete next file content.
|
|
44
44
|
* @param options - permission bits for the replacement inode.
|
|
45
45
|
*/
|
|
46
|
-
export declare function writeFileAtomic(filename: string, content: string, options: WriteFileAtomicOptions): Promise<void>;
|
|
46
|
+
export declare function writeFileAtomic(filename: string, content: string | Uint8Array, options: WriteFileAtomicOptions): Promise<void>;
|
|
47
47
|
/** Options for one {@link withFileLock} acquisition. */
|
|
48
48
|
export interface FileLockOptions {
|
|
49
|
+
/** Cancel acquisition and contention waits; the operation owns cancellation after it starts. */
|
|
50
|
+
signal?: AbortSignal;
|
|
49
51
|
/**
|
|
50
52
|
* Maximum time to wait for the lock, in milliseconds. State one when the
|
|
51
53
|
* holder's operation legitimately runs longer than file work — a credential
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-atomic-write",
|
|
3
3
|
"description": "Zero-dependency atomic file replacement: exclusive-create random-suffix temp + rename carrying the caller-stated permissions (writeFileAtomic)",
|
|
4
|
-
"version": "0.2.1-alpha.
|
|
4
|
+
"version": "0.2.1-alpha.3",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
],
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@deepseek-ai/cordis": "~4.0.5-alpha.
|
|
30
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@deepseek-ai/cordis": "~4.0.5-alpha.
|
|
33
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.2"
|
|
34
34
|
}
|
|
35
35
|
}
|