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

|
|
6
8
|

|
|
7
9
|

|
|
8
|
-

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