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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # qznt
2
2
 
3
- **qznt** (pronounced as in _ex-quisite_) is a strictly-typed, high-performant utility toolkit for modern TypeScript/Javascript and Node.js environments.
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
- // Deep object access (defaults to "dark" if mode is undefined)
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
- // Human-readable durations
30
- const timeRemaining = $.date.duration(Date.now() + 5000); // "5 seconds"
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` (Lists)**: Advanced `chunk`, `cluster`, `shuffle`, `unique`, and `seqMap`
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
- The `qznt.Loop` ensures asynchronous tasks never overlap. It waits for execution to finish before scheduling the next interval and supports precise pausing/resuming based on remaining time.
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 cleans up expired entries to prevent memory leaks without blocking the event loop.
73
- - `qznt.Storage`: A universal persistence layer. It automatically uses `localStorage` in the browser and falls back to a local JSON file in Node.js environments.
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 TypeScript's type safety across multiple sources.
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 TypeScript type guards, ensuring safety across your application.
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 _exquisite_ ways to transform data structures while maintaining total type safety.
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, seed) {
75
+ function choice(array, options = {}) {
76
+ const { seed, not, maxRerolls = 10 } = options;
75
77
  const random = seed !== void 0 ? prng(seed) : Math.random;
76
- return array[Math.floor(random() * array.length)];
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 = cumulativeWeights.findIndex((w) => w >= decider);
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, seed) {
162
+ function index(array, options = {}) {
163
+ const { seed, not, maxRerolls = 10 } = options;
152
164
  const random = seed !== void 0 ? prng(seed) : Math.random;
153
- return Math.floor(random() * array.length);
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, ignoreNaN) {
594
- return array.map(selector).reduce((a, b) => {
595
- const invalid = isNaN(b) && !ignoreNaN;
596
- if (invalid) throw new TypeError(`sum: '${b}' is not a valid number.`);
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 import_node_fs = __toESM(require("fs"));
825
- var import_node_path = __toESM(require("path"));
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 && import_node_fs.default.existsSync(this.filePath)) {
889
+ if (this.filePath && import_node_fs2.default.existsSync(this.filePath)) {
832
890
  try {
833
- const data = JSON.parse(import_node_fs.default.readFileSync(this.filePath, "utf-8"));
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
- import_node_fs.default.writeFileSync(this.filePath, JSON.stringify(out, null, 2));
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 = import_node_path.default.join(directory, fileName.endsWith(".json") ? fileName : `${fileName}.json`);
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,