@deepseek-ai/dsh-atomic-write 0.2.1-alpha.1 → 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 CHANGED
@@ -9,14 +9,14 @@
9
9
  en: ed491d62812ad8cc
10
10
  zh: 653e184dd035ec71
11
11
  /deepseek-ai-dsh-atomic-write/summary:
12
- en: 6c4c49bda04ed05f
13
- zh: 1080ddd179d0a3d2
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: 9cc27a0ee5ae0808
19
- zh: d0ee06b54452f600
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: 59308dc757e25273
28
- zh: 01113ed577a413cb
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: 5d0b395da8833bbb
52
- zh: 6191bab46d288be7
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 one already-rendered string 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.
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
- - **String content only** — no `Buffer` or stream form until a consumer needs one.
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 上使用调用方选择的权限位,从而安全地收窄现有文件的权限。这个零依赖库只接受字符串;它不提供 `cordis.yml` 插件,也不保证崩溃持久性,因为它不调用 `fsync`。
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
- 当文件型存储必须替换一份已渲染好的字符串、且绝不允许暴露部分写入、符号链接劫持或权限过宽状态时,使用 `writeFileAtomic`;当多个进程对同一文件执行读-修改-写入循环时,使用 `withFileLock`。最小路径是一次调用,传入最终内容与替换 inode 的权限位。
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
- - **仅支持字符串内容**——在有消费方需要之前,不提供 `Buffer` 或流式形态。
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 new Promise((resolve) => setTimeout(resolve, delay));
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 });
@@ -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.1",
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.1"
30
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2"
31
31
  },
32
32
  "devDependencies": {
33
- "@deepseek-ai/cordis": "~4.0.5-alpha.1"
33
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2"
34
34
  }
35
35
  }