@ix-xs/node-comfort 1.1.1 → 1.1.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.md +265 -50
- package/package.json +2 -2
- package/src/SQLite.js +719 -174
package/README.md
CHANGED
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
# @ix-xs/node-comfort
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
<div align="center">
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
[](https://www.npmjs.com/package/@ix-xs/node-comfort)
|
|
14
|
+
[](https://www.npmjs.com/package/@ix-xs/node-comfort)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
|
|
3
20
|
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
21
|
|
|
5
|
-
|
|
22
|
+
|
|
23
|
+
Everything is plain CommonJS with JSDoc. The package ships with type information for TypeScript and modern editors.
|
|
24
|
+
|
|
6
25
|
|
|
7
26
|
---
|
|
8
27
|
|
|
28
|
+
|
|
9
29
|
## Installation
|
|
10
30
|
|
|
31
|
+
|
|
11
32
|
```bash
|
|
12
33
|
npm install @ix-xs/node-comfort
|
|
13
34
|
# or
|
|
@@ -16,61 +37,78 @@ yarn add @ix-xs/node-comfort
|
|
|
16
37
|
pnpm add @ix-xs/node-comfort
|
|
17
38
|
```
|
|
18
39
|
|
|
40
|
+
|
|
19
41
|
Requires Node 20+ (uses `node:sqlite` and `process.loadEnvFile`).
|
|
20
42
|
|
|
43
|
+
|
|
21
44
|
---
|
|
22
45
|
|
|
46
|
+
|
|
23
47
|
## Import and global usage
|
|
24
48
|
|
|
25
|
-
|
|
49
|
+
|
|
50
|
+
The package exposes a **flat API**: all utilities are directly on the `nodeComfort` object.
|
|
51
|
+
|
|
26
52
|
|
|
27
53
|
```js
|
|
28
54
|
const nodeComfort = require("@ix-xs/node-comfort");
|
|
29
55
|
|
|
56
|
+
|
|
30
57
|
// Logger
|
|
31
58
|
nodeComfort.log("Hello");
|
|
32
59
|
nodeComfort.log("<% greenBright Success %>");
|
|
33
60
|
|
|
61
|
+
|
|
34
62
|
// FS
|
|
35
63
|
const files = nodeComfort.getFilesIn(".", true);
|
|
36
64
|
nodeComfort.createFolder("./dist");
|
|
37
65
|
|
|
66
|
+
|
|
38
67
|
// Checker
|
|
39
68
|
if (nodeComfort.isArray(files)) {
|
|
40
69
|
nodeComfort.log(`<% cyan Found ${files.length} files %>`);
|
|
41
70
|
}
|
|
42
71
|
|
|
72
|
+
|
|
43
73
|
// Utils
|
|
44
74
|
await nodeComfort.wait(500);
|
|
45
75
|
nodeComfort.dontCrash();
|
|
46
76
|
|
|
77
|
+
|
|
47
78
|
// SQLite
|
|
48
79
|
const db = new nodeComfort.SQLite();
|
|
49
80
|
await db.createTable({
|
|
50
81
|
name: "users",
|
|
51
|
-
|
|
82
|
+
columns: {
|
|
52
83
|
id: { type: "INTEGER", primaryKey: true, autoincrement: true },
|
|
53
84
|
name: { type: "TEXT", notNull: true },
|
|
54
85
|
},
|
|
55
86
|
});
|
|
56
87
|
```
|
|
57
88
|
|
|
89
|
+
|
|
58
90
|
---
|
|
59
91
|
|
|
92
|
+
|
|
60
93
|
## Logger
|
|
61
94
|
|
|
95
|
+
|
|
62
96
|
Colorized console logger with a minimal markup syntax and optional timestamps.
|
|
63
97
|
|
|
98
|
+
|
|
64
99
|
You wrap styles in delimiters (by default `<%` and `%>`):
|
|
65
100
|
|
|
101
|
+
|
|
66
102
|
- styles: `bold`, `italic`, `underline`, `overline`
|
|
67
103
|
- colors: `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `gray`, `black`
|
|
68
104
|
- bright: `redBright`, `greenBright`, `yellowBright`, `blueBright`, `magentaBright`, `cyanBright`, `whiteBright`
|
|
69
105
|
- background: `bgRed`, `bgBlue`, etc.
|
|
70
106
|
- RGB: `rgb(255, 0, 0)`
|
|
71
107
|
|
|
108
|
+
|
|
72
109
|
### Basic usage
|
|
73
110
|
|
|
111
|
+
|
|
74
112
|
```js
|
|
75
113
|
nodeComfort.log("Plain log");
|
|
76
114
|
nodeComfort.log("<% green Success %>");
|
|
@@ -78,8 +116,10 @@ nodeComfort.log("<% red bold Error:%> something went wrong");
|
|
|
78
116
|
nodeComfort.log("<% bgBlue white INFO %> message");
|
|
79
117
|
```
|
|
80
118
|
|
|
119
|
+
|
|
81
120
|
Multi-line strings keep indentation and timestamps readable:
|
|
82
121
|
|
|
122
|
+
|
|
83
123
|
```js
|
|
84
124
|
nodeComfort.log(`
|
|
85
125
|
<% yellow Multi-line %>
|
|
@@ -88,88 +128,114 @@ This is line 3
|
|
|
88
128
|
`);
|
|
89
129
|
```
|
|
90
130
|
|
|
131
|
+
|
|
91
132
|
### Timestamp and delimiters
|
|
92
133
|
|
|
134
|
+
|
|
93
135
|
```js
|
|
94
136
|
// Disable timestamps
|
|
95
137
|
nodeComfort.setTimestamp(false);
|
|
96
138
|
|
|
139
|
+
|
|
97
140
|
// Change delimiters if "<%" conflicts with other template syntax
|
|
98
141
|
nodeComfort.setDelimiter({ open: "{{", close: "}}" });
|
|
99
142
|
nodeComfort.log("{{red Hello}} world");
|
|
100
143
|
```
|
|
101
144
|
|
|
145
|
+
|
|
102
146
|
### Groups
|
|
103
147
|
|
|
148
|
+
|
|
104
149
|
Groups add indentation to related logs.
|
|
105
150
|
|
|
151
|
+
|
|
106
152
|
```js
|
|
107
153
|
nodeComfort.group("Startup");
|
|
108
154
|
|
|
155
|
+
|
|
109
156
|
nodeComfort.log("Loading config…");
|
|
110
157
|
nodeComfort.log("<% green OK %>");
|
|
111
158
|
|
|
159
|
+
|
|
112
160
|
nodeComfort.group("DB");
|
|
113
161
|
nodeComfort.log("Connecting…");
|
|
114
162
|
// ...
|
|
115
163
|
nodeComfort.groupEnd(); // end "DB"
|
|
116
164
|
|
|
165
|
+
|
|
117
166
|
nodeComfort.groupEnd(); // end "Startup"
|
|
118
167
|
```
|
|
119
168
|
|
|
169
|
+
|
|
120
170
|
---
|
|
121
171
|
|
|
172
|
+
|
|
122
173
|
## FS
|
|
123
174
|
|
|
175
|
+
|
|
124
176
|
File-system helper with safe path resolution, recursive walking, copy/move helpers, and a small watcher abstraction.
|
|
125
177
|
|
|
178
|
+
|
|
126
179
|
It resolves relative paths from the **caller file**, not just from `process.cwd()`, which makes it nicer inside libraries.
|
|
127
180
|
|
|
181
|
+
|
|
128
182
|
### Path helpers
|
|
129
183
|
|
|
184
|
+
|
|
130
185
|
```js
|
|
131
186
|
// Absolute path, even if it doesn't exist yet
|
|
132
187
|
const configPath = nodeComfort.createPath("./config/app.json");
|
|
133
188
|
|
|
189
|
+
|
|
134
190
|
// Existing folder/file or undefined
|
|
135
191
|
const folder = nodeComfort.getFolder("./src");
|
|
136
192
|
const file = nodeComfort.getFile("package.json");
|
|
137
193
|
```
|
|
138
194
|
|
|
195
|
+
|
|
139
196
|
### Create folders and files
|
|
140
197
|
|
|
198
|
+
|
|
141
199
|
```js
|
|
142
200
|
// Folders
|
|
143
201
|
nodeComfort.createFolder("./dist"); // true if created
|
|
144
202
|
nodeComfort.createFolder("./dist", true); // force recreate
|
|
145
203
|
|
|
204
|
+
|
|
146
205
|
// Files
|
|
147
206
|
nodeComfort.createFile("./dist/info.txt", false, "hello");
|
|
148
207
|
nodeComfort.createFile("./dist/data.json", true, { ok: true });
|
|
149
208
|
```
|
|
150
209
|
|
|
210
|
+
|
|
151
211
|
### List and delete
|
|
152
212
|
|
|
213
|
+
|
|
153
214
|
```js
|
|
154
215
|
// Folders inside a path
|
|
155
216
|
const dirs = nodeComfort.getFoldersIn("./src", true);
|
|
156
217
|
|
|
218
|
+
|
|
157
219
|
// Delete folders matching a filter
|
|
158
220
|
nodeComfort.deleteFoldersIn("./tmp", (folder) =>
|
|
159
221
|
folder.endsWith(".cache"),
|
|
160
222
|
);
|
|
161
223
|
|
|
224
|
+
|
|
162
225
|
// Files
|
|
163
226
|
const files = nodeComfort.getFilesIn("./src", true);
|
|
164
227
|
|
|
228
|
+
|
|
165
229
|
// Delete files matching a filter
|
|
166
230
|
nodeComfort.deleteFilesIn("./logs", false, (file) =>
|
|
167
231
|
file.endsWith(".log"),
|
|
168
232
|
);
|
|
169
233
|
```
|
|
170
234
|
|
|
235
|
+
|
|
171
236
|
### Copy
|
|
172
237
|
|
|
238
|
+
|
|
173
239
|
```js
|
|
174
240
|
// Copy one folder
|
|
175
241
|
nodeComfort.copyFolder({
|
|
@@ -180,6 +246,7 @@ nodeComfort.copyFolder({
|
|
|
180
246
|
force: true,
|
|
181
247
|
});
|
|
182
248
|
|
|
249
|
+
|
|
183
250
|
// Copy only some folders in a tree
|
|
184
251
|
nodeComfort.copyFoldersIn({
|
|
185
252
|
path: "./src",
|
|
@@ -189,6 +256,7 @@ nodeComfort.copyFoldersIn({
|
|
|
189
256
|
filter: (folder) => folder.endsWith("components"),
|
|
190
257
|
});
|
|
191
258
|
|
|
259
|
+
|
|
192
260
|
// Copy files
|
|
193
261
|
nodeComfort.copyFilesIn({
|
|
194
262
|
path: "./src",
|
|
@@ -198,6 +266,7 @@ nodeComfort.copyFilesIn({
|
|
|
198
266
|
filter: (file) => file.endsWith(".js"),
|
|
199
267
|
});
|
|
200
268
|
|
|
269
|
+
|
|
201
270
|
// Copy a single file
|
|
202
271
|
nodeComfort.copyFile({
|
|
203
272
|
path: "./src/index.js",
|
|
@@ -206,10 +275,13 @@ nodeComfort.copyFile({
|
|
|
206
275
|
});
|
|
207
276
|
```
|
|
208
277
|
|
|
278
|
+
|
|
209
279
|
### Move
|
|
210
280
|
|
|
281
|
+
|
|
211
282
|
Move helpers are basically copy + delete.
|
|
212
283
|
|
|
284
|
+
|
|
213
285
|
```js
|
|
214
286
|
// Move a folder tree
|
|
215
287
|
nodeComfort.moveFolder({
|
|
@@ -220,6 +292,7 @@ nodeComfort.moveFolder({
|
|
|
220
292
|
force: true,
|
|
221
293
|
});
|
|
222
294
|
|
|
295
|
+
|
|
223
296
|
// Move some folders under a path
|
|
224
297
|
nodeComfort.moveFoldersIn({
|
|
225
298
|
path: "./tmp",
|
|
@@ -229,6 +302,7 @@ nodeComfort.moveFoldersIn({
|
|
|
229
302
|
filter: (folder) => folder.includes("session"),
|
|
230
303
|
});
|
|
231
304
|
|
|
305
|
+
|
|
232
306
|
// Move files
|
|
233
307
|
nodeComfort.moveFilesIn({
|
|
234
308
|
path: "./logs",
|
|
@@ -238,6 +312,7 @@ nodeComfort.moveFilesIn({
|
|
|
238
312
|
filter: (file) => file.endsWith(".log"),
|
|
239
313
|
});
|
|
240
314
|
|
|
315
|
+
|
|
241
316
|
// Move a single file
|
|
242
317
|
nodeComfort.moveFile({
|
|
243
318
|
path: "./logs/app.log",
|
|
@@ -246,12 +321,15 @@ nodeComfort.moveFile({
|
|
|
246
321
|
});
|
|
247
322
|
```
|
|
248
323
|
|
|
324
|
+
|
|
249
325
|
### Read and watch
|
|
250
326
|
|
|
327
|
+
|
|
251
328
|
```js
|
|
252
329
|
// Read a UTF-8 file
|
|
253
330
|
const content = nodeComfort.readFile("./README.md");
|
|
254
331
|
|
|
332
|
+
|
|
255
333
|
// Watch a directory
|
|
256
334
|
const watcher = nodeComfort.watch({
|
|
257
335
|
path: "./src",
|
|
@@ -259,6 +337,7 @@ const watcher = nodeComfort.watch({
|
|
|
259
337
|
filter: (event, file) => file.endsWith(".js"),
|
|
260
338
|
});
|
|
261
339
|
|
|
340
|
+
|
|
262
341
|
watcher
|
|
263
342
|
.on("change", (file) => {
|
|
264
343
|
nodeComfort.log(`<% cyan File changed: %> ${file}`);
|
|
@@ -268,8 +347,10 @@ watcher
|
|
|
268
347
|
});
|
|
269
348
|
```
|
|
270
349
|
|
|
350
|
+
|
|
271
351
|
You can pause, resume and stop watching:
|
|
272
352
|
|
|
353
|
+
|
|
273
354
|
```js
|
|
274
355
|
watcher.pause();
|
|
275
356
|
// ...
|
|
@@ -278,56 +359,74 @@ watcher.resume();
|
|
|
278
359
|
watcher.stop();
|
|
279
360
|
```
|
|
280
361
|
|
|
362
|
+
|
|
281
363
|
---
|
|
282
364
|
|
|
365
|
+
|
|
283
366
|
## Checker
|
|
284
367
|
|
|
368
|
+
|
|
285
369
|
Runtime type-checking helpers. Everything is just a boolean guard that works on any input.
|
|
286
370
|
|
|
371
|
+
|
|
287
372
|
```js
|
|
288
373
|
nodeComfort.isArray(); // true[1][2]
|
|
289
|
-
nodeComfort.isArray("nope");
|
|
374
|
+
nodeComfort.isArray("nope"); // false
|
|
375
|
+
|
|
290
376
|
|
|
291
|
-
nodeComfort.isNumber(42);
|
|
292
|
-
nodeComfort.isNumber(NaN);
|
|
377
|
+
nodeComfort.isNumber(42); // true
|
|
378
|
+
nodeComfort.isNumber(NaN); // false
|
|
293
379
|
|
|
294
|
-
nodeComfort.isFunction(() => {}); // true
|
|
295
|
-
nodeComfort.isFunction(class {}); // true
|
|
296
380
|
|
|
297
|
-
nodeComfort.
|
|
298
|
-
nodeComfort.
|
|
299
|
-
nodeComfort.isObject(null); // false
|
|
381
|
+
nodeComfort.isFunction(() => {}); // true
|
|
382
|
+
nodeComfort.isFunction(class {}); // true
|
|
300
383
|
|
|
301
|
-
nodeComfort.isBoolean(false); // true
|
|
302
|
-
nodeComfort.isString("hello"); // true
|
|
303
|
-
nodeComfort.isUndefined(undefined); // true
|
|
304
|
-
nodeComfort.isNull(null); // true
|
|
305
384
|
|
|
306
|
-
nodeComfort.
|
|
307
|
-
nodeComfort.
|
|
308
|
-
nodeComfort.
|
|
385
|
+
nodeComfort.isObject({}); // true
|
|
386
|
+
nodeComfort.isObject([]); // true
|
|
387
|
+
nodeComfort.isObject(null); // false
|
|
309
388
|
|
|
310
|
-
|
|
389
|
+
|
|
390
|
+
nodeComfort.isBoolean(false); // true
|
|
391
|
+
nodeComfort.isString("hello"); // true
|
|
392
|
+
nodeComfort.isUndefined(undefined); // true
|
|
393
|
+
nodeComfort.isNull(null); // true
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
nodeComfort.isDate(new Date()); // true
|
|
397
|
+
nodeComfort.isMap(new Map()); // true
|
|
398
|
+
nodeComfort.isSet(new Set()); // true
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
nodeComfort.isError(new Error("x")); // true
|
|
311
402
|
nodeComfort.isError({ message: "x" }); // false
|
|
312
403
|
```
|
|
313
404
|
|
|
405
|
+
|
|
314
406
|
---
|
|
315
407
|
|
|
408
|
+
|
|
316
409
|
## Utils
|
|
317
410
|
|
|
411
|
+
|
|
318
412
|
Small async and process helpers.
|
|
319
413
|
|
|
414
|
+
|
|
320
415
|
### wait(ms)
|
|
321
416
|
|
|
417
|
+
|
|
322
418
|
```js
|
|
323
419
|
await nodeComfort.wait(500);
|
|
324
420
|
// code here runs after ~500ms
|
|
325
421
|
```
|
|
326
422
|
|
|
423
|
+
|
|
327
424
|
### when(predicate, payload?, options?)
|
|
328
425
|
|
|
426
|
+
|
|
329
427
|
Polling helper that emits events until a condition is met.
|
|
330
428
|
|
|
429
|
+
|
|
331
430
|
```js
|
|
332
431
|
const task = nodeComfort.when(
|
|
333
432
|
() => Math.random() > 0.8,
|
|
@@ -335,6 +434,7 @@ const task = nodeComfort.when(
|
|
|
335
434
|
{ interval: 200, max: 3, timeout: 5000 },
|
|
336
435
|
);
|
|
337
436
|
|
|
437
|
+
|
|
338
438
|
task
|
|
339
439
|
.on("trigger", (payload) => {
|
|
340
440
|
nodeComfort.log(`<% green Triggered: %> ${payload.label}`);
|
|
@@ -348,6 +448,7 @@ task
|
|
|
348
448
|
.start();
|
|
349
449
|
```
|
|
350
450
|
|
|
451
|
+
|
|
351
452
|
- `predicate`: boolean, promise, or function (sync/async) returning a boolean.
|
|
352
453
|
- `payload`: value passed to listeners on `trigger` / `timeout`.
|
|
353
454
|
- `options`:
|
|
@@ -355,13 +456,17 @@ task
|
|
|
355
456
|
- `timeout` (ms or `null`)
|
|
356
457
|
- `max` (number of triggers or `null` for infinite)
|
|
357
458
|
|
|
459
|
+
|
|
358
460
|
### dontCrash()
|
|
359
461
|
|
|
462
|
+
|
|
360
463
|
Installs process-level safety nets for errors, exits and signals.
|
|
361
464
|
|
|
465
|
+
|
|
362
466
|
```js
|
|
363
467
|
nodeComfort.dontCrash();
|
|
364
468
|
|
|
469
|
+
|
|
365
470
|
// Or customize:
|
|
366
471
|
nodeComfort.dontCrash()
|
|
367
472
|
.on("error", (err) => {
|
|
@@ -377,46 +482,60 @@ nodeComfort.dontCrash()
|
|
|
377
482
|
});
|
|
378
483
|
```
|
|
379
484
|
|
|
485
|
+
|
|
380
486
|
Events:
|
|
381
487
|
|
|
488
|
+
|
|
382
489
|
- `"error"` – uncaught exceptions + unhandled rejections
|
|
383
490
|
- `"exit"` – `process.on("exit")`
|
|
384
491
|
- `"sig"` – `SIGINT`, `SIGTERM`, `SIGQUIT`
|
|
385
492
|
- `"beforeExit"` – `process.on("beforeExit")`
|
|
386
493
|
|
|
494
|
+
|
|
387
495
|
### JSON helpers
|
|
388
496
|
|
|
497
|
+
|
|
389
498
|
```js
|
|
390
499
|
const json = nodeComfort.JSONString({ hello: "world" });
|
|
391
500
|
// pretty-printed with 4 spaces
|
|
392
501
|
|
|
502
|
+
|
|
393
503
|
const data = nodeComfort.JSONParse(json);
|
|
394
504
|
```
|
|
395
505
|
|
|
506
|
+
|
|
396
507
|
---
|
|
397
508
|
|
|
509
|
+
|
|
398
510
|
## SQLite
|
|
399
511
|
|
|
400
|
-
|
|
512
|
+
|
|
513
|
+
Thin wrapper over the built-in `node:sqlite` `DatabaseSync` with a small "model" system and CRUD-style helpers.
|
|
514
|
+
|
|
401
515
|
|
|
402
516
|
### Creating an instance
|
|
403
517
|
|
|
518
|
+
|
|
404
519
|
```js
|
|
405
520
|
// Default: ./db.sqlite (relative to cwd)
|
|
406
521
|
const db = new nodeComfort.SQLite();
|
|
407
522
|
|
|
523
|
+
|
|
408
524
|
// Custom path (folder created if needed)
|
|
409
525
|
const db2 = new nodeComfort.SQLite("data/app.sqlite");
|
|
410
526
|
```
|
|
411
527
|
|
|
528
|
+
|
|
412
529
|
The constructor ensures the parent folder exists, and enables WAL journal mode and foreign keys by default.
|
|
413
530
|
|
|
531
|
+
|
|
414
532
|
### Defining a table
|
|
415
533
|
|
|
534
|
+
|
|
416
535
|
```js
|
|
417
536
|
await db.createTable({
|
|
418
537
|
name: "users",
|
|
419
|
-
|
|
538
|
+
columns: {
|
|
420
539
|
id: {
|
|
421
540
|
type: "INTEGER",
|
|
422
541
|
primaryKey: true,
|
|
@@ -439,95 +558,191 @@ await db.createTable({
|
|
|
439
558
|
});
|
|
440
559
|
```
|
|
441
560
|
|
|
561
|
+
|
|
442
562
|
Models are kept in memory and used for defaults like ordering.
|
|
443
563
|
|
|
444
|
-
|
|
564
|
+
|
|
565
|
+
The `columns` property is the new, recommended way. The legacy `options` property is still supported for backward compatibility.
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
### Insert / update / upsert
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
```js
|
|
572
|
+
// Insert a new row
|
|
573
|
+
await db.insert("users", { name: "John" });
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
// Update rows matching a where clause
|
|
577
|
+
await db.update("users", { role: "admin" }, { name: "John" });
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
// Upsert (INSERT with ON CONFLICT DO UPDATE)
|
|
581
|
+
await db.upsert("users", { id: 1, role: "user" }, ["id"]);
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
When `where` or `data.id` is provided, `set` (legacy helper):
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
1. loads the existing row if it exists,
|
|
589
|
+
2. merges it with `data`,
|
|
590
|
+
3. inserts or updates depending on whether the row exists.
|
|
591
|
+
|
|
445
592
|
|
|
446
593
|
```js
|
|
447
594
|
// Insert
|
|
448
595
|
await db.set("users", { name: "John" });
|
|
449
596
|
|
|
597
|
+
|
|
450
598
|
// Upsert by id
|
|
451
599
|
await db.set("users", { id: 1, role: "admin" });
|
|
452
600
|
|
|
601
|
+
|
|
453
602
|
// Upsert with custom where
|
|
454
603
|
await db.set("users", { role: "user" }, { name: "John" });
|
|
455
604
|
```
|
|
456
605
|
|
|
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
606
|
|
|
463
607
|
### Get / list
|
|
464
608
|
|
|
609
|
+
|
|
465
610
|
```js
|
|
466
611
|
// One row or undefined
|
|
467
612
|
const user = await db.get("users", { name: "John" });
|
|
468
613
|
|
|
469
|
-
|
|
614
|
+
|
|
615
|
+
// Multiple rows with pagination and ordering
|
|
470
616
|
const admins = await db.getAll(
|
|
471
617
|
"users",
|
|
472
618
|
{ role: "admin" }, // where
|
|
473
|
-
|
|
474
|
-
|
|
619
|
+
{
|
|
620
|
+
limit: 100,
|
|
621
|
+
offset: 0,
|
|
622
|
+
orderBy: "createdAt",
|
|
623
|
+
direction: "DESC",
|
|
624
|
+
},
|
|
475
625
|
);
|
|
476
626
|
```
|
|
477
627
|
|
|
628
|
+
|
|
478
629
|
If the table is not registered, `getAll` returns an empty array.
|
|
479
630
|
|
|
631
|
+
|
|
480
632
|
### Delete / clear / count
|
|
481
633
|
|
|
634
|
+
|
|
482
635
|
```js
|
|
483
636
|
// Delete rows
|
|
484
637
|
const res = await db.delete("users", { role: "user" });
|
|
485
638
|
// res.ok, res.changes
|
|
486
639
|
|
|
640
|
+
|
|
487
641
|
// Delete all rows
|
|
488
642
|
await db.clearTable("users");
|
|
489
643
|
|
|
644
|
+
|
|
490
645
|
// Count rows
|
|
491
646
|
const totalUsers = await db.count("users");
|
|
492
647
|
const adminCount = await db.count("users", { role: "admin" });
|
|
493
648
|
```
|
|
494
649
|
|
|
495
|
-
|
|
650
|
+
|
|
651
|
+
### Indexes and constraints
|
|
652
|
+
|
|
496
653
|
|
|
497
654
|
```js
|
|
498
|
-
|
|
499
|
-
db.
|
|
500
|
-
|
|
655
|
+
// Create a composite unique index
|
|
656
|
+
await db.createIndex("UserAuthMethod", {
|
|
657
|
+
columns: ["provider", "providerUserId"],
|
|
658
|
+
unique: true,
|
|
659
|
+
});
|
|
501
660
|
|
|
502
|
-
---
|
|
503
661
|
|
|
504
|
-
|
|
662
|
+
// Create a non-unique index
|
|
663
|
+
await db.createIndex("Post", {
|
|
664
|
+
columns: ["userId"],
|
|
665
|
+
});
|
|
666
|
+
```
|
|
505
667
|
|
|
506
|
-
The library is written in JavaScript with JSDoc and compiled to `.d.ts` using `tsconfig.json` (JS input, declaration output only).
|
|
507
668
|
|
|
508
|
-
|
|
669
|
+
```js
|
|
670
|
+
// Table-level constraints
|
|
671
|
+
await db.createTable({
|
|
672
|
+
name: "UserAuthMethod",
|
|
673
|
+
columns: {
|
|
674
|
+
id: { type: "INTEGER", primaryKey: true, autoincrement: true },
|
|
675
|
+
userId: { type: "INTEGER", notNull: true },
|
|
676
|
+
provider: { type: "TEXT", notNull: true },
|
|
677
|
+
providerUserId: { type: "TEXT", notNull: true },
|
|
678
|
+
},
|
|
679
|
+
constraints: [
|
|
680
|
+
{
|
|
681
|
+
type: "unique",
|
|
682
|
+
columns: ["provider", "providerUserId"],
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
type: "foreignKey",
|
|
686
|
+
columns: ["userId"],
|
|
687
|
+
references: { table: "User", columns: ["id"] },
|
|
688
|
+
onDelete: "CASCADE",
|
|
689
|
+
},
|
|
690
|
+
],
|
|
691
|
+
});
|
|
692
|
+
```
|
|
509
693
|
|
|
510
|
-
```ts
|
|
511
|
-
import nodeComfort = require("@ix-xs/node-comfort");
|
|
512
694
|
|
|
513
|
-
|
|
695
|
+
### Transactions
|
|
514
696
|
|
|
515
|
-
const files = nodeComfort.getFilesIn("./src", true);
|
|
516
697
|
|
|
517
|
-
|
|
518
|
-
await db.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
698
|
+
```js
|
|
699
|
+
const result = await db.transaction(async () => {
|
|
700
|
+
await db.insert("User", {
|
|
701
|
+
email: "a@b.com",
|
|
702
|
+
createdAt: Date.now(),
|
|
703
|
+
});
|
|
704
|
+
await db.insert("Profile", {
|
|
705
|
+
userId: 1,
|
|
706
|
+
nickname: "John",
|
|
707
|
+
});
|
|
708
|
+
return { ok: true };
|
|
524
709
|
});
|
|
525
710
|
```
|
|
526
711
|
|
|
527
|
-
|
|
712
|
+
|
|
713
|
+
If the callback throws, the transaction is rolled back.
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
### Raw SQL helpers
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
```js
|
|
720
|
+
// Execute a write statement (INSERT/UPDATE/DELETE/CREATE/PRAGMA)
|
|
721
|
+
await db.exec("CREATE INDEX IF NOT EXISTS idx_email ON users (email)");
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
// Execute a query and get one row
|
|
725
|
+
const user = await db.queryOne("SELECT * FROM users WHERE id = ?", );[1]
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
// Execute a query and get all rows
|
|
729
|
+
const users = await db.queryAll("SELECT * FROM users WHERE role = ?", ["admin"]);
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
### Drop and close
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
```js
|
|
737
|
+
await db.deleteTable("users");
|
|
738
|
+
db.close();
|
|
739
|
+
```
|
|
740
|
+
|
|
528
741
|
|
|
529
742
|
---
|
|
530
743
|
|
|
744
|
+
|
|
531
745
|
## License
|
|
532
746
|
|
|
747
|
+
|
|
533
748
|
MIT.
|