@ix-xs/node-comfort 1.2.0 → 2.1.0

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +340 -0
  2. package/README.md +137 -548
  3. package/index.js +80 -35
  4. package/package.json +161 -33
  5. package/src/Arr.js +1050 -418
  6. package/src/Async.js +817 -0
  7. package/src/Cache.js +433 -0
  8. package/src/Checker.js +1202 -539
  9. package/src/Cli.js +1293 -0
  10. package/src/Color.js +556 -0
  11. package/src/Crypto.js +1082 -0
  12. package/src/Emitter.js +356 -0
  13. package/src/Env.js +689 -0
  14. package/src/FS.js +1626 -894
  15. package/src/Func.js +823 -356
  16. package/src/Http.js +700 -0
  17. package/src/Id.js +568 -200
  18. package/src/Logger.js +963 -378
  19. package/src/Num.js +965 -360
  20. package/src/Obj.js +1077 -379
  21. package/src/SQLite.js +1629 -1005
  22. package/src/Schema.js +2101 -0
  23. package/src/Str.js +1209 -432
  24. package/src/Sys.js +654 -0
  25. package/src/Time.js +1533 -440
  26. package/src/Utils.js +342 -325
  27. package/src/errors.js +335 -0
  28. package/src/internal/shared.js +415 -0
  29. package/types/index.d.ts +735 -526
  30. package/types/src/Arr.d.ts +533 -145
  31. package/types/src/Arr.d.ts.map +1 -1
  32. package/types/src/Async.d.ts +372 -0
  33. package/types/src/Async.d.ts.map +1 -0
  34. package/types/src/Cache.d.ts +221 -0
  35. package/types/src/Cache.d.ts.map +1 -0
  36. package/types/src/Checker.d.ts +651 -232
  37. package/types/src/Checker.d.ts.map +1 -1
  38. package/types/src/Cli.d.ts +664 -0
  39. package/types/src/Cli.d.ts.map +1 -0
  40. package/types/src/Color.d.ts +300 -0
  41. package/types/src/Color.d.ts.map +1 -0
  42. package/types/src/Crypto.d.ts +620 -0
  43. package/types/src/Crypto.d.ts.map +1 -0
  44. package/types/src/Emitter.d.ts +189 -0
  45. package/types/src/Emitter.d.ts.map +1 -0
  46. package/types/src/Env.d.ts +412 -0
  47. package/types/src/Env.d.ts.map +1 -0
  48. package/types/src/FS.d.ts +717 -238
  49. package/types/src/FS.d.ts.map +1 -1
  50. package/types/src/Func.d.ts +508 -135
  51. package/types/src/Func.d.ts.map +1 -1
  52. package/types/src/Http.d.ts +444 -0
  53. package/types/src/Http.d.ts.map +1 -0
  54. package/types/src/Id.d.ts +199 -66
  55. package/types/src/Id.d.ts.map +1 -1
  56. package/types/src/Logger.d.ts +414 -46
  57. package/types/src/Logger.d.ts.map +1 -1
  58. package/types/src/Num.d.ts +514 -137
  59. package/types/src/Num.d.ts.map +1 -1
  60. package/types/src/Obj.d.ts +453 -104
  61. package/types/src/Obj.d.ts.map +1 -1
  62. package/types/src/SQLite.d.ts +754 -308
  63. package/types/src/SQLite.d.ts.map +1 -1
  64. package/types/src/Schema.d.ts +1189 -0
  65. package/types/src/Schema.d.ts.map +1 -0
  66. package/types/src/Str.d.ts +617 -166
  67. package/types/src/Str.d.ts.map +1 -1
  68. package/types/src/Sys.d.ts +314 -0
  69. package/types/src/Sys.d.ts.map +1 -0
  70. package/types/src/Time.d.ts +759 -204
  71. package/types/src/Time.d.ts.map +1 -1
  72. package/types/src/Utils.d.ts +158 -102
  73. package/types/src/Utils.d.ts.map +1 -1
  74. package/types/src/errors.d.ts +316 -0
  75. package/types/src/errors.d.ts.map +1 -0
  76. package/types/src/internal/shared.d.ts +123 -0
  77. package/types/src/internal/shared.d.ts.map +1 -0
  78. package/types/index.d.ts.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,340 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [2.1.0] - 2026-10-12
8
+
9
+ Every module was read again, line by line. The result: about sixty new functions and options, a hot path
10
+ that is several times faster almost everywhere, a long list of edge cases fixed, and a new documentation
11
+ website. Nothing has to change in a project that used 2.0 the documented way. The few behaviours that
12
+ did change are listed under "Changed".
13
+
14
+ ### Added
15
+
16
+ - **logger**: `redact` hides the keys you name in everything that is logged, `onLog` receives each entry
17
+ (`LogEntry`) to forward it to another service, and `DEBUG=db,http:*` chooses which scopes print their
18
+ debug lines.
19
+ - **fs**: `fs.findUp()` (also `nc.findUp()`) walks up the folders to find a file, `writeFile()` and
20
+ `writeJSON()` take `fsync` to wait for the disk, and watchers emit `"error"`.
21
+ - **checker**: `isHostname`, `isMACAddress`, `isIBAN`, `isE164`, `isObjectId`, `isULID`, `isDataURL`,
22
+ `isMimeType`, `isTimeZone`, `isLocale`, `isCron` and `isASCII`.
23
+ - **env**: `env.example()` writes a `.env.example` from your specification, and `load({ cascade })` reads
24
+ `.env`, `.env.local`, `.env.{mode}` and `.env.{mode}.local` in the usual order.
25
+ - **schema**: `pipe()`, `discriminatedUnion()`, `toJSONSchema()`, object `required()`, `merge()` and
26
+ `keyof()`, number `gt()`, `lt()` and `safe()`, string `date()`, `slug()`, `hex()`, `base64()`, `ulid()`,
27
+ `semver()` and `hostname()`. `safeParse()` gives the list of `issues` without building an error.
28
+ - **http**: `params` fills a path such as `"users/:id"`, `schema` validates the response and gives
29
+ `data` its type,
30
+ `cookies` lists every `Set-Cookie`, headers can be a `Headers` object or pairs, and retries take `jitter`
31
+ and text delays (`"2s"`).
32
+ - **SQLite**: filters and sorts on JSON paths (`"settings.theme"`), `paginate()`, `increment()`,
33
+ `onConflict` for `insert()` and `insertMany()`, `defaultExpression` for columns,
34
+ `transaction({ immediate })`, a `synchronous` option, and reads from views and temporary tables.
35
+ - **time**: `time.parse()` reads a date from text with a pattern, `isSame()`, `range()`, `fromUnix()`, the
36
+ `Do` and `z` tokens, and `formatDuration()` accepts text such as `"90m"`.
37
+ - **id**: `uuidv5()`, `objectId()` and `prefixed()`.
38
+ - **crypto**: `verifyHmac()` for webhook signatures, `generateKeyPair()`, and JSON Web Tokens signed with
39
+ RSA, ECDSA or Ed25519 keys (`RS256`, `PS256`, `ES256`, `EdDSA`...).
40
+ - **cli**: `cli.tree()`, `cli.spin()` runs a promise under a spinner, `args()` learns commands
41
+ (`commands`), `env` fallbacks, `examples` in the help and `choices` for numbers. Tables accept cells of
42
+ several lines. Menus understand Home, End, Page Up, Page Down and Escape.
43
+ - **color**: `color.truncate()` cuts colored text to a width without breaking its colors.
44
+ - **sys**: `shutdown()`, `freePort()` and `localIP()`.
45
+ - **func**: `batch()` groups calls into one, `dedupe()` shares a call that is already running, and
46
+ `pipeAsync()`.
47
+ - **async**: `parallel()`, `rateLimit()` and `abortable()`.
48
+ - **Cache**: `getOrSet(..., { staleWhileRevalidate })` answers with the old value while the new one loads,
49
+ and `resetStats()`.
50
+ - **Emitter**: `on()` and `once()` take `{ signal }`, and `listeners()`.
51
+ - **str**: `upperFirst`, `lowerFirst`, `humanize` and `chunk`. **num**: `pad`, `percentChange` and
52
+ `product`. **arr**: `duplicates`, `xor`, `takeWhile`, `dropWhile` and `toSentence`. **obj**: `update`,
53
+ `sortKeys` and `paths`.
54
+ - **Durations are text everywhere.** The last options that only took milliseconds (`debounce`, `throttle`,
55
+ `memoize`, `retry`, `timeout`, `delay`, `waitFor`...) accept `"5m"` or `"1.5s"` like the rest of the
56
+ package.
57
+ - Errors serialize with `JSON.stringify()` (name, code, message, fields and cause), and `instanceof` works
58
+ across two copies of the package.
59
+
60
+ ### Faster
61
+
62
+ Measured on the same machine, before and after, with Node 24.
63
+
64
+ | Operation | Speedup |
65
+ | --- | --- |
66
+ | `nc.info()` | 124x |
67
+ | `time.parseDuration("5m")` | 68x |
68
+ | `str.plural()` | 57x |
69
+ | `time.format()` with month and day names | 39x |
70
+ | `id.code()` | 39x |
71
+ | `id.uuidv7()` | 28x |
72
+ | `num.format()` | 23x |
73
+ | `checker.isIP()` | 19x |
74
+ | `obj.get()` with a dot path | 14x |
75
+ | `emitter.emit()` | 10x |
76
+ | `schema.is()` on invalid data | 9x |
77
+ | `schema.safeParse()` on invalid data | 5x |
78
+ | `db.get()` and `db.getAll()` | 3x to 4x |
79
+
80
+ `sys.which("node")` went from 23 ms to 0.13 ms on Windows. What is slow in JavaScript (`Intl` objects,
81
+ random bytes, stack traces, prepared statements) is now created once and reused.
82
+
83
+ ### Changed
84
+
85
+ Behaviours that differ from 2.0. Each one fixes something that was wrong or unsafe.
86
+
87
+ - **logger**: `DEBUG=name` only turns on the debug lines of the scopes it names (it used to turn on all
88
+ of them). `DEBUG=*`, `DEBUG=1` and `LOG_LEVEL=debug` show everything.
89
+ - **time**: `startOf()`, `endOf()`, `add()` and `diff()` follow `setTimezone()` instead of the system zone.
90
+ Adding whole days or weeks keeps the time of day when the clocks change in between (it added 24 hours
91
+ per day); `"24h"` stays exact. `add(date, "soon")` throws a `TypeError` (unreadable text used to add
92
+ nothing), and so does a unit that does not exist (it was read as milliseconds).
93
+ - **crypto**: `decrypt()` always fails with a `NodeComfortError` whose code is `ERR_DECRYPT` (malformed
94
+ input used to throw a `TypeError`), and refuses shortened authentication tags. `hash()` and `hmac()`
95
+ throw a `TypeError` for `null`, `undefined`, plain objects and empty secrets. `safeEqual()` returns
96
+ `false` when a value is missing. A wrong key type for a token is a `TypeError`, and the algorithms a
97
+ token may use follow from the key that verifies it.
98
+ - **cli**: leaving a menu or a password prompt with Ctrl+C or Escape rejects with an `AbortError`.
99
+ `cli.table([])` returns `""`. The
100
+ `LineReader` type is gone from the public types.
101
+ - **http**: the query and headers of a call are added to those of the client instead of replacing them.
102
+ A request whose body is a stream is never retried. With `responseType: "stream"` the timeout stops when
103
+ the headers arrive. `download()` writes to a temporary file first, so a failed download never destroys
104
+ the file that was there.
105
+ - **schema**: `pick()`, `omit()`, `partial()` and `extend()` keep `strict()`. Names inherited from
106
+ `Object.prototype` (`toString`, `constructor`) are never read from the input.
107
+ - **SQLite**: a column whose default is `CURRENT_TIMESTAMP` gets the time of the insert (it used to store
108
+ that text), and with `dates: true` the dates SQLite writes itself are read as UTC. Columns of joins and renamed columns are converted like the others, with Node 22.16 or later.
109
+ - **env**: in `load()`, the first file that defines a variable wins, whatever `override` says. `override`
110
+ only decides whether files may replace what was already in the environment. A file you load yourself
111
+ wins over the `.env` loaded at startup, so `.env.local` and `cascade` work without turning anything off.
112
+ A reference such as `${HOST}` takes the value `HOST` really ends up with.
113
+ - **sys**: `run()` rejects on a timeout it cannot read (it used to mean no timeout), and on Windows a
114
+ timeout or an abort stops the whole process tree.
115
+ - **fs**: an atomic write keeps the permissions of the file it replaces.
116
+ - **func**: `retry()` stops as soon as the function is aborted. `memoize()` no longer confuses
117
+ `f(undefined)` with `f(null)`, and accepts `bigint` arguments.
118
+ - **num**: `min()` and `max()` return `NaN` when one value is `NaN`, like `Math.min()`. `sum([0.1, 0.2, 0.3])`
119
+ is `0.6`.
120
+ - **obj**: `isEmpty()` looks inside a `Map` and a `Set`, and is `false` for a `Date`, a `RegExp` and an
121
+ `Error`. `unset()` on an array index removes the item instead of leaving a hole. `clone()` keeps class
122
+ instances and copies buffers. `merge()` never shares a nested object with its inputs.
123
+ - **checker**: `isEmpty()` is `false` for a `Date`, a `RegExp` and an `Error`.
124
+ - **utils**: `JSONString()` never throws, and writes an error with its name, message and stack.
125
+
126
+ ### Fixed
127
+
128
+ - Timers longer than 24.8 days (`wait("30d")`, cache expiry, `timeout()`) no longer fire at once.
129
+ - Object helpers (`pick`, `omit`, `filter`, `mapValues`, `flatten`, `groupBy`, `keyBy`, `countBy`, `props`,
130
+ `env.parse`...) are safe with keys named `__proto__`, `constructor` or `toString`.
131
+ - `safeEqual()` no longer reveals the length of the secret.
132
+ - `str.stripTags()` leaves `a < b and c > d` alone, `unescapeHTML()` accepts invalid code points,
133
+ `capitalize()` handles letters outside the basic plane, and padding counts what you see.
134
+ - `num.formatBytes()` and `num.abbreviate()` change unit when rounding reaches the next one (`"1 MB"`,
135
+ not `"1024 KB"`).
136
+ - `isYesterday()` and `isTomorrow()` are right on the days the clocks change.
137
+ - `cache.getOrSet()` respects a `delete()`, a `clear()` or a `set()` made while the value was loading.
138
+ - A shutdown handler that never finishes can no longer let the process end with a success code.
139
+ - `sys.run()` and `sys.exec()` settle after a timeout or an abort even when something the command started
140
+ keeps its output open. They used to wait for it.
141
+ - An async `onLog` hook that fails no longer stops the process.
142
+ - Deleting a watched folder on Windows sent events without end. The watcher now reports it once and stops.
143
+ - `http.download()` reports a connection cut in the middle as an `HttpError` with the code `ERR_NETWORK`.
144
+ - `str.truncate()` never returns more than the length asked for, even when the omission is longer.
145
+ - `emitter.off()` removes a listener added with `once()`.
146
+ - `http` keeps every `Set-Cookie` header and reads the status of an error response in all cases.
147
+ - Tables no longer fail on very large inputs, and progress bars print labels that contain `$`.
148
+
149
+ ### Documentation
150
+
151
+ - A new website, in English and in French, with a search, a page for every namespace generated from the
152
+ types, and a landing page whose examples are real files, run at every build.
153
+ - The old addresses (`/guide/logger.html`, `/api/str.html`) redirect to the new pages.
154
+ - `docs/API.md` is gone: the reference lives on the website, and in your editor.
155
+
156
+ ## [2.0.0] - 2026-09-12
157
+
158
+ node-comfort becomes a complete, zero-dependency standard library for Node.js: 23 namespaces and classes,
159
+ more than 500 documented and typed functions, and an editor experience checked on every commit.
160
+
161
+ ### Security
162
+
163
+ - **Prototype pollution fixed** in `obj.set()` and `obj.unflatten()` (1.2.0). A path such as
164
+ `"__proto__.isAdmin"` coming from user input could modify `Object.prototype`. Every object helper
165
+ (`set`, `unset`, `merge`, `mergeWith`, `defaults`, `unflatten`, `clone`, `fromEntries`...) now ignores
166
+ `__proto__`, `constructor` and `prototype` keys. **Upgrade if you pass user-controlled paths or objects
167
+ to these functions.**
168
+ - The 1.x filesystem helpers searched the whole folder tree for a file with the same name when a relative
169
+ path did not exist, which could read, overwrite or delete the wrong file. This implicit search is gone
170
+ (see `fs` below).
171
+ - `id.code()` no longer has a modulo bias (rejection sampling).
172
+
173
+ ### Breaking changes
174
+
175
+ Each item explains what to change. Most projects only need the SQLite and Node.js items.
176
+
177
+ 1. **Node.js 22.13 or newer is required** (`engines`). Node 20 reaches end of life, and 1.x already
178
+ required `node:sqlite`, which is only available without a flag since Node 22.13.
179
+ 2. **Deep imports are no longer allowed**: the package defines an `exports` map. Replace
180
+ `require("@ix-xs/node-comfort/src/Logger")` by `require("@ix-xs/node-comfort").logger`, or by the
181
+ official entry point of the namespace: `require("@ix-xs/node-comfort/logger")`.
182
+ 3. **SQLite methods throw instead of returning `{ error }`.** Failures throw a `nc.errors.SQLiteError`
183
+ (with `sql` and `sqliteCode`, for example `"SQLITE_CONSTRAINT_UNIQUE"`). Success values are unchanged.
184
+ ```js
185
+ // 1.x
186
+ const result = await db.insert("users", data);
187
+ if (result.error) console.error(result.error);
188
+ // 2.0
189
+ try {
190
+ db.insert("users", data);
191
+ } catch (error) {
192
+ if (error.sqliteCode === "SQLITE_CONSTRAINT_UNIQUE") console.error("Email already used");
193
+ }
194
+ ```
195
+ `db.transaction()` re-throws the error after rolling back (it used to return `{ error }`).
196
+ 4. **SQLite methods are synchronous.** They return values directly, like the underlying engine. `await`
197
+ keeps working, but `.then()` chains on their results must be removed. `db.transaction()` still accepts
198
+ async callbacks (it then returns a promise).
199
+ 5. **`db.getAll()` no longer limits to 50 rows and no longer sorts by default.** Pass `{ limit: 50 }` to keep
200
+ a limit, and `{ orderBy: "id", direction: "DESC" }` to keep the old order. When `orderBy` is given without
201
+ `direction`, the order is now ascending (it was descending).
202
+ 6. **SQLite tables are read from the database file.** 1.x only knew the tables created with `createTable()`
203
+ in the same process (`get()` returned `undefined` and `getAll()` returned `[]` for the others). Tables are
204
+ now found through `sqlite_master`, and an unknown table throws a `SQLiteError`.
205
+ 7. **SQLite `BOOLEAN` and `JSON` columns are converted.** Reading a `BOOLEAN` column returns `true`/`false`
206
+ (it returned `1`/`0`) and a `JSON` column returns the parsed value. Writing booleans, objects, arrays and
207
+ dates now works (1.x threw "cannot be bound"). Disable with `new nc.SQLite(path, { booleans: false, json: false })`.
208
+ 8. **`warn`, `error` and `fatal` logs are written to stderr**, like `console.warn` and `console.error`.
209
+ Colors are disabled when the output is not a terminal (files, pipes, CI logs) and with `NO_COLOR`;
210
+ use `FORCE_COLOR=1` to force them.
211
+ 9. **Logger markup: `overline` draws a real overline.** It used to strike the text through; use
212
+ `strikethrough` for that.
213
+ 10. **`func.timeout()` rejects with a `nc.errors.TimeoutError`.** It is still an `Error` with the same
214
+ message; only `error.name` (`"TimeoutError"`) and `error.code` (`"ETIMEDOUT"`) are new.
215
+ 11. **`num.round()` rounds negative halves away from zero** (`-2.5` gives `-3`, it gave `-2`), consistently
216
+ with positive numbers, and never returns `-0`. Decimal precision is fixed: `round(1.005, 2)` is `1.01`.
217
+ 12. **`str.length()`, `str.reverse()`, `str.truncate()` and `str.center()` count graphemes**, not UTF-16
218
+ code units: `str.length("👨‍👩‍👧")` is `1`, and emoji or accents are never cut in half.
219
+ 13. **The filesystem helpers no longer search the folder tree** when a relative path does not exist
220
+ (see Security). Use the new `nc.find("config.json")` to search explicitly.
221
+ 14. **`dontCrash()` only removes the listeners it added.** 1.x removed every `uncaughtException`, `exit`
222
+ and signal listener of the process, including those of other libraries. Calling it again replaces the
223
+ previous handlers, and the returned controller has `dispose()`.
224
+ 15. **Stricter or more complete validators**: `isEmail()` accepts international addresses but rejects
225
+ consecutive or trailing dots; `isUUID()` accepts versions 1 to 8 and the nil/max UUIDs (use
226
+ `{ version }` to restrict); `isURL()` accepts an options object (`protocols`, `requireTld`).
227
+ 16. **`JSONString()` converts `Map` to objects and `Set` to arrays** (they gave `{}`), and an object used
228
+ twice is serialized twice instead of being replaced by `"[Circular]"` (only real cycles are).
229
+
230
+ ### Deprecated
231
+
232
+ - `id.hash()`, `id.hmac()` and `id.safeEqual()` moved to `nc.crypto` (same signatures). The aliases keep
233
+ working and will be removed in 3.0.0.
234
+
235
+ ### Added
236
+
237
+ **Packaging and editor experience**
238
+ - Lazy loading: `require("@ix-xs/node-comfort")` takes about 3 ms, and each namespace is loaded the first
239
+ time it is used. A script that only logs never loads the database, the HTTP client or the crypto code.
240
+ - An entry point per namespace (`@ix-xs/node-comfort/str`, `/http`, `/sqlite`...), which keeps ES module
241
+ imports light and makes bundling easier.
242
+ - A documentation website at https://ix-xs.github.io/node-comfort/: guides for every namespace, the full
243
+ API reference, recipes, and search.
244
+ - Complete TypeScript declarations generated from the JSDoc: hover documentation with examples for every
245
+ function, parameter and option, completion after `nc.` and `nodeComfort.`, auto-import of `nc`,
246
+ `nodeComfort` and every flat helper, type guards, typed dot paths, schema inference, generic caches,
247
+ typed events, SQLite columns suggested in `where` filters.
248
+ - ES module named imports (`import { str, isEmail, nc } from "@ix-xs/node-comfort"`) and
249
+ `const { nc } = require(...)`.
250
+ - `docs/API.md`, a complete API reference generated from the types.
251
+ - Tests (`node --test`) and CI on Linux, macOS and Windows with Node 22, 24 and the latest release;
252
+ editor checks with TypeScript 5.9, 6 and 7.
253
+
254
+ **New namespaces and classes**
255
+ - `nc.crypto`: `hashPassword`/`verifyPassword`/`needsRehash` (scrypt), `encrypt`/`decrypt` (AES-256-GCM),
256
+ `signJWT`/`verifyJWT`/`decodeJWT` (HS256/384/512), `totp`/`verifyTOTP`/`totpSecret`/`totpURI` (2FA),
257
+ `hash`, `hmac`, `safeEqual`, `generateKey`, `randomBytes`, `randomInt`, `toBase64`, `fromBase64`.
258
+ - `nc.color`: chainable terminal colors (`color.red.bold("x")`), hex/RGB/256 colors, automatic level
259
+ detection and downgrade, `strip`, `width`, `link`, `gradient`, `create`.
260
+ - `nc.cli`: typed `args` with generated `--help`, `prompt`, `password`, `confirm`, `select`,
261
+ `multiselect`, `spinner`, `progress`, `table`, `box`, `isInteractive`, `size`, `clear`.
262
+ - `nc.sys`: `run` (no shell, Windows-safe arguments), `exec`, `which`, `open`, `onShutdown` (graceful
263
+ shutdown), `isWindows`, `isMac`, `isLinux`, `isCI`, `isDocker`, `isWSL`, `info`, `memory`.
264
+ - `nc.async`: `map`/`forEach`/`filter` with a concurrency limit, `series`, `limit`, `queue` (priorities,
265
+ timeouts), `mutex`, `deferred`, `settle`, `props`, `poll`, `sleep`.
266
+ - `nc.schema`: schema validation with TypeScript inference (`string`, `number`, `boolean`, `bigint`, `date`,
267
+ `literal`, `enum`, `array`, `object`, `union`, `tuple`, `record`, `lazy`, `instanceOf`, `custom`, `coerce`,
268
+ `refine`, `transform`, `default`, `catch`...).
269
+ - `nc.http`: HTTP client on top of `fetch` with JSON, query objects, auth, timeouts, retries with
270
+ `Retry-After`, hooks, clients (`create`/`extend`), downloads with progress.
271
+ - `nc.env`: `.env` parser and loader, typed getters (`number`, `bool`, `port`, `url`, `duration`, `list`,
272
+ `json`, `oneOf`) and `validate()` reporting every invalid variable at once. `NODE_COMFORT_DOTENV=false`
273
+ disables the automatic `.env` loading.
274
+ - `nc.errors`: `NodeComfortError`, `AssertionError`, `TimeoutError`, `AbortError`, `ValidationError`,
275
+ `HttpError`, `JWTError`, `ProcessError`, `SQLiteError`, each with a stable `code`.
276
+ - `nc.Cache`: LRU cache with TTL, `getOrSet` (concurrent loads shared), `wrap`, `prune`, `stats`.
277
+ - `nc.Emitter`: typed event emitter with `once`, `onAny`, `emitAsync`, `waitFor`, `iterate`.
278
+
279
+ **Existing modules**
280
+ - Logger: `trace`, `fatal` and `silent` levels, `LOG_LEVEL`, `LOG_FORMAT=json`, structured fields,
281
+ `createLogger` with scope, file output with rotation, `child` (its `fields` are added to the parent's,
282
+ for per-request loggers), `table`, `box`, `divider`, markup colors in
283
+ hex and RGB, `setLevel`, `configure`; every function returns the logger for chaining.
284
+ - FS: `glob` (relative patterns, or absolute ones that search where they point), `find`, `readLines`,
285
+ `writeFile` (atomic), `ensureFolder`, `ensureFile`, `touch`, `remove`,
286
+ `copy`, `move`, `isFile`, `isFolder`, `folderSize`, `tempFolder`, `sanitizeFilename`, chunked
287
+ `hashFile`, `readJSON` with comments, atomic `writeJSON`, debounced `watch`.
288
+ - Checker: about 30 new guards (`isIP`, `isPort`, `isSemver`, `isJWT`, `isHexColor`, `isBase64`,
289
+ `isCreditCard`, `isArrayOf`, `isOneOf`, `isClass`, `isBlank`...), `assert` and `assertType`.
290
+ - `str`, `num`, `arr`, `obj`, `func`, `time`, `id`: more than 120 new functions, among them `str.dedent`,
291
+ `str.wrap`, `str.plural`, `str.closest`, `str.mask`, `num.currency`, `num.percentile`, `num.parseBytes`,
292
+ `arr.paginate`, `arr.windows`, `arr.cartesian`, `obj.diff`, `obj.mergeWith`, `func.pipe`,
293
+ `time.setLocale`/`time.setTimezone` (every date function in any language and time zone),
294
+ `time.calendar`, `time.cron`, `time.every`, `time.nextRun`, `id.uuidv7`, `id.snowflake`.
295
+ - `num.setLocale()` sets the default language of `format`, `currency`, `percent`, `abbreviate`,
296
+ `ordinal` and `formatBytes`, like `time.setLocale()` does for dates.
297
+ - SQLite: rich `where` operators (`gt`, `in`, `like`, `between`, `$or`...), `insertMany`, `exists`,
298
+ `tables`, `columns`, `hasTable`, `table()` handles, `migrate()` (versioned with `user_version`),
299
+ `backup()`, `vacuum()`, `iterate()`, `fn()` (SQL functions in JavaScript), nested transactions with
300
+ savepoints, prepared statement cache, `readOnly`/`busyTimeout` options, `using` support.
301
+
302
+ ### Fixed
303
+
304
+ - `const { merge } = nc.obj` (and any destructured helper) threw because functions relied on `this`.
305
+ - `titleCase()` broke words with accented letters.
306
+ - Logger groups were not indented consistently, and logging a circular object crashed.
307
+ - `unescapeHTML()` now decodes numeric entities (`&#39;`, `&#x27;`) and `&nbsp;`.
308
+ - `memoize()` no longer keeps rejected promises: a failed call is retried next time.
309
+ - `watch()` follows the real path of what it watches, so a short (8.3) or symlinked path no longer
310
+ crashes the watcher on Windows.
311
+ - Dates, maps, sets and plain objects coming from a worker or a `vm` context are recognized by
312
+ `obj`, `schema`, `time`, `checker.isPlainObject` and `JSONString` (1.x compared prototypes, which
313
+ are not shared between realms).
314
+
315
+ ### Migration checklist
316
+
317
+ 1. Upgrade Node.js to 22.13+.
318
+ 2. Replace deep imports by the namespaces (`nc.logger`, `nc.fs`...).
319
+ 3. SQLite: replace `if (result.error)` checks by `try/catch`, add `{ limit, orderBy, direction: "DESC" }`
320
+ where you relied on the old `getAll()` defaults, and check code that compared `BOOLEAN` columns to `1`/`0`.
321
+ 4. If you parse your application's output, remember that warnings and errors now go to stderr.
322
+ 5. Replace `nc.id.hash/hmac/safeEqual` by `nc.crypto.hash/hmac/safeEqual` (optional until 3.0).
323
+
324
+ ## [1.2.0]
325
+
326
+ ### Added
327
+
328
+ - Namespaces `str`, `num`, `arr`, `obj`, `func`, `time` and `id`.
329
+ - Checker: 22 new guards (`isEmail`, `isURL`, `isUUID`, `isEmpty`, `isPlainObject`...).
330
+ - Logger: `info`, `success`, `warn`, `error` and `debug` levels.
331
+ - FS: `readJSON`, `writeJSON`, `appendFile`, `exists`, `stat`, `fileSize`, `hashFile`, `emptyFolder`.
332
+ - SQLite: `:memory:` databases.
333
+
334
+ ### Fixed
335
+
336
+ - SQLite: the folder of the database was created in one place and the file opened in another.
337
+
338
+ [2.1.0]: https://github.com/ix-xs/node-comfort/compare/v2.0.0...v2.1.0
339
+ [2.0.0]: https://github.com/ix-xs/node-comfort/compare/v1.2.0...v2.0.0
340
+ [1.2.0]: https://github.com/ix-xs/node-comfort/releases/tag/v1.2.0