@procedures/fs 0.27.4 → 0.27.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @procedures/fs
2
2
 
3
+ ## 0.27.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 2daba21: Sync forms, the last line, the name of the temporary file and a clearer error for a long line in `@procedures/fs/files` (roadmap O52, the feedback of jarvis PR #86).
8
+
9
+ PATCH: new functions, new options and a new optional field of a result before 1.0.0. No default changes.
10
+
11
+ - `writeFileAtomicSync(path, data, options?)` is the sync form of `writeFileAtomic()`, with the same steps. On Windows, it tries the rename of a locked target again for `renameRetryMs`, with a short wait of `Atomics.wait()` between the tries.
12
+ - `healNdjsonTailSync(path, options?)` is the sync form of `healNdjsonTail()`, with the same repairs, result and errors.
13
+ - The new option `lastLine: true` of the two heal functions adds `lastLine` to the result: the last complete line after the repair, without its newline, or `undefined` for a file without a complete line. The function reads it from the tail only. Without the option, the heal of a file that ends in a newline still reads one byte.
14
+ - The error of a last line longer than `maxLineBytes` is still a `RangeError`. It now has the code `ERR_NDJSON_LINE_TOO_LONG` (the export `NDJSON_LINE_TOO_LONG`) and the fields `path`, `lineBytes` and `maxLineBytes`. Its text gives the length, the limit and the option, and tells that `Infinity` turns the limit off. To give the length, the function reads back to the start of the line, without a copy in memory.
15
+ - The new option `tempName(target)` of `writeFileAtomic()`, `writeFileAtomicSync()` and `editFile()` gives the name of the temporary file. A name with `/` or `\` is a `TypeError` with the code `ERR_INVALID_ARG_VALUE`. The default name stays `.<name>.<16 hex digits>.tmp`.
16
+ - When the group and the others cannot read the final file, the temporary file now starts with the mode `0o600`. Then the function sets the final mode before the rename.
17
+ - The async and the sync forms share one set of steps, so their edge cases are the same.
18
+ - @procedures/client@0.27.5
19
+
3
20
  ## 0.27.4
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -168,35 +168,60 @@ The entry `@procedures/fs/root` has the rules of the root without the procedures
168
168
 
169
169
  ## File helpers
170
170
 
171
- The entry `@procedures/fs/files` has four functions for the files of an app. They are not procedures, and an import of the entry registers nothing. The package root exports them too.
171
+ The entry `@procedures/fs/files` has helpers for the files of an app: the atomic write, the NDJSON tail heal, the keyed lock and the edit. They are not procedures, and an import of the entry registers nothing. The package root exports them too.
172
172
 
173
- ### `writeFileAtomic(path, data, options?)`
173
+ ### `writeFileAtomic(path, data, options?)` and `writeFileAtomicSync(path, data, options?)`
174
174
 
175
175
  The function writes a temporary file next to the target, flushes it to the disk and renames it over the target. A reader sees the old file or the whole new file.
176
176
 
177
177
  ```ts
178
- import { writeFileAtomic } from "@procedures/fs/files";
178
+ import { writeFileAtomic, writeFileAtomicSync } from "@procedures/fs/files";
179
179
 
180
180
  await writeFileAtomic("./state.json", JSON.stringify(state));
181
+ writeFileAtomicSync("./meta.json", JSON.stringify(meta));
181
182
  ```
182
183
 
183
184
  - After a failure, the function removes the temporary file.
184
185
  - The file keeps the permissions of the target. The option `mode` sets other permissions.
186
+ - When the group and the others cannot read the final file, the temporary file gets `0o600` before the data. Windows ignores most of the mode.
185
187
  - On Windows, an open handle of the target blocks the rename. The function tries again for `renameRetryMs` (2000 by default).
188
+ - `writeFileAtomicSync()` does the same steps. On Windows, a locked target blocks the thread for `renameRetryMs` at most.
186
189
 
187
- ### `healNdjsonTail(path, options?)`
190
+ The temporary file is in the folder of the target. Its default name is `.<name>.<16 hex digits>.tmp`. The `<name>` is the first 32 characters of the name of the target. A crash between the write and the rename leaves this file. For a `.env` file, add the pattern `..env.*.tmp` to `.gitignore`.
191
+
192
+ The option `tempName` gives a name of your own. The function gives it the absolute path of the target. A name with `/` or `\` is a `TypeError` with the code `ERR_INVALID_ARG_VALUE`. The name of a file that exists gives the error `EEXIST`.
193
+
194
+ ```ts
195
+ await writeFileAtomic("./.env", text, { tempName: (target) => `${basename(target)}.${process.pid}.tmp` });
196
+ ```
197
+
198
+ ### `healNdjsonTail(path, options?)` and `healNdjsonTailSync(path, options?)`
188
199
 
189
200
  The function repairs the end of an append-only NDJSON file after a crash. Start it before the next append.
190
201
 
191
202
  ```ts
192
- import { healNdjsonTail } from "@procedures/fs/files";
203
+ import { healNdjsonTail, healNdjsonTailSync } from "@procedures/fs/files";
193
204
 
194
205
  const { action, bytes } = await healNdjsonTail("./events.ndjson");
195
206
  // "none", "truncated" (a part of a line is cut off) or "newline" (a complete record gets its newline)
207
+
208
+ const { lastLine } = healNdjsonTailSync("./chat.ndjson", { lastLine: true });
209
+ const nextSeq = lastLine === undefined ? 0 : JSON.parse(lastLine).seq + 1;
196
210
  ```
197
211
 
198
212
  - The function reads only the last line, from the end of the file, up to `maxLineBytes` (16 MiB by default).
199
213
  - A missing file and an empty file get no change.
214
+ - With `lastLine: true`, the result has `lastLine`: the last complete line after the repair, without its newline. It is `undefined` when the file has no complete line.
215
+ - Without `lastLine`, the function reads one byte of a file that ends in a newline. With `lastLine`, it also reads the last line.
216
+ - `healNdjsonTailSync()` makes the same repairs, with the same options, the same result and the same errors.
217
+
218
+ A last line that is longer than `maxLineBytes` stops the function, and the file does not change. The error is a `RangeError` with the code `ERR_NDJSON_LINE_TOO_LONG`, also the export `NDJSON_LINE_TOO_LONG`. Its fields `lineBytes`, `maxLineBytes` and `path` give the length, the limit and the file.
219
+
220
+ ```text
221
+ A line at the end of ./ledger.ndjson has 15728640 bytes, more than the limit maxLineBytes (1048576 bytes). Set the option maxLineBytes to 15728640 or more, or to Infinity for no limit. The file did not change.
222
+ ```
223
+
224
+ The function does not cut or skip a long line, because that removes data from the file. For records of more than 16 MiB, set a larger `maxLineBytes`. The value `Infinity` turns the limit off. Then the function keeps all of the last line in memory.
200
225
 
201
226
  ### `withLock(key, fn, options?)` and `KeyedLock`
202
227
 
@@ -226,6 +251,7 @@ await editFile("./.env", (text) => `${text}TOKEN=abc\n`, { initial: "" });
226
251
 
227
252
  - A missing file gives `initial`, or `undefined` without it.
228
253
  - A transform that gives the same text or `undefined` writes nothing.
254
+ - The options of `writeFileAtomic()` apply to the write, also `tempName`.
229
255
  - On Windows, the lock ignores the case of the letters of the path.
230
256
 
231
257
  ## API Reference