qznt 1.0.1 → 1.0.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 +15 -12
- package/dist/index.cjs +76 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +45 -13
- package/dist/index.d.ts +45 -13
- package/dist/index.js +73 -14
- package/dist/index.js.map +1 -1
- package/dist/metafile-cjs.json +1 -1
- package/dist/metafile-esm.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# qznt
|
|
2
2
|
|
|
3
|
-
**qznt** (pronounced as in _ex-quisite_) is a
|
|
3
|
+
**qznt** (pronounced as in _ex-quisite_) is a lightweight, typed, high-performant utility toolkit for modern TypeScript/JavaScript and Node.js environments.
|
|
4
4
|
|
|
5
5
|
## 🚀 Installation
|
|
6
6
|
|
|
@@ -23,20 +23,21 @@ import { $ } from "qznt";
|
|
|
23
23
|
// or
|
|
24
24
|
import { obj, Loop, date } from "qznt";
|
|
25
25
|
|
|
26
|
-
//
|
|
26
|
+
// Nested object access (defaults to "dark" if mode is undefined)
|
|
27
27
|
const theme = qznt.obj.get(settings, "ui.theme.mode", "dark");
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
const
|
|
29
|
+
// Readable durations
|
|
30
|
+
const time = $.date.duration(Date.now() + 5000); // "in 5 seconds"
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## 📦 Namespaces
|
|
34
34
|
|
|
35
|
-
- **`qznt.arr` (
|
|
35
|
+
- **`qznt.arr` (Arrays)**: Advanced `chunk`, `cluster`, `shuffle`, `unique`, and `seqMap`
|
|
36
36
|
- **`qznt.async` (Promises)**: `retry` logic with exponential backoff and delay
|
|
37
37
|
- **`qznt.date` (Time)**: Shorthand parsing (`"1h 30m"`), `duration` (digital/hms), and `eta`
|
|
38
38
|
- **`qznt.fn` (Functions)**: `memoize` with TTL and custom resolvers
|
|
39
39
|
- **`qznt.format` (Strings)**: `currency`, `memory` (bytes), `ordinal`, and `compactNumber`
|
|
40
|
+
- **`qznt.fs` (File System)**: Efficient recursive directory scanning with `readDir`
|
|
40
41
|
- **`qznt.is` (Predicates)**: Type guards: `is.today`, `is.empty`, `is.object`, and `is.sorted`
|
|
41
42
|
- **`qznt.math` (Calculations)**: `lerp`, `invLerp`, `remap`, `percent`, and `sum`
|
|
42
43
|
- **`qznt.num` (Numbers)**: Essential logic like `clamp` and range handling
|
|
@@ -45,11 +46,13 @@ const timeRemaining = $.date.duration(Date.now() + 5000); // "5 seconds"
|
|
|
45
46
|
- **`qznt.timing` (Execution)**: `debounce`, `throttle`, and promise-based `wait`
|
|
46
47
|
- **`qznt.to` (Transformations)**: Powerful data mappers like `to.record`
|
|
47
48
|
|
|
49
|
+
_These are just the highlights, there's more inside._
|
|
50
|
+
|
|
48
51
|
## ✨ Featured Utilities
|
|
49
52
|
|
|
50
53
|
### The Smart `Loop`
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
`qznt.Loop` ensures async tasks never overlap. It waits for execution to finish before scheduling the next interval, and supports precise pausing/resuming. _This is usually more efficient than `node-cron` for tasks that don't need scheduling._
|
|
53
56
|
|
|
54
57
|
```ts
|
|
55
58
|
import qznt from "qznt";
|
|
@@ -69,8 +72,8 @@ heartbeat.resume(); // Resumes with the exact remaining delay
|
|
|
69
72
|
|
|
70
73
|
`qznt` provides high-performant data persistence and memory management.
|
|
71
74
|
|
|
72
|
-
- `qznt.Cache`: An in-memory TTL cache with Sampled Passive/Active Eviction. It automatically
|
|
73
|
-
- `qznt.Storage`: A
|
|
75
|
+
- `qznt.Cache`: An in-memory TTL cache with Sampled Passive/Active Eviction. It automatically purges expired entries to prevent memory leaks without blocking the event loop.
|
|
76
|
+
- `qznt.Storage`: A persistent cache. It automatically uses `localStorage` in browsers and falls back to local JSON files in Node.js environments. Think a mini, smart-Redis cache.
|
|
74
77
|
|
|
75
78
|
```ts
|
|
76
79
|
// Cache with a 1-minute global TTL
|
|
@@ -84,7 +87,7 @@ settings.set("theme", "dark");
|
|
|
84
87
|
|
|
85
88
|
### Seedable Randomness
|
|
86
89
|
|
|
87
|
-
Every random utility in `qznt` accepts an optional seed. This allows you to generate predictable random data for testing, games, or procedural generation.
|
|
90
|
+
Every random utility in `qznt.rnd` accepts an optional seed. This allows you to generate predictable random data for testing, games, or procedural generation.
|
|
88
91
|
|
|
89
92
|
```ts
|
|
90
93
|
// Always returns the same item for seed 12345
|
|
@@ -93,7 +96,7 @@ const item = qznt.rnd.choice(["Sword", "Shield", "Potion"], 12345);
|
|
|
93
96
|
|
|
94
97
|
### Object Merging
|
|
95
98
|
|
|
96
|
-
A deep, recursive merge that maintains
|
|
99
|
+
A deep, recursive merge that maintains type safety across multiple sources.
|
|
97
100
|
|
|
98
101
|
```ts
|
|
99
102
|
const config = qznt.obj.merge(defaultConfig, userConfig, envOverrides);
|
|
@@ -101,7 +104,7 @@ const config = qznt.obj.merge(defaultConfig, userConfig, envOverrides);
|
|
|
101
104
|
|
|
102
105
|
### Type Guards
|
|
103
106
|
|
|
104
|
-
The `is` namespace provides predicates that act as
|
|
107
|
+
The `qznt.is` namespace provides predicates that act as type guards, ensuring type safety across your app.
|
|
105
108
|
|
|
106
109
|
```ts
|
|
107
110
|
if (qznt.is.today(user.lastLogin)) {
|
|
@@ -115,7 +118,7 @@ if (qznt.is.empty(results)) {
|
|
|
115
118
|
|
|
116
119
|
### Type-Safe Transformations
|
|
117
120
|
|
|
118
|
-
The `to` and `arr` namespaces provide
|
|
121
|
+
The `qznt.to` and `qznt.arr` namespaces also provide _✨ exquisite ✨_ ways to transform data structures while maintaining type safety.
|
|
119
122
|
|
|
120
123
|
```ts
|
|
121
124
|
const userRecord = qznt.to.record(usersArray, u => ({
|
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
default: () => index_default,
|
|
42
42
|
fn: () => fn,
|
|
43
43
|
format: () => format,
|
|
44
|
+
fs: () => fs,
|
|
44
45
|
is: () => is,
|
|
45
46
|
math: () => math,
|
|
46
47
|
obj: () => obj,
|
|
@@ -71,9 +72,19 @@ function chance(percent2 = 0.5, seed) {
|
|
|
71
72
|
const random = seed !== void 0 ? prng(seed) : Math.random;
|
|
72
73
|
return random() < percent2;
|
|
73
74
|
}
|
|
74
|
-
function choice(array,
|
|
75
|
+
function choice(array, options = {}) {
|
|
76
|
+
const { seed, not, maxRerolls = 10 } = options;
|
|
75
77
|
const random = seed !== void 0 ? prng(seed) : Math.random;
|
|
76
|
-
|
|
78
|
+
const rnd2 = () => array[Math.floor(random() * array.length)];
|
|
79
|
+
let result = rnd2();
|
|
80
|
+
if (seed !== void 0 && array.length > 1) {
|
|
81
|
+
let rerolls = 0;
|
|
82
|
+
while (not !== void 0 && result === not && rerolls < maxRerolls) {
|
|
83
|
+
result = rnd2();
|
|
84
|
+
rerolls++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
77
88
|
}
|
|
78
89
|
function weighted(array, selector, seed) {
|
|
79
90
|
const random = seed !== void 0 ? prng(seed) : Math.random;
|
|
@@ -85,9 +96,9 @@ function weighted(array, selector, seed) {
|
|
|
85
96
|
cumulativeWeights.push(currentSum);
|
|
86
97
|
}
|
|
87
98
|
const decider = random() * currentSum;
|
|
88
|
-
let index2
|
|
99
|
+
let index2;
|
|
89
100
|
if (array.length < 20) {
|
|
90
|
-
cumulativeWeights.findIndex((w) => w >= decider);
|
|
101
|
+
index2 = cumulativeWeights.findIndex((w) => w >= decider);
|
|
91
102
|
} else {
|
|
92
103
|
let low = 0;
|
|
93
104
|
let high = cumulativeWeights.length - 1;
|
|
@@ -148,9 +159,19 @@ function float(min, max, seed) {
|
|
|
148
159
|
const random = seed !== void 0 ? prng(seed) : Math.random;
|
|
149
160
|
return random() * (max - min) + min;
|
|
150
161
|
}
|
|
151
|
-
function index(array,
|
|
162
|
+
function index(array, options = {}) {
|
|
163
|
+
const { seed, not, maxRerolls = 10 } = options;
|
|
152
164
|
const random = seed !== void 0 ? prng(seed) : Math.random;
|
|
153
|
-
|
|
165
|
+
const rnd2 = () => Math.floor(random() * array.length);
|
|
166
|
+
let result = rnd2();
|
|
167
|
+
if (seed !== void 0 && array.length > 1) {
|
|
168
|
+
let rerolls = 0;
|
|
169
|
+
while (not !== void 0 && result === not && rerolls < maxRerolls) {
|
|
170
|
+
result = rnd2();
|
|
171
|
+
rerolls++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
154
175
|
}
|
|
155
176
|
function int(min, max, seed) {
|
|
156
177
|
const random = seed !== void 0 ? prng(seed) : Math.random;
|
|
@@ -510,6 +531,41 @@ var format = {
|
|
|
510
531
|
compactNumber
|
|
511
532
|
};
|
|
512
533
|
|
|
534
|
+
// src/fs.ts
|
|
535
|
+
var fs_exports = {};
|
|
536
|
+
__export(fs_exports, {
|
|
537
|
+
fs: () => fs
|
|
538
|
+
});
|
|
539
|
+
var import_node_fs = __toESM(require("fs"));
|
|
540
|
+
var import_node_path = require("path");
|
|
541
|
+
function readDir(path2, options = {}) {
|
|
542
|
+
const { recursive = true } = options;
|
|
543
|
+
if (!import_node_fs.default.existsSync(path2)) return [];
|
|
544
|
+
if (!recursive) {
|
|
545
|
+
return import_node_fs.default.readdirSync(path2).filter((fn2) => {
|
|
546
|
+
return import_node_fs.default.statSync((0, import_node_path.join)(path2, fn2)).isFile();
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
const walk = (dir, base = "") => {
|
|
550
|
+
const results = [];
|
|
551
|
+
const entries = import_node_fs.default.readdirSync(dir, { withFileTypes: true });
|
|
552
|
+
for (const entry of entries) {
|
|
553
|
+
const relativePath = base ? (0, import_node_path.join)(base, entry.name) : entry.name;
|
|
554
|
+
const fullPath = (0, import_node_path.join)(dir, entry.name);
|
|
555
|
+
if (entry.isDirectory()) {
|
|
556
|
+
results.push(...walk(fullPath, relativePath));
|
|
557
|
+
} else if (entry.isFile()) {
|
|
558
|
+
results.push(relativePath);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return results;
|
|
562
|
+
};
|
|
563
|
+
return walk(path2);
|
|
564
|
+
}
|
|
565
|
+
var fs = {
|
|
566
|
+
readDir
|
|
567
|
+
};
|
|
568
|
+
|
|
513
569
|
// src/is.ts
|
|
514
570
|
var is_exports = {};
|
|
515
571
|
__export(is_exports, {
|
|
@@ -590,10 +646,12 @@ function remap(value, inMin, inMax, outMin, outMax) {
|
|
|
590
646
|
function secs(num) {
|
|
591
647
|
return Math.floor(num / 1e3);
|
|
592
648
|
}
|
|
593
|
-
function sum(array, selector
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
649
|
+
function sum(array, selector) {
|
|
650
|
+
const _array = selector ? array.map(selector) : array;
|
|
651
|
+
if (!_array.every((v) => typeof v === "number")) {
|
|
652
|
+
throw new TypeError(`sum: Array must only contain numbers.`);
|
|
653
|
+
}
|
|
654
|
+
return _array.reduce((a, b) => {
|
|
597
655
|
return (isNaN(b) ? 0 : b) < 0 ? a - -b : a + (b || 0);
|
|
598
656
|
}, 0);
|
|
599
657
|
}
|
|
@@ -821,16 +879,16 @@ function Pipe(value, ...fns) {
|
|
|
821
879
|
}
|
|
822
880
|
|
|
823
881
|
// src/Storage.ts
|
|
824
|
-
var
|
|
825
|
-
var
|
|
882
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
883
|
+
var import_node_path2 = __toESM(require("path"));
|
|
826
884
|
var Storage = class {
|
|
827
885
|
isNode = typeof window === "undefined";
|
|
828
886
|
filePath = null;
|
|
829
887
|
memoryCache = /* @__PURE__ */ new Map();
|
|
830
888
|
loadFromFile() {
|
|
831
|
-
if (this.filePath &&
|
|
889
|
+
if (this.filePath && import_node_fs2.default.existsSync(this.filePath)) {
|
|
832
890
|
try {
|
|
833
|
-
const data = JSON.parse(
|
|
891
|
+
const data = JSON.parse(import_node_fs2.default.readFileSync(this.filePath, "utf-8"));
|
|
834
892
|
Object.entries(data).forEach(([k, v]) => this.memoryCache.set(k, JSON.stringify(v)));
|
|
835
893
|
} catch (err) {
|
|
836
894
|
console.error(`Store: Failed to load file '${this.filePath}'`, err);
|
|
@@ -841,7 +899,7 @@ var Storage = class {
|
|
|
841
899
|
if (this.isNode && this.filePath) {
|
|
842
900
|
const out = {};
|
|
843
901
|
this.memoryCache.forEach((v, k) => out[k] = JSON.parse(v));
|
|
844
|
-
|
|
902
|
+
import_node_fs2.default.writeFileSync(this.filePath, JSON.stringify(out, null, 2));
|
|
845
903
|
}
|
|
846
904
|
}
|
|
847
905
|
/**
|
|
@@ -852,7 +910,7 @@ var Storage = class {
|
|
|
852
910
|
*/
|
|
853
911
|
constructor(fileName, directory = process.cwd()) {
|
|
854
912
|
if (this.isNode && fileName) {
|
|
855
|
-
this.filePath =
|
|
913
|
+
this.filePath = import_node_path2.default.join(directory, fileName.endsWith(".json") ? fileName : `${fileName}.json`);
|
|
856
914
|
this.loadFromFile();
|
|
857
915
|
}
|
|
858
916
|
}
|
|
@@ -1059,6 +1117,7 @@ var qznt = {
|
|
|
1059
1117
|
...fn_exports,
|
|
1060
1118
|
...date_exports,
|
|
1061
1119
|
...format_exports,
|
|
1120
|
+
...fs_exports,
|
|
1062
1121
|
...is_exports,
|
|
1063
1122
|
...math_exports,
|
|
1064
1123
|
...obj_exports,
|
|
@@ -1085,6 +1144,7 @@ var index_default = qznt;
|
|
|
1085
1144
|
date,
|
|
1086
1145
|
fn,
|
|
1087
1146
|
format,
|
|
1147
|
+
fs,
|
|
1088
1148
|
is,
|
|
1089
1149
|
math,
|
|
1090
1150
|
obj,
|