@ix-xs/node-comfort 2.0.0 → 2.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/CHANGELOG.md +150 -0
- package/README.md +32 -30
- package/index.js +3 -3
- package/package.json +8 -7
- package/src/Arr.js +222 -58
- package/src/Async.js +322 -90
- package/src/Cache.js +148 -44
- package/src/Checker.js +257 -26
- package/src/Cli.js +404 -145
- package/src/Color.js +224 -83
- package/src/Crypto.js +481 -106
- package/src/Emitter.js +104 -37
- package/src/Env.js +176 -42
- package/src/FS.js +203 -60
- package/src/Func.js +226 -76
- package/src/Http.js +311 -149
- package/src/Id.js +281 -96
- package/src/Logger.js +302 -78
- package/src/Num.js +182 -60
- package/src/Obj.js +340 -125
- package/src/SQLite.js +426 -105
- package/src/Schema.js +602 -87
- package/src/Str.js +316 -86
- package/src/Sys.js +197 -54
- package/src/Time.js +572 -317
- package/src/Utils.js +62 -41
- package/src/errors.js +54 -11
- package/src/internal/shared.js +415 -0
- package/types/index.d.ts +59 -17
- package/types/src/Arr.d.ts +78 -0
- package/types/src/Arr.d.ts.map +1 -1
- package/types/src/Async.d.ts +52 -1
- package/types/src/Async.d.ts.map +1 -1
- package/types/src/Cache.d.ts +33 -7
- package/types/src/Cache.d.ts.map +1 -1
- package/types/src/Checker.d.ts +155 -2
- package/types/src/Checker.d.ts.map +1 -1
- package/types/src/Cli.d.ts +97 -15
- package/types/src/Cli.d.ts.map +1 -1
- package/types/src/Color.d.ts +7 -1
- package/types/src/Color.d.ts.map +1 -1
- package/types/src/Crypto.d.ts +99 -13
- package/types/src/Crypto.d.ts.map +1 -1
- package/types/src/Emitter.d.ts +36 -17
- package/types/src/Emitter.d.ts.map +1 -1
- package/types/src/Env.d.ts +31 -1
- package/types/src/Env.d.ts.map +1 -1
- package/types/src/FS.d.ts +36 -5
- package/types/src/FS.d.ts.map +1 -1
- package/types/src/Func.d.ts +147 -19
- package/types/src/Func.d.ts.map +1 -1
- package/types/src/Http.d.ts +113 -43
- package/types/src/Http.d.ts.map +1 -1
- package/types/src/Id.d.ts +60 -8
- package/types/src/Id.d.ts.map +1 -1
- package/types/src/Logger.d.ts +53 -2
- package/types/src/Logger.d.ts.map +1 -1
- package/types/src/Num.d.ts +48 -7
- package/types/src/Num.d.ts.map +1 -1
- package/types/src/Obj.d.ts +67 -4
- package/types/src/Obj.d.ts.map +1 -1
- package/types/src/SQLite.d.ts +123 -14
- package/types/src/SQLite.d.ts.map +1 -1
- package/types/src/Schema.d.ts +256 -7
- package/types/src/Schema.d.ts.map +1 -1
- package/types/src/Str.d.ts +49 -1
- package/types/src/Str.d.ts.map +1 -1
- package/types/src/Sys.d.ts +51 -6
- package/types/src/Sys.d.ts.map +1 -1
- package/types/src/Time.d.ts +130 -34
- package/types/src/Time.d.ts.map +1 -1
- package/types/src/Utils.d.ts +15 -11
- package/types/src/Utils.d.ts.map +1 -1
- package/types/src/errors.d.ts +27 -3
- package/types/src/errors.d.ts.map +1 -1
- package/types/src/internal/shared.d.ts +123 -0
- package/types/src/internal/shared.d.ts.map +1 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Helpers shared by the modules. Not part of the public API.
|
|
4
|
+
|
|
5
|
+
const { types } = require("node:util");
|
|
6
|
+
|
|
7
|
+
/** Keys that must never be copied onto an object: they reach its prototype. */
|
|
8
|
+
const UNSAFE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {unknown} value
|
|
12
|
+
* @returns {value is Record<string, any>}
|
|
13
|
+
*/
|
|
14
|
+
const isPlainObject = (value) => {
|
|
15
|
+
if (typeof value !== "object" || value === null) return false;
|
|
16
|
+
const proto = Object.getPrototypeOf(value);
|
|
17
|
+
// The last check accepts objects from another realm (a worker, a `vm`).
|
|
18
|
+
return proto === null || proto === Object.prototype || Object.getPrototypeOf(proto) === null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const MS = { ms: 1, s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000 };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The defaults set with `time.setLocale()` and `time.setTimezone()`. English
|
|
25
|
+
* by default, so output never depends on the machine.
|
|
26
|
+
* @type {{ locale: string, timeZone: string | undefined }}
|
|
27
|
+
*/
|
|
28
|
+
const timeConfig = { locale: "en", timeZone: undefined };
|
|
29
|
+
|
|
30
|
+
/** @type {Record<string, keyof typeof MS | "M" | "y">} */
|
|
31
|
+
const UNIT_ALIASES = {
|
|
32
|
+
ms: "ms", msec: "ms", milli: "ms", millis: "ms", millisecond: "ms", milliseconds: "ms",
|
|
33
|
+
s: "s", sec: "s", secs: "s", second: "s", seconds: "s",
|
|
34
|
+
m: "m", min: "m", mins: "m", minute: "m", minutes: "m",
|
|
35
|
+
h: "h", hr: "h", hrs: "h", hour: "h", hours: "h",
|
|
36
|
+
d: "d", day: "d", days: "d",
|
|
37
|
+
w: "w", wk: "w", wks: "w", week: "w", weeks: "w",
|
|
38
|
+
M: "M", mo: "M", month: "M", months: "M",
|
|
39
|
+
y: "y", yr: "y", yrs: "y", year: "y", years: "y",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} unit
|
|
44
|
+
* @returns {keyof typeof MS | "M" | "y" | undefined}
|
|
45
|
+
*/
|
|
46
|
+
const unitOf = (unit) => UNIT_ALIASES[unit] ?? UNIT_ALIASES[unit.toLowerCase()];
|
|
47
|
+
|
|
48
|
+
/** @type {Map<string, number | null>} */
|
|
49
|
+
const _durations = new Map();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} text Already trimmed, not empty.
|
|
53
|
+
* @returns {number | null}
|
|
54
|
+
*/
|
|
55
|
+
const _readDuration = (text) => {
|
|
56
|
+
if (/^[-+]?\d+(?:\.\d+)?$/.test(text)) return Number(text);
|
|
57
|
+
|
|
58
|
+
const iso = /^([-+])?P(?:(\d+(?:[.,]\d+)?)W)?(?:(\d+(?:[.,]\d+)?)D)?(?:T(?:(\d+(?:[.,]\d+)?)H)?(?:(\d+(?:[.,]\d+)?)M)?(?:(\d+(?:[.,]\d+)?)S)?)?$/i.exec(text);
|
|
59
|
+
if (iso && /\d/.test(text)) {
|
|
60
|
+
const [, sign, w, d, h, m, s] = iso;
|
|
61
|
+
const n = (/** @type {string | undefined} */ v) => (v ? Number(v.replace(",", ".")) : 0);
|
|
62
|
+
const total = n(w) * MS.w + n(d) * MS.d + n(h) * MS.h + n(m) * MS.m + n(s) * MS.s;
|
|
63
|
+
return sign === "-" ? -total : total;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const clock = /^([-+])?(?:(\d+):)?(\d+):(\d{1,2})(?:\.(\d{1,3}))?$/.exec(text);
|
|
67
|
+
if (clock) {
|
|
68
|
+
const [, sign, h, m, s, ms] = clock;
|
|
69
|
+
const total = Number(h ?? 0) * MS.h + Number(m) * MS.m + Number(s) * MS.s + (ms ? Number(ms.padEnd(3, "0")) : 0);
|
|
70
|
+
return sign === "-" ? -total : total;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const regex = /(-?\d*\.?\d+)\s*([a-zA-Z]+)/g;
|
|
74
|
+
let total = 0;
|
|
75
|
+
let consumed = "";
|
|
76
|
+
let match;
|
|
77
|
+
while ((match = regex.exec(text)) !== null) {
|
|
78
|
+
const unit = unitOf(match[2]);
|
|
79
|
+
if (!unit || unit === "M" || unit === "y") return null;
|
|
80
|
+
total += Number(match[1]) * MS[unit];
|
|
81
|
+
consumed += match[0];
|
|
82
|
+
}
|
|
83
|
+
if (!consumed || consumed.replace(/\s+/g, "") !== text.replace(/[\s,]+|and/g, "")) return null;
|
|
84
|
+
return total;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A duration in milliseconds, or `null` when the input isn't one.
|
|
89
|
+
* @param {unknown} input
|
|
90
|
+
* @returns {number | null}
|
|
91
|
+
*/
|
|
92
|
+
const parseDuration = (input) => {
|
|
93
|
+
if (typeof input === "number") return Number.isFinite(input) ? input : null;
|
|
94
|
+
if (typeof input !== "string") return null;
|
|
95
|
+
const cached = _durations.get(input);
|
|
96
|
+
if (cached !== undefined) return cached;
|
|
97
|
+
const text = input.trim();
|
|
98
|
+
const result = text ? _readDuration(text) : null;
|
|
99
|
+
// Durations are almost always literals, so a handful of entries covers a whole program.
|
|
100
|
+
if (_durations.size >= 500) _durations.clear();
|
|
101
|
+
_durations.set(input, result);
|
|
102
|
+
return result;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Milliseconds from a number or a duration string.
|
|
107
|
+
* @param {number | string} value
|
|
108
|
+
* @param {string} [what="duration"] Names the value in the error message.
|
|
109
|
+
* @returns {number}
|
|
110
|
+
* @throws {TypeError} If it can't be read.
|
|
111
|
+
*/
|
|
112
|
+
const toMs = (value, what = "duration") => {
|
|
113
|
+
if (typeof value === "number") {
|
|
114
|
+
if (Number.isNaN(value)) throw new TypeError(`Invalid ${what}: NaN`);
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
const ms = parseDuration(value);
|
|
118
|
+
if (ms === null) throw new TypeError(`Invalid ${what}: ${JSON.stringify(value) ?? String(value)}`);
|
|
119
|
+
return ms;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const MAX_TIMEOUT = 2 ** 31 - 1;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* `setTimeout` that also works past its 24.8-day limit. `Infinity` never fires.
|
|
126
|
+
* @param {() => void} fn
|
|
127
|
+
* @param {number} ms
|
|
128
|
+
* @param {boolean} [unref=false]
|
|
129
|
+
* @returns {() => void} Cancels the timer.
|
|
130
|
+
*/
|
|
131
|
+
const setLongTimeout = (fn, ms, unref = false) => {
|
|
132
|
+
if (ms === Infinity) return () => {};
|
|
133
|
+
/** @type {NodeJS.Timeout | undefined} */
|
|
134
|
+
let timer;
|
|
135
|
+
const schedule = (/** @type {number} */ remaining) => {
|
|
136
|
+
timer = setTimeout(() => (remaining > MAX_TIMEOUT ? schedule(remaining - MAX_TIMEOUT) : fn()), Math.min(remaining, MAX_TIMEOUT));
|
|
137
|
+
if (unref) timer.unref();
|
|
138
|
+
};
|
|
139
|
+
schedule(Math.max(0, ms) || 0);
|
|
140
|
+
return () => clearTimeout(timer);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolves after `ms`, or rejects with an `AbortError` when the signal fires.
|
|
145
|
+
* @param {number} ms
|
|
146
|
+
* @param {AbortSignal} [signal]
|
|
147
|
+
* @param {boolean} [unref=false]
|
|
148
|
+
* @returns {Promise<void>}
|
|
149
|
+
*/
|
|
150
|
+
const sleep = (ms, signal, unref = false) =>
|
|
151
|
+
new Promise((resolve, reject) => {
|
|
152
|
+
const { AbortError } = require("../errors.js");
|
|
153
|
+
if (signal?.aborted) return reject(new AbortError(undefined, { cause: signal.reason }));
|
|
154
|
+
const onAbort = () => {
|
|
155
|
+
cancel();
|
|
156
|
+
reject(new AbortError(undefined, { cause: signal?.reason }));
|
|
157
|
+
};
|
|
158
|
+
const cancel = setLongTimeout(() => {
|
|
159
|
+
signal?.removeEventListener("abort", onAbort);
|
|
160
|
+
resolve();
|
|
161
|
+
}, ms, unref);
|
|
162
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Builds a cache of `Intl` objects, which are slow to construct (tens of
|
|
167
|
+
* microseconds) and fast to use.
|
|
168
|
+
* @template T
|
|
169
|
+
* @param {(locale: string | undefined, options: any) => T} create
|
|
170
|
+
* @returns {(locale?: string, options?: object) => T}
|
|
171
|
+
*/
|
|
172
|
+
const _intlCache = (create) => {
|
|
173
|
+
/** @type {Map<string, T>} */
|
|
174
|
+
const cache = new Map();
|
|
175
|
+
return (locale, options) => {
|
|
176
|
+
const key = options === undefined ? `${locale ?? ""}` : `${locale ?? ""}|${JSON.stringify(options)}`;
|
|
177
|
+
let found = cache.get(key);
|
|
178
|
+
if (found === undefined) {
|
|
179
|
+
found = create(locale, options);
|
|
180
|
+
if (cache.size >= 200) cache.clear();
|
|
181
|
+
cache.set(key, found);
|
|
182
|
+
}
|
|
183
|
+
return found;
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const intl = {
|
|
188
|
+
number: _intlCache((locale, options) => new Intl.NumberFormat(locale, options)),
|
|
189
|
+
dateTime: _intlCache((locale, options) => new Intl.DateTimeFormat(locale, options)),
|
|
190
|
+
plural: _intlCache((locale, options) => new Intl.PluralRules(locale, options)),
|
|
191
|
+
collator: _intlCache((locale, options) => new Intl.Collator(locale, options)),
|
|
192
|
+
relativeTime: _intlCache((locale, options) => new Intl.RelativeTimeFormat(locale, options)),
|
|
193
|
+
list: _intlCache((locale, options) => new Intl.ListFormat(locale, options)),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** @type {Intl.Segmenter | undefined} */
|
|
197
|
+
let _segmenter;
|
|
198
|
+
|
|
199
|
+
/** The grapheme segmenter, built on first use. */
|
|
200
|
+
const segmenter = () => (_segmenter ??= new Intl.Segmenter(undefined, { granularity: "grapheme" }));
|
|
201
|
+
|
|
202
|
+
const ASCII = /^[\x00-\x7f]*$/;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Splits text into graphemes, so emoji and accents stay whole.
|
|
206
|
+
* @param {string} input
|
|
207
|
+
* @returns {string[]}
|
|
208
|
+
*/
|
|
209
|
+
const graphemes = (input) => {
|
|
210
|
+
if (ASCII.test(input)) return input.split("");
|
|
211
|
+
return Array.from(segmenter().segment(input), (s) => s.segment);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Counts graphemes without building an array.
|
|
216
|
+
* @param {string} input
|
|
217
|
+
* @returns {number}
|
|
218
|
+
*/
|
|
219
|
+
const graphemeCount = (input) => {
|
|
220
|
+
if (ASCII.test(input)) return input.length;
|
|
221
|
+
let count = 0;
|
|
222
|
+
for (const _ of segmenter().segment(input)) count++;
|
|
223
|
+
return count;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Adds numbers with Neumaier's compensation, so `0.1 + 0.2 + 0.3` is `0.6`.
|
|
228
|
+
* @param {Iterable<number>} values
|
|
229
|
+
* @returns {number}
|
|
230
|
+
*/
|
|
231
|
+
const preciseSum = (values) => {
|
|
232
|
+
let sum = 0;
|
|
233
|
+
let compensation = 0;
|
|
234
|
+
for (const value of values) {
|
|
235
|
+
const next = sum + value;
|
|
236
|
+
compensation += Math.abs(sum) >= Math.abs(value) ? sum - next + value : value - next + sum;
|
|
237
|
+
sum = next;
|
|
238
|
+
}
|
|
239
|
+
const total = sum + compensation;
|
|
240
|
+
return Number.isNaN(total) ? sum : total;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/** @type {WeakMap<object, number>} */
|
|
244
|
+
const _objectIds = new WeakMap();
|
|
245
|
+
/** @type {Map<symbol, number>} */
|
|
246
|
+
const _symbolIds = new Map();
|
|
247
|
+
let _nextId = 0;
|
|
248
|
+
|
|
249
|
+
/** @param {object} value */
|
|
250
|
+
const _identity = (value) => {
|
|
251
|
+
let id = _objectIds.get(value);
|
|
252
|
+
if (id === undefined) _objectIds.set(value, (id = ++_nextId));
|
|
253
|
+
return id;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @param {unknown} value
|
|
258
|
+
* @returns {string}
|
|
259
|
+
*/
|
|
260
|
+
const _keyPart = (value) => {
|
|
261
|
+
switch (typeof value) {
|
|
262
|
+
case "string": return `s${value.length}:${value}`;
|
|
263
|
+
case "number": return Object.is(value, -0) ? "n-0" : `n${value}`;
|
|
264
|
+
case "boolean": return value ? "t" : "f";
|
|
265
|
+
case "undefined": return "u";
|
|
266
|
+
case "bigint": return `i${value}`;
|
|
267
|
+
case "symbol": {
|
|
268
|
+
let id = _symbolIds.get(value);
|
|
269
|
+
if (id === undefined) _symbolIds.set(value, (id = ++_nextId));
|
|
270
|
+
return `y${id}`;
|
|
271
|
+
}
|
|
272
|
+
case "function": return `F${_identity(value)}`;
|
|
273
|
+
default: {
|
|
274
|
+
if (value === null) return "l";
|
|
275
|
+
const object = /** @type {object} */ (value);
|
|
276
|
+
if (types.isDate(object)) return `d${object.getTime()}`;
|
|
277
|
+
if (!Array.isArray(object) && !isPlainObject(object)) return `O${_identity(object)}`;
|
|
278
|
+
try {
|
|
279
|
+
// The replacer keeps what JSON drops or refuses: undefined, bigint, functions, symbols.
|
|
280
|
+
return `j${JSON.stringify(object, (_, v) => (v !== null && typeof v === "object" ? v : typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? v : _keyPart(v)))}`;
|
|
281
|
+
} catch {
|
|
282
|
+
return `O${_identity(object)}`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* A cache key for a list of arguments. Primitives are compared by value and
|
|
290
|
+
* type, plain objects and arrays by content, everything else by identity.
|
|
291
|
+
* @param {readonly unknown[]} args
|
|
292
|
+
* @returns {unknown} A primitive usable as a `Map` key.
|
|
293
|
+
*/
|
|
294
|
+
const argsKey = (args) => {
|
|
295
|
+
if (args.length === 1) {
|
|
296
|
+
const only = args[0];
|
|
297
|
+
// Numbers and booleans can't clash with the tagged strings used for everything else.
|
|
298
|
+
return typeof only === "number" || typeof only === "boolean" ? only : _keyPart(only);
|
|
299
|
+
}
|
|
300
|
+
if (args.length === 0) return "";
|
|
301
|
+
let key = "";
|
|
302
|
+
for (let i = 0; i < args.length; i++) key += `${_keyPart(args[i])}\u0001`;
|
|
303
|
+
return key;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Parsed cron fields. `lastDay` is set by `L` in the day-of-month field.
|
|
308
|
+
* @typedef {{ seconds: Set<number>, minutes: Set<number>, hours: Set<number>, days: Set<number>, months: Set<number>, weekdays: Set<number>, anyDay: boolean, anyWeekday: boolean, lastDay: boolean }} CronSpec
|
|
309
|
+
*/
|
|
310
|
+
|
|
311
|
+
/** @type {Record<string, string>} */
|
|
312
|
+
const CRON_MACROS = {
|
|
313
|
+
"@yearly": "0 0 1 1 *", "@annually": "0 0 1 1 *", "@monthly": "0 0 1 * *", "@weekly": "0 0 * * 0",
|
|
314
|
+
"@daily": "0 0 * * *", "@midnight": "0 0 * * *", "@hourly": "0 * * * *",
|
|
315
|
+
};
|
|
316
|
+
const MONTH_NAMES = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
|
|
317
|
+
const DAY_NAMES = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
318
|
+
|
|
319
|
+
/** @type {Map<string, CronSpec>} */
|
|
320
|
+
const _cronSpecs = new Map();
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Parses a cron expression with 5 fields, or 6 with seconds first.
|
|
324
|
+
* @param {string} expression
|
|
325
|
+
* @returns {CronSpec}
|
|
326
|
+
* @throws {SyntaxError} If the expression is invalid.
|
|
327
|
+
*/
|
|
328
|
+
const parseCron = (expression) => {
|
|
329
|
+
const cached = _cronSpecs.get(expression);
|
|
330
|
+
if (cached) return cached;
|
|
331
|
+
const text = CRON_MACROS[expression.trim().toLowerCase()] ?? expression.trim();
|
|
332
|
+
const fields = text.split(/\s+/);
|
|
333
|
+
if (fields.length !== 5 && fields.length !== 6) throw new SyntaxError(`Invalid cron expression "${expression}": expected 5 or 6 fields`);
|
|
334
|
+
if (fields.length === 5) fields.unshift("0");
|
|
335
|
+
/** @type {(field: string, min: number, max: number, names?: string[]) => Set<number>} */
|
|
336
|
+
const parse = (field, min, max, names) => {
|
|
337
|
+
/** @type {Set<number>} */
|
|
338
|
+
const values = new Set();
|
|
339
|
+
for (const part of field.split(",")) {
|
|
340
|
+
const [rangePart, stepPart] = part.split("/");
|
|
341
|
+
const step = stepPart === undefined ? 1 : Number(stepPart);
|
|
342
|
+
if (!Number.isInteger(step) || step < 1) throw new SyntaxError(`Invalid step "${part}" in cron expression "${expression}"`);
|
|
343
|
+
const toNumber = (/** @type {string} */ token) => {
|
|
344
|
+
const index = names ? names.indexOf(token.toLowerCase()) : -1;
|
|
345
|
+
const n = index >= 0 ? index + (min === 1 ? 1 : 0) : token === "" ? NaN : Number(token);
|
|
346
|
+
if (!Number.isInteger(n)) throw new SyntaxError(`Invalid value "${token}" in cron expression "${expression}"`);
|
|
347
|
+
return n;
|
|
348
|
+
};
|
|
349
|
+
let from = min;
|
|
350
|
+
let to = max;
|
|
351
|
+
if (rangePart !== "*" && rangePart !== "?") {
|
|
352
|
+
const [a, b] = rangePart.split("-");
|
|
353
|
+
from = toNumber(a);
|
|
354
|
+
to = b === undefined ? (stepPart === undefined ? from : max) : toNumber(b);
|
|
355
|
+
}
|
|
356
|
+
if (from < min || to > max || from > to) throw new SyntaxError(`Value out of range in "${part}" (allowed ${min}-${max}) in cron expression "${expression}"`);
|
|
357
|
+
for (let v = from; v <= to; v += step) values.add(v);
|
|
358
|
+
}
|
|
359
|
+
return values;
|
|
360
|
+
};
|
|
361
|
+
const weekdays = parse(fields[5], 0, 7, DAY_NAMES);
|
|
362
|
+
if (weekdays.has(7)) {
|
|
363
|
+
weekdays.delete(7);
|
|
364
|
+
weekdays.add(0);
|
|
365
|
+
}
|
|
366
|
+
// "L" alone, or in a list like "15,L", means the last day of the month.
|
|
367
|
+
const dayParts = fields[3].split(",");
|
|
368
|
+
const lastDay = dayParts.some((part) => part.toUpperCase() === "L");
|
|
369
|
+
const dayField = dayParts.filter((part) => part.toUpperCase() !== "L").join(",");
|
|
370
|
+
/** @type {CronSpec} */
|
|
371
|
+
const spec = {
|
|
372
|
+
seconds: parse(fields[0], 0, 59),
|
|
373
|
+
minutes: parse(fields[1], 0, 59),
|
|
374
|
+
hours: parse(fields[2], 0, 23),
|
|
375
|
+
days: dayField === "" ? new Set() : parse(dayField, 1, 31),
|
|
376
|
+
months: parse(fields[4], 1, 12, MONTH_NAMES),
|
|
377
|
+
weekdays,
|
|
378
|
+
anyDay: fields[3] === "*" || fields[3] === "?",
|
|
379
|
+
anyWeekday: fields[5] === "*" || fields[5] === "?",
|
|
380
|
+
lastDay,
|
|
381
|
+
};
|
|
382
|
+
if (_cronSpecs.size >= 200) _cronSpecs.clear();
|
|
383
|
+
_cronSpecs.set(expression, spec);
|
|
384
|
+
return spec;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Blocks the thread for a few milliseconds. Only for short retries of
|
|
389
|
+
* synchronous file operations.
|
|
390
|
+
* @param {number} ms
|
|
391
|
+
*/
|
|
392
|
+
const sleepSync = (ms) => {
|
|
393
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
module.exports = {
|
|
397
|
+
UNSAFE_KEYS,
|
|
398
|
+
MS,
|
|
399
|
+
timeConfig,
|
|
400
|
+
ASCII,
|
|
401
|
+
isPlainObject,
|
|
402
|
+
unitOf,
|
|
403
|
+
parseDuration,
|
|
404
|
+
toMs,
|
|
405
|
+
setLongTimeout,
|
|
406
|
+
sleep,
|
|
407
|
+
sleepSync,
|
|
408
|
+
intl,
|
|
409
|
+
segmenter,
|
|
410
|
+
graphemes,
|
|
411
|
+
graphemeCount,
|
|
412
|
+
preciseSum,
|
|
413
|
+
argsKey,
|
|
414
|
+
parseCron,
|
|
415
|
+
};
|
package/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/build-types.js. Do not edit by hand.
|
|
2
|
-
// @ix-xs/node-comfort v2.
|
|
2
|
+
// @ix-xs/node-comfort v2.1.0
|
|
3
3
|
|
|
4
4
|
import _Arr = require("./src/Arr");
|
|
5
5
|
import _Async = require("./src/Async");
|
|
@@ -49,8 +49,9 @@ declare namespace nodeComfort {
|
|
|
49
49
|
* `#ff8800`, `bg#1e1e2e`, `rgb(255, 128, 0)` and `bgRgb(...)`.
|
|
50
50
|
*
|
|
51
51
|
* Levels, from chatty to severe: `trace`, `debug`, `info`, `warn`, `error`,
|
|
52
|
-
* `fatal`. The default is `info
|
|
53
|
-
*
|
|
52
|
+
* `fatal`. The default is `info`; `LOG_LEVEL` changes it. `DEBUG=1` shows
|
|
53
|
+
* debug messages, and `DEBUG=db,http:*` only the debug and trace messages of
|
|
54
|
+
* the loggers with these scopes. Warnings and errors go to stderr.
|
|
54
55
|
*
|
|
55
56
|
* @example
|
|
56
57
|
* nc.info("Server listening on port 3000");
|
|
@@ -169,15 +170,16 @@ declare namespace nodeComfort {
|
|
|
169
170
|
*/
|
|
170
171
|
export import time = _Time;
|
|
171
172
|
/**
|
|
172
|
-
* Unique ids: UUID v4 and v7, ULID, nanoid-style ids,
|
|
173
|
-
* and short codes.
|
|
174
|
-
* `node:crypto`, with no bias.
|
|
173
|
+
* Unique ids: UUID v4, v5 and v7, ULID, ObjectId, nanoid-style ids,
|
|
174
|
+
* Snowflakes, tokens and short codes. Random ones use the secure generator
|
|
175
|
+
* of `node:crypto`, with no bias.
|
|
175
176
|
*
|
|
176
177
|
* @example
|
|
177
|
-
* id.uuidv7();
|
|
178
|
-
* id.nano();
|
|
179
|
-
* id.
|
|
180
|
-
* id.
|
|
178
|
+
* id.uuidv7(); // "0190a5c4-7c1e-7b2a-9f3e-1c2d3e4f5a6b", sorts by creation time
|
|
179
|
+
* id.nano(); // "V1StGXR8_Z5jdHi6B-myT"
|
|
180
|
+
* id.prefixed("user"); // "user_7Hq2mXc0LrT9aZ4bWk1PdE8s"
|
|
181
|
+
* id.code(); // "K7QF9X"
|
|
182
|
+
* id.token(32); // for API keys and reset links
|
|
181
183
|
*/
|
|
182
184
|
export import id = _Id;
|
|
183
185
|
/**
|
|
@@ -262,7 +264,7 @@ declare namespace nodeComfort {
|
|
|
262
264
|
* });
|
|
263
265
|
*
|
|
264
266
|
* const user = User.parse(req.body); // typed, or a ValidationError listing every issue
|
|
265
|
-
* const result = User.safeParse(input); // { success, data } or { success, error }
|
|
267
|
+
* const result = User.safeParse(input); // { success, data } or { success, error, issues }
|
|
266
268
|
*/
|
|
267
269
|
export import schema = _Schema;
|
|
268
270
|
/**
|
|
@@ -284,7 +286,8 @@ declare namespace nodeComfort {
|
|
|
284
286
|
*
|
|
285
287
|
* The `.env` file in the working directory is loaded when the package is
|
|
286
288
|
* required; variables that are already set are never overwritten. Set
|
|
287
|
-
* `NODE_COMFORT_DOTENV=false` to turn that off.
|
|
289
|
+
* `NODE_COMFORT_DOTENV=false` to turn that off. What it loaded has the
|
|
290
|
+
* lowest priority: a file you load yourself afterwards wins over it.
|
|
288
291
|
*
|
|
289
292
|
* @example
|
|
290
293
|
* const config = env.validate({
|
|
@@ -296,9 +299,13 @@ declare namespace nodeComfort {
|
|
|
296
299
|
*/
|
|
297
300
|
export import env = _Env;
|
|
298
301
|
/**
|
|
299
|
-
* The errors node-comfort throws
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
+
* The errors node-comfort throws when something goes wrong at run time: a
|
|
303
|
+
* request fails, data is invalid, a command exits badly. They all extend
|
|
304
|
+
* `NodeComfortError` (itself a regular `Error`) and carry a stable `code`,
|
|
305
|
+
* so you can branch on the kind of failure without parsing messages.
|
|
306
|
+
*
|
|
307
|
+
* A mistake in your own code, like a wrong argument, still throws a plain
|
|
308
|
+
* `TypeError` or `RangeError`, as it does everywhere in JavaScript.
|
|
302
309
|
*
|
|
303
310
|
* @example
|
|
304
311
|
* try {
|
|
@@ -387,6 +394,7 @@ declare namespace nodeComfort {
|
|
|
387
394
|
export import getFilesIn = _FS.getFilesIn;
|
|
388
395
|
export import glob = _FS.glob;
|
|
389
396
|
export import find = _FS.find;
|
|
397
|
+
export import findUp = _FS.findUp;
|
|
390
398
|
export import readFile = _FS.readFile;
|
|
391
399
|
export import readLines = _FS.readLines;
|
|
392
400
|
export import readJSON = _FS.readJSON;
|
|
@@ -484,6 +492,18 @@ declare namespace nodeComfort {
|
|
|
484
492
|
export import isAlphanumeric = _Checker.isAlphanumeric;
|
|
485
493
|
export import isCreditCard = _Checker.isCreditCard;
|
|
486
494
|
export import isJWT = _Checker.isJWT;
|
|
495
|
+
export import isHostname = _Checker.isHostname;
|
|
496
|
+
export import isMACAddress = _Checker.isMACAddress;
|
|
497
|
+
export import isIBAN = _Checker.isIBAN;
|
|
498
|
+
export import isE164 = _Checker.isE164;
|
|
499
|
+
export import isObjectId = _Checker.isObjectId;
|
|
500
|
+
export import isULID = _Checker.isULID;
|
|
501
|
+
export import isDataURL = _Checker.isDataURL;
|
|
502
|
+
export import isMimeType = _Checker.isMimeType;
|
|
503
|
+
export import isTimeZone = _Checker.isTimeZone;
|
|
504
|
+
export import isLocale = _Checker.isLocale;
|
|
505
|
+
export import isCron = _Checker.isCron;
|
|
506
|
+
export import isASCII = _Checker.isASCII;
|
|
487
507
|
export import assert = _Checker.assert;
|
|
488
508
|
export import assertType = _Checker.assertType;
|
|
489
509
|
export import wait = _Utils.wait;
|
|
@@ -495,8 +515,11 @@ declare namespace nodeComfort {
|
|
|
495
515
|
// Types
|
|
496
516
|
export import AnyTypedArray = _Checker.AnyTypedArray;
|
|
497
517
|
export import ArgsOptions = _Cli.ArgsOptions;
|
|
518
|
+
export import BatchedFunction = _Func.BatchedFunction;
|
|
519
|
+
export import BatchOptions = _Func.BatchOptions;
|
|
498
520
|
export import BorderStyle = _Cli.BorderStyle;
|
|
499
521
|
export import BoxOptions = _Cli.BoxOptions;
|
|
522
|
+
export import CacheEntry = _Cache.CacheEntry;
|
|
500
523
|
export import CacheOptions = _Cache.CacheOptions;
|
|
501
524
|
export import CacheRemovalReason = _Cache.CacheRemovalReason;
|
|
502
525
|
export import CacheSetOptions = _Cache.CacheSetOptions;
|
|
@@ -527,9 +550,9 @@ declare namespace nodeComfort {
|
|
|
527
550
|
export import CopyFoldersOptions = _FS.CopyFoldersOptions;
|
|
528
551
|
export import CopyOptions = _FS.CopyOptions;
|
|
529
552
|
export import CronOptions = _Time.CronOptions;
|
|
530
|
-
export import CronSpec = _Time.CronSpec;
|
|
531
553
|
export import CurrencyOptions = _Num.CurrencyOptions;
|
|
532
554
|
export import DateInput = _Time.DateInput;
|
|
555
|
+
export import DateRangeOptions = _Time.DateRangeOptions;
|
|
533
556
|
export import DebouncedFunction = _Func.DebouncedFunction;
|
|
534
557
|
export import DebounceOptions = _Func.DebounceOptions;
|
|
535
558
|
export import DecodedJWT = _Crypto.DecodedJWT;
|
|
@@ -562,24 +585,32 @@ declare namespace nodeComfort {
|
|
|
562
585
|
export import HashPasswordOptions = _Crypto.HashPasswordOptions;
|
|
563
586
|
export import HttpBody = _Http.HttpBody;
|
|
564
587
|
export import HttpClient = _Http.HttpClient;
|
|
588
|
+
export import HttpHeaders = _Http.HttpHeaders;
|
|
565
589
|
export import HttpHooks = _Http.HttpHooks;
|
|
566
590
|
export import HttpMethod = _Http.HttpMethod;
|
|
567
591
|
export import HttpOptions = _Http.HttpOptions;
|
|
592
|
+
export import HttpOptionsOf = _Http.HttpOptionsOf;
|
|
568
593
|
export import HttpRequestConfig = _Http.HttpRequestConfig;
|
|
569
594
|
export import HttpResponse = _Http.HttpResponse;
|
|
570
595
|
export import HttpRetryOptions = _Http.HttpRetryOptions;
|
|
571
596
|
export import IndexDefinition = _SQLite.IndexDefinition;
|
|
572
597
|
export import Infer = _Schema.Infer;
|
|
598
|
+
export import InsertOptions = _SQLite.InsertOptions;
|
|
573
599
|
export import InsertResult = _SQLite.InsertResult;
|
|
574
600
|
export import IsBase64Options = _Checker.IsBase64Options;
|
|
601
|
+
export import IsHostnameOptions = _Checker.IsHostnameOptions;
|
|
575
602
|
export import IsURLOptions = _Checker.IsURLOptions;
|
|
576
603
|
export import IsUUIDOptions = _Checker.IsUUIDOptions;
|
|
577
604
|
export import Iteratee = _Arr.Iteratee;
|
|
605
|
+
export import JSONSchema = _Schema.JSONSchema;
|
|
578
606
|
export import JWTAlgorithm = _Crypto.JWTAlgorithm;
|
|
579
607
|
export import JWTClaims = _Crypto.JWTClaims;
|
|
608
|
+
export import JWTKey = _Crypto.JWTKey;
|
|
609
|
+
export import KeyPair = _Crypto.KeyPair;
|
|
580
610
|
export import Limiter = _Async.Limiter;
|
|
581
|
-
export import
|
|
611
|
+
export import ListenOptions = _Emitter.ListenOptions;
|
|
582
612
|
export import ListOptions = _FS.ListOptions;
|
|
613
|
+
export import LogEntry = _Logger.LogEntry;
|
|
583
614
|
export import LogFileOptions = _Logger.LogFileOptions;
|
|
584
615
|
export import Logger = _Logger.Logger;
|
|
585
616
|
export import LoggerOptions = _Logger.LoggerOptions;
|
|
@@ -598,9 +629,11 @@ declare namespace nodeComfort {
|
|
|
598
629
|
export import ObjectOutput = _Schema.ObjectOutput;
|
|
599
630
|
export import OrdinalOptions = _Num.OrdinalOptions;
|
|
600
631
|
export import Page = _Arr.Page;
|
|
632
|
+
export import PaginateOptions = _SQLite.PaginateOptions;
|
|
601
633
|
export import ParseBytesOptions = _Num.ParseBytesOptions;
|
|
602
634
|
export import ParseContext = _Schema.ParseContext;
|
|
603
635
|
export import ParsedArgs = _Cli.ParsedArgs;
|
|
636
|
+
export import ParseDateOptions = _Time.ParseDateOptions;
|
|
604
637
|
export import ParsedFlags = _Cli.ParsedFlags;
|
|
605
638
|
export import ParsedSnowflake = _Id.ParsedSnowflake;
|
|
606
639
|
export import PathInput = _Obj.PathInput;
|
|
@@ -621,9 +654,11 @@ declare namespace nodeComfort {
|
|
|
621
654
|
export import QueueAddOptions = _Async.QueueAddOptions;
|
|
622
655
|
export import QueueOptions = _Async.QueueOptions;
|
|
623
656
|
export import RandomStringOptions = _Str.RandomStringOptions;
|
|
657
|
+
export import RateLimiter = _Async.RateLimiter;
|
|
624
658
|
export import ReadJSONOptions = _FS.ReadJSONOptions;
|
|
625
659
|
export import RefineOptions = _Schema.RefineOptions;
|
|
626
660
|
export import RelativeOptions = _Time.RelativeOptions;
|
|
661
|
+
export import ResponseSchema = _Http.ResponseSchema;
|
|
627
662
|
export import RetryOptions = _Func.RetryOptions;
|
|
628
663
|
export import RunOptions = _Sys.RunOptions;
|
|
629
664
|
export import RunResult = _Sys.RunResult;
|
|
@@ -633,6 +668,7 @@ declare namespace nodeComfort {
|
|
|
633
668
|
export import Settled = _Async.Settled;
|
|
634
669
|
export import Shape = _Schema.Shape;
|
|
635
670
|
export import ShutdownOptions = _Sys.ShutdownOptions;
|
|
671
|
+
export import ShutdownReason = _Sys.ShutdownReason;
|
|
636
672
|
export import SignJWTOptions = _Crypto.SignJWTOptions;
|
|
637
673
|
export import Simplify = _Schema.Simplify;
|
|
638
674
|
export import SlugifyOptions = _Str.SlugifyOptions;
|
|
@@ -640,8 +676,10 @@ declare namespace nodeComfort {
|
|
|
640
676
|
export import SortByOptions = _Arr.SortByOptions;
|
|
641
677
|
export import SortDirection = _SQLite.SortDirection;
|
|
642
678
|
export import SortKey = _Arr.SortKey;
|
|
679
|
+
export import SortKeysOptions = _Obj.SortKeysOptions;
|
|
643
680
|
export import Spinner = _Cli.Spinner;
|
|
644
681
|
export import SpinnerOptions = _Cli.SpinnerOptions;
|
|
682
|
+
export import SpinOptions = _Cli.SpinOptions;
|
|
645
683
|
export import SQLiteOptions = _SQLite.SQLiteOptions;
|
|
646
684
|
export import StartOfUnit = _Time.StartOfUnit;
|
|
647
685
|
export import Step = _Schema.Step;
|
|
@@ -659,9 +697,13 @@ declare namespace nodeComfort {
|
|
|
659
697
|
export import ThrottleOptions = _Func.ThrottleOptions;
|
|
660
698
|
export import TimeZoneOptions = _Time.TimeZoneOptions;
|
|
661
699
|
export import TokenEncoding = _Id.TokenEncoding;
|
|
700
|
+
export import ToSentenceOptions = _Arr.ToSentenceOptions;
|
|
662
701
|
export import TOTPOptions = _Crypto.TOTPOptions;
|
|
663
702
|
export import TOTPURIOptions = _Crypto.TOTPURIOptions;
|
|
703
|
+
export import TransactionOptions = _SQLite.TransactionOptions;
|
|
704
|
+
export import TreeOptions = _Cli.TreeOptions;
|
|
664
705
|
export import TruncateOptions = _Str.TruncateOptions;
|
|
706
|
+
export import UUIDNamespace = _Id.UUIDNamespace;
|
|
665
707
|
export import ValidationIssue = _errors.ValidationIssue;
|
|
666
708
|
export import VarianceOptions = _Num.VarianceOptions;
|
|
667
709
|
export import VerifyJWTOptions = _Crypto.VerifyJWTOptions;
|