@ix-xs/node-comfort 1.0.56 → 1.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.
package/README.md ADDED
@@ -0,0 +1,533 @@
1
+ # @ix-xs/node-comfort
2
+
3
+ Small collection of Node.js utilities I use across projects: a logger, a file-system helper, a SQLite wrapper, a runtime type checker, and a few async/process helpers.
4
+
5
+ Everything is plain CommonJS with JSDoc, and ships with `.d.ts` so you get autocomplete in TypeScript and modern editors.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @ix-xs/node-comfort
13
+ # or
14
+ yarn add @ix-xs/node-comfort
15
+ # or
16
+ pnpm add @ix-xs/node-comfort
17
+ ```
18
+
19
+ Requires Node 20+ (uses `node:sqlite` and `process.loadEnvFile`).
20
+
21
+ ---
22
+
23
+ ## Import and global usage
24
+
25
+ The package exposes a **flat API**: all methods are directly on the `nodeComfort` object.
26
+
27
+ ```js
28
+ const nodeComfort = require("@ix-xs/node-comfort");
29
+
30
+ // Logger
31
+ nodeComfort.log("Hello");
32
+ nodeComfort.log("<% greenBright Success %>");
33
+
34
+ // FS
35
+ const files = nodeComfort.getFilesIn(".", true);
36
+ nodeComfort.createFolder("./dist");
37
+
38
+ // Checker
39
+ if (nodeComfort.isArray(files)) {
40
+ nodeComfort.log(`<% cyan Found ${files.length} files %>`);
41
+ }
42
+
43
+ // Utils
44
+ await nodeComfort.wait(500);
45
+ nodeComfort.dontCrash();
46
+
47
+ // SQLite
48
+ const db = new nodeComfort.SQLite();
49
+ await db.createTable({
50
+ name: "users",
51
+ options: {
52
+ id: { type: "INTEGER", primaryKey: true, autoincrement: true },
53
+ name: { type: "TEXT", notNull: true },
54
+ },
55
+ });
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Logger
61
+
62
+ Colorized console logger with a minimal markup syntax and optional timestamps.
63
+
64
+ You wrap styles in delimiters (by default `<%` and `%>`):
65
+
66
+ - styles: `bold`, `italic`, `underline`, `overline`
67
+ - colors: `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `gray`, `black`
68
+ - bright: `redBright`, `greenBright`, `yellowBright`, `blueBright`, `magentaBright`, `cyanBright`, `whiteBright`
69
+ - background: `bgRed`, `bgBlue`, etc.
70
+ - RGB: `rgb(255, 0, 0)`
71
+
72
+ ### Basic usage
73
+
74
+ ```js
75
+ nodeComfort.log("Plain log");
76
+ nodeComfort.log("<% green Success %>");
77
+ nodeComfort.log("<% red bold Error:%> something went wrong");
78
+ nodeComfort.log("<% bgBlue white INFO %> message");
79
+ ```
80
+
81
+ Multi-line strings keep indentation and timestamps readable:
82
+
83
+ ```js
84
+ nodeComfort.log(`
85
+ <% yellow Multi-line %>
86
+ This is line 2
87
+ This is line 3
88
+ `);
89
+ ```
90
+
91
+ ### Timestamp and delimiters
92
+
93
+ ```js
94
+ // Disable timestamps
95
+ nodeComfort.setTimestamp(false);
96
+
97
+ // Change delimiters if "<%" conflicts with other template syntax
98
+ nodeComfort.setDelimiter({ open: "{{", close: "}}" });
99
+ nodeComfort.log("{{red Hello}} world");
100
+ ```
101
+
102
+ ### Groups
103
+
104
+ Groups add indentation to related logs.
105
+
106
+ ```js
107
+ nodeComfort.group("Startup");
108
+
109
+ nodeComfort.log("Loading config…");
110
+ nodeComfort.log("<% green OK %>");
111
+
112
+ nodeComfort.group("DB");
113
+ nodeComfort.log("Connecting…");
114
+ // ...
115
+ nodeComfort.groupEnd(); // end "DB"
116
+
117
+ nodeComfort.groupEnd(); // end "Startup"
118
+ ```
119
+
120
+ ---
121
+
122
+ ## FS
123
+
124
+ File-system helper with safe path resolution, recursive walking, copy/move helpers, and a small watcher abstraction.
125
+
126
+ It resolves relative paths from the **caller file**, not just from `process.cwd()`, which makes it nicer inside libraries.
127
+
128
+ ### Path helpers
129
+
130
+ ```js
131
+ // Absolute path, even if it doesn't exist yet
132
+ const configPath = nodeComfort.createPath("./config/app.json");
133
+
134
+ // Existing folder/file or undefined
135
+ const folder = nodeComfort.getFolder("./src");
136
+ const file = nodeComfort.getFile("package.json");
137
+ ```
138
+
139
+ ### Create folders and files
140
+
141
+ ```js
142
+ // Folders
143
+ nodeComfort.createFolder("./dist"); // true if created
144
+ nodeComfort.createFolder("./dist", true); // force recreate
145
+
146
+ // Files
147
+ nodeComfort.createFile("./dist/info.txt", false, "hello");
148
+ nodeComfort.createFile("./dist/data.json", true, { ok: true });
149
+ ```
150
+
151
+ ### List and delete
152
+
153
+ ```js
154
+ // Folders inside a path
155
+ const dirs = nodeComfort.getFoldersIn("./src", true);
156
+
157
+ // Delete folders matching a filter
158
+ nodeComfort.deleteFoldersIn("./tmp", (folder) =>
159
+ folder.endsWith(".cache"),
160
+ );
161
+
162
+ // Files
163
+ const files = nodeComfort.getFilesIn("./src", true);
164
+
165
+ // Delete files matching a filter
166
+ nodeComfort.deleteFilesIn("./logs", false, (file) =>
167
+ file.endsWith(".log"),
168
+ );
169
+ ```
170
+
171
+ ### Copy
172
+
173
+ ```js
174
+ // Copy one folder
175
+ nodeComfort.copyFolder({
176
+ path: "./templates",
177
+ dest: "./dist/templates",
178
+ recursive: true,
179
+ withFiles: true,
180
+ force: true,
181
+ });
182
+
183
+ // Copy only some folders in a tree
184
+ nodeComfort.copyFoldersIn({
185
+ path: "./src",
186
+ dest: "./dist",
187
+ recursive: true,
188
+ withFiles: false,
189
+ filter: (folder) => folder.endsWith("components"),
190
+ });
191
+
192
+ // Copy files
193
+ nodeComfort.copyFilesIn({
194
+ path: "./src",
195
+ dest: "./dist",
196
+ recursive: true,
197
+ force: true,
198
+ filter: (file) => file.endsWith(".js"),
199
+ });
200
+
201
+ // Copy a single file
202
+ nodeComfort.copyFile({
203
+ path: "./src/index.js",
204
+ dest: "./dist", // or "./dist/index.js"
205
+ force: true,
206
+ });
207
+ ```
208
+
209
+ ### Move
210
+
211
+ Move helpers are basically copy + delete.
212
+
213
+ ```js
214
+ // Move a folder tree
215
+ nodeComfort.moveFolder({
216
+ path: "./build",
217
+ dest: "./dist",
218
+ recursive: true,
219
+ withFiles: true,
220
+ force: true,
221
+ });
222
+
223
+ // Move some folders under a path
224
+ nodeComfort.moveFoldersIn({
225
+ path: "./tmp",
226
+ dest: "./archive",
227
+ recursive: true,
228
+ withFiles: false,
229
+ filter: (folder) => folder.includes("session"),
230
+ });
231
+
232
+ // Move files
233
+ nodeComfort.moveFilesIn({
234
+ path: "./logs",
235
+ dest: "./logs/archive",
236
+ recursive: false,
237
+ force: true,
238
+ filter: (file) => file.endsWith(".log"),
239
+ });
240
+
241
+ // Move a single file
242
+ nodeComfort.moveFile({
243
+ path: "./logs/app.log",
244
+ dest: "./logs/archive/app.log",
245
+ force: true,
246
+ });
247
+ ```
248
+
249
+ ### Read and watch
250
+
251
+ ```js
252
+ // Read a UTF-8 file
253
+ const content = nodeComfort.readFile("./README.md");
254
+
255
+ // Watch a directory
256
+ const watcher = nodeComfort.watch({
257
+ path: "./src",
258
+ recursive: true,
259
+ filter: (event, file) => file.endsWith(".js"),
260
+ });
261
+
262
+ watcher
263
+ .on("change", (file) => {
264
+ nodeComfort.log(`<% cyan File changed: %> ${file}`);
265
+ })
266
+ .on("rename", (file) => {
267
+ nodeComfort.log(`<% yellow File renamed: %> ${file}`);
268
+ });
269
+ ```
270
+
271
+ You can pause, resume and stop watching:
272
+
273
+ ```js
274
+ watcher.pause();
275
+ // ...
276
+ watcher.resume();
277
+ // ...
278
+ watcher.stop();
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Checker
284
+
285
+ Runtime type-checking helpers. Everything is just a boolean guard that works on any input.
286
+
287
+ ```js
288
+ nodeComfort.isArray(); // true[1][2]
289
+ nodeComfort.isArray("nope"); // false
290
+
291
+ nodeComfort.isNumber(42); // true
292
+ nodeComfort.isNumber(NaN); // false
293
+
294
+ nodeComfort.isFunction(() => {}); // true
295
+ nodeComfort.isFunction(class {}); // true
296
+
297
+ nodeComfort.isObject({}); // true
298
+ nodeComfort.isObject([]); // true
299
+ nodeComfort.isObject(null); // false
300
+
301
+ nodeComfort.isBoolean(false); // true
302
+ nodeComfort.isString("hello"); // true
303
+ nodeComfort.isUndefined(undefined); // true
304
+ nodeComfort.isNull(null); // true
305
+
306
+ nodeComfort.isDate(new Date()); // true
307
+ nodeComfort.isMap(new Map()); // true
308
+ nodeComfort.isSet(new Set()); // true
309
+
310
+ nodeComfort.isError(new Error("x")); // true
311
+ nodeComfort.isError({ message: "x" }); // false
312
+ ```
313
+
314
+ ---
315
+
316
+ ## Utils
317
+
318
+ Small async and process helpers.
319
+
320
+ ### wait(ms)
321
+
322
+ ```js
323
+ await nodeComfort.wait(500);
324
+ // code here runs after ~500ms
325
+ ```
326
+
327
+ ### when(predicate, payload?, options?)
328
+
329
+ Polling helper that emits events until a condition is met.
330
+
331
+ ```js
332
+ const task = nodeComfort.when(
333
+ () => Math.random() > 0.8,
334
+ { label: "Lucky" },
335
+ { interval: 200, max: 3, timeout: 5000 },
336
+ );
337
+
338
+ task
339
+ .on("trigger", (payload) => {
340
+ nodeComfort.log(`<% green Triggered: %> ${payload.label}`);
341
+ })
342
+ .on("timeout", () => {
343
+ nodeComfort.log("<% red Timeout %>");
344
+ })
345
+ .on("error", (err) => {
346
+ nodeComfort.log(`<% red Error in predicate %>\n${err}`);
347
+ })
348
+ .start();
349
+ ```
350
+
351
+ - `predicate`: boolean, promise, or function (sync/async) returning a boolean.
352
+ - `payload`: value passed to listeners on `trigger` / `timeout`.
353
+ - `options`:
354
+ - `interval` (ms, default `50`)
355
+ - `timeout` (ms or `null`)
356
+ - `max` (number of triggers or `null` for infinite)
357
+
358
+ ### dontCrash()
359
+
360
+ Installs process-level safety nets for errors, exits and signals.
361
+
362
+ ```js
363
+ nodeComfort.dontCrash();
364
+
365
+ // Or customize:
366
+ nodeComfort.dontCrash()
367
+ .on("error", (err) => {
368
+ console.error("Global error:", err);
369
+ process.exit(1);
370
+ })
371
+ .on("exit", (code) => {
372
+ nodeComfort.log(`<% gray Process exited with code ${code} %>`);
373
+ })
374
+ .on("sig", (signal) => {
375
+ nodeComfort.log(`<% yellow Caught signal ${signal}, exiting… %>`);
376
+ process.exit(0);
377
+ });
378
+ ```
379
+
380
+ Events:
381
+
382
+ - `"error"` – uncaught exceptions + unhandled rejections
383
+ - `"exit"` – `process.on("exit")`
384
+ - `"sig"` – `SIGINT`, `SIGTERM`, `SIGQUIT`
385
+ - `"beforeExit"` – `process.on("beforeExit")`
386
+
387
+ ### JSON helpers
388
+
389
+ ```js
390
+ const json = nodeComfort.JSONString({ hello: "world" });
391
+ // pretty-printed with 4 spaces
392
+
393
+ const data = nodeComfort.JSONParse(json);
394
+ ```
395
+
396
+ ---
397
+
398
+ ## SQLite
399
+
400
+ Thin wrapper over the built-in `node:sqlite` `DatabaseSync` with a small “model” system and a couple of CRUD-style helpers.
401
+
402
+ ### Creating an instance
403
+
404
+ ```js
405
+ // Default: ./db.sqlite (relative to cwd)
406
+ const db = new nodeComfort.SQLite();
407
+
408
+ // Custom path (folder created if needed)
409
+ const db2 = new nodeComfort.SQLite("data/app.sqlite");
410
+ ```
411
+
412
+ The constructor ensures the parent folder exists, and enables WAL journal mode and foreign keys by default.
413
+
414
+ ### Defining a table
415
+
416
+ ```js
417
+ await db.createTable({
418
+ name: "users",
419
+ options: {
420
+ id: {
421
+ type: "INTEGER",
422
+ primaryKey: true,
423
+ autoincrement: true,
424
+ },
425
+ name: {
426
+ type: "TEXT",
427
+ notNull: true,
428
+ },
429
+ role: {
430
+ type: "TEXT",
431
+ values: ["admin", "user"],
432
+ defaultValue: "user",
433
+ },
434
+ createdAt: {
435
+ type: "TEXT",
436
+ defaultValue: new Date().toISOString(),
437
+ },
438
+ },
439
+ });
440
+ ```
441
+
442
+ Models are kept in memory and used for defaults like ordering.
443
+
444
+ ### Insert / update
445
+
446
+ ```js
447
+ // Insert
448
+ await db.set("users", { name: "John" });
449
+
450
+ // Upsert by id
451
+ await db.set("users", { id: 1, role: "admin" });
452
+
453
+ // Upsert with custom where
454
+ await db.set("users", { role: "user" }, { name: "John" });
455
+ ```
456
+
457
+ When `where` or `data.id` is provided, `set`:
458
+
459
+ 1. loads the existing row if it exists,
460
+ 2. merges it with `data`,
461
+ 3. runs `INSERT OR REPLACE`.
462
+
463
+ ### Get / list
464
+
465
+ ```js
466
+ // One row or undefined
467
+ const user = await db.get("users", { name: "John" });
468
+
469
+ // Multiple rows
470
+ const admins = await db.getAll(
471
+ "users",
472
+ { role: "admin" }, // where
473
+ 100, // limit
474
+ 0, // offset
475
+ );
476
+ ```
477
+
478
+ If the table is not registered, `getAll` returns an empty array.
479
+
480
+ ### Delete / clear / count
481
+
482
+ ```js
483
+ // Delete rows
484
+ const res = await db.delete("users", { role: "user" });
485
+ // res.ok, res.changes
486
+
487
+ // Delete all rows
488
+ await db.clearTable("users");
489
+
490
+ // Count rows
491
+ const totalUsers = await db.count("users");
492
+ const adminCount = await db.count("users", { role: "admin" });
493
+ ```
494
+
495
+ ### Drop and close
496
+
497
+ ```js
498
+ await db.deleteTable("users");
499
+ db.close();
500
+ ```
501
+
502
+ ---
503
+
504
+ ## TypeScript / editor support
505
+
506
+ The library is written in JavaScript with JSDoc and compiled to `.d.ts` using `tsconfig.json` (JS input, declaration output only).
507
+
508
+ You can still use it comfortably from TypeScript:
509
+
510
+ ```ts
511
+ import nodeComfort = require("@ix-xs/node-comfort");
512
+
513
+ nodeComfort.log("<% green Type-safe logging %>");
514
+
515
+ const files = nodeComfort.getFilesIn("./src", true);
516
+
517
+ const db = new nodeComfort.SQLite();
518
+ await db.createTable({
519
+ name: "sessions",
520
+ options: {
521
+ id: { type: "TEXT", primaryKey: true },
522
+ data: { type: "TEXT" },
523
+ },
524
+ });
525
+ ```
526
+
527
+ Methods are available directly on the default export (no nested namespaces).
528
+
529
+ ---
530
+
531
+ ## License
532
+
533
+ MIT.
package/index.js CHANGED
@@ -1,15 +1,13 @@
1
- const checker = require("./src/checker");
2
- const cli = require("./src/cli");
3
- const fs = require("./src/fs");
4
- const localStorage = require("./src/localStorage");
5
- const logger = require("./src/logger");
6
- const utils = require("./src/utils");
1
+ const Logger = require("./src/Logger");
2
+ const FS = require("./src/FS");
3
+ const Checker = require("./src/Checker");
4
+ const Utils = require("./src/Utils");
5
+ const SQLite = require("./src/SQLite");
7
6
 
8
7
  module.exports = {
9
- ...checker,
10
- ...cli,
11
- ...fs,
12
- localStorage,
13
- ...logger,
14
- ...utils,
8
+ ...Logger,
9
+ ...FS,
10
+ ...Checker,
11
+ ...Utils,
12
+ SQLite,
15
13
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ix-xs/node-comfort",
3
- "version": "1.0.56",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -8,7 +8,18 @@
8
8
  "test": "echo \"Error: no test specified\" && exit 1"
9
9
  },
10
10
  "keywords": [],
11
- "author": "",
12
- "license": "ISC",
13
- "type": "commonjs"
14
- }
11
+ "author": "ix-xs",
12
+ "license": "MIT",
13
+ "type": "commonjs",
14
+ "engines": {
15
+ "node": ">=20.12.0"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/ix-xs/node-comfort.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/ix-xs/node-comfort/issues"
23
+ },
24
+ "homepage": "https://github.com/ix-xs/node-comfort#readme"
25
+ }