@deepseek-ai/dsh-atomic-write 0.1.6-alpha.1 → 0.1.7-alpha.1
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 +2 -2
- package/README.md +6 -4
- package/README.zh.md +6 -4
- package/lib/index.js +8 -3
- package/lib/types/index.d.ts +3 -2
- package/package.json +3 -3
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/util/atomic-write/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: fa2a77c8ba11e1dd80fecef6da09fa32a608b523
|
|
6
|
+
README.zh.md: 020d379c876733d5de11c63887de8d3168eac3b3
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Use `writeFileAtomic` when a file-backed store must replace one already-rendered
|
|
|
33
33
|
import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write'
|
|
34
34
|
|
|
35
35
|
declare const text: string
|
|
36
|
-
await writeFileAtomic('/home/u/.dsh/
|
|
36
|
+
await writeFileAtomic('/home/u/.dsh/cordis.patch.yml', text, { mode: 0o600 })
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Parent directories are created as needed, and readers observe either the old or the new complete content. On Windows, transient replacement interference reported as `EACCES`, `EBUSY`, or `EPERM` is retried for a bounded interval; any remaining failure removes the temporary file and leaves the target untouched.
|
|
@@ -48,9 +48,9 @@ import { withFileLock, writeFileAtomic } from '@deepseek-ai/dsh-atomic-write'
|
|
|
48
48
|
declare const render: (previous: string) => string
|
|
49
49
|
declare const readCurrent: () => Promise<string>
|
|
50
50
|
|
|
51
|
-
await withFileLock('/home/u/.dsh/
|
|
51
|
+
await withFileLock('/home/u/.dsh/cordis.patch.yml', async () => {
|
|
52
52
|
const previous = await readCurrent()
|
|
53
|
-
await writeFileAtomic('/home/u/.dsh/
|
|
53
|
+
await writeFileAtomic('/home/u/.dsh/cordis.patch.yml', render(previous), { mode: 0o600 })
|
|
54
54
|
})
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -58,6 +58,8 @@ Only writers contend — readers never take the lock — and a contender backs o
|
|
|
58
58
|
|
|
59
59
|
### Failures to plan for
|
|
60
60
|
|
|
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
|
+
|
|
61
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; later writers time out, and an operator removes it only after verifying that no writer still owns it.
|
|
62
64
|
|
|
63
65
|
-----
|
|
@@ -98,7 +100,7 @@ The package is built on one separation: the atomic commit owns the swap, and the
|
|
|
98
100
|
|
|
99
101
|
Read these pages when you need the consuming stores or the family this primitive belongs to.
|
|
100
102
|
|
|
101
|
-
- [
|
|
103
|
+
- [Profile configuration editor](../../boot/config-editor/README.md) — the profile patch every edit replaces through this package.
|
|
102
104
|
- [Credentials store](../../credentials/credentials-local/README.md) — the credentials file this package locks and replaces.
|
|
103
105
|
- [util group map](../README.md) — the zero-dependency utility family this package belongs to.
|
|
104
106
|
|
package/README.zh.md
CHANGED
|
@@ -33,7 +33,7 @@ kind: "package-library"
|
|
|
33
33
|
import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write'
|
|
34
34
|
|
|
35
35
|
declare const text: string
|
|
36
|
-
await writeFileAtomic('/home/u/.dsh/
|
|
36
|
+
await writeFileAtomic('/home/u/.dsh/cordis.patch.yml', text, { mode: 0o600 })
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
父目录会按需创建,读取方只会观察到旧内容或完整的新内容。在 Windows 上,报告为 `EACCES`、`EBUSY` 或 `EPERM` 的瞬时替换干扰会在有界时间内重试;任何剩余失败都会移除临时文件,并保持目标文件不变。
|
|
@@ -48,9 +48,9 @@ import { withFileLock, writeFileAtomic } from '@deepseek-ai/dsh-atomic-write'
|
|
|
48
48
|
declare const render: (previous: string) => string
|
|
49
49
|
declare const readCurrent: () => Promise<string>
|
|
50
50
|
|
|
51
|
-
await withFileLock('/home/u/.dsh/
|
|
51
|
+
await withFileLock('/home/u/.dsh/cordis.patch.yml', async () => {
|
|
52
52
|
const previous = await readCurrent()
|
|
53
|
-
await writeFileAtomic('/home/u/.dsh/
|
|
53
|
+
await writeFileAtomic('/home/u/.dsh/cordis.patch.yml', render(previous), { mode: 0o600 })
|
|
54
54
|
})
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -58,6 +58,8 @@ await withFileLock('/home/u/.dsh/settings.yaml', async () => {
|
|
|
58
58
|
|
|
59
59
|
### 需要规划的失败
|
|
60
60
|
|
|
61
|
+
Windows 在无法观察到锁时会对 `EPERM` 重试一次,因为持锁方可能在独占创建与存在性检查之间释放锁。再次出现无法确认锁存在的 `EPERM` 时,会重新抛出错误且不运行操作。
|
|
62
|
+
|
|
61
63
|
锁的父目录必须已经存在,因此 `withFileLock` 会在运行操作之前拒绝无效的父目录层级。持锁进程退出时会把锁文件留在原地;后续写入方超时失败,操作者只有在确认没有写入方仍持有该锁后才会移除它。
|
|
62
64
|
|
|
63
65
|
-----
|
|
@@ -98,7 +100,7 @@ await withFileLock('/home/u/.dsh/settings.yaml', async () => {
|
|
|
98
100
|
|
|
99
101
|
当你需要了解使用本原语的存储或它所属的工具家族时,阅读以下页面。
|
|
100
102
|
|
|
101
|
-
- [用户设置文件存储](../../
|
|
103
|
+
- [用户设置文件存储](../../boot/config-editor/README.zh.md)——每次写入都通过本包替换的设置文档。
|
|
102
104
|
- [凭据存储](../../credentials/credentials-local/README.zh.md)——本包加锁并替换的凭据文件。
|
|
103
105
|
- [util 组映射](../README.zh.md)——本包所属的零依赖工具家族。
|
|
104
106
|
|
package/lib/index.js
CHANGED
|
@@ -109,8 +109,9 @@ const DEFAULT_LOCK_WAIT_MS = 2e3;
|
|
|
109
109
|
* rename-based commit of {@link writeFileAtomic}, readers stay lock-free and
|
|
110
110
|
* only writers contend. `EEXIST` is contention directly; an `EPERM` is
|
|
111
111
|
* contention only when a fresh `lstat` confirms the lock path exists, covering
|
|
112
|
-
* Windows exclusive-create behavior
|
|
113
|
-
*
|
|
112
|
+
* Windows exclusive-create behavior. Windows retries one unconfirmed EPERM
|
|
113
|
+
* because the holder can release before the probe; a repeated unconfirmed
|
|
114
|
+
* permission error is rethrown. Contention backs off exponentially and times out
|
|
114
115
|
* after the deadline. The contender never removes an existing lock because
|
|
115
116
|
* file age cannot prove that its owner stopped; orphan recovery is an operator
|
|
116
117
|
* action. The parent directory must exist.
|
|
@@ -123,6 +124,7 @@ async function withFileLock(filename, operation, options) {
|
|
|
123
124
|
const lockPath = `${filename}.lock`;
|
|
124
125
|
const deadline = Date.now() + (options?.waitMs ?? DEFAULT_LOCK_WAIT_MS);
|
|
125
126
|
let delay = LOCK_RETRY_INITIAL_MS;
|
|
127
|
+
let retriedUnconfirmedPermissionError = false;
|
|
126
128
|
for (;;) {
|
|
127
129
|
try {
|
|
128
130
|
await writeFile(lockPath, `${process.pid}\n`, {
|
|
@@ -131,7 +133,10 @@ async function withFileLock(filename, operation, options) {
|
|
|
131
133
|
});
|
|
132
134
|
break;
|
|
133
135
|
} catch (error) {
|
|
134
|
-
if (!await isLockContention(error, lockPath))
|
|
136
|
+
if (!await isLockContention(error, lockPath)) {
|
|
137
|
+
if (process.platform !== "win32" || error?.code !== "EPERM" || retriedUnconfirmedPermissionError) throw error;
|
|
138
|
+
retriedUnconfirmedPermissionError = true;
|
|
139
|
+
}
|
|
135
140
|
}
|
|
136
141
|
if (Date.now() >= deadline) throw new Error(`atomic-write: timed out waiting for the writer lock at ${lockPath}`);
|
|
137
142
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
package/lib/types/index.d.ts
CHANGED
|
@@ -61,8 +61,9 @@ export interface FileLockOptions {
|
|
|
61
61
|
* rename-based commit of {@link writeFileAtomic}, readers stay lock-free and
|
|
62
62
|
* only writers contend. `EEXIST` is contention directly; an `EPERM` is
|
|
63
63
|
* contention only when a fresh `lstat` confirms the lock path exists, covering
|
|
64
|
-
* Windows exclusive-create behavior
|
|
65
|
-
*
|
|
64
|
+
* Windows exclusive-create behavior. Windows retries one unconfirmed EPERM
|
|
65
|
+
* because the holder can release before the probe; a repeated unconfirmed
|
|
66
|
+
* permission error is rethrown. Contention backs off exponentially and times out
|
|
66
67
|
* after the deadline. The contender never removes an existing lock because
|
|
67
68
|
* file age cannot prove that its owner stopped; orphan recovery is an operator
|
|
68
69
|
* action. The parent directory must exist.
|
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.1.
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
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.
|
|
30
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
33
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
34
34
|
}
|
|
35
35
|
}
|