{"_id":"@buckeyestudio/toh-atomic-write","name":"@buckeyestudio/toh-atomic-write","dist-tags":{"next":"0.1.1-rc.2","latest":"0.1.1-rc.2"},"versions":{"0.1.1-rc.2":{"name":"@buckeyestudio/toh-atomic-write","description":"Zero-dependency atomic file replacement: exclusive-create random-suffix temp + rename carrying the caller-stated permissions (writeFileAtomic)","version":"0.1.1-rc.2","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/dustinwloring1988/theopen-harness.git","directory":"packages/util/atomic-write"},"type":"module","main":"lib/index.js","types":"lib/types/index.d.ts","exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./src/*":"./src/*","./package.json":"./package.json"},"license":"MIT","author":{"name":"buckeyestudio"},"peerDependencies":{"@buckeyestudio/toh-invariants":"^0.1.1-rc.2","@buckeyestudio/cordis":"^4.0.1"},"devDependencies":{"@buckeyestudio/toh-invariants":"^0.1.1-rc.2","@buckeyestudio/cordis":"^4.0.1"},"_id":"@buckeyestudio/toh-atomic-write@0.1.1-rc.2","bugs":{"url":"https://github.com/dustinwloring1988/theopen-harness/issues"},"homepage":"https://github.com/dustinwloring1988/theopen-harness#readme","_integrity":"sha512-+maH9EXL5JRh0CdWFrHvlkdMEmE4qgXK6bZxhxeBo38Y3fzjU8DqhzTBOwNHNR0/G09M8+nGAnbRqptUwgOWmQ==","_resolved":"/home/runner/work/theopen-harness/theopen-harness/dist/npm/buckeyestudio-toh-atomic-write-0.1.1-rc.2.tgz","_from":"file:/home/runner/work/theopen-harness/theopen-harness/dist/npm/buckeyestudio-toh-atomic-write-0.1.1-rc.2.tgz","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-+maH9EXL5JRh0CdWFrHvlkdMEmE4qgXK6bZxhxeBo38Y3fzjU8DqhzTBOwNHNR0/G09M8+nGAnbRqptUwgOWmQ==","shasum":"6ef85284ad1b6e48a7716ad9fd97c0ef17d82d03","tarball":"https://registry.npmjs.org/@buckeyestudio/toh-atomic-write/-/toh-atomic-write-0.1.1-rc.2.tgz","fileCount":9,"unpackedSize":20380,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCMrOyX+3jppN+2345uq4JbvTxK3aA8qXAqu6FLFM8zWwIhAP+hbi58V1+nTQ9xyP2fJtUOu53UKqYfeFiUmtJlG+Db"}]},"_npmUser":{"name":"buckeyestudio","email":"dustinwloring1988@gmail.com"},"directories":{},"maintainers":[{"name":"buckeyestudio","email":"dustinwloring1988@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/toh-atomic-write_0.1.1-rc.2_1787489020707_0.872779800981476"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-23T12:43:40.541Z","0.1.1-rc.2":"2026-08-23T12:43:40.850Z","modified":"2026-08-23T12:43:41.069Z"},"maintainers":[{"name":"buckeyestudio","email":"dustinwloring1988@gmail.com"}],"description":"Zero-dependency atomic file replacement: exclusive-create random-suffix temp + rename carrying the caller-stated permissions (writeFileAtomic)","homepage":"https://github.com/dustinwloring1988/theopen-harness#readme","repository":{"type":"git","url":"git+https://github.com/dustinwloring1988/theopen-harness.git","directory":"packages/util/atomic-write"},"author":{"name":"buckeyestudio"},"bugs":{"url":"https://github.com/dustinwloring1988/theopen-harness/issues"},"license":"MIT","readme":"# toh-atomic-write\n\n[English](README.md) | 中文\n\n零依赖的原子文件替换，供绝不允许在磁盘上留下不完整、被符号链接劫持或权限过宽内容的文件型存储共用：用户设置文档（`toh-settings-file`）与凭据存储（`toh-credentials-local`）。\n\n## 接口面\n\n```ts\nimport { withFileLock, writeFileAtomic } from '@buckeyestudio/toh-atomic-write'\n\ndeclare const text: string\ndeclare const render: (previous: string) => string\n\nawait writeFileAtomic('/home/u/.toh/settings.yaml', text, { mode: 0o600 })\n\n// Read-modify-write against the same file from several processes.\nawait withFileLock('/home/u/.toh/settings.yaml', async () => {\n  await writeFileAtomic('/home/u/.toh/settings.yaml', render(text), { mode: 0o600 })\n})\n```\n\n`writeFileAtomic` 提交一份已经渲染好的字符串。约定按故障利用它的先后顺序列出：\n\n- **独占创建临时文件**（`wx` + 随机后缀）：open 拒绝跟随预先埋在可猜测临时路径上的符号链接。\n- **全新 inode 携带 `mode` 走完 rename**：替换权限过宽的旧文件时直接收窄，不存在 chmod 竞态。`mode` 为必填，让权限决策始终可见于每个调用点（与所有新建 inode 一样受进程 umask 影响）。\n- **`rename` 替换的是符号链接目标本身**，绝不写穿到其指向的文件。\n- **同目录兄弟文件**保证 rename 落在同一文件系统上，交换保持原子。\n- 自动创建父目录；任何失败都会移除临时文件并重新抛出该失败；读取方只会观察到旧内容或完整的新内容。\n\n`withFileLock` 跨进程串行化同一文件的写入方，服务于单靠原子提交无法保证安全的读-渲染-提交循环。锁是以 `wx` 创建的同目录 `<filename>.lock`，因此读取方从不参与竞争；等待方按指数退避，超时即失败而非无限阻塞。`EEXIST` 直接表示竞争；只有一次新的 `lstat` 确认锁路径存在时，`EPERM` 才表示竞争，从而兼容 Windows 的独占创建行为，又不掩盖无关的权限故障。竞争者绝不移除现有锁：锁龄无法区分已经崩溃的所有者与被暂停但仍存活的写入方。\n\n等待多久是持锁方所跑操作的属性，因此由每次调用经 `waitMs` 声明。默认值只按纯文件工作量级选定；若持锁方的循环包含一次网络往返——例如刷新过期 token 的凭据变更——就应声明更长的值，否则该文件的其他写入方会在这段时间内全部失败。退避节奏保持固定：它决定竞争者多久问一次，调用方没有理由改变它。\n\n## 模型体验\n\n无：本包是纯文件系统原语，此处没有任何内容会到达模型请求。\n\n#### KV Cache 影响\n\n无；此处没有任何内容会进入请求前缀。\n\n## 已知限制与暂缓事项\n\n- **原子但不保证持久**——不对文件或其所在目录做 `fsync`，因此崩溃后可能观察到 rename 被回退。此处的文件型存储在启动时重新读取并重新发布，把持久性留作调用方的策略。\n- **仅支持字符串内容**——在有消费方需要之前，不提供 `Buffer` 或流式形态。\n- **遗留锁需要操作者恢复**——进程持锁退出时可能留下同级锁文件。后续写入方超时也不会删除它；操作者只有在确认没有写入方仍拥有该锁后才会移除。文件存续时间本身不能安全证明它已无人持有。\n","readmeFilename":"README.zh.md","_rev":"1-b2c8e8ae0b006645df299405d25a6804"}